Masukkan Password
82 Menerapkan Fungsi Validasi
Lecture ini adalah lanjutan langsung dari lecture sebelumnya. Kalau lecture sebelumnya baru membangun tempat untuk menyimpan informasi validator, maka lecture ini menyelesaikan dua hal besar:
- Memperbaiki penyimpanan metadata validator agar semua decorator tetap tersimpan.
- Mengimplementasikan
validate()untuk benar-benar memeriksa value berdasarkan decorator yang dipasang.
Ini penting karena di sini kita melihat bagaimana decorator bisa dipakai untuk membangun sistem validation sederhana.
1. Gambaran besar dulu
Tujuan akhirnya adalah kita bisa membuat class seperti:
class User {
@required
@minLength(3)
username: string;
@positiveNumber
age: number;
}
Lalu:
const u1 = new User("John", 28);
kita panggil:
validate(u1)
Dan hasilnya:
username → valid
age → valid
↓
true
Sedangkan:
const u2 = new User("", -30);
akan menghasilkan:
username → invalid
age → invalid
↓
false
2. Masalah pertama: validator sebelumnya saling menimpa
Ini adalah bagian pertama yang perlu benar-benar dipahami.
Misalnya:
class User {
@minLength(3)
@required
username: string;
@positiveNumber
age: number;
}
Kita ingin metadata akhirnya:
{
User: {
username: ["minLength", "required"],
age: ["positiveNumber"]
}
}
Tetapi implementasi sebelumnya punya masalah.
Misalnya minLength melakukan:
validateObject[User] = {
username: ["minLength"]
};
Kemudian required melakukan:
validateObject[User] = {
username: ["required"]
};
Akibatnya:
Awalnya:
username → ["minLength"]
↓ required dijalankan
username → ["required"]
"minLength" hilang!
3. Analogi sederhana: menulis ulang buku
Bayangkan kita punya buku:
User:
username → minLength
Kemudian decorator required datang.
Kalau kita menulis:
User:
username → required
kita sebenarnya menghapus catatan lama.
Padahal yang kita inginkan:
User:
username → minLength, required
Jadi kita harus:
Pertahankan data lama + tambahkan data baru.
Inilah alasan kita membutuhkan spread operator.
4. Masalah kedua: property berbeda juga saling menimpa
Bukan cuma validator pada property yang sama.
Misalnya:
username → ["required", "minLength"]
age → ["positiveNumber"]
Ketika decorator positiveNumber dijalankan, kalau kita menulis object baru:
validateObject["User"] = {
age: ["positiveNumber"]
};
maka:
Sebelumnya:
User:
username → [...]
Setelah positiveNumber:
User:
age → ["positiveNumber"]
Property username hilang.
Jadi ada dua jenis data lama yang harus dipertahankan:
1. Property sebelumnya
2. Validator sebelumnya pada property yang sama
5. Spread operator untuk mempertahankan property lama
Misalnya sebelumnya:
validateObject["User"]
berisi:
{
username: ["minLength"]
}
Kemudian kita ingin menambahkan:
age: ["positiveNumber"]
Kita bisa melakukan konsep:
{
...validateObject["User"],
age: ["positiveNumber"]
}
Hasilnya:
{
username: ["minLength"],
age: ["positiveNumber"]
}
Perhatikan:
...validateObject["User"]
artinya:
Ambil semua property yang sudah ada di object lama.
6. Analogi lemari
Misalnya kita punya lemari:
Lemari:
├── username
Kemudian ingin memasukkan:
age
Kalau kita membuat lemari baru dari awal:
Lemari baru:
└── age
username hilang.
Tetapi dengan spread:
Lemari lama
↓
ambil semua isinya
↓
tambahkan age
↓
Lemari baru
├── username
└── age
Itulah fungsi:
...validateObject[...]
7. Masalah berikutnya: bagaimana kalau data lama belum ada?
Ini bagian yang cukup tricky.
Ketika decorator pertama kali dijalankan:
validateObject
masih:
{}
Belum ada:
User
Jadi:
validateObject["User"]
menghasilkan:
undefined
Kalau kita langsung melakukan:
{
...validateObject["User"]
}
kita bisa mengalami masalah karena tidak ada object sebelumnya yang bisa kita gunakan.
8. Optional chaining ?.
Lecture menggunakan:
validateObject[target.constructor.name]?.[propName]
Mari kita baca pelan-pelan.
Misalnya:
target.constructor.name
adalah:
"User"
maka:
validateObject["User"]
misalnya:
{
username: ["minLength"]
}
Kemudian:
?.[propName]
misalnya propName = "username".
Maka:
validateObject["User"]?.["username"]
menghasilkan:
["minLength"]
9. Apa fungsi ?.?
Misalnya User belum ada:
validateObject["User"]
hasilnya:
undefined
Kalau kita melakukan:
validateObject["User"]["username"]
kita mencoba mengakses:
username
dari:
undefined
dan itu error.
Dengan:
validateObject["User"]?.["username"]
JavaScript mengatakan:
Kalau
validateObject["User"]ada, lanjutkan. Kalau tidak ada, hasilnyaundefined.
Jadi:
User ada?
│
├── Ya → ambil username
│
└── Tidak → undefined
10. Tetapi spread undefined tetap bermasalah
Ini yang kemudian ditemukan oleh lecturer.
Misalnya:
const previousElements = undefined;
kemudian:
[...previousElements]
akan bermasalah karena undefined bukan sesuatu yang bisa di-iterate.
Jadi kita tidak boleh sembarangan melakukan:
[...undefined]
Kita harus memastikan kalau belum ada data, gunakan array kosong:
[]
11. Konsep previousElements
Lecture kemudian membuat:
let previousElements: string[] = [];
Tujuannya:
Menyimpan validator-validator yang sebelumnya sudah ada.
Kemudian secara konsep:
if (validateObject[User]?.[username]) {
previousElements = validateObject[User][username];
}
Kalau belum ada:
previousElements = []
Kalau sudah ada:
previousElements = ["minLength"]
12. Kemudian tambahkan validator baru
Misalnya:
previousElements = ["minLength"]
dan sekarang decorator required dijalankan.
Kita ingin:
["minLength"] + ["required"]
menjadi:
[
...previousElements,
"required"
]
Hasil:
["minLength", "required"]
Inilah penggunaan spread operator pada array.
13. Jadi ada dua spread operator
Ini sangat penting.
Spread object
Untuk mempertahankan property sebelumnya:
{
...validateObject[ClassName],
[propName]: ...
}
Artinya:
Pertahankan semua property yang sudah ada.
Spread array
Untuk mempertahankan validator sebelumnya:
[
...previousElements,
"required"
]
Artinya:
Pertahankan semua validator sebelumnya, lalu tambahkan validator baru.
14. Gambarkan dua levelnya
Metadata kita punya struktur:
validateObject
│
↓
User
│
↓
properties
│
├── username
│ ↓
│ validators
│ ↓
│ ["minLength"]
│
└── age
↓
validators
↓
["positiveNumber"]
Maka kita perlu menjaga:
Object level
{
...oldObject
}
Array level
[
...oldArray,
newValidator
]
15. Hasil metadata yang benar
Setelah semua decorator selesai:
validateObject
menjadi kira-kira:
{
User: {
username: [
"minLength",
"required"
],
age: [
"positiveNumber"
]
}
}
Urutan bisa bergantung pada urutan eksekusi decorator dalam model legacy yang dipakai lecture.
Yang penting:
Semua validator tetap tersimpan.
16. Sekarang masuk ke validate()
Setelah masalah metadata selesai, lecture mulai mengimplementasikan:
function validate(obj: object): boolean {
...
}
Sekarang tugasnya adalah:
Membaca metadata tersebut dan mengecek object yang diberikan.
Misalnya:
validate(u1);
dengan:
u1 = new User("John", 28)
17. Pertama: cari class yang sedang divalidasi
Kita memiliki object:
obj
Misalnya:
obj → u1
u1 adalah instance dari:
User
Kita bisa mendapatkan constructor-nya:
obj.constructor
Kemudian nama class:
obj.constructor.name
hasil:
"User"
18. Kenapa perlu nama class?
Karena metadata kita memiliki struktur:
{
User: {
username: [...],
age: [...]
}
}
Jadi validate() harus tahu:
“Saya sedang memvalidasi class apa?”
Kita mendapatkan:
const validateClass = validateObject[obj.constructor.name];
Kalau:
obj = u1
maka:
obj.constructor.name
adalah:
"User"
sehingga:
validateObject["User"]
menghasilkan:
{
username: ["required", "minLength"],
age: ["positiveNumber"]
}
19. Kenapa ada pengecekan falsy?
Lecture melakukan konsep:
if (!validateClass) {
return true;
}
Ini sangat masuk akal.
Bayangkan kita punya:
class Product {
name: string;
}
dan tidak ada satupun decorator validator.
Maka:
validateObject
tidak mempunyai:
Product
Jadi:
validateObject["Product"]
adalah:
undefined
Artinya:
Tidak ada aturan validasi yang perlu dijalankan.
Maka:
return true;
20. Kenapa tidak dianggap invalid?
Karena:
Tidak ada validator ≠ data invalid.
Misalnya class:
class Product {
name: string;
}
tidak mempunyai:
@required
@minLength
@positiveNumber
Kita tidak punya aturan untuk mengatakan bahwa name harus memenuhi sesuatu.
Jadi sistem validator ini mengatakan:
Tidak ada aturan
↓
Tidak ada yang perlu diperiksa
↓
true
21. Variabel isValid
Kemudian dibuat:
let isValid = true;
Ini adalah status keseluruhan validasi.
Awalnya kita berasumsi:
semuanya valid
Kemudian jika menemukan satu saja error:
isValid = false
22. Loop pertama: setiap property
Metadata:
{
username: ["required", "minLength"],
age: ["positiveNumber"]
}
Kita perlu mengunjungi:
username
age
Karena itu digunakan:
for (const prop in validateClass) {
...
}
Misalnya iterasi pertama:
prop = "username"
Iterasi kedua:
prop = "age"
23. Loop kedua: setiap validator
Untuk:
username
kita punya:
["required", "minLength"]
Maka kita perlu mengunjungi:
required
minLength
Karena itu digunakan:
for (const element of validateClass[prop]) {
...
}
Jadi ada dua loop:
for...in
↓
property
↓
for...of
↓
validator
24. Bedakan for...in dan for...of
Ini penting untuk memahami kode.
for...in
Digunakan untuk mendapatkan key/property.
Contoh:
const user = {
username: "John",
age: 28
};
for (const prop in user)
menghasilkan:
username
age
for...of
Digunakan untuk mendapatkan value dari iterable, misalnya array.
Contoh:
const validators = [
"required",
"minLength"
];
for (const element of validators)
menghasilkan:
required
minLength
Jadi:
for...in → property/key
for...of → item/value
25. Mendapatkan array validator
Misalnya:
prop = "username"
Maka:
validateClass[prop]
menghasilkan:
["required", "minLength"]
Sedangkan:
prop = "age"
menghasilkan:
["positiveNumber"]
26. Kemudian menggunakan switch
Karena element berisi nama validator:
required
minLength
positiveNumber
lecture menggunakan:
switch (element) {
case "required":
...
break;
case "minLength":
...
break;
case "positiveNumber":
...
break;
}
Artinya:
validator apa?
│
├── required → validasi required
│
├── minLength → validasi panjang
│
└── positiveNumber → validasi angka positif
27. Validasi required
Sekarang kita punya:
obj[prop]
Misalnya:
prop = "username"
maka:
obj[prop]
sama seperti:
obj.username
Kalau:
prop = "age"
maka:
obj[prop]
sama seperti:
obj.age
Ini disebut bracket notation.
28. Kenapa obj[prop]?
Karena nama property diketahui secara dinamis.
Kita tidak bisa selalu menulis:
obj.username
karena mungkin property yang sedang diperiksa adalah:
username
atau:
age
atau property lainnya.
Jadi:
obj[prop]
lebih fleksibel.
Misalnya:
prop = "username"
maka:
obj[prop]
→ obj.username
Kalau:
prop = "age"
maka:
obj[prop]
→ obj.age
29. Validasi required dengan truthy/falsy
Lecture menggunakan konsep:
!!obj[prop]
Ini mengubah value menjadi boolean.
Contoh:
!!"John"
hasil:
true
Sedangkan:
!!""
hasil:
false
Dan:
!!null
→ false
!!undefined
→ false
30. Kenapa menggunakan !!?
Satu !:
!"John"
menghasilkan:
false
Karena "John" truthy.
Kemudian !!:
!!"John"
menjadi:
"John"
↓ !
false
↓ !
true
Jadi:
!!value
adalah cara singkat untuk:
Mengubah value menjadi boolean.
31. Contoh required
Kalau:
username = "John"
maka:
!!username
→ true.
Kalau:
username = ""
maka:
!!username
→ false.
Kalau:
username = null
→ false.
Kalau:
username = undefined
→ false.
Jadi cocok untuk validator required.
32. Tetapi kenapa digabung dengan isValid?
Ini bagian yang sangat penting.
Misalnya:
username
├── required
└── minLength
Kita tidak boleh hanya melakukan:
isValid = hasilValidatorBaru;
Karena hasil validator berikutnya bisa menghapus hasil sebelumnya.
Contoh:
required → true
minLength → false
Kalau kita hanya menyimpan hasil terakhir:
isValid = false
memang benar dalam contoh ini.
Tetapi kalau:
required → false
minLength → true
hasil akhirnya malah menjadi:
isValid = true
Padahal object tetap invalid karena required gagal.
33. Solusinya: AND (&&)
Kita menggunakan:
isValid = isValid && validationResult;
Misalnya:
isValid = true
required = true
true && true
↓
true
Kemudian:
isValid = true
minLength = false
true && false
↓
false
Setelah isValid menjadi false, validator berikutnya:
false && true
↓
false
Tetap false.
34. Ini adalah konsep “semua harus lolos”
Misalnya ada tiga validator:
required
minLength
positiveNumber
Kita ingin:
required AND
minLength AND
positiveNumber
Jadi:
true AND true AND true → true
true AND false AND true → false
false AND true AND true → false
Satu saja gagal:
semuanya dianggap gagal.
35. Validasi minLength
Lecture kemudian melakukan:
obj[prop].length >= 3
Jadi misalnya:
username = "John"
maka:
"John".length
adalah:
4
dan:
4 >= 3
→ true.
36. Kalau username terlalu pendek
Misalnya:
username = "Jo"
maka:
"Jo".length = 2
dan:
2 >= 3
→ false.
Maka:
minLength → FAIL
dan:
isValid = false
37. Kenapa lecture hard-code 3?
Lecture menggunakan:
obj[prop].length >= 3
padahal decorator-nya:
@minLength(length)
sebenarnya menerima:
length
Ini memang belum ideal.
Misalnya:
@minLength(5)
harusnya validator mengetahui angka 5.
Tetapi lecture mengatakan bahwa untuk sekarang nilai 3 di-hard-code sebagai contoh sederhana.
Jadi:
Ini adalah implementasi pembelajaran, belum merupakan validator production-ready.
Nanti metadata seharusnya menyimpan konfigurasi seperti minLength: 5.
38. Validasi positiveNumber
Untuk:
@positiveNumber
age: number;
kita ingin:
age > 0
Jadi:
+obj[prop] > 0
Lecture menggunakan unary +:
+obj[prop]
untuk mengubah value menjadi number.
39. Apa itu unary +?
Misalnya:
+"28"
hasil:
28
Jadi:
+"28" > 0
menjadi:
28 > 0
→ true.
Kalau:
+"-30" > 0
menjadi:
-30 > 0
→ false.
40. Contoh validasi lengkap
Misalnya:
const u1 = new User("John", 28);
Metadata:
{
User: {
username: ["required", "minLength"],
age: ["positiveNumber"]
}
}
validate(u1) melakukan:
Property username
value = "John"
required:
"John" → true
minLength:
"John".length = 4
4 >= 3 → true
Jadi:
username → valid
Property age
value = 28
positiveNumber:
28 > 0
→ true.
Jadi:
age → valid
Akhirnya:
isValid = true
41. Contoh username terlalu pendek
Misalnya:
const u = new User("Jo", 28);
Pemeriksaan:
username
│
├── required
│ ↓
│ true
│
└── minLength
↓
2 >= 3
↓
false
Maka:
true && false
↓
false
Hasil:
validate(u)
→ false.
42. Contoh age negatif
const u = new User("John", -10);
Username:
required → true
minLength → true
Age:
positiveNumber
↓
-10 > 0
↓
false
Akhir:
true && true && false
↓
false
Object invalid.
43. Contoh username kosong + age negatif
const u = new User("", -30);
Username:
required
↓
"" → false
Min length juga gagal:
"".length = 0
0 >= 3 → false
Age:
-30 > 0 → false
Jadi:
false AND false AND false
↓
false
44. Flow lengkap validate()
Sekarang kita bisa melihat algoritmanya:
validate(object)
│
↓
Cari nama class
object.constructor.name
│
↓
Cari metadata class
validateObject[className]
│
↓
Ada validator?
│ │
tidak ada
│ │
↓ ↓
true loop property
│
↓
username / age
│
↓
loop validators
│
┌───────┼────────────┐
↓ ↓ ↓
required minLength positiveNumber
│ │ │
└───────┼────────────┘
↓
update isValid
│
↓
return isValid
45. Ada tiga “lapisan” dalam sistem ini
Menurut saya ini cara paling mudah memahami seluruh kode.
Lapisan 1 — Decorator
Menentukan aturan:
@required
@minLength(3)
@positiveNumber
↓
Lapisan 2 — Metadata
Menyimpan aturan:
{
User: {
username: ["required", "minLength"],
age: ["positiveNumber"]
}
}
↓
Lapisan 3 — Validator engine
Menjalankan aturan:
validate(user)
↓
true / false
Jadi:
DECORATOR
↓
METADATA
↓
VALIDATE()
Ini adalah inti lecture.
46. Kenapa metadata sangat penting?
Tanpa metadata, validate() tidak tahu harus melakukan apa.
Misalnya object:
{
username: "John",
age: 28
}
validate() hanya melihat data:
username = John
age = 28
Tapi dia tidak tahu:
username harus required?
username minLength?
age positive?
Decorator memberikan informasi itu.
Jadi decorator seperti memberikan instruksi kepada validation engine.
47. Analogi formulir pendaftaran
Bayangkan ada formulir:
Username: John
Age: 28
Di belakangnya ada aturan:
Username:
✓ wajib
✓ minimal 3 karakter
Age:
✓ harus positif
Decorator adalah cara kita menempelkan aturan:
username
↑
@required
@minLength(3)
age
↑
@positiveNumber
Kemudian validate() adalah petugas yang membaca aturan tersebut:
"Username harus required"
↓
cek username
"Username min 3"
↓
cek panjang
"Age positive"
↓
cek age
48. Kenapa constructor tidak melakukan validasi langsung?
Salah satu desain lecture ini adalah:
const u1 = new User("John", 28);
validate(u1);
Bukan:
constructor(...) {
// semua validasi di sini
}
Ini membuat validation engine menjadi terpisah dari class.
Misalnya:
validate(user);
validate(product);
validate(order);
Satu sistem dapat menangani banyak class.
49. Tapi ada kelemahan implementasi lecture
Ini penting supaya kamu tidak menganggap kode lecture sebagai implementasi production-ready.
Pertama: minLength belum menyimpan angka length
@minLength(5)
tetapi metadata hanya menyimpan:
"minLength"
sehingga validate() tidak tahu angka 5.
Lecture mengakalinya dengan:
.length >= 3
yang hard-coded.
Kedua: validator hanya diidentifikasi dengan string
Misalnya:
"required"
"minLength"
"positiveNumber"
Ini sederhana untuk belajar, tetapi sistem yang lebih besar biasanya menggunakan struktur metadata yang lebih kaya.
Ketiga: required dan truthy/falsy tidak selalu berarti hal yang sama
Misalnya angka:
0
adalah falsy.
Kalau suatu hari kita ingin:
@required
age: number;
maka !!age akan menganggap:
0 → false
Padahal mungkin 0 secara bisnis adalah value yang valid.
Jadi validator production biasanya memiliki aturan yang lebih spesifik.
50. Bagian yang paling penting: &&
Kalau ada beberapa validator:
required
minLength
positiveNumber
hasil akhirnya harus:
semua validator harus true
Secara logika:
isValid =
requiredResult &&
minLengthResult &&
positiveNumberResult;
Lecture melakukannya secara bertahap:
isValid = isValid && result;
Misalnya:
Awal
isValid = true
required = true
true && true = true
minLength = false
true && false = false
positiveNumber = true
false && true = false
Hasil akhir:
false
Ini memastikan sekali ada validator gagal, keseluruhan object tetap invalid.
51. Mengapa tidak langsung return false ketika gagal?
Secara desain, sebenarnya kita bisa saja menghentikan proses saat menemukan validator pertama yang gagal.
Tetapi lecture memilih mempertahankan:
isValid
sehingga semua pemeriksaan tetap bisa diproses.
Ini bisa berguna kalau suatu saat kita ingin mengumpulkan semua error:
username:
- terlalu pendek
age:
- harus positif
bukan hanya:
invalid
Jadi pendekatan isValid ini bisa dikembangkan lebih jauh.
52. Urutan proses keseluruhan
Mari kita gabungkan dari awal sampai akhir.
Saat class dibaca:
class User {
@required
@minLength(3)
username: string;
@positiveNumber
age: number;
}
Decorator dijalankan.
↓
Decorator mengisi metadata:
validateObject = {
User: {
username: ["minLength", "required"],
age: ["positiveNumber"]
}
}
↓
Kita membuat object:
const u1 = new User("John", 28);
↓
Kita memanggil:
validate(u1);
↓
validate() mencari:
u1.constructor.name
↓
User
↓
Ambil:
validateObject["User"]
↓
Loop:
username
age
↓
Loop validator:
username
├── minLength
└── required
age
└── positiveNumber
↓
Periksa value:
"John" → valid
28 → valid
↓
Return:
true
53. Apa yang terjadi pada u2?
const u2 = new User("", -30);
Kemudian:
validate(u2);
Sistem membaca:
username → ["minLength", "required"]
age → ["positiveNumber"]
Kemudian:
username = ""
↓
required → false
↓
isValid = false
Kemudian:
minLength
↓
0 >= 3
↓
false
Kemudian:
age = -30
↓
-30 > 0
↓
false
Akhir:
isValid = false
54. Hubungan dengan lecture sebelumnya
Lecture sebelumnya:
“Bagaimana decorator menyimpan validator?”
Lecture ini:
“Bagaimana kita mempertahankan semua validator dan menjalankannya?”
Jadi progression-nya:
Lecture sebelumnya
↓
Buat decorator
↓
Simpan metadata
↓
STOP
Lecture sekarang
↓
Perbaiki metadata
↓
Pertahankan semua validator
↓
Implement validate()
↓
Loop property
↓
Loop validator
↓
Jalankan aturan
↓
true / false
55. Konsep paling penting dari lecture ini
Kalau kamu hanya mau mengingat 7 hal, ingat ini:
1. Spread object
{
...oldObject,
newProperty: value
}
→ mempertahankan property lama.
2. Spread array
[
...oldArray,
newValue
]
→ mempertahankan validator lama.
3. Optional chaining
object?.property
→ aman ketika object mungkin undefined/null.
4. for...in
for (const prop in object)
→ loop key/property.
5. for...of
for (const item of array)
→ loop isi/value array.
6. !!value
→ mengubah value menjadi boolean.
!!"John" // true
!!"" // false
7. isValid && result
→ semua validator harus lolos.
🧠 Mental model terakhir
Bayangkan decorator validation sebagai stiker aturan.
┌──────────────────────────────┐
│ User │
│ │
│ username │
│ 🏷 required │
│ 🏷 minLength(3) │
│ │
│ age │
│ 🏷 positiveNumber │
└──────────────────────────────┘
Decorator membaca stiker tersebut dan mencatatnya:
validateObject
│
↓
User
│
├── username
│ ├── required
│ └── minLength
│
└── age
└── positiveNumber
Kemudian validate() menjadi petugas pemeriksa:
validate(user)
│
↓
"Ini class User"
│
↓
"Apa aturannya?"
│
↓
┌────────────┴────────────┐
↓ ↓
username age
↓ ↓
required + minLength positiveNumber
↓ ↓
valid? valid?
└────────────┬────────────┘
↓
SEMUA valid?
│ │
YES NO
↓ ↓
true false
Dan itulah sebenarnya pola yang sedang diajarkan lecture ini:
Decorator menempelkan aturan → metadata menyimpan aturan →
validate()membaca dan menjalankan aturan.
Di dunia nyata, konsep seperti ini memang sangat dekat dengan bagaimana library/framework validation bekerja. Biasanya kita tidak perlu membuat validation engine sendiri seperti contoh lecture ini; kita memakai library yang sudah menyediakan validator. Custom validator baru dibuat ketika aturan yang dibutuhkan memang tidak tersedia.