Persistence
Use memory locally, connect the SQL store, or implement SurveyStore.
Every server instance requires a SurveyStore as database.
memoryAdapter() is the process-local reference implementation.
@dimah-survey/db adapts a FumaDB client for durable SQL storage. You may also
implement the contract directly.
Your application owns its tables, migrations, indexes, and database client. dimah-survey owns the behavioral contract.
Choose a store
| Store | Use it for | Persistence |
|---|---|---|
memoryAdapter() | Tests and local development | Process only |
db(client) | Supported SQL databases through FumaDB | Durable |
Custom SurveyStore | Existing data layer or specialized storage | Your implementation |
Pass the same store object to both fill and editor.
Connect the SQL store
import { DimahSurveyDB, db } from "@dimah-survey/db";
import { dimahSurvey } from "@dimah-survey/server";
import { drizzleAdapter } from "fumadb/adapters/drizzle";
const database = db(
DimahSurveyDB.client(
drizzleAdapter({ db: drizzleOrm, provider: "sqlite" }),
),
);
export const editor = dimahSurvey({
audience: "editor",
database,
});The SQL table names are dimah_survey and dimah_response. Drizzle export
names remain survey and response, because those are the model keys db()
queries.
draft_json, published_json, and settings are separate columns. settings
is not null and has no database default. Each response stores a copy of the
published document in definition. db() does not update definition after
insert.
Generate an application-owned schema
Configure the FumaDB CLI in your application:
import { createCli } from "fumadb/cli";
import { DimahSurveyDB } from "@dimah-survey/db";
import { drizzleAdapter } from "fumadb/adapters/drizzle";
import { drizzle } from "drizzle-orm/node-sqlite";
await createCli({
db: DimahSurveyDB.client(
drizzleAdapter({ db: drizzle(":memory:"), provider: "sqlite" }),
),
command: "dimah-survey",
version: "YOUR_APP_VERSION",
}).main();The CLI version identifies your wrapper command; the schema version to
generate is currently 1.0.0:
dimah-survey generate 1.0.0 -o ./db/survey.tsMigrate the generated file as part of your application. FumaDB does not emit
secondary indexes, so also apply
dimah_response_one_open_draft: one draft per survey and identified
respondent. Prisma cannot express that predicate; apply the index SQL after
creating the tables.
These exported files are readable references to copy or generate from. Do not import them as your runtime ORM schema:
@dimah-survey/db/schema/drizzle.ts@dimah-survey/db/schema/tables.sql@dimah-survey/db/schema/indexes.sql@dimah-survey/db/schema/schema.prisma
There is no survey version table. History is response.definition.
Implement a custom store
import type { SurveyStore } from "@dimah-survey/core";Prop
Type
Start and submit callbacks are not part of the HTTP body. The server passes them into the store write:
import type {
StartResponseLifecycle,
SubmitResponseLifecycle,
} from "@dimah-survey/core";Prop
Type
Prop
Type
A conforming store preserves these invariants:
Documents and snapshots
- Keep
draftJson,publishedJson, andsettingsindependent. - Insert default settings with a new survey; only
saveSurveySettingschanges them afterward. - Copy
publishedJsonintoresponse.definitionat start and never update that column. - Partial save replaces
data; persist the object passed by the server.
Atomic writes
- Check
expectedUpdatedAtinside the write. - Evaluate start reuse, collection windows, response caps, and one-open-draft rules in the same lock or transaction as the mutation.
- Enforce one draft per survey and identified respondent in application logic; use the partial unique index as a database backstop.
- Run the start and submit lifecycle callbacks inside that same boundary.
Queries and transitions
- Anonymous start always inserts. Identified start follows
"one-open"or"single"settings. - Start, partial save, and submit enforce the collection window and response cap.
- Reopen enforces
reopenand refuses a second identified draft. - Lists honor pagination, sort by
updatedAtdescending, and omitdefinitionanddatafrom summaries.
Use memoryAdapter() and the shared store contract tests as the behavioral
reference when implementing an adapter.