Files
skills/docs/api-stack/api/api-plugin-tenant/src/schemas/routes.md
2026-05-29 08:20:56 +01:00

3.3 KiB

routes

Purpose

Defines the Zod request/response schemas the five management routes mount when the plugin is configured with a TenantStore. Every body, query, and response shape flows through these schemas so the OpenAPI 3.1 document published at /openapi.json is in lock-step with the runtime behaviour — operators and SDKs generated from the spec see the same surface the runtime enforces.

Features

  • TenantResponseSchema — wire-shape projection of the domain Tenant (Dates serialised as ISO strings).
  • TenantListResponseSchema — paginated list response (items + total).
  • CreateTenantBodySchema / UpdateTenantBodySchema — strict body schemas; unknown keys are rejected.
  • TenantIdParamSchema / ListTenantsQuerySchema — path-param and query schemas with coercion where appropriate (limit, offset are coerced from query string numerals).
  • Slug validation regex: lowercase alphanumeric + hyphen, starting with an alphanumeric. Keeps URLs predictable and prevents subtle case-folding bugs in path-prefix resolution.

Usage: Basic Example

import { CreateTenantBodySchema } from "@suaveplan/api-plugin-tenant";

const result = CreateTenantBodySchema.safeParse({
    name: "Acme",
    slug: "acme",
});
if (!result.success) throw new Error(result.error.message);

Usage: Advanced Example

import {
    CreateTenantBodySchema,
    ListTenantsQuerySchema,
    TenantResponseSchema,
} from "@suaveplan/api-plugin-tenant";

// Query coercion — limit/offset arrive as strings from the URL.
const query = ListTenantsQuerySchema.parse({
    status: "active",
    limit: "50",
    offset: "0",
});
console.log(query.limit); // number, not string

// Round-trip a Tenant through the response schema.
const wire = TenantResponseSchema.parse({
    id: "tenant_1",
    name: "Acme",
    slug: "acme",
    status: "active",
    settings: {},
    enabledPlugins: ["billing"],
    domains: [],
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
});

// Reject invalid slug at the boundary.
const bad = CreateTenantBodySchema.safeParse({
    name: "X",
    slug: "ACME!", // uppercase + punctuation
});
console.assert(!bad.success);

API Reference

  • TenantResponseSchema — single-tenant wire shape.
  • TenantListResponseSchema — { items: TenantResponse[]; total: number }.
  • CreateTenantBodySchema — strict object; name, slug required.
  • UpdateTenantBodySchema — strict object; all fields optional.
  • TenantIdParamSchema — { id: string }.
  • ListTenantsQuerySchema — { status?, limit?, offset? } with numeric coercion on limit and offset.
  • DeleteTenantResponseSchema — { success: true }.
  • Type aliases (CreateTenantBody, UpdateTenantBody, etc.) are exported for handler signatures.

Implementation Notes

strict() is applied to CreateTenantBodySchema, UpdateTenantBodySchema, and the nested settings shape so unknown keys are rejected instead of silently dropped. This matters because the same shape round-trips through OpenAPI codegen on the client side — unknown keys on a strict schema imply the client has drifted, and the right behaviour is to fail loudly. The slug regex is intentionally conservative so URLs survive percent-encoding, CDN normalisation, and case-insensitive DNS lookups without ambiguity.