dimah-formv0.2.0

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:

Request and data flow
Your widgetsYou render inputs. No library UI.
createFormClientTyped protocol. useFormResponse binds fields.
guardYour auth and policy
dimahForm()Validate against the response snapshot
databasememoryAdapter() or db()
Responsedefinition snapshot + answers. Submit never consults the live form.


Package Boundaries

Each package has one job and a one-way dependency graph:

PackageEnvironmentResponsibilityKey Exports
@dimah-form/coreUniversalProtocol SSOT, Zod schemas, field definitions, answer validation, error codes, and type inference.defineForm, defineFieldType, APIError, FORM_ERROR_CODES
@dimah-form/serverServerBackend router, HTTP framework adapters, form.api, lifecycle hook dispatcher, and memoryAdapter().dimahForm(), toNextJsHandler, toHonoHandler, memoryAdapter
@dimah-form/reactBrowserHeadless React client, session state machine, visibility resolver, field bindings, and autosave.createFormClient(), useFormResponse(), fieldLabel, fieldOptions
@dimah-form/dbServerOptional. Production SQL adapter using FumaDB for Drizzle, Prisma, and Kysely.DimahFormDB, db()

Request Pipeline

Every mutation and query runs the same six stages:

Execution Lifecycle
  1. 1. ParseZod query / body check
  2. 2. GuardAuth & permissions
  3. 3. ValidateAgainst snapshot
  4. 4. on* HookPre-write mutation
  5. 5. PersistStore write & CAS
  6. 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

On this page