Architecture
Package boundaries, data flow, and the six-stage request pipeline.
The library owns the protocol, definition snapshots, and submit validation. Your app owns widgets, auth, and the database adapter. Those four boundaries are the architecture.
Principles
Headless by Design
No widgets, CSS, or component registry. Render fields with the design system you already have.
Snapshot Invariant
A fill freezes the definition at start. Editing the live form does not break in-flight drafts.
Protocol Single Source of Truth
@dimah-form/core defines all routes, payloads, and validators. Server and client never drift.
Zero-ORM Server Engine
@dimah-form/server has no database dependencies. Storage is abstracted through the ResponseStore interface.
System Flow
The diagram shows how widgets, HTTP routes, guard, validation, and the store fit together:
flowchart TD
subgraph Client ["Browser / React App"]
UI["Your UI Widgets / Stepper"] --> Hook["useFormResponse"]
Hook --> HTTPClient["createFormClient"]
end
subgraph Server ["Backend Runtime (@dimah-form/server)"]
HTTPClient --> Route["HTTP Handler (Next.js / Hono / Express)"]
Route --> Guard["guard ({ request, operation, ... })"]
Guard --> Engine["dimahForm() Router"]
Engine --> SnapshotVal["Validate vs response.definition Snapshot"]
SnapshotVal --> PreHooks["on* Pre-Write Lifecycle Hooks"]
end
subgraph Storage ["Persistence (ResponseStore)"]
PreHooks --> Store["memoryAdapter, db, or custom"]
Store --> Tables[("questionnaire & response rows")]
Store --> PostHooks["after* Post-Write Lifecycle Hooks"]
end
LiveDoc["Code Forms / DB Form"] -.->|"freezes snapshot at start"| TablesPackage Boundaries
Each package has one job and a one-way dependency graph:
| Package | Environment | Responsibility | Key Exports |
|---|---|---|---|
@dimah-form/core | Universal | Protocol SSOT, Zod schemas, field definitions, answer validation, error codes, and type inference. | defineForm, defineFieldType, APIError, FORM_ERROR_CODES |
@dimah-form/server | Server | Backend router, HTTP framework adapters, form.api, lifecycle hook dispatcher, and memoryAdapter(). | dimahForm(), toNextJsHandler, toHonoHandler, memoryAdapter |
@dimah-form/react | Browser | Headless React client, session state machine, visibility resolver, field bindings, and autosave. | createFormClient(), useFormResponse(), fieldLabel, fieldOptions |
@dimah-form/db | Server | Optional. Production SQL adapter using FumaDB for Drizzle, Prisma, and Kysely. | DimahFormDB, db() |
Request Pipeline
Every mutation and query runs the same six stages:
- 1. ParseZod query / body check
- 2. GuardAuth & permissions
- 3. ValidateAgainst snapshot
- 4. on* HookPre-write mutation
- 5. PersistStore write & CAS
- 6. after* HookSide-effects
1. Inbound Parse & Schema Validation
The HTTP adapter parses the incoming HTTP request and validates query parameters or body payloads against the Zod schemas defined in @dimah-form/core. Malformed requests immediately return 400 VALIDATION_ERROR.
2. Security Guard
The optional guard hook executes before any database or form logic. You inspect the request, operation, formId, or responseId, verify user session cookies/tokens, and enforce permissions. Throwing an APIError halts execution and sends a structured error response.
3. Snapshot Validation
When saving drafts or submitting responses, answer values are validated against the frozen response.definition snapshot, never against the live form schema. Submitting verifies that all visible required fields are populated and valid. Hidden fields (per showWhen) are automatically stripped.
4. Pre-Write Hooks (on*)
Pre-persistence hooks (such as onStart, onDraft, onSubmit) run before the database transaction. You can attach user identifiers (response.respondentId = user.id) or enrich metadata directly on the in-memory record.
5. Persistence & Optimistic Concurrency (CAS)
The ResponseStore writes the record to the database. If an expectedUpdatedAt timestamp was provided, the store verifies that the row has not been modified by another client in the meantime. If the timestamp differs, a STALE_UPDATE (409) conflict is raised.
6. Post-Write Hooks (after*)
After the database write successfully commits, post-persistence hooks (such as afterSubmit, afterDraft) trigger side-effects, such as sending confirmation emails, posting webhooks, or enqueuing background worker jobs.
Next Steps
Snapshots
Learn how snapshots ensure schema immutability and handle draft lifecycles.
Forms
Author schemas with defineForm, configure field types, and enable $Infer.
React Client
Deep dive into useFormResponse, visible fields, and draft actions.
Server
Mount HTTP adapters across Next.js, Hono, Express, Fastify, and SvelteKit.