Entity
<!-- generated:meta -->
| Grup | data |
| Plane | resource |
| Spec struct | EntitySpec |
<!-- /generated:meta -->
Kapan Memakai
kind: Entity adalah kind terpenting FormSpec — merepresentasikan data bisnis stateful yang dipersist. 95% kasus pembuatan aplikasi jawabannya Entity. Needing kind lain berarti memperluas framework, bukan membangun app.
Pilih karakteristik yang tepat (mutually exclusive — formspec apply menolak lebih dari satu):
| Karakteristik | Arti | Wajib |
|---|---|---|
master | Data referensi stabil (Customer, Product) | Boleh punya lifecycle atau tidak |
transaction | Append-heavy, time-partitioned (Invoice, Journal Entry) | Wajib field transaction_date |
reference | Seed data read-only (Provinsi, Tarif Pajak) | — |
summary | Projeksi terkelola sistem (GL Balance) | CUD permanen nonaktif via API |
Kapan TIDAK pakai Entity:
- Komputasi tanpa state →
kind: Service - Hanya butuh UI override → tambah
kind: Form/kind: Table(Entity tetap ada)
Sumber kontrak: docs/spec/backend/01-core-basic.md §1.
Contoh Manifest
yaml
apiVersion: formspec.dev/v1
kind: Entity
metadata:
name: arisan-group
module: arisan-master
description: "Grup arisan — kumpulan anggota dengan iuran bulanan tetap"
spec:
characteristic: master
lifecycle: plain_crud
display_field: name
plural: arisan-groups
fields:
- name: code
type: string
required: true
unique: true
title: "Kode Grup"
- name: name
type: string
required: true
title: "Nama Grup"
- name: monthly_amount
type: money
required: true
title: "Iuran Bulanan"
state_machine:
field: status
initial: active
states:
- { name: active, label: "Aktif" }
- { name: completed, label: "Selesai" }
transitions:
# Approval inline: transisi ini ditahan sampai pasti.
- from: active
to: completed
via: complete
approval:
steps:
- name: manager-check
permission: manager-check
approvers: 1
escalation: { after: 4h, reassign: head-check }
on_reject: { to: active }
actions:
- name: submit
disabled: true
- name: complete
description: "Tandai grup selesai"
required_permission: arisan-master.arisan-group.complete
audit: true
expose:
- type: rest
actions: [list, find, create, update, delete]Atribut
<!-- generated:attributes -->
| Atribut | Tipe | Wajib | Contoh | Deskripsi |
|---|---|---|---|---|
plural | string | — | invoices | |
characteristic | enum (master · transaction · reference · summary) | ✅ | master | Characteristic is required: it is the axis the engine branches on |
auth | EntityAuth | — | ||
persist | PersistSpec | — | ||
fields | []Field | — | ||
actions | []Action | — | ||
input_sets | []InputSet | — | InputSets declares named, reusable lists of action inputs. A transition or | |
state_machine | StateMachine | — | ||
events | []EventDecl | — | ||
deliver | []DeliveryDecl | — | ||
indexes | []IndexDecl | — | ||
row_scope | []FilterSpec | — | RowScope declares the row-level filters the server enforces on every read | |
create_scope | []CreateScopeSpec | — | CreateScope pins a dimension field on CREATE from a record the payload | |
scope | ScopeDecl | — | Scope declares that this entity's rows are partitioned along a named | |
assignments | []AssignmentDecl | — | Assignments declares that this entity records which principal is assigned | |
maintained_by | string | — | MaintainedBy names the script that keeps a characteristic: summary | |
invariants | []InvariantDecl | — | Invariants declares properties that must hold for a `characteristic: | |
sources | []SummarySource | — | Summary-source contract for Entity characteristic: summary (Core Extended | |
join_key | string | — | ||
rebuild | RebuildSpec | — | ||
extend_storage | ExtendStorage | — | ||
expose | []ExposeConfig | — | ||
backdate_policy | BackdatePolicy | — | ||
forward_date_policy | ForwardDatePolicy | — | ||
hooks | []HookDecl | — | ||
rate_limit | RateLimitSpec | — | 1.4.1 resource-level rate limit (02-core-extended.md §17) | |
soft_deactivate | SoftDeactivateDecl | — | 1.4.10 | |
cache | CacheSpec | — | Cache opts this entity into the framework read-through cache on | |
lifecycle | enum (two_step_autosave · two_step_manual · plain_crud) | — | plain_crud | |
display_field | string | — | name |
<!-- /generated:attributes -->
Referensi Struct
Nama struct di kolom Tipe di atas adalah tipe Go di pkg/spec/entity.go (dan pkg/spec/spec.go). Kontrak normatifnya didokumentasikan di docs/spec/backend/:
| Struct | Dokumentasi normatif |
|---|---|
Field | 05-field-types.md — katalog tipe (§1), money (§2), validasi (§3), tree (§4), keamanan & computed (§5) |
EntityAuth | 01-core-basic.md §1.4 |
Action | 01-core-basic.md §5 |
EventDecl | 01-core-basic.md §7 |
StateMachine | 02-core-extended.md §1 |
DeliveryDecl | 02-core-extended.md §3 |
BackdatePolicy / ForwardDatePolicy | 02-core-extended.md §9 |
HookDecl | 02-core-extended.md §15 |
RateLimitSpec | 02-core-extended.md §17 |
SoftDeactivateDecl | 02-core-extended.md §19 |
PersistSpec | 04-persist-backend.md |
ExtendStorage | 03-entity-extension.md |
Gotchas
- Tidak ada
spec.version. Kontrak versi ada diapiVersion(formspec.dev/v1); menulisversion:dispecditolakadditional properties 'version' not allowed. characteristicwajib — satu-satunya propertirequireddispec.- Reserved fields (
owner,created_at,modified,doc_status,amends,amended_by,version) tidak boleh dipakai ulang sebagai nama field custom. lifecycleadalah string enum — bukan map{doc_status: true}. Nilai yang valid ada di tabel Atribut di atas.exposeadalah array{type, actions}— shorthandall/read/nonetidak ada. Omit expose = UI only (external API → 404).target:di field relation diam-diam diabaikan → dangling relation. Pakairelation: { type: belongs_to, resource: <module.entity> }.- Update setelah
submitselalu ditolak, tanpa pengecualian. Perubahan pasca-submit lewat custom action bernama. transactionWAJIB punya fieldtransaction_dateeksplisit.deleteguard absolut (setaraON DELETE RESTRICT), tanpaoverride_permission.- Dua lapis state berjalan paralel:
doc_status(framework) + customstate_machine(developer) — lihatdocs/spec/backend/01-core-basic.md§1.6. - Approval bukan kind terpisah — dideklarasikan pada transisi (
state_machine.transitions[].approval, backend/02-core-extended.md §2). Gate itu menahan transisi, jadi ia mengawal semua state asal transisi tersebut. Transisi tanpaviatidak bisa diberiapproval(tidak ada namanya). - Duty approval = permission, bukan nama role (
steps[].permissiondanescalation.reassign), keduanya di-qualify jadiworkflow.{module}.{entity}.{transition}.{duty}dan diberikan lewat grant{ page: "workflow:{entity}.{transition}", actions: [{name: {duty}}] }. escalationwajib lengkap:after+reassign, danreassigntidak boleh menunjuk duty step itu sendiri (no-op) —formspec validatemenolaknya.- Cross-ref:
ai_skills/formspec-kinds·docs/spec/backend/01-core-basic.md·docs/spec/backend/02-core-extended.md