Beignet API reference
    Preparing search index...

    Module @beignet/core

    @beignet/core

    Core framework primitives for Beignet

    This package provides Beignet's framework primitives: contracts, server runtime, typed client, use cases, ports, domain helpers, app errors, config, events, idempotency, outbox, mail, notifications, schedules, uploads, pagination helpers, testing helpers, and OpenAPI generation.

    npm install @beignet/core

    # Use with your preferred Standard Schema library
    npm install zod
    # or
    npm install valibot
    # or
    npm install arktype

    This package requires TypeScript 5.0 or higher for proper type inference.

    Install @beignet/core once, then import the framework area you need. The package intentionally has no root entrypoint; use explicit subpaths so imports name the framework area they depend on.

    Import path Responsibility
    @beignet/core/application Use case builder and test helpers
    @beignet/core/client Typed HTTP client
    @beignet/core/config Environment config validation
    @beignet/core/contracts HTTP contract builders, types, path helpers, and contract metadata
    @beignet/core/domain Entities, value objects, and domain events
    @beignet/core/errors Error catalogs and response helpers
    @beignet/core/events Events and listeners
    @beignet/core/idempotency Retry-safe command, webhook, and job primitives
    @beignet/core/jobs Job definitions and inline job dispatch
    @beignet/core/mail Mail port and memory mailer
    @beignet/core/notifications Notification definitions, dispatchers, mail channels, and test adapters
    @beignet/core/openapi OpenAPI generation
    @beignet/core/outbox Durable event and job outbox
    @beignet/core/pagination Offset/cursor page types, normalizers, and result helpers
    @beignet/core/ports App-facing ports, auth, audit, policies, cache, storage, logging, and redaction
    @beignet/core/ports/testing Port and policy test helpers
    @beignet/core/providers Provider lifecycle and instrumentation primitives
    @beignet/core/schedules Scheduled task primitives
    @beignet/core/server Framework-agnostic server runtime and hook helpers
    @beignet/core/testing Test factories, seed definitions, and seed runners
    @beignet/core/uploads Upload definitions, router, signer port, and test signer
    @beignet/core/uploads/client Browser upload client for server and direct uploads

    A contract is the single source of truth for an API endpoint. It describes:

    • HTTP method and path (with path parameters)
    • Path parameters, query parameters, request headers, and request body schemas
    • Response schemas (per status code, including error responses)
    • Metadata for auth, rate limiting, idempotency, etc.

    A contract group allows you to share configuration across related endpoints, such as a common namespace, authentication requirements, and shared response schemas.

    import { z } from "zod";
    import { createContractGroup } from "@beignet/core/contracts";

    // Create a contract group for related endpoints
    const todos = createContractGroup()
    .namespace("todos")
    .prefix("/api/todos")
    .meta({ auth: "required" })
    .headers(z.object({
    authorization: z.string().startsWith("Bearer "),
    }));

    // Define schemas
    const TodoSchema = z.object({
    id: z.string(),
    title: z.string(),
    completed: z.boolean(),
    });

    const CreateTodoRequest = z.object({
    title: z.string().min(1),
    completed: z.boolean().optional(),
    });

    // Define contracts
    export const getTodo = todos
    .get("/:id")
    .pathParams(z.object({ id: z.string() }))
    .responses({ 200: TodoSchema })
    .errors({
    TodoNotFound: {
    code: "TODO_NOT_FOUND",
    status: 404,
    message: "Todo not found",
    details: z.object({ id: z.string() }),
    },
    });

    export const createTodo = todos
    .post("/")
    .body(CreateTodoRequest)
    .responses({ 201: TodoSchema });

    export const listTodos = todos
    .get("/")
    .query(z.object({
    completed: z.boolean().optional(),
    limit: z.coerce.number().optional(),
    }))
    .responses({ 200: z.array(TodoSchema) });

    Clients and OpenAPI generation infer required path argument keys from literal path templates. Use .pathParams(...) when you want runtime validation, coercion, richer OpenAPI schemas, or parameter descriptions.

    Use .headers(...) for request headers that are part of the endpoint contract. Declare header keys in lowercase; server and client runtime matching is case-insensitive.

    Request bodies are supported for POST, PUT, and PATCH contracts only.

    If you do not pass name, Beignet generates one from the HTTP method and full path:

    createContract({ method: "GET", path: "/users/:id" }).name;
    // "getUsersById"

    createContract({ method: "POST", path: "/api/todos" }).name;
    // "createTodos"

    Auto-generated names ignore a leading /api segment, include path parameters as By..., and are used as defaults in places like React Query keys and OpenAPI operationIds. Pass name explicitly when you need a custom stable identifier.

    Use .prefix(...) on a contract group to compose shared URL path segments without repeating them on every route:

    const api = createContractGroup().prefix("/api/v1");

    const todos = api
    .namespace("todos")
    .prefix("/todos");

    export const listTodos = todos.get("/");
    // GET /api/v1/todos

    export const getTodo = todos.get("/:id");
    // GET /api/v1/todos/:id

    Prefixes compose immutably and normalize boundary slashes. namespace() controls resource identity for contract names, OpenAPI tags, and client cache grouping; prefix() only controls URL paths.

    Use @beignet/core/testing to keep feature tests and demo seed data port-based. Factories build app-owned records, and optional persist functions write through the context you pass in:

    import { defineFactory, defineSeed, runSeeds } from "@beignet/core/testing";

    const postFactory = defineFactory("post", {
    defaults: ({ sequence }) => ({
    title: `Post ${sequence}`,
    content: "Created in a test.",
    }),
    persist: (ctx: AppContext, post) => ctx.ports.posts.create(post),
    });

    const demoPostsSeed = defineSeed("demo-posts", {
    run: async (ctx: AppContext) => {
    await postFactory.createList(ctx, 3);
    },
    });

    export async function seedDemoPosts(ctx: AppContext) {
    await runSeeds({ ctx, seeds: [demoPostsSeed] });
    }

    Keep factories and seeds app-owned. They should not import database clients, ORM table objects, or provider SDKs directly.

    Use @beignet/core/pagination to keep list use cases and repository ports consistent without coupling them to an ORM:

    import { normalizeOffsetPage } from "@beignet/core/pagination";

    const page = normalizeOffsetPage(input, {
    defaultLimit: 20,
    maxLimit: 100,
    });

    return ctx.ports.posts.findMany({
    page,
    filters: { status: input.status },
    sort: { field: "createdAt", direction: "desc" },
    });

    Beignet's convention is items for list contents and page for pagination metadata. Keep filters and sort options app-owned plain objects.

    Use @beignet/core/idempotency when a command, webhook, or job may be retried and must not perform duplicate work:

    import {
    createIdempotencyFingerprint,
    runIdempotently,
    } from "@beignet/core/idempotency";

    const result = await runIdempotently(ctx.ports.idempotency, {
    namespace: "todos.create",
    key: input.idempotencyKey,
    scope: {
    tenantId: ctx.tenant?.id,
    actorId: ctx.actor?.id,
    },
    fingerprint: await createIdempotencyFingerprint(input, {
    omit: ["idempotencyKey"],
    }),
    ttlSec: 60 * 60 * 24,
    run: () => ctx.ports.uow.transaction((tx) => tx.todos.create(input)),
    });

    The memory store is useful for tests and local examples:

    import { createMemoryIdempotencyStore } from "@beignet/core/idempotency";

    const idempotency = createMemoryIdempotencyStore();

    Production apps should back IdempotencyPort with atomic SQL or Redis storage. For high-integrity workflows, prefer exposing a transaction-scoped tx.idempotency port from the app Unit of Work so reservation, business writes, audit records, domain-event records, and idempotency completion commit together.

    Use @beignet/core/outbox when events or jobs must be recorded in the same database transaction as the business write, then delivered later with retries:

    import {
    createOutboxEventRecorder,
    defineOutboxRegistry,
    drainOutbox,
    } from "@beignet/core/outbox";
    import {
    createDrizzleTursoOutboxPort,
    createDrizzleTursoUnitOfWork,
    } from "@beignet/provider-drizzle-turso";

    const uow = createDrizzleTursoUnitOfWork({
    db,
    createTransactionPorts: (tx) => {
    const outbox = createDrizzleTursoOutboxPort(tx);

    return {
    posts: createPostRepository(tx),
    events: createOutboxEventRecorder(outbox),
    outbox,
    };
    },
    });

    const registry = defineOutboxRegistry({
    events: [PostPublished],
    jobs: [SendPostPublishedEmailJob],
    });

    await drainOutbox({
    outbox: ctx.ports.outbox,
    registry,
    eventBus: ctx.ports.eventBus,
    jobs: ctx.ports.jobs,
    });

    The outbox is at-least-once delivery. Use idempotent listeners or jobs when a duplicate delivery would be harmful.

    Use metadata to drive cross-cutting concerns like authentication, rate limiting, and idempotency:

    const sendMessage = messages
    .post("/api/messages")
    .body(SendMessageRequest)
    .responses({ 201: SendMessageResponse })
    .meta({
    auth: "required",
    idempotency: {
    required: true,
    header: "idempotency-key",
    scope: "actor-tenant",
    ttlSec: 300,
    },
    rateLimit: {
    max: 60,
    windowSec: 60,
    scope: "user",
    },
    });

    Add OpenAPI-specific metadata for documentation using the .openapi() method:

    export const getTodo = todos
    .get("/api/todos/:id")
    .pathParams(z.object({ id: z.string() }))
    .responses({ 200: TodoSchema })
    .openapi({
    summary: "Get a todo by ID",
    description: "Retrieves a single todo item by its unique identifier",
    tags: ["todos"],
    deprecated: false,
    operationId: "getTodoById",
    externalDocs: {
    url: "https://docs.example.com/todos",
    description: "Todo documentation",
    },
    security: [{ bearerAuth: [] }],
    });

    Contracts expose their schemas for runtime introspection:

    getTodo.schema.pathParams;  // Path parameter schema
    getTodo.schema.query; // Query parameter schema
    getTodo.schema.body; // Request body schema
    getTodo.schema.responses; // Response schemas by status code
    getTodo.path; // "/api/todos/:id"
    getTodo.pathTemplate; // "/api/todos/:id" (alias)
    getTodo.method; // "GET"
    getTodo.metadata; // { auth: "required", ... }

    Creates a new contract group for defining related endpoints.

    const group = createContractGroup()
    .namespace("myNamespace") // Optional resource namespace
    .prefix("/api/v1") // Optional URL path prefix
    .meta({ auth: "required" }) // Shared metadata
    .headers(AuthHeaders) // Shared request headers
    .errors({ // Shared catalog errors
    TenantSuspended: errors.TenantSuspended,
    });

    Any non-empty response map is treated as a response contract. Include successful statuses such as 200 or 201 alongside custom error statuses; use responses: {} only when you want to skip response validation. Prefer .errors(...) for expected business failures that should use Beignet's standard error envelope.

    Method Description
    .get(path) Define a GET endpoint
    .post(path) Define a POST endpoint
    .put(path) Define a PUT endpoint
    .patch(path) Define a PATCH endpoint
    .delete(path) Define a DELETE endpoint
    .pathParams(schema) Define path parameter schema
    .query(schema) Define query parameter schema
    .headers(schema) Define request header schema
    .body(schema) Define request body schema
    .responses({ ... }) Define or merge response schemas by status code
    .errors({ ... }) Declare route-owned catalog errors using Beignet's standard error envelope
    .meta(metadata) Add custom metadata
    .openapi(options) Add OpenAPI metadata (summary, tags, etc.)

    This package works with any Standard Schema compatible library:

    • Zod - Most popular, excellent TypeScript inference
    • Valibot - Lightweight alternative to Zod
    • ArkType - High-performance runtime validation

    OpenAPI generation currently requires Zod schemas, even though core contracts can use any Standard Schema-compatible library.

    MIT

    Modules

    application
    client
    config
    contracts
    domain
    errors
    errors/http
    events
    idempotency
    jobs
    mail
    notifications
    openapi
    outbox
    pagination
    ports
    ports/testing
    providers
    schedules
    server
    testing
    uploads
    uploads/client