Skip to content

Latest commit

 

History

History
348 lines (264 loc) · 28.5 KB

rest-server.mdx

File metadata and controls

348 lines (264 loc) · 28.5 KB
title Rest Server schema — API Protocol reference
navTitle Rest Server
description Defines the REST API server configuration for automatically generating RESTful CRUD endpoints, metadata endpoints, and batch operations.

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

REST API Server Protocol

Defines the REST API server configuration for automatically generating RESTful CRUD endpoints, metadata endpoints, and batch operations.

Features:

  • Automatic CRUD endpoint generation from Object definitions
  • Standard REST conventions (GET, POST, PUT, PATCH, DELETE)
  • Metadata API endpoints
  • Batch operation endpoints
  • OpenAPI/Swagger documentation generation

Architecture alignment:

  • Salesforce: REST API with Object CRUD
  • Microsoft Dynamics: Web API with entity operations
  • Strapi: Auto-generated REST endpoints

WHO CAN WRITE THIS CONFIG (#15543) — read this before planning a deployment around any key below. A RestServerConfig is the ARGUMENT a host passes when it constructs the server: never a stack collection member and never a stored metadata row.

There is exactly ONE door, and it is programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site that reaches new RestServer(...). ⚠️ HonoServerPlugin (packages/plugins/plugin-hono-server) declares a restConfig?: RestServerConfig option, but it is NOT a second door: its one reader takes api.basePath for the SPA fallback, and that plugin never constructs a REST server, so nothing below api.basePath reaches anything.

No shipped boot path opens that door with a config of its own. os serve (packages/cli/src/commands/serve.ts) reads the stack config's own top-level api: block and forwards exactly two keys out of it — api.enableProjectScoping and api.projectResolution — into a fixed argument, through an as any cast; the dev plugin (packages/plugins/plugin-dev/src/dev-plugin.ts) calls createRestApiPlugin() with no config at all. So the CLI does read a config file — it just forwards those two keys and nothing else.

⇒ On a CLI-started deployment every OTHER key here is EMBEDDER-ONLY: the whole of crud, metadata and batch, and the rest of api. Its value is whatever the .default() below says, and no flag, config file or CLI option moves it.

⚠️ ONE CARVE-OUT, and it is the security-relevant key: the REST server's normalizeConfig folds the environment into the effective value of metadata.maskObjectFields — OS_ALLOW_UNMASKED_OBJECT_METADATA turns the ADR-0106 D8 mask OFF for a deployment that cannot otherwise reach the key. That env var is the only thing outside an embedder's argument that changes any value here. See maskObjectFields below.

This is the recorded posture, not a gap awaiting a fix: the keys keep their runtime reads and their embedder consumer, and the reachability answer ADR-0049 asks for is written per key in the liveness ledger (packages/spec/liveness/crud_endpoints.json, metadata_endpoints.json, batch_endpoints.json).

**Source:** `packages/spec/src/api/rest-server.zod.ts`

TypeScript Usage

import { BatchEndpointsConfigSchema, CrudEndpointsConfigSchema, CrudOperation, EndpointRegistrySchema, GeneratedEndpointSchema, MetadataEndpointsConfigSchema, RestApiConfigSchema, RestServerConfigSchema, RouteGenerationConfigSchema } from '@objectstack/spec/api';
import type { BatchEndpointsConfig, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';

// Validate data
const result = BatchEndpointsConfigSchema.parse(data);

BatchEndpointsConfig

Properties

Property Type Required Description
maxBatchSize integer optional (default: 200) Maximum records per batch operation
enableBatchEndpoint boolean optional (default: true) Enable POST /data/:object/batch endpoint
operations { createMany?: boolean; updateMany?: boolean; deleteMany?: boolean } optional Enable/disable specific batch operations
defaultAtomic never optional [REMOVED] batch.defaultAtomic was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no batch handler ever consulted it: atomicity is decided per request by options.atomic in the batch body (BatchOptions, ADR-0119 D4, opt-in), and a server-side default that flipped it silently would change the failure semantics of callers who send nothing. Delete the key; a caller that needs all-or-nothing sends options: { atomic: true }.

Nested Shape: BatchEndpointsConfig.operations

Property Type Required Description
createMany boolean optional (default: true) Enable POST /data/:object/createMany
updateMany boolean optional (default: true) Enable POST /data/:object/updateMany
deleteMany boolean optional (default: true) Enable POST /data/:object/deleteMany
upsertMany never optional [REMOVED] batch.operations.upsertMany was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it gated a route that was never built: there is no POST /data/:object/upsertMany and no protocol member behind it, so false disabled nothing. Delete the key. Upsert is an operation type of the generic POST /data/:object/batch endpoint (BatchOperationType 'upsert', keyed by externalId), gated by batch.enableBatchEndpoint.

CrudEndpointsConfig

Properties

Property Type Required Description
operations { create?: boolean; read?: boolean; update?: boolean; delete?: boolean; … } optional Enable/disable operations
patterns never optional [REMOVED] crud.patterns was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: every CRUD route is mounted from the fixed method/path pairs in the REST server's registerCrudEndpoints, so a custom pattern was validated and ignored. Delete the key. The mounted CRUD paths are the contract the client SDK, the discovery document and the served /openapi.json all describe, and crud.dataPrefix is the one live knob that moves them; an endpoint on a custom path or method is a declarative api endpoint (type: 'object_operation', ApiEndpoint in @objectstack/spec/api), which is matched, executed and documented.
dataPrefix string optional (default: "/data") URL prefix for data endpoints
objectParamStyle never optional [REMOVED] crud.objectParamStyle was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: every CRUD route takes the object name as a path segment, so 'query' was validated and mounted exactly what 'path' mounts. Delete the key; the object name is always a path segment.

Nested Shape: CrudEndpointsConfig.operations

Property Type Required Description
create boolean optional (default: true) Enable create operation
read boolean optional (default: true) Enable read operation
update boolean optional (default: true) Enable update operation
delete boolean optional (default: true) Enable delete operation
list boolean optional (default: true) Enable list operation

CrudOperation

Allowed Values

  • create
  • read
  • update
  • delete
  • list

EndpointRegistry

Properties

Property Type Required Description
endpoints { id: string; method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; path: string; object: string; … }[] ✅ All generated endpoints
total integer ✅ Total number of endpoints
byObject Record<string, { id: string; method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; path: string; object: string; … }[]> optional Endpoints grouped by object
byOperation Record<string, { id: string; method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; path: string; object: string; … }[]> optional Endpoints grouped by operation

Nested Shape: EndpointRegistry.endpoints[number]

Property Type Required Description
id string ✅ Unique endpoint identifier
method Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'> ✅ HTTP method
path string ✅ Full URL path
object string ✅ Object name (snake_case)
operation Enum<'create' | 'read' | 'update' | 'delete' | 'list'> | string ✅ Operation type
handler string ✅ Handler function identifier
metadata { summary?: string; description?: string; tags?: string[]; deprecated?: boolean } optional

Nested Shape: EndpointRegistry.byObject[string][number]

Property Type Required Description
id string ✅ Unique endpoint identifier
method Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'> ✅ HTTP method
path string ✅ Full URL path
object string ✅ Object name (snake_case)
operation Enum<'create' | 'read' | 'update' | 'delete' | 'list'> | string ✅ Operation type
handler string ✅ Handler function identifier
metadata { summary?: string; description?: string; tags?: string[]; deprecated?: boolean } optional

Nested Shape: EndpointRegistry.byOperation[string][number]

Property Type Required Description
id string ✅ Unique endpoint identifier
method Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'> ✅ HTTP method
path string ✅ Full URL path
object string ✅ Object name (snake_case)
operation Enum<'create' | 'read' | 'update' | 'delete' | 'list'> | string ✅ Operation type
handler string ✅ Handler function identifier
metadata { summary?: string; description?: string; tags?: string[]; deprecated?: boolean } optional

GeneratedEndpoint

Properties

Property Type Required Description
id string ✅ Unique endpoint identifier
method Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'> ✅ HTTP method
path string ✅ Full URL path
object string ✅ Object name (snake_case)
operation Enum<'create' | 'read' | 'update' | 'delete' | 'list'> | string ✅ Operation type
handler string ✅ Handler function identifier
metadata { summary?: string; description?: string; tags?: string[]; deprecated?: boolean } optional

MetadataEndpointsConfig

Properties

Property Type Required Description
prefix string optional (default: "/meta") URL prefix for metadata endpoints
enableCache boolean optional (default: true) Enable HTTP cache headers (ETag, Last-Modified)
cacheTtl never optional [REMOVED] metadata.cacheTtl was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: metadata.enableCache selects the protocol's getMetaItemCached read path, which takes no TTL, and no Cache-Control / ETag header was ever built from this value. Delete the key; metadata.enableCache is the live switch, and a declarative api endpoint's cacheTtl is the key that does reach the wire.
maskObjectFields boolean optional (default: true) [ADR-0106 D8] Mask served object schemas to the caller's readable fields
endpoints { types?: boolean; items?: boolean; item?: boolean; maintenance?: boolean } optional Enable/disable specific endpoints

Nested Shape: MetadataEndpointsConfig.endpoints

Property Type Required Description
types boolean optional (default: true) Mount the metadata type list — GET /meta and GET /meta/types (one handler, two paths)
items boolean optional (default: true) Mount the per-type item list — GET /meta/:type, and nothing else
item boolean optional (default: true) Mount the whole per-item face — GET, PUT and DELETE /meta/:type/:name, its /references and /layers reads, the history family (/history, /audit, /diff, /published, /publish, /rollback) and GET /meta/book/:name/tree
maintenance boolean optional (default: true) Mount the whole-store maintenance operations — GET /meta/diagnostics, GET /meta/_drafts and the POST /meta/_migrate-stored write door
schema never optional [REMOVED] metadata.endpoints.schema was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it gated a route that does not exist: the REST server mounts no GET /meta/:type/:name/schema, so false removed nothing and true added nothing. Delete the key; endpoints.types / items / item / maintenance are the switches that gate real mounts.

RestApiConfig

Properties

Property Type Required Description
version string optional (default: "v1") API version (e.g., v1, v2, 2024-01)
basePath string optional (default: "/api") Base URL path for API
apiPath string optional Full API path (defaults to {basePath}/{version})
enableCrud boolean optional (default: true) Enable automatic CRUD endpoint generation
enableMetadata boolean optional (default: true) Enable metadata API endpoints
enableUi boolean optional (default: true) Enable UI API endpoints (Views, Menus, Layouts)
enableBatch boolean optional (default: true) Enable batch operation endpoints
enableDiscovery boolean optional (default: true) Enable API discovery endpoint
enableOpenApi boolean optional (default: true) Enable the OpenAPI 3.1 document endpoint (GET <basePath>/openapi.json)
enableSearch boolean optional (default: true) Enable structured search endpoints (server-wide search opt-out; embedder-only, not settable from os serve)
enableProjectScoping boolean optional (default: false) Enable project-scoped routing for data/meta/AI APIs
projectResolution Enum<'required' | 'optional' | 'auto'> optional (default: "auto") Project ID resolution strategy
requireAuth never optional [REMOVED] api.requireAuth was removed in @objectstack/spec 17. Anonymous access to object data is now always denied — auth is a kernel concern, not a deployment posture. Delete the key. To publish something publicly, declare it: a public form view (sharing.allowAnonymous), a share link, or book.audience: 'public' — each derives its own narrow authorization instead of opening the whole data plane. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
documentation { title?: string; description?: string; termsOfService?: string; contact?: object; … } optional Publisher identity of the served OpenAPI document: each member set here overlays its info on both /openapi.json doors, and nothing set serves the bundled info unchanged. info.version is always the protocol version
responseFormat never optional [REMOVED] api.responseFormat was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — nothing ever read it: envelope, includeMetadata and includePagination were parsed, defaulted and copied into the REST server's config and never consulted, so envelope: false unwrapped no response. Delete the key. Response shapes are fixed, not a server-wide option: each route answers in the response schema @objectstack/spec/api declares for it, which is what the client SDK parses and the served /openapi.json describes, so no configuration changes them.

Nested Shape: RestApiConfig.documentation

Property Type Required Description
enabled never optional [REMOVED] api.documentation.enabled was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — nothing ever read it: whether the server publishes its OpenAPI document is decided by the sibling api.enableOpenApi at the mount, so enabled: false turned nothing off. Delete the key; api.enableOpenApi: false is the switch that leaves the /openapi.json document unmounted.
title string optional Title of the served OpenAPI document (info.title); unset keeps the bundled title
description string optional Description of the served OpenAPI document (info.description); unset keeps the bundled description. Your app's own release number belongs here
version never optional [REMOVED] api.documentation.version was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — nothing ever read it, and the served OpenAPI document's info.version has one source: the protocol version, i.e. the version of the @objectstack/spec package that generated the document, which no deployment configuration overrides. Delete the key. To publish your app's own release number, write it into api.documentation.description, which the served info.description carries.
termsOfService string optional Terms-of-service URL of the served OpenAPI document (info.termsOfService); unset serves none
contact { name?: string; url?: string; email?: string } optional Contact of the served OpenAPI document; replaces the bundled info.contact whole, so a member left out is absent rather than inherited. Unset keeps the bundled contact
license { name: string; url?: string } optional License of the served OpenAPI document; replaces the bundled info.license whole, so a license without url serves no URL. Unset keeps the bundled license

RestServerConfig

Properties

Property Type Required Description
api { version?: string; basePath?: string; apiPath?: string; enableCrud?: boolean; … } optional REST API configuration
crud { operations?: object; dataPrefix?: string } optional CRUD endpoints configuration (embedder-only: written by a host that constructs this config, never by os serve or the dev plugin)
metadata { prefix?: string; enableCache?: boolean; maskObjectFields?: boolean; endpoints?: object } optional Metadata endpoints configuration (embedder-only: written by a host that constructs this config, never by os serve or the dev plugin)
batch { maxBatchSize?: integer; enableBatchEndpoint?: boolean; operations?: object } optional Batch endpoints configuration (embedder-only: written by a host that constructs this config, never by os serve or the dev plugin)
routes { } optional Route generation configuration
openApi31 never optional [REMOVED] RestServerConfig.openApi31 was removed in @objectstack/spec 17 (ADR-0049) — no runtime ever read it: the REST server forwards only api/crud/metadata/batch/routes, and the served /openapi.json is the pre-generated contract enriched with the live server URL and the registered objects, so webhook/callback definitions declared here never appeared in it. Delete the key. Config-driven OpenAPI 3.1 webhooks/callbacks documentation is a new capability and must arrive via the enforce route of ADR-0049 (a new ADR), not by re-declaring the key; for a real outbound webhook use Webhook from @objectstack/spec/automation.

Nested Shape: RestServerConfig.api

Property Type Required Description
version string optional (default: "v1") API version (e.g., v1, v2, 2024-01)
basePath string optional (default: "/api") Base URL path for API
apiPath string optional Full API path (defaults to {basePath}/{version})
enableCrud boolean optional (default: true) Enable automatic CRUD endpoint generation
enableMetadata boolean optional (default: true) Enable metadata API endpoints
enableUi boolean optional (default: true) Enable UI API endpoints (Views, Menus, Layouts)
enableBatch boolean optional (default: true) Enable batch operation endpoints
enableDiscovery boolean optional (default: true) Enable API discovery endpoint
enableOpenApi boolean optional (default: true) Enable the OpenAPI 3.1 document endpoint (GET <basePath>/openapi.json)
enableSearch boolean optional (default: true) Enable structured search endpoints (server-wide search opt-out; embedder-only, not settable from os serve)
enableProjectScoping boolean optional (default: false) Enable project-scoped routing for data/meta/AI APIs
projectResolution Enum<'required' | 'optional' | 'auto'> optional (default: "auto") Project ID resolution strategy
requireAuth never optional [REMOVED] api.requireAuth was removed in @objectstack/spec 17. Anonymous access to object data is now always denied — auth is a kernel concern, not a deployment posture. Delete the key. To publish something publicly, declare it: a public form view (sharing.allowAnonymous), a share link, or book.audience: 'public' — each derives its own narrow authorization instead of opening the whole data plane. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
documentation { title?: string; description?: string; termsOfService?: string; contact?: object; … } optional Publisher identity of the served OpenAPI document: each member set here overlays its info on both /openapi.json doors, and nothing set serves the bundled info unchanged. info.version is always the protocol version
responseFormat never optional [REMOVED] api.responseFormat was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — nothing ever read it: envelope, includeMetadata and includePagination were parsed, defaulted and copied into the REST server's config and never consulted, so envelope: false unwrapped no response. Delete the key. Response shapes are fixed, not a server-wide option: each route answers in the response schema @objectstack/spec/api declares for it, which is what the client SDK parses and the served /openapi.json describes, so no configuration changes them.

Nested Shape: RestServerConfig.crud

Property Type Required Description
operations { create?: boolean; read?: boolean; update?: boolean; delete?: boolean; … } optional Enable/disable operations
patterns never optional [REMOVED] crud.patterns was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: every CRUD route is mounted from the fixed method/path pairs in the REST server's registerCrudEndpoints, so a custom pattern was validated and ignored. Delete the key. The mounted CRUD paths are the contract the client SDK, the discovery document and the served /openapi.json all describe, and crud.dataPrefix is the one live knob that moves them; an endpoint on a custom path or method is a declarative api endpoint (type: 'object_operation', ApiEndpoint in @objectstack/spec/api), which is matched, executed and documented.
dataPrefix string optional (default: "/data") URL prefix for data endpoints
objectParamStyle never optional [REMOVED] crud.objectParamStyle was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: every CRUD route takes the object name as a path segment, so 'query' was validated and mounted exactly what 'path' mounts. Delete the key; the object name is always a path segment.

Nested Shape: RestServerConfig.metadata

Property Type Required Description
prefix string optional (default: "/meta") URL prefix for metadata endpoints
enableCache boolean optional (default: true) Enable HTTP cache headers (ETag, Last-Modified)
cacheTtl never optional [REMOVED] metadata.cacheTtl was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: metadata.enableCache selects the protocol's getMetaItemCached read path, which takes no TTL, and no Cache-Control / ETag header was ever built from this value. Delete the key; metadata.enableCache is the live switch, and a declarative api endpoint's cacheTtl is the key that does reach the wire.
maskObjectFields boolean optional (default: true) [ADR-0106 D8] Mask served object schemas to the caller's readable fields
endpoints { types?: boolean; items?: boolean; item?: boolean; maintenance?: boolean } optional Enable/disable specific endpoints

Nested Shape: RestServerConfig.batch

Property Type Required Description
maxBatchSize integer optional (default: 200) Maximum records per batch operation
enableBatchEndpoint boolean optional (default: true) Enable POST /data/:object/batch endpoint
operations { createMany?: boolean; updateMany?: boolean; deleteMany?: boolean } optional Enable/disable specific batch operations
defaultAtomic never optional [REMOVED] batch.defaultAtomic was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no batch handler ever consulted it: atomicity is decided per request by options.atomic in the batch body (BatchOptions, ADR-0119 D4, opt-in), and a server-side default that flipped it silently would change the failure semantics of callers who send nothing. Delete the key; a caller that needs all-or-nothing sends options: { atomic: true }.

RouteGenerationConfig

Properties

Property Type Required Description
includeObjects never optional [REMOVED] routes.includeObjects was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: route registration iterates every registered object, so an include list still mounted all of them. Delete the key. Per-object API exposure is declared on the object and enforced by the REST data surface: enable.apiEnabled: false hides the object (404) and enable.apiMethods whitelists its operations (405 for the rest).
excludeObjects never optional [REMOVED] routes.excludeObjects was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: route registration iterates every registered object, so an excluded object was still mounted. Delete the key. Per-object API exposure is declared on the object and enforced by the REST data surface: enable.apiEnabled: false hides the object (404) and enable.apiMethods whitelists its operations (405 for the rest).
nameTransform never optional [REMOVED] routes.nameTransform was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the enum was validated and every value mounted exactly what 'none' mounts. Delete the key; the object name is the canonical id on every surface, the REST path segment included, so there is no URL transform to configure.
overrides never optional [REMOVED] routes.overrides was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the per-object record: enabled, basePath and operations were validated and turned nothing on or off. Delete the key. Per-object exposure lives on the object (enable.apiEnabled hides it, enable.apiMethods whitelists its operations — both enforced by the REST data surface); the data base path is deployment-wide (crud.dataPrefix); and an endpoint on a custom path is a declarative api endpoint.