Operator satisfies TypeScript: Pola Nyata

Foto oleh Martin Vorel via Wikimedia Commons (CC BY-SA 4.0)
Ia memeriksa bahwa sebuah nilai sesuai dengan tipe tertentu tanpa mengubah tipe inferensi nilai itu sendiri. Berbeda dengan anotasi tipe yang melebarkan nilai menjadi tipe yang dideklarasikan, satisfies menjaga tipe literal sempit yang diinferensi TypeScript dari apa yang Anda tulis. Anda tetap mendapat error untuk salah ketik, kunci hilang, atau tipe nilai salah.
Assertion as membungkam compiler dan memaksa sebuah tipe, sehingga bisa menyembunyikan error nyata seperti salah ketik atau properti hilang. Operator satisfies melakukan sebaliknya: ia memvalidasi nilai terhadap tipe dan melaporkan ketidakcocokan, sambil mempertahankan tipe inferensi spesifik nilai itu. Gunakan as hanya saat Anda benar-benar tahu lebih dari compiler.
Gunakan keduanya bersama saat Anda butuh nilai literal readonly yang persis sekaligus validasi skema. Tulis nilainya, lalu as const untuk mengunci literal, lalu satisfies untuk memeriksa bentuk. Urutannya penting: meletakkan satisfies setelah as const memastikan tipe literal readonly itulah yang divalidasi terhadap batasan Anda.
Operator satisfies diperkenalkan di TypeScript 4.9, dirilis pada November 2022. Ia sudah stabil sejak saat itu, jadi proyek apa pun di TypeScript 5.x atau compiler Go-native 7.0 yang dirilis pada 2026 sudah mendukungnya. Tidak ada biaya runtime karena ia adalah konstruk murni tingkat-tipe yang terhapus saat kompilasi.
Ya. Memakai satisfies terhadap Record yang dikunci oleh tipe union memberi Anda pemeriksaan kelengkapan saat kompilasi. Jika Anda lupa satu kunci build gagal, dan jika Anda menambah kunci tak dikenal ia juga gagal. Ini membuatnya ideal untuk feature flag, peta izin, dan kamus locale di mana setiap kasus harus ditangani.

