@formancy/server-core
v0.6.0
Published
The formancy backend's use-cases: publish, resolve, revalidate, store — framework-free, against repository ports.
Readme
@formancy/server-core
The formancy backend's use-cases, with no HTTP framework and no database driver in sight: publish, resolve, submit, drafts, listing, export, and the identity and authorization model.
Everything talks to a Storage port. The same code runs over Postgres in
@formancy/server and over a Map in tests, which is what makes these
behaviours testable without a database and portable across ones.
Two load-bearing decisions
Submission is a replay, not a check. createSubmission rebuilds the engine
from the exact schema version the client says it rendered, runs it over the
submitted data, and stores the canonical result: computed values are
recomputed and overwrite whatever arrived, and fields the server evaluates as
hidden are stripped. A client cannot smuggle data into a hidden branch by lying
about what was shown. Client validation is UX; this is truth.
Nothing ambient. Identity, time, hashing and randomness are injected. That is what lets a submission be replayed later and produce byte-identical computed values — and it is why the auth tests can use deterministic fakes while the real server gets argon2id and a CSPRNG.
Use
import { publishForm, formToFill, createSubmission, can } from '@formancy/server-core'
const published = await publishForm(deps, { path: 'contact-us', schema })
// publishForm is the save gate: structural validation, then the engine's own
// compile — a form that could loop is refused here and never persisted.
// What a respondent is handed: the current version, and the token its response is
// sent back with, so that a response sent twice is stored once.
const form = await formToFill(deps, 'contact-us')
const outcome = await createSubmission(deps, {
path: 'contact-us',
declaredSchemaHash: form.schemaHash,
token: form.submissionToken,
data: payload,
})
// The same token again: { ok: false, kind: 'token_spent', id }, and nothing stored.publishForm, keepExamples and createSubmission write their audit row inside
the transaction of what they record. Give them a requestId and the row names
that request, so it can be read beside a request log — @formancy/server passes
Fastify's request id. Without one, the row names none.
Authorization is one readable table (can(actor, action)), deliberately not
Postgres row-level security: RLS couples the application to per-request database
roles and hides authorization from the test suite.
Docs: apps/docs (Quickstart: self-hosting).
