FieldCraftDocsServicesBlogWork With Me →

Postgres Adapter

Store form responses directly in PostgreSQL with Drizzle ORM.

Install

npm install @squaredr/fieldcraft-postgres

Quick Start

import { createPostgresAdapter } from "@squaredr/fieldcraft-postgres";

const adapter = createPostgresAdapter({
connectionString: process.env.DATABASE_URL!,
});

const engine = createEngine(schema, {
adapters: adapter,
});

Configuration

OptionTypeDefaultDescription
connectionStringstringPostgreSQL connection URL (required)
tablestring"formengine_responses"Table name for responses
encryptFieldsstring[]Field IDs to encrypt at rest
encryptionKeystringBase64-encoded 32-byte key
sslbooleantrue in prodEnable SSL connections
onSuccess(response) => voidCalled after successful insert
onError(error) => voidCalled on error

Database Schema

The adapter creates two tables via Drizzle ORM. Run the migration to set them up.

Responses Table

CREATE TABLE formengine_responses (
id TEXT PRIMARY KEY,
schema_id TEXT NOT NULL,
schema_version TEXT NOT NULL,
session_token TEXT NOT NULL,
data TEXT NOT NULL,
metadata JSONB,
completion_time_ms INTEGER,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX fe_responses_schema_idx ON formengine_responses (schema_id);
CREATE INDEX fe_responses_session_idx ON formengine_responses (session_token);

Drafts Table

CREATE TABLE formengine_drafts (
id TEXT PRIMARY KEY,
schema_id TEXT NOT NULL,
session_token TEXT NOT NULL,
partial_data JSONB NOT NULL,
current_section_id TEXT,
visited_section_ids JSONB,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX fe_drafts_session_unique_idx
ON formengine_drafts (schema_id, session_token);
CREATE INDEX fe_drafts_expires_idx ON formengine_drafts (expires_at);

Encryption

Enable AES-256-GCM encryption for sensitive fields. The entire data column is encrypted when encryptFields is set.

const adapter = createPostgresAdapter({
connectionString: process.env.DATABASE_URL!,
encryptFields: ["ssn", "credit_card", "medical_record"],
encryptionKey: process.env.ENCRYPTION_KEY!, // 32-byte base64 string
});

The key must be a base64-encoded 32-byte string. Generate one with: openssl rand -base64 32

Draft Adapter

Use the dedicated draft adapter for server-side draft persistence:

import {
createPostgresAdapter,
createPostgresDraftAdapter,
} from "@squaredr/fieldcraft-postgres";

const submitAdapter = createPostgresAdapter({
connectionString: process.env.DATABASE_URL!,
});

const draftAdapter = createPostgresDraftAdapter({
connectionString: process.env.DATABASE_URL!,
table: "formengine_drafts", // default
ttlHours: 72, // default
});

const engine = createEngine(schema, {
adapters: submitAdapter,
draftAdapter,
sessionToken: "user-123",
});

Draft Adapter Config

OptionTypeDefaultDescription
connectionStringstringPostgreSQL connection URL
tablestring"formengine_drafts"Table name
ttlHoursnumber72Draft expiry in hours
sslbooleantrue in prodEnable SSL