Foto oleh Martin Vorel via Wikimedia Commons (CC BY-SA 4.0)
Ringkasan Utama
Operator satisfies memeriksa bahwa sebuah nilai sesuai dengan sebuah tipe tanpa melebarkan nilai itu menjadi tipe tersebut. Anda tetap memegang tipe literal yang sempit yang diinferensi TypeScript dari apa yang benar-benar Anda tulis, sambil tetap menangkap salah ketik, kunci yang hilang, dan tipe nilai yang salah saat menulis. Posisinya berada di antara anotasi tipe dan cast as.
Selama bertahun-tahun saya menulis objek config di TypeScript dan diam-diam menerima pertukaran yang buruk. Jika saya menganotasi objek dengan sebuah tipe, saya mendapat validasi tapi kehilangan nilai spesifik yang diketahui compiler. Jika saya melewati anotasi, saya menyimpan nilai spesifik tapi tidak mendapat pemeriksaan sama sekali. Operator satisfies, ditambahkan di TypeScript 4.9 pada akhir 2022 dan sekarang menjadi sesuatu yang saya gunakan setiap minggu, melarutkan pertukaran itu. Tulisan ini adalah kumpulan pola yang benar-benar saya pakai untuknya di kode NestJS dan Next.js produksi.
Untuk memahami mengapa satisfies ada, lihat dua alat yang digantikannya. Anotasi tipe memaksa nilai naik untuk cocok dengan tipe yang dideklarasikan, sehingga compiler melupakan kunci dan literal spesifik yang Anda tulis. Assertion tipe dengan as melakukan sebaliknya: ia membungkam compiler sepenuhnya, sehingga salah ketik atau properti yang hilang lolos begitu saja. Tidak satu pun memberi Anda validasi plus presisi sekaligus.
type RouteConfig = Record<string, { path: string; auth: boolean }>;
// Annotation: validated, but the value is WIDENED.
const routesA: RouteConfig = {
home: { path: "/", auth: false },
admin: { path: "/admin", auth: true },
};
routesA.dashboard; // no error — TS forgot the exact keys
// Cast: keeps keys, but silences real errors.
const routesB = {
home: { path: "/", atuh: false }, // typo — NOT caught
} as RouteConfig;Dengan satisfies, nilai menyimpan tipe inferensinya sendiri dan tipe yang dideklarasikan hanya digunakan sebagai batasan untuk diperiksa. Model mental yang saya pakai: dengan anotasi tipe yang menang, dengan satisfies nilai yang menang. Anda mendapat error jika objek melanggar batasan, tapi tipe variabel tetap persis sesempit apa yang Anda tulis. Itu berarti autocomplete pada kunci sebenarnya dan tidak ada properti hantu.
const routes = {
home: { path: "/", auth: false },
admin: { path: "/admin", auth: true },
} satisfies Record<string, { path: string; auth: boolean }>;
routes.home.path; // string, autocompleted
routes.dashboard; // Error: property 'dashboard' does not exist
// and a typo like `atuh: false` fails right here at author timeAturan satu baris paling jelas yang saya berikan ke rekan tim: gunakan anotasi saat Anda ingin variabel dapat dipakai ulang sebagai tipe lebar, gunakan satisfies saat Anda ingin mengunci bentuk persis yang Anda tulis dan tetap diberi tahu saat ia bergeser. Gunakan as hanya saat Anda benar-benar tahu lebih dari compiler.
Ini adalah contoh kanonik dari catatan rilis TypeScript 4.9 dan yang meyakinkan saya. Misal sebuah config menyimpan nilai yang kadang string dan kadang tuple. Anotasi ia dan setiap nilai runtuh menjadi union, sehingga Anda tidak bisa lagi memanggil metode string pada entri string tanpa penyempitan. Dengan satisfies batasan tetap diberlakukan, tapi setiap properti menyimpan tipe presisinya dan metodenya tetap tersedia.
type Color = string | [number, number, number];
const palette = {
primary: "#0ea5e9",
danger: [239, 68, 68],
muted: "#64748b",
} satisfies Record<string, Color>;
palette.primary.toUpperCase(); // OK — TS knows it's a string
palette.danger.map((c) => c); // OK — TS knows it's a tuple
// Without satisfies (plain annotation), both lines would error.Ketika kunci harus mencakup union yang diketahui secara persis, satisfies terhadap Record yang dikunci oleh union itu memberi Anda pemeriksaan kelengkapan saat kompilasi. Lupakan satu kunci dan build gagal; tambahkan kunci tak dikenal dan ia juga gagal. Saya memakai ini untuk feature flag, peta izin, dan kamus locale, agar anggota enum baru tidak bisa dirilis setengah terpasang.
type Role = "admin" | "editor" | "viewer";
const permissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
} satisfies Record<Role, string[]>;
// If you add a fourth Role later and forget it here,
// this line stops compiling — a free exhaustiveness guard.
function can(role: Role): string[] {
return permissions[role];
}Sendiri, as const membekukan nilai ke tipe literal readonly terdalamnya tapi tidak memberi validasi skema, sehingga salah ketik pada kunci atau nilai bertipe salah tidak terdeteksi saat Anda menulisnya. Gabungkan keduanya dan Anda mendapat keduanya: literal terkunci dan bentuk diperiksa. Urutannya penting. Letakkan satisfies setelah as const agar tipe literal readonly itulah yang divalidasi terhadap batasan.
const endpoints = {
users: "/api/users",
orders: "/api/orders",
} as const satisfies Record<string, `/api/${string}`>;
type Endpoint = typeof endpoints[keyof typeof endpoints];
// Endpoint = "/api/users" | "/api/orders" — exact literals,
// AND every value is verified to match the /api/ template.Jangan membalik urutan menjadi satisfies sebelum as const. Itu memvalidasi objek yang sudah dilebarkan dan mutable terlebih dahulu lalu membekukannya, yang bisa menghilangkan penyempitan literal yang Anda inginkan. Ketika Anda butuh keduanya, urutannya selalu nilai, lalu as const, lalu satisfies.
Ini bukan pengganti universal untuk anotasi. Jika sebuah nilai adalah parameter fungsi atau field kelas yang di-assign kode lain sebagai tipe lebar, anotasi ia, karena Anda ingin tipe lebar menjadi kontraknya. Gunakan satisfies untuk nilai terminal yang Anda tulis sekali dan baca berkali-kali: config, peta, tabel konstanta. Dan jangan pernah memakainya untuk menutupi error yang tidak Anda pahami, itu yang digoda as untuk dilakukan dan satisfies sengaja tidak.
| Pendekatan | Memvalidasi bentuk | Menjaga tipe sempit | Terbaik untuk |
|---|---|---|---|
| Anotasi (tipe titik dua) | Ya | Tidak — melebarkan | Variabel, param, field yang dipakai ulang |
| cast as | Tidak — membungkam error | Ya | Pelarian langka saat Anda tahu lebih dari TS |
| satisfies | Ya | Ya | Objek config, peta route/izin |
| as const + satisfies | Ya | Ya — literal + readonly | Tabel konstanta yang butuh literal persis |
Tidak satu pun dari ini butuh toolchain paling mutakhir. Operator ini sudah stabil sejak TypeScript 4.9, jadi proyek apa pun di 5.x atau compiler Go-native 7.0 baru yang dirilis pertengahan 2026 sudah memilikinya. Tidak ada biaya runtime juga, satisfies adalah konstruk murni tingkat-tipe yang terhapus sepenuhnya saat kompilasi, persis seperti anotasi. Ini salah satu kebiasaan dengan leverage tertinggi dan risiko terendah yang saya pungut dalam tahun-tahun TypeScript harian.