@rebasepro/server 0.19.2-canary.gef769df → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{auth-BLD80igz.js → auth-DJsLXsCR.js} +3 -3
- package/dist/{auth-BLD80igz.js.map → auth-DJsLXsCR.js.map} +1 -1
- package/dist/{cron-routes-B0hgbL0a.js → cron-routes-Bfwni8Zg.js} +2 -2
- package/dist/{cron-routes-B0hgbL0a.js.map → cron-routes-Bfwni8Zg.js.map} +1 -1
- package/dist/{cron-store-DuZMJvSh.js → cron-store-Bsiw4Q6u.js} +2 -2
- package/dist/{cron-store-DuZMJvSh.js.map → cron-store-Bsiw4Q6u.js.map} +1 -1
- package/dist/{history-recorder-oJZctx5W.js → history-recorder-r5_IzSHK.js} +2 -2
- package/dist/{history-recorder-oJZctx5W.js.map → history-recorder-r5_IzSHK.js.map} +1 -1
- package/dist/{history-store-BPErMNQq.js → history-store-D4RVK-uZ.js} +2 -2
- package/dist/{history-store-BPErMNQq.js.map → history-store-D4RVK-uZ.js.map} +1 -1
- package/dist/index.es.js +21 -16
- package/dist/index.es.js.map +1 -1
- package/dist/{jobs-2oLrObSd.js → jobs-CW5lm_Ix.js} +2 -2
- package/dist/{jobs-2oLrObSd.js.map → jobs-CW5lm_Ix.js.map} +1 -1
- package/dist/{openapi-generator-CaA6xKaL.js → openapi-generator-DGyLbISS.js} +2 -2
- package/dist/{openapi-generator-CaA6xKaL.js.map → openapi-generator-DGyLbISS.js.map} +1 -1
- package/dist/{query-parser-0EB_LGgY.js → query-parser-BQiPZrM-.js} +2 -2
- package/dist/{query-parser-0EB_LGgY.js.map → query-parser-BQiPZrM-.js.map} +1 -1
- package/dist/{src-Dgk200Dh.js → src-DqZ9YiGA.js} +123 -19
- package/dist/src-DqZ9YiGA.js.map +1 -0
- package/package.json +5 -5
- package/dist/src-Dgk200Dh.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi-generator-CaA6xKaL.js","names":[],"sources":["../../types/src/types/relations.ts","../src/api/openapi-generator.ts"],"sourcesContent":["import type { AnyCollectionConfig } from \"./collections\";\nimport type { Properties } from \"./properties\";\n\n/**\n * @group Models\n */\nexport type OnAction = \"cascade\" | \"restrict\" | \"no action\" | \"set null\" | \"set default\";\n\n/**\n * The key a junction row's own columns are carried under, in both directions.\n *\n * A read that includes a `manyToMany` relation serves each related row with its\n * link's columns nested here — `{ id: 5, name: \"ts\", _pivot: { role: \"owner\" } }`\n * — and a membership write may name the same key on an element to state what\n * the link should hold. One constant because the two have to be the same word:\n * a wire name that differs between the read and the write it round-trips\n * through is a shape no client can echo back.\n *\n * Leading underscore, like `_matches`: it reads as metadata about the row\n * rather than as one of its columns. A payload property may not be named\n * `_pivot` either — `checkJunctionPayload` refuses it — so the key means one\n * thing wherever it appears.\n *\n * @group Models\n */\nexport const JUNCTION_PIVOT_KEY = \"_pivot\";\n\n/**\n * What kind of link a relation is.\n *\n * The discriminant. Every other field a relation carries belongs to exactly one\n * of these, which is the point: a relation used to be a single open interface\n * where `cardinality`, `direction`, `localKey`, `foreignKeyOnTarget`, `through`\n * and `joinPath` were all optional and any combination typechecked. Which link\n * you meant then had to be *inferred* from which fields you happened to set,\n * and the inference was ~200 lines that guessed, fell back on naming\n * conventions, and swallowed its own failures.\n *\n * Most of that guessing produced bugs rather than convenience. A `many`\n * relation carrying a `localKey` — a combination the old type permitted — made\n * the write path stamp the parent's own foreign key onto the child row. Under\n * these kinds that state cannot be written down.\n *\n * @group Models\n */\nexport type RelationKind = \"belongsTo\" | \"hasOne\" | \"hasMany\" | \"manyToMany\" | \"via\";\n\n/** Fields every relation carries, whatever its kind. @group Models */\nexport interface RelationBase {\n /**\n * The name this link is addressed by: the key in `include`, the tab in the\n * admin panel, and the path segment of a nested URL.\n *\n * Defaults to the declaring property's key, or to the target's slug for an\n * entry in `relations`.\n */\n relationName?: string;\n\n /** The collection on the other end. */\n target: () => AnyCollectionConfig;\n\n /**\n * What the database does to this side's foreign key when the target row's\n * key changes. Emitted as the constraint's `ON UPDATE`.\n *\n * Unset means no clause, which Postgres reads as `NO ACTION`. Set\n * `\"cascade\"` when the target's key is a natural key that can be edited —\n * a slug, a SKU — so the pointers follow it.\n *\n * Only a `belongsTo` puts the key on this table, so this is the only kind\n * where the clause is written here; on the other kinds it describes the\n * constraint the target's own column carries.\n */\n onUpdate?: OnAction;\n /**\n * What the database does to this side's rows when the target row is\n * deleted. Emitted as the constraint's `ON DELETE`.\n *\n * Defaults, when unset, to `\"set null\"` for an optional relation and\n * **`\"restrict\"`** for a required one. `NOT NULL` says a child cannot exist\n * without a parent; it does not say deleting the parent should delete the\n * child. Ask for `\"cascade\"` when that is what you mean — it is the one\n * value that destroys rows you did not name.\n *\n * A `manyToMany` is the exception: its junction rows default to\n * `\"cascade\"`, because the row deleted there is the link and not the target.\n */\n onDelete?: OnAction;\n\n /**\n * Presentation overrides applied when this relation is rendered as a tab.\n *\n * Whether the link is *required* is not here: it is\n * `validation: { required: true }` on the declaring property, the same key\n * every other field uses. A relation carried its own copy until 0.18, and\n * the two disagreed by construction — the DDL generator read the property\n * (so the column was `NOT NULL`) while codegen read the relation (so the\n * generated `Insert` type made it optional), and a `create()` that\n * typechecked failed at the database.\n */\n overrides?: Partial<AnyCollectionConfig>;\n}\n\n/**\n * This collection holds the foreign key. One target row per source row.\n *\n * ```ts\n * author: { kind: \"belongsTo\", target: () => authors, localKey: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface BelongsToRelation extends RelationBase {\n kind: \"belongsTo\";\n /**\n * Column on **this** collection's table holding the target's key.\n * Defaults to `<relationName>_id`.\n */\n localKey?: string;\n}\n\n/**\n * The target holds the foreign key, and at most one target row points back.\n *\n * ```ts\n * profile: { kind: \"hasOne\", target: () => profiles, foreignKeyOnTarget: \"user_id\" }\n * ```\n * @group Models\n */\nexport interface HasOneRelation extends RelationBase {\n kind: \"hasOne\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n /**\n * Column on **this** collection's table whose value `foreignKeyOnTarget`\n * holds. Defaults to this collection's primary key.\n *\n * Set it when the two sides are joined on a natural key rather than on the\n * row id — an external identity id, a SKU, a tenant slug. See\n * {@link HasManyRelation.sourceKey}, which this mirrors.\n */\n sourceKey?: string;\n}\n\n/**\n * The target holds the foreign key, and many target rows point back. The\n * children belong to this parent alone — deleting one deletes a row.\n *\n * ```ts\n * posts: { kind: \"hasMany\", target: () => posts, foreignKeyOnTarget: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface HasManyRelation extends RelationBase {\n kind: \"hasMany\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n /**\n * Column on **this** collection's table whose value `foreignKeyOnTarget`\n * holds. Defaults to this collection's primary key.\n *\n * The mirror of `localKey` on {@link BelongsToRelation}: that one names the\n * column this side reads from, this one names the column the other side\n * points at. Without it the pair can only be joined on the row id, which\n * makes a natural-key link — `auth_user_id ↔ auth_user_id`, a SKU, a tenant\n * slug — inexpressible as `hasMany`, and it has to drop to the read-only\n * `via`.\n *\n * The column must be unique: the link addresses one source row per value,\n * and Postgres will not accept a foreign key against a non-unique column.\n *\n * ```ts\n * applications: {\n * kind: \"hasMany\",\n * target: () => talentApplications,\n * sourceKey: \"auth_user_id\",\n * foreignKeyOnTarget: \"auth_user_id\"\n * }\n * ```\n */\n sourceKey?: string;\n}\n\n/**\n * Both sides hold many, through a junction table. The target rows are shared,\n * so this collection owns the *link* and not the row: removing one removes a\n * junction row and leaves the target alone.\n *\n * Declared the same way from either side — there is no owning and inverse\n * version. Swap `sourceColumn` and `targetColumn` to describe the other\n * direction.\n *\n * ```ts\n * tags: { kind: \"manyToMany\", target: () => tags }\n * ```\n * @group Models\n */\nexport interface ManyToManyRelation extends RelationBase {\n kind: \"manyToMany\";\n /**\n * The junction table and its two key columns. Every part defaults: the\n * table to both table names sorted and joined, the columns to\n * `<collection>_id` and `<relationName>_id`.\n */\n through?: {\n table?: string;\n /** Junction column holding **this** collection's key. */\n sourceColumn?: string;\n /** Junction column holding the **target's** key. */\n targetColumn?: string;\n /**\n * Extra columns the junction row carries, declared exactly like a\n * collection's properties.\n *\n * A membership is often not only a membership. \"This user is in that\n * organisation\" is really \"…as an `owner`, since March\"; \"this tag is\n * on that post\" is really \"…in third place\". Until this existed the\n * junction was two key columns and nothing else, so the role and the\n * position had to become a collection of their own — which is a\n * different data model, a different set of policies and a different\n * URL, for what is still one link.\n *\n * The properties are read by the same planner that reads a\n * collection's, so a payload column gets the type, `NOT NULL`,\n * `DEFAULT`, `UNIQUE` and enum type it would get on a table. What it\n * does **not** get is `indexes` (declared per collection, and no\n * collection declares a junction), `search`, `vector`, or anything a\n * relation would put on it — a payload property may not be a\n * `relation`, a `reference` or a `vector`, and config validation\n * refuses one that is.\n *\n * On the wire the values travel under {@link JUNCTION_PIVOT_KEY}: a\n * read serves `{ …target, _pivot: { role } }`, and a membership write\n * accepts `{ id, _pivot: { role } }` beside the bare ids.\n *\n * ```ts\n * members: {\n * kind: \"manyToMany\",\n * target: () => users,\n * through: {\n * table: \"org_members\",\n * properties: {\n * role: { type: \"string\", enum: [\"owner\", \"admin\", \"member\"],\n * defaultValue: \"member\", validation: { required: true } },\n * joinedAt: { type: \"date\", autoValue: \"on_create\" }\n * }\n * }\n * }\n * ```\n *\n * Both sides of the same junction may declare it, and both must agree:\n * `resolveJunctionSpecs` refuses two declarations of the same payload\n * key that do not describe the same column, because only one of them\n * could ever be created.\n */\n properties?: Properties;\n };\n}\n\n/**\n * An explicit chain of joins, for links the four shapes above cannot express:\n * multi-hop paths, composite keys, or a join whose condition is not a plain\n * foreign key.\n *\n * Read-only. Rebase will not infer how to write through an arbitrary join\n * chain, and guessing is what this type exists to stop.\n *\n * ```ts\n * permissions: {\n * kind: \"via\",\n * target: () => permissions,\n * cardinality: \"many\",\n * joinPath: [\n * { table: \"user_roles\", on: { from: \"id\", to: \"user_id\" } },\n * { table: \"role_permissions\", on: { from: \"role_id\", to: \"role_id\" } },\n * { table: \"permissions\", on: { from: \"permission_id\", to: \"id\" } }\n * ]\n * }\n * ```\n * @group Models\n */\nexport interface ViaRelation extends RelationBase {\n kind: \"via\";\n /** Whether the chain yields one row or many. Cannot be derived from a join chain. */\n cardinality: \"one\" | \"many\";\n /**\n * The joins, in order, from this collection's table to the target's.\n *\n * Each step names a table and the columns to join it on; the last step's\n * table is the target. Read-only, because Rebase will not work out how to\n * write through an arbitrary chain, and guessing is what this kind exists to\n * stop.\n */\n joinPath: JoinStep[];\n}\n\n/**\n * A link from one collection to another, as authored.\n *\n * A closed union: pick the kind that describes the link and the type offers\n * exactly the fields that kind needs. See {@link ResolvedRelation} for the form\n * the runtime works with, which has every default filled in.\n *\n * @group Models\n */\nexport type Relation =\n | BelongsToRelation\n | HasOneRelation\n | HasManyRelation\n | ManyToManyRelation\n | ViaRelation;\n\n/**\n * A relation with every default filled in — the form the runtime works with.\n *\n * The authored {@link Relation} and this are deliberately different types.\n * They used to be one, which meant no reader could tell which fields had been\n * supplied and which had been guessed, and so every consumer re-derived what it\n * needed with its own chain of `if (through) … else if (localKey) …` fallbacks.\n * Those chains disagreed with each other; that disagreement is what produced\n * silently wrong reads and corrupt writes.\n *\n * Here each variant carries exactly its own fields, all required. A consumer\n * switches on `kind` and gets what it needs without a fallback, and the cases\n * it forgot are a compile error rather than a wrong answer at runtime.\n *\n * @group Models\n */\nexport type ResolvedRelation =\n | ResolvedBelongsTo\n | ResolvedHasOne\n | ResolvedHasMany\n | ResolvedManyToMany\n | ResolvedVia;\n\n/** Fields present on every resolved relation. @group Models */\nexport interface ResolvedRelationBase {\n /** Always set: defaulted during resolution if the author omitted it. */\n relationName: string;\n /**\n * The collection on the other end.\n *\n * Still a thunk — a relation between two collections that import each other\n * has to be — but normalised: resolution unwraps the module namespace the\n * author's `() => import(…)` may hand back, so every consumer gets the\n * config and not a `{ default: … }` wrapper.\n */\n target: () => AnyCollectionConfig;\n /** The target's slug, resolved once so consumers need not call `target()`. */\n targetSlug: string;\n /** As authored — see {@link RelationBase.onUpdate}. Defaults are not filled in. */\n onUpdate?: OnAction;\n /**\n * As authored — see {@link RelationBase.onDelete}. `undefined` here means\n * the author said nothing, and the DDL generator picks the default; it does\n * **not** mean \"no action\".\n */\n onDelete?: OnAction;\n /** Presentation overrides applied when this relation is rendered as a tab. */\n overrides?: Partial<AnyCollectionConfig>;\n /**\n * Whether one row or many come back. Derived from `kind` — kept because it\n * is what most consumers actually branch on, and because `via` is the one\n * kind where it is authored rather than implied.\n */\n cardinality: \"one\" | \"many\";\n /**\n * Whether Rebase knows how to write through this link. False only for\n * {@link ResolvedVia}, whose join chain it will not invent a write for.\n */\n writable: boolean;\n /**\n * Whether the target rows are shared with other parents. True for\n * many-to-many and for multi-hop `via`: what the parent owns is the link,\n * so removing one must not delete the row.\n */\n shared: boolean;\n}\n\n/** @group Models */\nexport interface ResolvedBelongsTo extends ResolvedRelationBase {\n kind: \"belongsTo\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on this collection's table. */\n localKey: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasOne extends ResolvedRelationBase {\n kind: \"hasOne\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n /** @see ResolvedHasMany.sourceKey */\n sourceKey?: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasMany extends ResolvedRelationBase {\n kind: \"hasMany\";\n cardinality: \"many\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n /**\n * Column on the source's table that `foreignKeyOnTarget` points at, or\n * `undefined` for the source's primary key.\n *\n * The one optional field on a resolved relation, and deliberately so. Every\n * other default is filled in here because it can be: a table name and a\n * column name are derivable from the relation and its two endpoints alone.\n * The primary key is not — this driver resolves it from `isId`, then the\n * Drizzle schema, then a column named `id`, and the middle tier does not\n * exist at resolution time.\n *\n * So `undefined` is a sentinel with exactly one meaning, not a field a\n * consumer is invited to guess at. Read it through `sourceKeyField()`,\n * which is the only place that turns it into a column name.\n */\n sourceKey?: string;\n}\n\n/** @group Models */\nexport interface ResolvedManyToMany extends ResolvedRelationBase {\n kind: \"manyToMany\";\n cardinality: \"many\";\n writable: true;\n shared: true;\n /**\n * The junction table and its two key columns, with every default filled in:\n * the table from both table names sorted and joined, the columns from each\n * endpoint's slug.\n */\n through: {\n table: string;\n sourceColumn: string;\n targetColumn: string;\n /**\n * The payload columns as authored, or `{}` when there are none —\n * never `undefined`, so a consumer reads one shape.\n * See {@link ManyToManyRelation.through}.\n */\n properties: Properties;\n };\n}\n\n/** @group Models */\nexport interface ResolvedVia extends ResolvedRelationBase {\n kind: \"via\";\n writable: false;\n /** The chain as authored — see {@link ViaRelation.joinPath}. Nothing to default. */\n joinPath: JoinStep[];\n}\n\n// ── Narrowing helpers ────────────────────────────────────────────────\n//\n// Consumers that only care about one axis — \"does this list many rows\",\n// \"is there a column on the target\" — should ask that question rather\n// than enumerate kinds, so adding a kind later does not silently skip them.\n\n/** Relations whose target row carries this collection's key. @group Models */\nexport type ResolvedForeignKeyOnTarget = ResolvedHasOne | ResolvedHasMany;\n\n/** @group Models */\nexport function hasForeignKeyOnTarget(relation: ResolvedRelation): relation is ResolvedForeignKeyOnTarget {\n return relation.kind === \"hasOne\" || relation.kind === \"hasMany\";\n}\n\n/** @group Models */\nexport function isManyToMany(relation: ResolvedRelation): relation is ResolvedManyToMany {\n return relation.kind === \"manyToMany\";\n}\n\n/** @group Models */\nexport function isToMany(relation: ResolvedRelation): boolean {\n return relation.cardinality === \"many\";\n}\n\n/**\n * Defines a single, explicit step in a multi-join path.\n *\n * Each step represents one JOIN operation in the sequence. The `from` columns\n * refer to the previous table in the chain (or the source table for the first step),\n * and the `to` columns refer to the current table being joined.\n *\n * @example Single column join:\n * ```typescript\n * {\n * table: \"authors\",\n * on: {\n * from: \"author_id\", // Column from previous table (e.g., posts.author_id)\n * to: \"id\" // Column from current table (authors.id)\n * }\n * }\n * ```\n *\n * @example Multi-column composite key join:\n * ```typescript\n * {\n * table: \"order_items\",\n * on: {\n * from: [\"order_id\", \"store_id\"], // Multiple columns from previous table\n * to: [\"order_id\", \"store_id\"] // Corresponding columns in current table\n * }\n * }\n * ```\n */\nexport interface JoinStep {\n /**\n * The database table name to join TO in this step.\n * This is the table you're joining into, not the table you're joining from.\n *\n * @example \"authors\", \"user_roles\", \"product_categories\"\n */\n table: string;\n\n /**\n * The join condition for this step. Defines how the previous table\n * connects to the current table.\n *\n * - `from`: Column name(s) on the PREVIOUS table in the join chain\n * - `to`: Column name(s) on the CURRENT table (specified in `table`)\n *\n * For the first step, `from` refers to the source collection's table.\n * For subsequent steps, `from` refers to the table from the previous step.\n *\n * Both `from` and `to` support:\n * - Single column: `\"user_id\"`\n * - Multiple columns: `[\"company_id\", \"region_id\"]` for composite keys\n *\n * When using arrays, both `from` and `to` must have the same length,\n * and columns are matched by position (index 0 with index 0, etc.).\n */\n on: {\n from: string | string[];\n to: string | string[];\n };\n}\n","import { CollectionConfig, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, isToMany, ResolvedRelation, VectorProperty, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from \"@rebasepro/types\";\nimport { effectiveAccess, fieldKeyForColumn, findRelation, getTenantConfig, isRelationRequired, resolveCollectionRelations } from \"@rebasepro/common\";\n\n/**\n * OpenAPI 3.0.3 specification generator.\n *\n * Produces a spec that exactly mirrors the REST API consumed by the\n * Rebase SDK client (`@rebasepro/client`).\n *\n * Routes are mounted at `{basePath}/data/{slug}` by `initializeRebaseBackend`.\n */\n\nexport interface OpenApiGeneratorOptions {\n /** Base path for the API (e.g. \"/api\"). Defaults to \"/api\". */\n basePath?: string;\n /** Whether auth is enabled on data routes. Defaults to true. */\n requireAuth?: boolean;\n /**\n * The list-pagination bounds the REST layer applies, so the spec states the\n * ones a request will actually meet.\n *\n * These were hardcoded as `default: 20, maximum: 100` — neither of which\n * the server has ever used. The spec drives the API Explorer and is what a\n * generated client is built from, so an understated ceiling is a request\n * the client refuses to make, and an overstated one is a 400 nobody\n * predicted.\n */\n listLimits?: { defaultLimit?: number; maxLimit?: number };\n}\n\nexport function generateOpenApiSpec(\n collections: CollectionConfig[],\n options: OpenApiGeneratorOptions = {}\n): Record<string, unknown> {\n const basePath = options.basePath ?? \"/api\";\n const requireAuth = options.requireAuth ?? true;\n const defaultLimit = options.listLimits?.defaultLimit ?? DEFAULT_LIST_LIMIT;\n const maxLimit = options.listLimits?.maxLimit ?? MAX_LIST_LIMIT;\n\n /**\n * The query parameters every list endpoint honours.\n *\n * Written once because it was written twice: the root listing named eight\n * and the subcollection listing named four, though both go through the same\n * `parseQueryOptions` and the same fetch. Four capabilities were therefore\n * unreachable from a generated client on nested routes, and `or`/`and` were\n * undocumented on both.\n */\n const listQueryParameters = () => [\n { name: \"limit\", in: \"query\", schema: { type: \"integer\", default: defaultLimit, minimum: 1, maximum: maxLimit },\n description: `Maximum number of records to return. Must be a whole number between 1 and ${maxLimit}; a larger one is rejected with 400 INVALID_LIMIT rather than trimmed, so a short page always means a short collection. Page past the ceiling with \\`offset\\`.` },\n { name: \"offset\", in: \"query\", schema: { type: \"integer\", default: 0 },\n description: \"Number of records to skip\" },\n { name: \"page\", in: \"query\", schema: { type: \"integer\", minimum: 1 },\n description: \"Page number (alternative to offset). Calculates offset as (page-1)*limit\" },\n {\n name: \"after\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Keyset cursor: continue after the row the previous page ended on. Pass back \"\n + \"`meta.nextCursor` from that response, unchanged — it is opaque, and encodes both the \"\n + \"sort keys and the last row's values for them. Unlike `offset`, a row inserted or \"\n + \"deleted before the cursor cannot shift the window, so a walk neither repeats nor \"\n + \"skips rows. Cannot be combined with `offset`/`page` (400 CURSOR_WITH_OFFSET), and an \"\n + \"`orderBy` different from the one the cursor was issued under is refused \"\n + \"(400 CURSOR_ORDER_MISMATCH) rather than seeked in an order nobody asked for.\"\n },\n {\n name: \"orderBy\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Sort field and direction. Accepts `field:asc`, `field:desc`, or `field:desc:last` — \"\n + \"the third segment places NULLs (`first`/`last`), defaulting to Postgres's own \"\n + \"convention (last ascending, first descending). Also accepts a JSON array \"\n + \"`[{\\\"field\\\":\\\"name\\\",\\\"direction\\\":\\\"asc\\\",\\\"nulls\\\":\\\"last\\\"}]` — several entries sort by \"\n + \"each in turn, the second breaking ties on the first.\",\n example: \"created_at:desc:last\"\n },\n {\n name: \"where\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"JSON object filter, mapping each field to a `[operator, value]` tuple. \"\n + \"Combines with the per-field `?field=op.value` parameters below; on the same field, the per-field parameter wins.\",\n example: \"{\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\"\n },\n {\n name: \"or\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Disjunction of conditions, AND-ed with `where` and `searchString`.\",\n example: \"(status.eq.draft,status.eq.review)\"\n },\n {\n name: \"and\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Conjunction of conditions, AND-ed with `where` and `searchString`. Ignored when `or` is also present.\",\n example: \"(views.gte.10,status.eq.draft)\"\n },\n {\n name: \"not\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Negation, AND-ed with `where` and `searchString`. Negates the **conjunction** of its \"\n + \"conditions: `not(a)` is `NOT a`, `not(a,b)` is `NOT (a AND b)`. Groups nest, so \"\n + \"`not(or(a,b))` is the De Morgan case. Compiles to a real SQL `NOT (...)`, which — \"\n + \"three-valued logic — also excludes rows whose column is NULL. Ignored when `or` or \"\n + \"`and` is also present.\",\n example: \"(status.eq.draft,views.gte.10)\"\n },\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load, in either of two spellings. **Comma-separated names or dotted \"\n + \"paths** — `author,comments.author`, up to 3 hops deep; `*` loads every relation one \"\n + \"hop deep. **JSON**, when a relation needs narrowing — \"\n + \"`{\\\"comments\\\":{\\\"limit\\\":5,\\\"where\\\":{\\\"published\\\":[\\\"==\\\",true]},\"\n + \"\\\"orderBy\\\":\\\"created_at:desc\\\",\\\"fields\\\":\\\"id,body\\\",\\\"include\\\":{\\\"author\\\":true}}}`. \"\n + \"A value starting with `{` is read as the JSON form. A name that is not a relation of \"\n + \"the collection is a 400 UNKNOWN_RELATION, not a silently missing field.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return. A projection pushed into the SELECT, so a query \"\n + \"that needs two fields of a wide row reads two columns. The primary key is always \"\n + \"returned (a row that cannot be addressed cannot be updated, deleted, or paged past), \"\n + \"and `excludeFromApi` columns stay hidden whether or not they are named here. An \"\n + \"unknown column is a 400 UNKNOWN_FIELD.\",\n example: \"id,name,created_at\"\n },\n {\n name: \"distinct\",\n in: \"query\",\n schema: { type: \"boolean\" },\n description:\n \"`SELECT DISTINCT` over the returned columns. Only meaningful alongside `fields`: the \"\n + \"primary key is always in the projection, so without narrowing it every row is \"\n + \"already distinct. `meta.total` counts distinct rows too. Refused (400) alongside \"\n + \"`searchString` or a vector search, which attach a per-row score that makes every row \"\n + \"distinct by construction, and (400 DISTINCT_ORDER_BY_NOT_SELECTED) when `orderBy` \"\n + \"names a column `fields` does not return.\",\n example: \"true\"\n },\n {\n name: \"searchString\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Text search. By default a case-insensitive substring match OR-ed across the \" +\n \"collection's top-level string properties. A collection declaring a `search` block \" +\n \"gets ranked full-text matching over the fields it names, and rows carry a `_score`.\"\n },\n // Vector search has been served here since vectors landed and was\n // documented nowhere, so the only way to find it was to read the query\n // parser. All four are needed together; `vector_search` and `vector`\n // are ignored unless both are present.\n {\n name: \"vector_search\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Name of the `vector` property to run a nearest-neighbour search against. Requires `vector`.\",\n example: \"embedding\"\n },\n {\n name: \"vector\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"The query embedding, as a JSON array of numbers. Its length must match the property's declared `dimensions`.\",\n example: \"[0.12,-0.04,0.98]\"\n },\n {\n name: \"vector_distance\",\n in: \"query\",\n schema: { type: \"string\", enum: [\"cosine\", \"l2\", \"inner_product\"], default: \"cosine\" },\n description: \"Distance function used for ordering.\"\n },\n {\n name: \"vector_threshold\",\n in: \"query\",\n schema: { type: \"number\" },\n description: \"Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field.\"\n }\n ];\n\n const spec: Record<string, unknown> = {\n openapi: \"3.0.3\",\n info: {\n title: \"Rebase API\",\n version: \"1.0.0\",\n description:\n \"Auto-generated REST API from Rebase collection definitions. \" +\n \"This is the same API consumed by the `@rebasepro/client` SDK.\"\n },\n servers: [\n {\n url: basePath,\n description: \"API Server\"\n }\n ],\n paths: {} as Record<string, unknown>,\n components: {\n schemas: {\n ErrorResponse: {\n type: \"object\",\n properties: {\n error: {\n type: \"object\",\n required: [\"message\", \"code\"],\n properties: {\n message: { type: \"string\" },\n code: { type: \"string\" },\n details: {}\n }\n }\n }\n },\n PaginationMeta: {\n type: \"object\",\n properties: {\n total: { type: \"integer\",\ndescription: \"Total number of matching records\" },\n limit: { type: \"integer\",\ndescription: \"Page size used for this query\" },\n offset: { type: \"integer\",\ndescription: \"Number of records skipped\" },\n hasMore: { type: \"boolean\",\ndescription: \"Whether more records exist beyond this page\" },\n nextCursor: {\n type: \"string\",\n description:\n \"Opaque keyset cursor continuing this listing — pass it back as `?after=`. \"\n + \"Present when `hasMore` is true and the page returned at least one row; \"\n + \"absent on the last page and on an ordering no cursor can describe \"\n + \"(relevance, whose scores are computed per query and not stored). Do not \"\n + \"parse it: the encoding exists to be changed.\"\n }\n }\n }\n } as Record<string, unknown>,\n securitySchemes: {} as Record<string, unknown>\n },\n tags: [] as Array<{ name: string; description?: string }>\n };\n\n // ── Security Schemes ─────────────────────────────────────────────────\n if (requireAuth) {\n (spec.components as Record<string, unknown>).securitySchemes = {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description:\n \"JWT access token obtained from `POST /auth/login` or `POST /auth/register`. \" +\n \"Can also be a static service key for server-to-server authentication.\"\n }\n // No `?token=` scheme. It was declared here — globally, so on every\n // operation — and no data route has ever accepted one: both\n // `createAuthMiddleware` and `createAdapterAuthMiddleware` read the\n // `Authorization` header and nothing else, deliberately, because\n // URLs leak into access logs, proxies, Referer headers and browser\n // history (`auth/middleware.ts`). Following it cost a caller twice:\n // unauthenticated, *and* a 400, since `token` is not in the query\n // parser's `reservedQueryKeys` and so compiles as a filter on a\n // column named `token`. `queryTokenAuth` is real but is mounted\n // only on storage file serving, for `<img src>`; if those routes\n // are ever documented, the scheme belongs on them, per-operation.\n };\n (spec as Record<string, unknown>).security = [\n { bearerAuth: [] }\n ];\n }\n\n const paths = spec.paths as Record<string, unknown>;\n const schemas = (spec.components as Record<string, unknown>).schemas as Record<string, unknown>;\n const tags = spec.tags as Array<{ name: string; description?: string }>;\n\n // The names a listing has already spent. A collection is free to have a\n // `limit` or a `fields` column, and the query parser reads those names as\n // pagination and field selection before any filter is compiled — so the\n // per-field filter could never fire, and documenting it a second time put\n // two parameters with the same (`name`, `in`) pair on one operation, which\n // is invalid OpenAPI: Swagger UI renders a duplicate and several generators\n // abort. Taken from the parameter list itself so the two cannot drift.\n const reservedParameterNames = new Set(listQueryParameters().map(p => p.name));\n\n // Every component name this document will carry, known before the first\n // schema is built: a relation may point at a collection that appears later\n // in the list, or at one that is not documented here at all, and a `$ref`\n // at a component that does not exist is a document Swagger UI renders empty\n // and a strict generator refuses.\n const registeredSchemas = new Set((collections || []).map(schemaNameFor));\n\n /**\n * `Prefer: return=minimal`, on every route that would otherwise send a row\n * back. Documented rather than left implicit because a client generated\n * from this spec cannot send a header the spec does not mention.\n */\n const preferHeader = {\n name: \"Prefer\",\n in: \"header\",\n required: false,\n schema: { type: \"string\", enum: [\"return=minimal\"] },\n description:\n \"`return=minimal` asks the server not to send the written row back. Single writes then \" +\n \"answer `204 No Content`; bulk and batch writes answer `200` carrying the ids only. \" +\n \"The response repeats it in `Preference-Applied` when it was honoured.\"\n };\n\n const ifMatchHeader = {\n name: \"If-Match\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"The `ETag` this edit was made against, from the `GET` that read the row. The write is \" +\n \"refused with `412` if the row has changed since — which is the difference between \" +\n \"\\\"update the row I read\\\" and \\\"overwrite whatever is there now\\\". `*` means only that \" +\n \"the row must exist.\"\n };\n\n const preconditionFailed = {\n 412: {\n description:\n \"The row changed since the ETag in `If-Match` was issued. Nothing was written: \" +\n \"re-read the row, re-apply the change, and send the new ETag\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n const minimalResponse = {\n 204: {\n description: \"Written. `Prefer: return=minimal` was honoured, so there is no body\",\n headers: {\n \"Preference-Applied\": { schema: { type: \"string\" }, description: \"`return=minimal`\" }\n }\n }\n };\n\n // ── POST /data/_batch — writes across collections, one transaction ────\n //\n // Registered before the per-collection paths for the same reason the route\n // is: `_batch` is not a collection, and reading it as one would document a\n // table that does not exist.\n if ((collections || []).length > 0) {\n paths[\"/data/_batch\"] = {\n post: {\n tags: [\"Data\"],\n summary: \"Write across collections in one transaction\",\n description:\n \"All-or-nothing across collections — an order and its line items, a user and \" +\n \"their membership row. `/bulk` is one collection at a time, and sending the two \" +\n \"halves as separate requests is exactly the sequence that can half-succeed.\\n\\n\" +\n \"Operations run in order, each through the same pipeline its single-row route \" +\n \"uses: the same validation, callbacks and row-level security, as the same role. \" +\n \"An operation may name itself with `ref`, and a later one may stand \" +\n \"`{ \\\"$ref\\\": \\\"order.id\\\" }` wherever a value goes — in `values`, at any depth, \" +\n \"or as an `id`. Only backward references resolve.\\n\\n\" +\n \"Capped at the same number of entries as a bulk write, because one batch is one \" +\n \"transaction and holds its locks for the whole of it.\",\n operationId: \"batchWrite\",\n parameters: [\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this batch so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot tell a replay from a second batch, and \" +\n \"the whole batch is written twice.\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"operations\"],\n properties: {\n operations: {\n type: \"array\",\n items: { $ref: \"#/components/schemas/BatchOperation\" }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description:\n \"One entry per operation, in order: the written row for a create, update or \" +\n \"upsert, and `null` for a delete\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { type: \"object\", nullable: true } },\n meta: { type: \"object\", properties: { operations: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 400: {\n description:\n \"Malformed body, an unknown collection or field, an illegal field operation \" +\n \"or conflict target, a forward `$ref`, or more operations than the limit. \" +\n \"Nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 404: {\n description: \"An `update` or `delete` names a row that does not exist; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n schemas.FieldOperation = {\n type: \"object\",\n description:\n \"A change to the column's current value rather than the value to store. Exactly one \" +\n \"operator per field. Only on an update: an operation over a value that does not exist \" +\n \"yet is refused with a 400.\",\n properties: {\n $inc: { type: \"number\", description: \"Add to a `number` column; negative to subtract.\" },\n $push: { description: \"Append a value, or each of an array of values, to an `array` column.\" },\n $pull: { description: \"Remove every occurrence of a value from an `array` column.\" },\n $merge: { type: \"object\", description: \"Shallow-merge an object into a `map` column.\" }\n }\n };\n\n schemas.BatchOperation = {\n type: \"object\",\n required: [\"op\", \"collection\"],\n properties: {\n op: { type: \"string\", enum: [\"create\", \"update\", \"upsert\", \"delete\"] },\n collection: { type: \"string\", description: \"The collection slug this operation writes to.\" },\n id: {\n description:\n \"Required for `update` and `delete`. May instead be a reference marker — an \" +\n \"object whose single key is `$ref` and whose value is `<ref name>.<field>`, \" +\n \"e.g. `{ \\\"$ref\\\": \\\"order.id\\\" }` — naming a column of the row an earlier \" +\n \"operation wrote. (Described rather than declared as a schema: `$ref` is a \" +\n \"reserved word to every OpenAPI reader, and a property named `$ref` is read \" +\n \"as a reference and mangled.)\",\n oneOf: [{ type: \"string\" }, { type: \"integer\" }, { type: \"object\" }]\n },\n values: {\n type: \"object\",\n description:\n \"The row's fields. Values may be `$ref` markers; an `update` may also carry \" +\n \"field operations.\",\n additionalProperties: true\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"`upsert` only: the columns the conflict is matched on. Must carry a declared \" +\n \"uniqueness guarantee. Defaults to the primary key.\"\n },\n ref: {\n type: \"string\",\n description: \"Names this operation's result, so a later one can `$ref` its columns.\"\n }\n }\n };\n }\n\n // ── Collection routes ────────────────────────────────────────────────\n for (const collection of (collections || [])) {\n const schemaName = schemaNameFor(collection);\n const slug = collection.slug;\n\n tags.push({\n name: collection.name,\n description: collection.description || `CRUD operations for ${collection.name}`\n });\n\n // Build component schema for this collection\n schemas[schemaName] = buildCollectionSchema(collection, registeredSchemas);\n\n // Build an \"input\" schema (no read-only/auto fields like autoValue dates)\n schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);\n\n // The update body — same columns, no `required`. PATCH and PUT both\n // merge, so a field left out means \"unchanged\", not \"omitted by mistake\".\n schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);\n\n const dataPath = `/data/${slug}`;\n\n // ── GET /data/{slug}/count — How many rows match ──────────────\n //\n // Served for every collection since the route existed and described\n // here for the first time. A spec is what a generated client can see,\n // so an endpoint missing from it is an endpoint that client does not\n // have — and this is the one a paginating UI needs to know how many\n // pages there are.\n //\n // Registered before the list path so its literal segment cannot be read\n // as an `{id}`, which is the same ordering the router uses.\n paths[`${dataPath}/count`] = {\n get: {\n tags: [collection.name],\n summary: `Count ${collection.name}`,\n description:\n \"The number of rows the same filters would return, without returning them. \" +\n \"Takes the filter and search parameters of the list endpoint; `limit`, `offset` \" +\n \"and `orderBy` are not part of the question and are ignored.\",\n operationId: `count${schemaName}`,\n parameters: [\n ...listQueryParameters().filter(p => p.name === \"searchString\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"The number of matching rows\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"count\"],\n properties: {\n count: { type: \"integer\", description: \"Rows matching the filters\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug}/aggregate — count/sum/avg/min/max ────────\n //\n // Before the list path for the same reason `/count` is: a literal\n // segment that a generated client would otherwise be told is an `{id}`.\n paths[`${dataPath}/aggregate`] = {\n get: {\n tags: [collection.name],\n summary: `Aggregate ${collection.name}`,\n description:\n \"Aggregate values over the rows the same filters would return. Takes the filter and \" +\n \"search parameters of the list endpoint. Row-level security applies to the rows \" +\n \"being aggregated, so a caller who can read nothing counts nothing.\",\n operationId: `aggregate${schemaName}`,\n parameters: [\n {\n name: \"select\",\n in: \"query\",\n required: true,\n description:\n \"Comma-separated aggregates, e.g. `count()`, `sum(total)`, `avg(total),max(total)`. \" +\n \"Results are keyed `count`, `sum_total`, `avg_total` and so on.\",\n schema: { type: \"string\" },\n example: \"count(),sum(total)\"\n },\n {\n name: \"groupBy\",\n in: \"query\",\n required: false,\n description: \"Comma-separated fields to group by. Each grouped field is returned alongside the aggregates.\",\n schema: { type: \"string\" },\n example: \"status\"\n },\n ...listQueryParameters().filter(p => p.name === \"searchString\" || p.name === \"limit\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"One row per group, or a single row when `groupBy` is absent\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"data\"],\n properties: {\n data: {\n type: \"array\",\n items: { type: \"object\", additionalProperties: true }\n }\n }\n }\n }\n }\n },\n 501: { description: \"This backend's data driver does not implement aggregates\" },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug} — List entities ──────────────────────────\n paths[dataPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${collection.name}`,\n operationId: `list${schemaName}`,\n parameters: [\n ...listQueryParameters(),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"Paginated list of entities\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: { $ref: `#/components/schemas/${schemaName}` }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n },\n post: {\n tags: [collection.name],\n summary: `Create ${collection.singularName || collection.name}`,\n operationId: `create${schemaName}`,\n parameters: [\n {\n name: \"on_conflict\",\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to upsert on, turning the create into \" +\n \"INSERT ... ON CONFLICT DO UPDATE. They must carry a declared uniqueness \" +\n \"guarantee — `validation.unique`, a `unique` index, or the primary key — \" +\n \"or the request is refused with a 400 naming the targets that do exist. \" +\n \"Left off, this is a plain insert and a duplicate key still raises.\",\n example: \"email\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Input` }\n }\n }\n },\n responses: {\n 201: {\n description: \"Created entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n ...minimalResponse,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── Bulk: one transaction, all-or-nothing ─────────────────────\n //\n // These went undocumented while they existed, which is the same defect\n // the update verb had: an endpoint the server serves and the spec does\n // not mention cannot be reached by a generated client at all.\n const idempotencyHeader = {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this write so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot distinguish a replay from a second \" +\n \"genuine batch, and the whole batch is written twice. A key names one request: \" +\n \"re-send the identical request to replay its answer, and use a new key for a \" +\n \"different one.\"\n };\n\n const bulkErrors = {\n 400: {\n description: \"Malformed body, an unknown field, or more rows than the per-batch limit\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description:\n \"A request with the same Idempotency-Key is still in flight. Retry it: the \" +\n \"first attempt's result is replayed once it lands\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n };\n\n paths[`/data/${slug}/bulk`] = {\n post: {\n tags: [collection.name],\n summary: `Create many ${collection.name} in one transaction`,\n description:\n \"All-or-nothing: if any row is rejected none of them land, and the error names \" +\n \"the offending index. Every row still runs callbacks, relations and row-level \" +\n \"security. Capped server-side because one batch holds its locks for its whole \" +\n \"duration.\",\n operationId: `createMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"rows\"],\n properties: {\n rows: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}Input` } },\n upsert: {\n type: \"boolean\",\n description: \"Write each row as INSERT ... ON CONFLICT DO UPDATE.\"\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"The columns the conflict is matched on, instead of the primary key. \" +\n \"They must carry a declared uniqueness guarantee. Naming them without \" +\n \"`upsert: true` is a 400 rather than a silently ignored field.\"\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The written rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n ...bulkErrors\n }\n },\n patch: {\n tags: [collection.name],\n summary: `Update many ${collection.name} in one transaction`,\n description:\n \"Each entry names its row and the fields to change. `{ id, data }` rather than \" +\n \"flat rows carrying their own key, because on a table keyed on something other \" +\n \"than `id` a flat row cannot say whether a column is the address or a value to \" +\n \"write. An id matching no row fails the batch.\",\n operationId: `updateMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"updates\"],\n properties: {\n updates: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"id\", \"data\"],\n properties: {\n id: { type: \"string\", description: \"The row to update\" },\n data: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The updated rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n paths[`/data/${slug}/bulk/delete`] = {\n post: {\n tags: [collection.name],\n summary: `Delete many ${collection.name} in one transaction`,\n description:\n \"A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but \" +\n \"widely dropped by proxies and CDNs, and several generators ignore \" +\n \"`requestBody` on a DELETE operation — a generated client would send the \" +\n \"request with no ids at all. Takes ids rather than a filter: a mistyped \" +\n \"condition that empties a table cannot be reviewed at the call site the way \" +\n \"an explicit list can. `beforeDelete`/`afterDelete` fire per row.\",\n operationId: `deleteMany${schemaName}`,\n parameters: [idempotencyHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"ids\"],\n properties: {\n ids: {\n type: \"array\",\n items: { oneOf: [{ type: \"string\" }, { type: \"integer\" }] }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"How many rows were deleted\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n meta: { type: \"object\", properties: { deleted: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was deleted\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n // ── GET/PUT/DELETE /data/{slug}/{id} ──────────────────────────\n const entityPath = `/data/${slug}/{id}`;\n paths[entityPath] = {\n get: {\n tags: [collection.name],\n summary: `Get ${collection.singularName || collection.name} by ID`,\n operationId: `get${schemaName}ById`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n // The same two parameters the list route documents, and the\n // same code serves them — one row and a page of them go\n // through one pipeline, so anything true of `include` or\n // `fields` there is true here.\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load: comma-separated names or dotted paths \"\n + \"(`author,comments.author`, up to 3 hops), `*` for all one hop deep, or the \"\n + \"JSON form for per-relation `limit`/`where`/`orderBy`/`fields`. An unknown \"\n + \"name is a 400 UNKNOWN_RELATION.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return, as a SELECT projection. The primary key \"\n + \"always survives and `excludeFromApi` columns stay hidden.\",\n example: \"id,title\"\n }\n ],\n responses: {\n 200: {\n description: \"Entity found\",\n headers: {\n ETag: {\n schema: { type: \"string\" },\n description:\n \"This row's version. Send it back as `If-Match` on a later PATCH or \" +\n \"DELETE to have the write refused if the row has changed in between.\"\n }\n },\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n },\n patch: updateOperation(collection, schemaName, requireAuth, {\n ifMatchHeader,\n preferHeader,\n preconditionFailed,\n minimalResponse\n }),\n delete: {\n tags: [collection.name],\n summary: `Delete ${collection.singularName || collection.name}`,\n operationId: `delete${schemaName}`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n ifMatchHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this delete so a retry replays its answer. A delete replayed after \" +\n \"the first attempt committed would otherwise answer 404 — which an offline \" +\n \"queue reads as a permanent failure for a delete that in fact succeeded.\"\n }\n ],\n responses: {\n 204: { description: \"Deleted successfully\" },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...preconditionFailed,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n }\n\n // ── Subcollection routes ─────────────────────────────────────────────\n //\n // A second pass, after every collection's component schema exists. These\n // routes `$ref` the *target's* schema, and the first pass builds schemas in\n // array order — so doing this inline meant a subcollection whose target\n // appeared later in the list silently degraded to an untyped `object`.\n //\n // The names come from the *resolved* relations, not from the authored\n // `relations` array. `relationName` is optional at the authoring surface —\n // it defaults to the property key, or to the target's slug — so reading the\n // raw field skipped every relation that relied on the default, and missed\n // relations declared inline on a property entirely, since those are not in\n // the array. These are the same resolved names the nested-path router\n // matches, so the spec and the routes cannot drift apart.\n //\n // A to-one relation is left out. `posts/1/author` resolves, but it\n // addresses a single row, and documenting it as a paginated list would\n // describe a response shape the client never gets.\n for (const collection of (collections || [])) {\n const slug = collection.slug;\n const schemaName = schemaNameFor(collection);\n const relations = Object.values(resolveCollectionRelations(collection))\n .filter(isToMany);\n for (const relation of relations) {\n const relationName = relation.relationName;\n const targetCollection = relation.target();\n const targetSchema = schemaNameFor(targetCollection);\n\n const subPath = `/data/${slug}/{parentId}/${relationName}`;\n\n // Only add if the schema exists (target collection is also registered)\n paths[subPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,\n operationId: `list${schemaName}${toPascalCase(relationName)}`,\n parameters: [\n { name: \"parentId\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: `${collection.singularName || collection.name} ID` },\n // The nested list handler goes through the same\n // `parseQueryOptions` and the same fetch as the root\n // one, so it honours the same parameters. It documented\n // four of them.\n ...listQueryParameters()\n ],\n responses: {\n 200: {\n description: `List of related ${relationName}`,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: schemas[targetSchema]\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n }\n }\n\n return spec;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Is this property part of the shape the document may describe?\n *\n * One exclusion, and it is the document telling the truth about what the server\n * does: `excludeFromApi` is a server-side guarantee that the column \"is\n * stripped from every row the API serves, for every caller, including admins\n * and service keys\" — `stripExcluded` in the row pipeline enforces it. A schema\n * that lists such a column describes a field that is never present, and it\n * describes it to everyone: `/docs` is mounted on the app, not on the data\n * router, so it carries none of the auth middleware `{basePath}/data` does.\n * Every project scaffolded by `rebase init` published its `users` collection's\n * `passwordHash` and `emailVerificationToken` this way.\n *\n * A `relation` property used to be excluded here too, on the reasoning that the\n * property is virtual. It is — but the row is not empty where it stands: the\n * owning side carries a foreign key under a *wire* name (`authorId`), and a\n * read that includes the relation carries the target's row under the relation's\n * own name. Excluding both left `/api/docs` describing a strictly smaller\n * collection than `generated/sdk/database.types.ts` did for the same config.\n * Relations are now emitted by {@link emitRelationProperties}, under the same\n * keys and in the same order as the SDK's `Row`; the direct-property loops skip\n * them so the two passes cannot emit one key twice.\n *\n * Written as one predicate rather than a `continue` per loop because it kept\n * being fixed in one loop at a time: this is the same rule the SDK generator\n * applies to its `Row` type (`packages/codegen/src/generate-types.ts`).\n */\nfunction isDocumentedProperty(property: Property, direction: \"read\" | \"write\" = \"read\"): boolean {\n const access = effectiveAccess(property);\n return access?.[direction]?.length !== 0;\n}\n\n/**\n * The `x-rebase-access` annotation, or nothing for a field with no rule.\n *\n * A vendor extension rather than a schema keyword because OpenAPI has no way to\n * say \"this property is present for some callers\" — `readOnly` is about the\n * direction of a field, not about who. Generators ignore what they do not know,\n * so a client built from this spec still compiles; a *human* reading `/docs`, or\n * a gateway that wants to enforce the same rule at the edge, gets the role lists\n * verbatim. Emitted for the role case only: a field closed to everybody is\n * absent from the document entirely, which is a stronger statement.\n */\nfunction accessAnnotation(property: Property): Record<string, unknown> | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const annotation: Record<string, unknown> = {};\n if (access.read !== undefined) annotation.read = [...access.read];\n if (access.write !== undefined) annotation.write = [...access.write];\n return Object.keys(annotation).length > 0 ? annotation : undefined;\n}\n\n/** The sentence `x-rebase-access` deserves in prose, for a reader of `/docs`. */\nfunction accessDescription(property: Property): string | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const parts: string[] = [];\n const phrase = (roles: readonly string[]) =>\n roles.length === 0 ? \"nobody through the API\" : roles.map(r => `\\`${r}\\``).join(\", \") + \" (and `admin`)\";\n if (access.read !== undefined) parts.push(`readable by ${phrase(access.read)}`);\n if (access.write !== undefined) parts.push(`writable by ${phrase(access.write)}`);\n if (parts.length === 0) return undefined;\n return `Field access: ${parts.join(\"; \")}. A caller without the role does not receive the field at all — it is absent, not null.`;\n}\n\n/**\n * The keys `stripExcluded` deletes: the property name *and* its column name.\n *\n * Seeding the emitted set with both is what stops a foreign key derived from a\n * relation putting an `excludeFromApi` column back on the surface under its\n * other name. Same pair, same reason, as `excludedApiKeys` in the SDK\n * generator.\n */\nfunction excludedApiKeys(collection: CollectionConfig, direction: \"read\" | \"write\" = \"read\"): Set<string> {\n const excluded = new Set<string>();\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if (isDocumentedProperty(property as Property, direction)) continue;\n excluded.add(key);\n const columnName = (property as { columnName?: unknown }).columnName;\n if (typeof columnName === \"string\") excluded.add(columnName);\n }\n return excluded;\n}\n\n/**\n * The collection's primary key, as the key it is addressed by on the wire and\n * the property that declares it.\n *\n * `id` was a literal in three places — seeded before the read loop, assigned\n * after the input loop, inherited by the update schema from the input one — and\n * the two spellings disagreed: a declared `id: { type: \"number\" }` overwrote\n * the read seed and was overwritten by the input assignment, so the same field\n * was `integer` in `Post` and `string` in `PostInput`. One helper now answers\n * the question for all three.\n */\nfunction idPropertyEntry(collection: CollectionConfig | undefined): [string, Property] | undefined {\n for (const [key, property] of Object.entries(collection?.properties ?? {})) {\n if ((property as unknown as Record<string, unknown>)?.isId) return [key, property as Property];\n }\n return undefined;\n}\n\n/**\n * The schema of a primary key or of a foreign key pointing at one: the declared\n * property's own type, stripped of the field-level facts (description,\n * validation bounds) that belong to the column and not to a reference to it.\n *\n * Falls back to `string` for a collection that declares no primary key, which\n * is what every schema here assumed unconditionally before.\n */\nfunction idSchemaFor(collection: CollectionConfig | undefined): Record<string, unknown> {\n const declared = idPropertyEntry(collection);\n if (!declared) return { type: \"string\" };\n const converted = convertPropertyToSchema(declared[1]);\n const schema: Record<string, unknown> = { type: converted.type ?? \"string\" };\n if (converted.format) schema.format = converted.format;\n return schema;\n}\n\n/**\n * Relations resolve or they do not; a document is still owed for a collection\n * whose target thunk throws (a circular import, usually). The SDK generator\n * warns and carries on with no relation fields, and so does this — the\n * alternative is `/api/docs` 500ing for the whole project.\n */\nfunction resolveRelationsForDocument(collection: CollectionConfig): Record<string, ResolvedRelation> {\n try {\n return resolveCollectionRelations(collection);\n } catch {\n return {};\n }\n}\n\n/** Unwrap a target handed back as a module namespace — `() => import(\"./authors\")`. */\nfunction relationTarget(relation: ResolvedRelation): CollectionConfig | undefined {\n try {\n let target = relation.target() as CollectionConfig & { default?: CollectionConfig; __esModule?: boolean };\n if (target && (target.default || target.__esModule)) {\n target = (target.default ?? target) as typeof target;\n }\n return target;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The schema an *included* relation arrives as: the target's own row.\n *\n * `$ref` when the target is one of the collections this document describes, and\n * an open object when it is not — a dangling pointer makes Swagger UI render an\n * empty model and makes a strict generator abort, which is worse than a vague\n * one.\n */\nfunction includedRelationSchema(\n relation: ResolvedRelation,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const target = relationTarget(relation);\n const targetSchema = target ? schemaNameFor(target) : undefined;\n const item: Record<string, unknown> = targetSchema && registeredSchemas.has(targetSchema)\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" };\n return relation.cardinality === \"many\" ? { type: \"array\", items: item } : item;\n}\n\n/**\n * Emit a collection's relations onto a read schema: the foreign keys first,\n * then the relations themselves.\n *\n * The order and the keys are the SDK `Row`'s, deliberately — the parity test\n * next door compares the two key sets, and the only way that stays true is for\n * both to be derived the same way rather than kept in step by hand.\n *\n * A `belongsTo` reaches the wire under the *field* name of its local column\n * (`author_id` → `authorId`), which is what `fieldKeyForColumn` answers; a\n * relation addressed by the same name as its own foreign key is served as\n * either the scalar or the nested row depending on `include`, so it is\n * documented as both.\n */\nfunction emitRelationProperties(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n required: string[],\n emitted: Set<string>,\n registeredSchemas: ReadonlySet<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n for (const [relationKey, relation] of Object.entries(resolved)) {\n if (relation.kind !== \"belongsTo\" || !relation.localKey) continue;\n const fieldKey = fieldKeyForColumn(collection, relation.localKey);\n if (emitted.has(fieldKey)) continue;\n\n const foreignKey = idSchemaFor(relationTarget(relation));\n const shadowedByInclude = relationKey === fieldKey;\n properties[fieldKey] = shadowedByInclude\n ? { oneOf: [foreignKey, includedRelationSchema(relation, registeredSchemas)] }\n : { ...foreignKey, description: `Foreign key into \\`${relation.targetSlug}\\`` };\n emitted.add(fieldKey);\n\n if (isRelationRequired(collection, relation) && !shadowedByInclude) required.push(fieldKey);\n }\n\n for (const [key, relation] of Object.entries(resolved)) {\n if (emitted.has(key)) continue;\n properties[key] = includedRelationSchema(relation, registeredSchemas);\n emitted.add(key);\n }\n\n // A `relation` property whose relation did not resolve. Still a field of the\n // row, just not a precisely describable one.\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n if (emitted.has(key)) continue;\n properties[key] = { type: \"object\" };\n emitted.add(key);\n }\n}\n\n/**\n * Build the component schema for a collection (output / read shape).\n *\n * Every declared property except the ones {@link isDocumentedProperty} rules\n * out, plus the foreign keys and relations {@link emitRelationProperties} adds.\n */\nfunction buildCollectionSchema(\n collection: CollectionConfig,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n const properties: Record<string, unknown> = {\n [idKey]: { ...idSchemaFor(collection), description: \"Unique identifier\" }\n };\n const required: string[] = [idKey];\n const excluded = excludedApiKeys(collection);\n const emitted = new Set<string>(excluded);\n emitted.add(idKey);\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property)) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required && key !== idKey) {\n required.push(key);\n }\n }\n\n emitRelationProperties(collection, properties, required, emitted, registeredSchemas);\n annotateTenantField(collection, properties, \"read\");\n\n return {\n type: \"object\",\n required: required.length > 0 ? required : undefined,\n properties\n };\n}\n\n/**\n * Mark the tenant field, on whichever schema is being built.\n *\n * A vendor extension for the same reason `x-rebase-access` is one: OpenAPI has\n * no keyword for \"the server fills this in from who you are, and refuses a\n * value that is not yours\". `readOnly` is the closest and it is wrong — the\n * field *is* writable, by a caller sending their own tenant, and a bypass role\n * may send any. Generators ignore what they do not know, so a client built from\n * this document still compiles; a human reading `/docs`, or a gateway wanting\n * to enforce the same boundary at the edge, learns the field is special and\n * why.\n *\n * Applied after the property loops rather than inside `convertPropertyToSchema`\n * because tenancy is a fact about the *collection*, and that function is handed\n * a property with no idea which collection it came from.\n */\nfunction annotateTenantField(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n direction: \"read\" | \"write\"\n): void {\n const tenant = getTenantConfig(collection);\n if (!tenant) return;\n const schema = properties[tenant.field];\n if (!schema || typeof schema !== \"object\") return;\n\n const existing = (schema as { description?: unknown }).description;\n const sentence = direction === \"write\"\n ? \"The tenant this row belongs to. Omit it and the server stamps the tenant you are calling as; \" +\n \"send another tenant's and the write is refused with `TENANT_MISMATCH`. It cannot be changed \" +\n \"on an update (`TENANT_IMMUTABLE`).\"\n : \"The tenant this row belongs to. Rows of other tenants are not returned at all.\";\n\n properties[tenant.field] = {\n ...(schema as Record<string, unknown>),\n description: typeof existing === \"string\" && existing ? `${existing} — ${sentence}` : sentence,\n \"x-rebase-tenant\": true\n };\n}\n\n/**\n * The PATCH/PUT operation for `/data/{slug}/{id}`.\n *\n * Split out because both verbs serve it and they must not drift: the update\n * body is a **partial**, and describing it with the create schema was the bug\n * this replaces. `<Name>Input` marks every `validation.required` property as\n * required — correct for POST, wrong for an update, where omitting a field\n * means \"leave it alone\" rather than \"I forgot it\". A client generated from\n * that spec demanded fields the server does not, and a spec-validating gateway\n * would have rejected partial updates the server accepts.\n */\nfunction updateOperation(\n collection: CollectionConfig,\n schemaName: string,\n requireAuth: boolean,\n shared: {\n ifMatchHeader: Record<string, unknown>;\n preferHeader: Record<string, unknown>;\n preconditionFailed: Record<string, unknown>;\n minimalResponse: Record<string, unknown>;\n }\n): Record<string, unknown> {\n return {\n tags: [collection.name],\n summary: `Update ${collection.singularName || collection.name}`,\n description:\n \"Partial update: only the properties present in the body are written; the rest are left \" +\n \"unchanged.\\n\\n\" +\n \"A property's value may instead be a field operation — `{ \\\"views\\\": { \\\"$inc\\\": 1 } }`, \" +\n \"`{ \\\"tags\\\": { \\\"$push\\\": \\\"new\\\" } }`, `{ \\\"tags\\\": { \\\"$pull\\\": \\\"old\\\" } }`, \" +\n \"`{ \\\"meta\\\": { \\\"$merge\\\": { \\\"seen\\\": true } } }` — which is applied inside the \" +\n \"statement holding the row lock. That is the difference between a counter that is correct \" +\n \"under concurrency and one that silently loses increments, because expressing the same \" +\n \"change as a value means reading it first. `$inc` needs a `number` property, `$push`/`$pull` \" +\n \"an `array`, `$merge` a `map`; anything else is a 400. See the `FieldOperation` schema.\",\n operationId: `update${schemaName}`,\n parameters: [\n { name: \"id\", in: \"path\", required: true, schema: { type: \"string\" }, description: \"Entity ID\" },\n shared.ifMatchHeader,\n shared.preferHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this update so a retry replays its answer instead of applying the edit \" +\n \"again. A PATCH is not naturally idempotent — a field operation emphatically is \" +\n \"not — so a retry after a lost response applies it twice.\"\n }\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n },\n responses: {\n 200: {\n description: \"Updated entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: {\n description: \"Entity not found\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...shared.preconditionFailed,\n ...shared.minimalResponse,\n ...errorResponses(requireAuth)\n }\n };\n}\n\n/**\n * The update body: the create schema with `required` dropped.\n *\n * Derived rather than rebuilt so the two cannot describe different columns —\n * the only difference between creating and updating is which fields you must\n * supply, and that is exactly the one thing removed here.\n */\nfunction buildCollectionUpdateSchema(collection: CollectionConfig): Record<string, unknown> {\n const { required: _required, ...rest } = buildCollectionInputSchema(collection);\n return rest;\n}\n\n/**\n * Build an input schema (for POST/PUT) — excludes auto-generated fields.\n *\n * `excludeFromApi` columns are left out too, and the server now agrees: a write\n * naming one is refused (`write-validation.ts`), which is what the flag's name\n * and the generated SDK's own documentation always said. This comment used to\n * record that such a write was \"still accepted\" — the document was right and\n * the server was the thing that had not caught up.\n */\nfunction buildCollectionInputSchema(collection: CollectionConfig): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n // The write half, not the read half: a field readable by nobody but\n // writable by an `admin` belongs in the input body and not in the row, and\n // the two schemas used to share one exclusion set and so got both wrong.\n const excluded = excludedApiKeys(collection, \"write\");\n const emitted = new Set<string>(excluded);\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property, \"write\")) continue;\n\n // Skip auto-value date fields from the input schema\n if (property.type === \"date\" && property.autoValue) continue;\n\n // Skip auto-generated ID fields\n if (\"isId\" in property && property.isId && property.isId !== \"manual\" && property.isId !== true) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required) {\n required.push(key);\n }\n }\n\n // Allow explicit ID for create (optional). Typed from the declared primary\n // key rather than as a `string` literal: a serial `id` was `integer` on the\n // read schema and `string` here, for the same column, in every document the\n // generator has ever produced.\n if (!emitted.has(idKey)) {\n properties[idKey] = {\n ...idSchemaFor(collection),\n description: \"Optional: client-assigned ID. If omitted, the server generates one.\"\n };\n emitted.add(idKey);\n }\n\n // The two ways a write may name a `belongsTo` target, both of which the\n // server accepts and the generated SDK's `Insert` already offered: the\n // foreign key under its own wire name (`authorId`), and the relation\n // property (`author`), which the write transformer maps onto that column.\n // Neither reached the document, so the spec described a create that could\n // not set a relation at all.\n emitWritableRelations(collection, properties, emitted);\n annotateTenantField(collection, properties, \"write\");\n\n // The tenant field is never required on input, whatever the property\n // declares: the column is NOT NULL, and the server is what fills it.\n // Listing it would tell every generated client to demand a value its caller\n // is not supposed to compute.\n const tenantField = getTenantConfig(collection)?.field;\n const requiredOnInput = tenantField ? required.filter(key => key !== tenantField) : required;\n\n return {\n type: \"object\",\n required: requiredOnInput.length > 0 ? requiredOnInput : undefined,\n properties\n };\n}\n\n/**\n * The writable half of a collection's relations: `belongsTo` only.\n *\n * A to-many is not writable through the body — the server links rows through\n * the nested routes — so offering `tags: [...]` on a create would describe a\n * write that does nothing. Same rule, same reason, as `emitWritableRelations`\n * in the SDK generator, whose `Insert` type this mirrors key for key.\n *\n * Neither spelling is listed in `required`, even for a relation the collection\n * declares `validation: { required: true }` on: the two keys are alternatives,\n * and a schema naming both would tell a spec-validating gateway to reject a\n * create that the server accepts. (The SDK's `Insert` marks both non-optional\n * for the same relation, which is the same fact stated less carefully; a\n * document is the half that a gateway enforces.)\n */\nfunction emitWritableRelations(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n emitted: Set<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n const emit = (key: string, relation: ResolvedRelation): void => {\n if (emitted.has(key)) return;\n properties[key] = {\n ...idSchemaFor(relationTarget(relation)),\n description: `The \\`${relation.targetSlug}\\` row this belongs to.`\n };\n emitted.add(key);\n };\n\n for (const relation of Object.values(resolved)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n emit(fieldKeyForColumn(collection, relation.localKey), relation);\n }\n }\n\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const relation = findRelation(resolved, key);\n if (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n }\n}\n\n/**\n * Convert a Rebase Property to an OpenAPI 3.0 schema object.\n */\nfunction convertPropertyToSchema(property: Property): Record<string, unknown> {\n const schema = convertPropertyTypeToSchema(property);\n const annotation = accessAnnotation(property);\n if (!annotation) return schema;\n\n const sentence = accessDescription(property);\n return {\n ...schema,\n ...(sentence\n ? { description: schema.description ? `${schema.description} — ${sentence}` : sentence }\n : {}),\n \"x-rebase-access\": annotation\n };\n}\n\n/** The JSON Schema a property's *type* compiles to, before any access annotation. */\nfunction convertPropertyTypeToSchema(property: Property): Record<string, unknown> {\n const base: Record<string, unknown> = {};\n\n if (property.name) {\n base.description = property.name;\n }\n\n switch (property.type) {\n case \"string\": {\n const sp = property as StringProperty;\n base.type = \"string\";\n\n if (sp.enum) {\n const enumValues = resolveEnumValues(sp.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (sp.validation) {\n if (sp.validation.min !== undefined) base.minLength = sp.validation.min;\n if (sp.validation.max !== undefined) base.maxLength = sp.validation.max;\n if (sp.validation.length !== undefined) {\n base.minLength = sp.validation.length;\n base.maxLength = sp.validation.length;\n }\n if (sp.validation.matches !== undefined) {\n base.pattern = String(sp.validation.matches);\n }\n }\n\n if (sp.email) base.format = \"email\";\n if (sp.url) base.format = \"uri\";\n if (sp.storage) base.format = \"uri\";\n\n return base;\n }\n\n case \"number\": {\n const np = property as NumberProperty;\n // `isId` is on this list because the DDL generator puts it there:\n // a numeric primary key is `INTEGER GENERATED BY DEFAULT AS\n // IDENTITY` for `\"increment\"` and `INTEGER` for every other form\n // (`generate-postgres-ddl-logic.ts`), so the column the scaffold's\n // `posts.id` creates is an integer and the document called it a\n // `number` — which lets a generated client send `1.5` for a row id.\n const isInteger = np.validation?.integer\n || Boolean(np.isId)\n || np.columnType === \"integer\" || np.columnType === \"serial\"\n || np.columnType === \"bigserial\" || np.columnType === \"bigint\";\n base.type = isInteger ? \"integer\" : \"number\";\n\n if (np.enum) {\n const enumValues = resolveEnumValues(np.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (np.validation) {\n if (np.validation.min !== undefined) base.minimum = np.validation.min;\n if (np.validation.max !== undefined) base.maximum = np.validation.max;\n if (np.validation.moreThan !== undefined) {\n base.minimum = np.validation.moreThan;\n base.exclusiveMinimum = true;\n }\n if (np.validation.lessThan !== undefined) {\n base.maximum = np.validation.lessThan;\n base.exclusiveMaximum = true;\n }\n }\n\n return base;\n }\n\n case \"boolean\":\n base.type = \"boolean\";\n return base;\n\n case \"date\": {\n base.type = \"string\";\n if (property.mode === \"date\") {\n base.format = \"date\";\n } else {\n base.format = \"date-time\";\n }\n if (property.autoValue) {\n base.readOnly = true;\n base.description = (base.description || \"\") +\n (property.autoValue === \"on_create\" ? \" (Auto-set on creation)\" : \" (Auto-updated)\");\n }\n return base;\n }\n\n case \"geopoint\":\n base.type = \"object\";\n base.properties = {\n latitude: { type: \"number\" },\n longitude: { type: \"number\" }\n };\n base.required = [\"latitude\", \"longitude\"];\n return base;\n\n case \"reference\":\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Reference ID)\";\n return base;\n\n case \"array\": {\n const ap = property as ArrayProperty;\n base.type = \"array\";\n\n if (ap.oneOf) {\n // Discriminated union (e.g., content blocks)\n const typeField = ap.oneOf.typeField || \"type\";\n const valueField = ap.oneOf.valueField || \"value\";\n const variants: Record<string, unknown>[] = [];\n\n for (const [variantKey, variantProp] of Object.entries(ap.oneOf.properties)) {\n variants.push({\n type: \"object\",\n properties: {\n [typeField]: { type: \"string\",\nenum: [variantKey] },\n [valueField]: convertPropertyToSchema(variantProp)\n },\n required: [typeField, valueField]\n });\n }\n\n base.items = { oneOf: variants };\n } else if (ap.of) {\n if (Array.isArray(ap.of)) {\n base.items = { oneOf: ap.of.map(p => convertPropertyToSchema(p)) };\n } else {\n base.items = convertPropertyToSchema(ap.of);\n }\n } else {\n base.items = {};\n }\n\n if (ap.validation) {\n if (ap.validation.min !== undefined) base.minItems = ap.validation.min;\n if (ap.validation.max !== undefined) base.maxItems = ap.validation.max;\n }\n\n return base;\n }\n\n case \"map\": {\n const mp = property as MapProperty;\n base.type = \"object\";\n\n if (mp.properties) {\n const props: Record<string, unknown> = {};\n const req: string[] = [];\n\n for (const [key, subProp] of Object.entries(mp.properties)) {\n props[key] = convertPropertyToSchema(subProp);\n if (subProp.validation?.required) {\n req.push(key);\n }\n }\n\n base.properties = props;\n if (req.length > 0) base.required = req;\n } else if (mp.keyValue) {\n base.additionalProperties = true;\n }\n\n return base;\n }\n\n case \"vector\": {\n const vp = property as VectorProperty;\n base.type = \"array\";\n base.items = { type: \"number\" };\n base.description = (base.description || \"\") + ` (Vector(${vp.dimensions}))`;\n return base;\n }\n case \"binary\": {\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Binary/Base64)\";\n return base;\n }\n default:\n base.type = \"string\";\n return base;\n }\n}\n\n/**\n * Resolve EnumValues (array or record) into a flat array of enum values.\n */\nfunction resolveEnumValues(enumDef: Record<string | number, unknown> | Array<{ id: string | number }>): Array<string | number> {\n if (Array.isArray(enumDef)) {\n return enumDef.map(e => (typeof e === \"object\" && e !== null && \"id\" in e) ? e.id : e as string | number);\n }\n return Object.keys(enumDef).map(k => {\n // Preserve numeric keys as numbers\n const num = Number(k);\n return isNaN(num) ? k : num;\n });\n}\n\n/**\n * Build PostgREST-style filter parameters for a collection.\n * These are additional query parameters like `?status=eq.active&price=gte.100`.\n *\n * `excludeFromApi` columns are not offered: the server does filter on them, and\n * that is exactly the problem — a filter on a column no response can contain\n * answers questions about the value one row at a time, which is a worse\n * disclosure than the column name alone.\n */\nfunction buildFilterParameters(\n collection: CollectionConfig,\n reservedNames: ReadonlySet<string> = new Set()\n): Array<Record<string, unknown>> {\n const params: Array<Record<string, unknown>> = [];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (!isDocumentedProperty(property)) continue;\n // A `relation` property is not a column, so there is nothing to compare\n // against. The foreign key beside it is filterable and is still not\n // offered here — a separate gap from the schema one, and one the query\n // layer has to answer first.\n if (property.type === \"relation\") continue;\n if (property.type === \"map\" || property.type === \"array\" || property.type === \"geopoint\") {\n continue;\n }\n // A column whose name a list parameter already owns is unfilterable\n // over the wire — see `reservedParameterNames`.\n if (reservedNames.has(key)) continue;\n\n params.push({\n name: key,\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n `Filter by \\`${key}\\`. Supports PostgREST operators: ` +\n \"`eq.value`, `neq.value`, `gt.value`, `gte.value`, `lt.value`, `lte.value`, \" +\n \"`in.(a,b,c)`, `nin.(a,b,c)`, `cs.value` (array-contains), `csa.(a,b)` (array-contains-any). \" +\n \"Plain values imply equality.\",\n example: property.type === \"string\" ? \"eq.active\" : property.type === \"number\" ? \"gte.100\" : undefined\n });\n }\n\n return params;\n}\n\n/**\n * Standard error responses included on every endpoint.\n */\nfunction errorResponses(requireAuth: boolean): Record<string, unknown> {\n const responses: Record<string, unknown> = {\n 400: {\n description: \"Bad request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 500: {\n description: \"Internal server error\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n if (requireAuth) {\n responses[401] = {\n description: \"Authentication required or invalid token\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n responses[403] = {\n description: \"Insufficient permissions\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n }\n\n return responses;\n}\n\n/**\n * Prefix a noun with \"a\" or \"an\" based on its leading sound.\n */\nfunction withIndefiniteArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? \"an\" : \"a\"} ${noun}`;\n}\n\n/**\n * The component-schema name for a collection — and the stem of every\n * `operationId` and `$ref` that mentions it.\n *\n * `toPascalCase` keeps ASCII letters and digits and nothing else, so a name\n * written in a script that has none of them — a Cyrillic or Japanese\n * `singularName`, which the docs' six locales make ordinary rather than exotic\n * — reduced to the empty string. The schema was then stored under `\"\"` and\n * every reference to it read `#/components/schemas/`, an unresolvable pointer:\n * Swagger UI renders the model empty and a strict generator fails outright. So\n * fall through the names until one survives, and keep a constant as the floor.\n *\n * Two collections whose names PascalCase identically still share one component;\n * that needs a disambiguation rule, not a fallback.\n */\nfunction schemaNameFor(collection: CollectionConfig): string {\n return toPascalCase(collection.singularName || \"\")\n || toPascalCase(collection.name || \"\")\n || toPascalCase(collection.slug || \"\")\n || \"Collection\";\n}\n\n/**\n * Convert a string to PascalCase for schema names.\n */\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .split(\" \")\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"openapi-generator-DGyLbISS.js","names":[],"sources":["../../types/src/types/relations.ts","../src/api/openapi-generator.ts"],"sourcesContent":["import type { AnyCollectionConfig } from \"./collections\";\nimport type { Properties } from \"./properties\";\n\n/**\n * @group Models\n */\nexport type OnAction = \"cascade\" | \"restrict\" | \"no action\" | \"set null\" | \"set default\";\n\n/**\n * The key a junction row's own columns are carried under, in both directions.\n *\n * A read that includes a `manyToMany` relation serves each related row with its\n * link's columns nested here — `{ id: 5, name: \"ts\", _pivot: { role: \"owner\" } }`\n * — and a membership write may name the same key on an element to state what\n * the link should hold. One constant because the two have to be the same word:\n * a wire name that differs between the read and the write it round-trips\n * through is a shape no client can echo back.\n *\n * Leading underscore, like `_matches`: it reads as metadata about the row\n * rather than as one of its columns. A payload property may not be named\n * `_pivot` either — `checkJunctionPayload` refuses it — so the key means one\n * thing wherever it appears.\n *\n * @group Models\n */\nexport const JUNCTION_PIVOT_KEY = \"_pivot\";\n\n/**\n * What kind of link a relation is.\n *\n * The discriminant. Every other field a relation carries belongs to exactly one\n * of these, which is the point: a relation used to be a single open interface\n * where `cardinality`, `direction`, `localKey`, `foreignKeyOnTarget`, `through`\n * and `joinPath` were all optional and any combination typechecked. Which link\n * you meant then had to be *inferred* from which fields you happened to set,\n * and the inference was ~200 lines that guessed, fell back on naming\n * conventions, and swallowed its own failures.\n *\n * Most of that guessing produced bugs rather than convenience. A `many`\n * relation carrying a `localKey` — a combination the old type permitted — made\n * the write path stamp the parent's own foreign key onto the child row. Under\n * these kinds that state cannot be written down.\n *\n * @group Models\n */\nexport type RelationKind = \"belongsTo\" | \"hasOne\" | \"hasMany\" | \"manyToMany\" | \"via\";\n\n/** Fields every relation carries, whatever its kind. @group Models */\nexport interface RelationBase {\n /**\n * The name this link is addressed by: the key in `include`, the tab in the\n * admin panel, and the path segment of a nested URL.\n *\n * Defaults to the declaring property's key, or to the target's slug for an\n * entry in `relations`.\n */\n relationName?: string;\n\n /** The collection on the other end. */\n target: () => AnyCollectionConfig;\n\n /**\n * What the database does to this side's foreign key when the target row's\n * key changes. Emitted as the constraint's `ON UPDATE`.\n *\n * Unset means no clause, which Postgres reads as `NO ACTION`. Set\n * `\"cascade\"` when the target's key is a natural key that can be edited —\n * a slug, a SKU — so the pointers follow it.\n *\n * Only a `belongsTo` puts the key on this table, so this is the only kind\n * where the clause is written here; on the other kinds it describes the\n * constraint the target's own column carries.\n */\n onUpdate?: OnAction;\n /**\n * What the database does to this side's rows when the target row is\n * deleted. Emitted as the constraint's `ON DELETE`.\n *\n * Defaults, when unset, to `\"set null\"` for an optional relation and\n * **`\"restrict\"`** for a required one. `NOT NULL` says a child cannot exist\n * without a parent; it does not say deleting the parent should delete the\n * child. Ask for `\"cascade\"` when that is what you mean — it is the one\n * value that destroys rows you did not name.\n *\n * A `manyToMany` is the exception: its junction rows default to\n * `\"cascade\"`, because the row deleted there is the link and not the target.\n */\n onDelete?: OnAction;\n\n /**\n * Presentation overrides applied when this relation is rendered as a tab.\n *\n * Whether the link is *required* is not here: it is\n * `validation: { required: true }` on the declaring property, the same key\n * every other field uses. A relation carried its own copy until 0.18, and\n * the two disagreed by construction — the DDL generator read the property\n * (so the column was `NOT NULL`) while codegen read the relation (so the\n * generated `Insert` type made it optional), and a `create()` that\n * typechecked failed at the database.\n */\n overrides?: Partial<AnyCollectionConfig>;\n}\n\n/**\n * This collection holds the foreign key. One target row per source row.\n *\n * ```ts\n * author: { kind: \"belongsTo\", target: () => authors, localKey: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface BelongsToRelation extends RelationBase {\n kind: \"belongsTo\";\n /**\n * Column on **this** collection's table holding the target's key.\n * Defaults to `<relationName>_id`.\n */\n localKey?: string;\n}\n\n/**\n * The target holds the foreign key, and at most one target row points back.\n *\n * ```ts\n * profile: { kind: \"hasOne\", target: () => profiles, foreignKeyOnTarget: \"user_id\" }\n * ```\n * @group Models\n */\nexport interface HasOneRelation extends RelationBase {\n kind: \"hasOne\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n /**\n * Column on **this** collection's table whose value `foreignKeyOnTarget`\n * holds. Defaults to this collection's primary key.\n *\n * Set it when the two sides are joined on a natural key rather than on the\n * row id — an external identity id, a SKU, a tenant slug. See\n * {@link HasManyRelation.sourceKey}, which this mirrors.\n */\n sourceKey?: string;\n}\n\n/**\n * The target holds the foreign key, and many target rows point back. The\n * children belong to this parent alone — deleting one deletes a row.\n *\n * ```ts\n * posts: { kind: \"hasMany\", target: () => posts, foreignKeyOnTarget: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface HasManyRelation extends RelationBase {\n kind: \"hasMany\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n /**\n * Column on **this** collection's table whose value `foreignKeyOnTarget`\n * holds. Defaults to this collection's primary key.\n *\n * The mirror of `localKey` on {@link BelongsToRelation}: that one names the\n * column this side reads from, this one names the column the other side\n * points at. Without it the pair can only be joined on the row id, which\n * makes a natural-key link — `auth_user_id ↔ auth_user_id`, a SKU, a tenant\n * slug — inexpressible as `hasMany`, and it has to drop to the read-only\n * `via`.\n *\n * The column must be unique: the link addresses one source row per value,\n * and Postgres will not accept a foreign key against a non-unique column.\n *\n * ```ts\n * applications: {\n * kind: \"hasMany\",\n * target: () => talentApplications,\n * sourceKey: \"auth_user_id\",\n * foreignKeyOnTarget: \"auth_user_id\"\n * }\n * ```\n */\n sourceKey?: string;\n}\n\n/**\n * Both sides hold many, through a junction table. The target rows are shared,\n * so this collection owns the *link* and not the row: removing one removes a\n * junction row and leaves the target alone.\n *\n * Declared the same way from either side — there is no owning and inverse\n * version. Swap `sourceColumn` and `targetColumn` to describe the other\n * direction.\n *\n * ```ts\n * tags: { kind: \"manyToMany\", target: () => tags }\n * ```\n * @group Models\n */\nexport interface ManyToManyRelation extends RelationBase {\n kind: \"manyToMany\";\n /**\n * The junction table and its two key columns. Every part defaults: the\n * table to both table names sorted and joined, the columns to\n * `<collection>_id` and `<relationName>_id`.\n */\n through?: {\n table?: string;\n /** Junction column holding **this** collection's key. */\n sourceColumn?: string;\n /** Junction column holding the **target's** key. */\n targetColumn?: string;\n /**\n * Extra columns the junction row carries, declared exactly like a\n * collection's properties.\n *\n * A membership is often not only a membership. \"This user is in that\n * organisation\" is really \"…as an `owner`, since March\"; \"this tag is\n * on that post\" is really \"…in third place\". Until this existed the\n * junction was two key columns and nothing else, so the role and the\n * position had to become a collection of their own — which is a\n * different data model, a different set of policies and a different\n * URL, for what is still one link.\n *\n * The properties are read by the same planner that reads a\n * collection's, so a payload column gets the type, `NOT NULL`,\n * `DEFAULT`, `UNIQUE` and enum type it would get on a table. What it\n * does **not** get is `indexes` (declared per collection, and no\n * collection declares a junction), `search`, `vector`, or anything a\n * relation would put on it — a payload property may not be a\n * `relation`, a `reference` or a `vector`, and config validation\n * refuses one that is.\n *\n * On the wire the values travel under {@link JUNCTION_PIVOT_KEY}: a\n * read serves `{ …target, _pivot: { role } }`, and a membership write\n * accepts `{ id, _pivot: { role } }` beside the bare ids.\n *\n * ```ts\n * members: {\n * kind: \"manyToMany\",\n * target: () => users,\n * through: {\n * table: \"org_members\",\n * properties: {\n * role: { type: \"string\", enum: [\"owner\", \"admin\", \"member\"],\n * defaultValue: \"member\", validation: { required: true } },\n * joinedAt: { type: \"date\", autoValue: \"on_create\" }\n * }\n * }\n * }\n * ```\n *\n * Both sides of the same junction may declare it, and both must agree:\n * `resolveJunctionSpecs` refuses two declarations of the same payload\n * key that do not describe the same column, because only one of them\n * could ever be created.\n */\n properties?: Properties;\n };\n}\n\n/**\n * An explicit chain of joins, for links the four shapes above cannot express:\n * multi-hop paths, composite keys, or a join whose condition is not a plain\n * foreign key.\n *\n * Read-only. Rebase will not infer how to write through an arbitrary join\n * chain, and guessing is what this type exists to stop.\n *\n * ```ts\n * permissions: {\n * kind: \"via\",\n * target: () => permissions,\n * cardinality: \"many\",\n * joinPath: [\n * { table: \"user_roles\", on: { from: \"id\", to: \"user_id\" } },\n * { table: \"role_permissions\", on: { from: \"role_id\", to: \"role_id\" } },\n * { table: \"permissions\", on: { from: \"permission_id\", to: \"id\" } }\n * ]\n * }\n * ```\n * @group Models\n */\nexport interface ViaRelation extends RelationBase {\n kind: \"via\";\n /** Whether the chain yields one row or many. Cannot be derived from a join chain. */\n cardinality: \"one\" | \"many\";\n /**\n * The joins, in order, from this collection's table to the target's.\n *\n * Each step names a table and the columns to join it on; the last step's\n * table is the target. Read-only, because Rebase will not work out how to\n * write through an arbitrary chain, and guessing is what this kind exists to\n * stop.\n */\n joinPath: JoinStep[];\n}\n\n/**\n * A link from one collection to another, as authored.\n *\n * A closed union: pick the kind that describes the link and the type offers\n * exactly the fields that kind needs. See {@link ResolvedRelation} for the form\n * the runtime works with, which has every default filled in.\n *\n * @group Models\n */\nexport type Relation =\n | BelongsToRelation\n | HasOneRelation\n | HasManyRelation\n | ManyToManyRelation\n | ViaRelation;\n\n/**\n * A relation with every default filled in — the form the runtime works with.\n *\n * The authored {@link Relation} and this are deliberately different types.\n * They used to be one, which meant no reader could tell which fields had been\n * supplied and which had been guessed, and so every consumer re-derived what it\n * needed with its own chain of `if (through) … else if (localKey) …` fallbacks.\n * Those chains disagreed with each other; that disagreement is what produced\n * silently wrong reads and corrupt writes.\n *\n * Here each variant carries exactly its own fields, all required. A consumer\n * switches on `kind` and gets what it needs without a fallback, and the cases\n * it forgot are a compile error rather than a wrong answer at runtime.\n *\n * @group Models\n */\nexport type ResolvedRelation =\n | ResolvedBelongsTo\n | ResolvedHasOne\n | ResolvedHasMany\n | ResolvedManyToMany\n | ResolvedVia;\n\n/** Fields present on every resolved relation. @group Models */\nexport interface ResolvedRelationBase {\n /** Always set: defaulted during resolution if the author omitted it. */\n relationName: string;\n /**\n * The collection on the other end.\n *\n * Still a thunk — a relation between two collections that import each other\n * has to be — but normalised: resolution unwraps the module namespace the\n * author's `() => import(…)` may hand back, so every consumer gets the\n * config and not a `{ default: … }` wrapper.\n */\n target: () => AnyCollectionConfig;\n /** The target's slug, resolved once so consumers need not call `target()`. */\n targetSlug: string;\n /** As authored — see {@link RelationBase.onUpdate}. Defaults are not filled in. */\n onUpdate?: OnAction;\n /**\n * As authored — see {@link RelationBase.onDelete}. `undefined` here means\n * the author said nothing, and the DDL generator picks the default; it does\n * **not** mean \"no action\".\n */\n onDelete?: OnAction;\n /** Presentation overrides applied when this relation is rendered as a tab. */\n overrides?: Partial<AnyCollectionConfig>;\n /**\n * Whether one row or many come back. Derived from `kind` — kept because it\n * is what most consumers actually branch on, and because `via` is the one\n * kind where it is authored rather than implied.\n */\n cardinality: \"one\" | \"many\";\n /**\n * Whether Rebase knows how to write through this link. False only for\n * {@link ResolvedVia}, whose join chain it will not invent a write for.\n */\n writable: boolean;\n /**\n * Whether the target rows are shared with other parents. True for\n * many-to-many and for multi-hop `via`: what the parent owns is the link,\n * so removing one must not delete the row.\n */\n shared: boolean;\n}\n\n/** @group Models */\nexport interface ResolvedBelongsTo extends ResolvedRelationBase {\n kind: \"belongsTo\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on this collection's table. */\n localKey: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasOne extends ResolvedRelationBase {\n kind: \"hasOne\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n /** @see ResolvedHasMany.sourceKey */\n sourceKey?: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasMany extends ResolvedRelationBase {\n kind: \"hasMany\";\n cardinality: \"many\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n /**\n * Column on the source's table that `foreignKeyOnTarget` points at, or\n * `undefined` for the source's primary key.\n *\n * The one optional field on a resolved relation, and deliberately so. Every\n * other default is filled in here because it can be: a table name and a\n * column name are derivable from the relation and its two endpoints alone.\n * The primary key is not — this driver resolves it from `isId`, then the\n * Drizzle schema, then a column named `id`, and the middle tier does not\n * exist at resolution time.\n *\n * So `undefined` is a sentinel with exactly one meaning, not a field a\n * consumer is invited to guess at. Read it through `sourceKeyField()`,\n * which is the only place that turns it into a column name.\n */\n sourceKey?: string;\n}\n\n/** @group Models */\nexport interface ResolvedManyToMany extends ResolvedRelationBase {\n kind: \"manyToMany\";\n cardinality: \"many\";\n writable: true;\n shared: true;\n /**\n * The junction table and its two key columns, with every default filled in:\n * the table from both table names sorted and joined, the columns from each\n * endpoint's slug.\n */\n through: {\n table: string;\n sourceColumn: string;\n targetColumn: string;\n /**\n * The payload columns as authored, or `{}` when there are none —\n * never `undefined`, so a consumer reads one shape.\n * See {@link ManyToManyRelation.through}.\n */\n properties: Properties;\n };\n}\n\n/** @group Models */\nexport interface ResolvedVia extends ResolvedRelationBase {\n kind: \"via\";\n writable: false;\n /** The chain as authored — see {@link ViaRelation.joinPath}. Nothing to default. */\n joinPath: JoinStep[];\n}\n\n// ── Narrowing helpers ────────────────────────────────────────────────\n//\n// Consumers that only care about one axis — \"does this list many rows\",\n// \"is there a column on the target\" — should ask that question rather\n// than enumerate kinds, so adding a kind later does not silently skip them.\n\n/** Relations whose target row carries this collection's key. @group Models */\nexport type ResolvedForeignKeyOnTarget = ResolvedHasOne | ResolvedHasMany;\n\n/** @group Models */\nexport function hasForeignKeyOnTarget(relation: ResolvedRelation): relation is ResolvedForeignKeyOnTarget {\n return relation.kind === \"hasOne\" || relation.kind === \"hasMany\";\n}\n\n/** @group Models */\nexport function isManyToMany(relation: ResolvedRelation): relation is ResolvedManyToMany {\n return relation.kind === \"manyToMany\";\n}\n\n/** @group Models */\nexport function isToMany(relation: ResolvedRelation): boolean {\n return relation.cardinality === \"many\";\n}\n\n/**\n * Defines a single, explicit step in a multi-join path.\n *\n * Each step represents one JOIN operation in the sequence. The `from` columns\n * refer to the previous table in the chain (or the source table for the first step),\n * and the `to` columns refer to the current table being joined.\n *\n * @example Single column join:\n * ```typescript\n * {\n * table: \"authors\",\n * on: {\n * from: \"author_id\", // Column from previous table (e.g., posts.author_id)\n * to: \"id\" // Column from current table (authors.id)\n * }\n * }\n * ```\n *\n * @example Multi-column composite key join:\n * ```typescript\n * {\n * table: \"order_items\",\n * on: {\n * from: [\"order_id\", \"store_id\"], // Multiple columns from previous table\n * to: [\"order_id\", \"store_id\"] // Corresponding columns in current table\n * }\n * }\n * ```\n */\nexport interface JoinStep {\n /**\n * The database table name to join TO in this step.\n * This is the table you're joining into, not the table you're joining from.\n *\n * @example \"authors\", \"user_roles\", \"product_categories\"\n */\n table: string;\n\n /**\n * The join condition for this step. Defines how the previous table\n * connects to the current table.\n *\n * - `from`: Column name(s) on the PREVIOUS table in the join chain\n * - `to`: Column name(s) on the CURRENT table (specified in `table`)\n *\n * For the first step, `from` refers to the source collection's table.\n * For subsequent steps, `from` refers to the table from the previous step.\n *\n * Both `from` and `to` support:\n * - Single column: `\"user_id\"`\n * - Multiple columns: `[\"company_id\", \"region_id\"]` for composite keys\n *\n * When using arrays, both `from` and `to` must have the same length,\n * and columns are matched by position (index 0 with index 0, etc.).\n */\n on: {\n from: string | string[];\n to: string | string[];\n };\n}\n","import { CollectionConfig, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, isToMany, ResolvedRelation, VectorProperty, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from \"@rebasepro/types\";\nimport { effectiveAccess, fieldKeyForColumn, findRelation, getTenantConfig, isRelationRequired, resolveCollectionRelations } from \"@rebasepro/common\";\n\n/**\n * OpenAPI 3.0.3 specification generator.\n *\n * Produces a spec that exactly mirrors the REST API consumed by the\n * Rebase SDK client (`@rebasepro/client`).\n *\n * Routes are mounted at `{basePath}/data/{slug}` by `initializeRebaseBackend`.\n */\n\nexport interface OpenApiGeneratorOptions {\n /** Base path for the API (e.g. \"/api\"). Defaults to \"/api\". */\n basePath?: string;\n /** Whether auth is enabled on data routes. Defaults to true. */\n requireAuth?: boolean;\n /**\n * The list-pagination bounds the REST layer applies, so the spec states the\n * ones a request will actually meet.\n *\n * These were hardcoded as `default: 20, maximum: 100` — neither of which\n * the server has ever used. The spec drives the API Explorer and is what a\n * generated client is built from, so an understated ceiling is a request\n * the client refuses to make, and an overstated one is a 400 nobody\n * predicted.\n */\n listLimits?: { defaultLimit?: number; maxLimit?: number };\n}\n\nexport function generateOpenApiSpec(\n collections: CollectionConfig[],\n options: OpenApiGeneratorOptions = {}\n): Record<string, unknown> {\n const basePath = options.basePath ?? \"/api\";\n const requireAuth = options.requireAuth ?? true;\n const defaultLimit = options.listLimits?.defaultLimit ?? DEFAULT_LIST_LIMIT;\n const maxLimit = options.listLimits?.maxLimit ?? MAX_LIST_LIMIT;\n\n /**\n * The query parameters every list endpoint honours.\n *\n * Written once because it was written twice: the root listing named eight\n * and the subcollection listing named four, though both go through the same\n * `parseQueryOptions` and the same fetch. Four capabilities were therefore\n * unreachable from a generated client on nested routes, and `or`/`and` were\n * undocumented on both.\n */\n const listQueryParameters = () => [\n { name: \"limit\", in: \"query\", schema: { type: \"integer\", default: defaultLimit, minimum: 1, maximum: maxLimit },\n description: `Maximum number of records to return. Must be a whole number between 1 and ${maxLimit}; a larger one is rejected with 400 INVALID_LIMIT rather than trimmed, so a short page always means a short collection. Page past the ceiling with \\`offset\\`.` },\n { name: \"offset\", in: \"query\", schema: { type: \"integer\", default: 0 },\n description: \"Number of records to skip\" },\n { name: \"page\", in: \"query\", schema: { type: \"integer\", minimum: 1 },\n description: \"Page number (alternative to offset). Calculates offset as (page-1)*limit\" },\n {\n name: \"after\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Keyset cursor: continue after the row the previous page ended on. Pass back \"\n + \"`meta.nextCursor` from that response, unchanged — it is opaque, and encodes both the \"\n + \"sort keys and the last row's values for them. Unlike `offset`, a row inserted or \"\n + \"deleted before the cursor cannot shift the window, so a walk neither repeats nor \"\n + \"skips rows. Cannot be combined with `offset`/`page` (400 CURSOR_WITH_OFFSET), and an \"\n + \"`orderBy` different from the one the cursor was issued under is refused \"\n + \"(400 CURSOR_ORDER_MISMATCH) rather than seeked in an order nobody asked for.\"\n },\n {\n name: \"orderBy\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Sort field and direction. Accepts `field:asc`, `field:desc`, or `field:desc:last` — \"\n + \"the third segment places NULLs (`first`/`last`), defaulting to Postgres's own \"\n + \"convention (last ascending, first descending). Also accepts a JSON array \"\n + \"`[{\\\"field\\\":\\\"name\\\",\\\"direction\\\":\\\"asc\\\",\\\"nulls\\\":\\\"last\\\"}]` — several entries sort by \"\n + \"each in turn, the second breaking ties on the first.\",\n example: \"created_at:desc:last\"\n },\n {\n name: \"where\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"JSON object filter, mapping each field to a `[operator, value]` tuple. \"\n + \"Combines with the per-field `?field=op.value` parameters below; on the same field, the per-field parameter wins.\",\n example: \"{\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\"\n },\n {\n name: \"or\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Disjunction of conditions, AND-ed with `where` and `searchString`.\",\n example: \"(status.eq.draft,status.eq.review)\"\n },\n {\n name: \"and\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Conjunction of conditions, AND-ed with `where` and `searchString`. Ignored when `or` is also present.\",\n example: \"(views.gte.10,status.eq.draft)\"\n },\n {\n name: \"not\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Negation, AND-ed with `where` and `searchString`. Negates the **conjunction** of its \"\n + \"conditions: `not(a)` is `NOT a`, `not(a,b)` is `NOT (a AND b)`. Groups nest, so \"\n + \"`not(or(a,b))` is the De Morgan case. Compiles to a real SQL `NOT (...)`, which — \"\n + \"three-valued logic — also excludes rows whose column is NULL. Ignored when `or` or \"\n + \"`and` is also present.\",\n example: \"(status.eq.draft,views.gte.10)\"\n },\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load, in either of two spellings. **Comma-separated names or dotted \"\n + \"paths** — `author,comments.author`, up to 3 hops deep; `*` loads every relation one \"\n + \"hop deep. **JSON**, when a relation needs narrowing — \"\n + \"`{\\\"comments\\\":{\\\"limit\\\":5,\\\"where\\\":{\\\"published\\\":[\\\"==\\\",true]},\"\n + \"\\\"orderBy\\\":\\\"created_at:desc\\\",\\\"fields\\\":\\\"id,body\\\",\\\"include\\\":{\\\"author\\\":true}}}`. \"\n + \"A value starting with `{` is read as the JSON form. A name that is not a relation of \"\n + \"the collection is a 400 UNKNOWN_RELATION, not a silently missing field.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return. A projection pushed into the SELECT, so a query \"\n + \"that needs two fields of a wide row reads two columns. The primary key is always \"\n + \"returned (a row that cannot be addressed cannot be updated, deleted, or paged past), \"\n + \"and `excludeFromApi` columns stay hidden whether or not they are named here. An \"\n + \"unknown column is a 400 UNKNOWN_FIELD.\",\n example: \"id,name,created_at\"\n },\n {\n name: \"distinct\",\n in: \"query\",\n schema: { type: \"boolean\" },\n description:\n \"`SELECT DISTINCT` over the returned columns. Only meaningful alongside `fields`: the \"\n + \"primary key is always in the projection, so without narrowing it every row is \"\n + \"already distinct. `meta.total` counts distinct rows too. Refused (400) alongside \"\n + \"`searchString` or a vector search, which attach a per-row score that makes every row \"\n + \"distinct by construction, and (400 DISTINCT_ORDER_BY_NOT_SELECTED) when `orderBy` \"\n + \"names a column `fields` does not return.\",\n example: \"true\"\n },\n {\n name: \"searchString\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Text search. By default a case-insensitive substring match OR-ed across the \" +\n \"collection's top-level string properties. A collection declaring a `search` block \" +\n \"gets ranked full-text matching over the fields it names, and rows carry a `_score`.\"\n },\n // Vector search has been served here since vectors landed and was\n // documented nowhere, so the only way to find it was to read the query\n // parser. All four are needed together; `vector_search` and `vector`\n // are ignored unless both are present.\n {\n name: \"vector_search\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Name of the `vector` property to run a nearest-neighbour search against. Requires `vector`.\",\n example: \"embedding\"\n },\n {\n name: \"vector\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"The query embedding, as a JSON array of numbers. Its length must match the property's declared `dimensions`.\",\n example: \"[0.12,-0.04,0.98]\"\n },\n {\n name: \"vector_distance\",\n in: \"query\",\n schema: { type: \"string\", enum: [\"cosine\", \"l2\", \"inner_product\"], default: \"cosine\" },\n description: \"Distance function used for ordering.\"\n },\n {\n name: \"vector_threshold\",\n in: \"query\",\n schema: { type: \"number\" },\n description: \"Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field.\"\n }\n ];\n\n const spec: Record<string, unknown> = {\n openapi: \"3.0.3\",\n info: {\n title: \"Rebase API\",\n version: \"1.0.0\",\n description:\n \"Auto-generated REST API from Rebase collection definitions. \" +\n \"This is the same API consumed by the `@rebasepro/client` SDK.\"\n },\n servers: [\n {\n url: basePath,\n description: \"API Server\"\n }\n ],\n paths: {} as Record<string, unknown>,\n components: {\n schemas: {\n ErrorResponse: {\n type: \"object\",\n properties: {\n error: {\n type: \"object\",\n required: [\"message\", \"code\"],\n properties: {\n message: { type: \"string\" },\n code: { type: \"string\" },\n details: {}\n }\n }\n }\n },\n PaginationMeta: {\n type: \"object\",\n properties: {\n total: { type: \"integer\",\ndescription: \"Total number of matching records\" },\n limit: { type: \"integer\",\ndescription: \"Page size used for this query\" },\n offset: { type: \"integer\",\ndescription: \"Number of records skipped\" },\n hasMore: { type: \"boolean\",\ndescription: \"Whether more records exist beyond this page\" },\n nextCursor: {\n type: \"string\",\n description:\n \"Opaque keyset cursor continuing this listing — pass it back as `?after=`. \"\n + \"Present when `hasMore` is true and the page returned at least one row; \"\n + \"absent on the last page and on an ordering no cursor can describe \"\n + \"(relevance, whose scores are computed per query and not stored). Do not \"\n + \"parse it: the encoding exists to be changed.\"\n }\n }\n }\n } as Record<string, unknown>,\n securitySchemes: {} as Record<string, unknown>\n },\n tags: [] as Array<{ name: string; description?: string }>\n };\n\n // ── Security Schemes ─────────────────────────────────────────────────\n if (requireAuth) {\n (spec.components as Record<string, unknown>).securitySchemes = {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description:\n \"JWT access token obtained from `POST /auth/login` or `POST /auth/register`. \" +\n \"Can also be a static service key for server-to-server authentication.\"\n }\n // No `?token=` scheme. It was declared here — globally, so on every\n // operation — and no data route has ever accepted one: both\n // `createAuthMiddleware` and `createAdapterAuthMiddleware` read the\n // `Authorization` header and nothing else, deliberately, because\n // URLs leak into access logs, proxies, Referer headers and browser\n // history (`auth/middleware.ts`). Following it cost a caller twice:\n // unauthenticated, *and* a 400, since `token` is not in the query\n // parser's `reservedQueryKeys` and so compiles as a filter on a\n // column named `token`. `queryTokenAuth` is real but is mounted\n // only on storage file serving, for `<img src>`; if those routes\n // are ever documented, the scheme belongs on them, per-operation.\n };\n (spec as Record<string, unknown>).security = [\n { bearerAuth: [] }\n ];\n }\n\n const paths = spec.paths as Record<string, unknown>;\n const schemas = (spec.components as Record<string, unknown>).schemas as Record<string, unknown>;\n const tags = spec.tags as Array<{ name: string; description?: string }>;\n\n // The names a listing has already spent. A collection is free to have a\n // `limit` or a `fields` column, and the query parser reads those names as\n // pagination and field selection before any filter is compiled — so the\n // per-field filter could never fire, and documenting it a second time put\n // two parameters with the same (`name`, `in`) pair on one operation, which\n // is invalid OpenAPI: Swagger UI renders a duplicate and several generators\n // abort. Taken from the parameter list itself so the two cannot drift.\n const reservedParameterNames = new Set(listQueryParameters().map(p => p.name));\n\n // Every component name this document will carry, known before the first\n // schema is built: a relation may point at a collection that appears later\n // in the list, or at one that is not documented here at all, and a `$ref`\n // at a component that does not exist is a document Swagger UI renders empty\n // and a strict generator refuses.\n const registeredSchemas = new Set((collections || []).map(schemaNameFor));\n\n /**\n * `Prefer: return=minimal`, on every route that would otherwise send a row\n * back. Documented rather than left implicit because a client generated\n * from this spec cannot send a header the spec does not mention.\n */\n const preferHeader = {\n name: \"Prefer\",\n in: \"header\",\n required: false,\n schema: { type: \"string\", enum: [\"return=minimal\"] },\n description:\n \"`return=minimal` asks the server not to send the written row back. Single writes then \" +\n \"answer `204 No Content`; bulk and batch writes answer `200` carrying the ids only. \" +\n \"The response repeats it in `Preference-Applied` when it was honoured.\"\n };\n\n const ifMatchHeader = {\n name: \"If-Match\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"The `ETag` this edit was made against, from the `GET` that read the row. The write is \" +\n \"refused with `412` if the row has changed since — which is the difference between \" +\n \"\\\"update the row I read\\\" and \\\"overwrite whatever is there now\\\". `*` means only that \" +\n \"the row must exist.\"\n };\n\n const preconditionFailed = {\n 412: {\n description:\n \"The row changed since the ETag in `If-Match` was issued. Nothing was written: \" +\n \"re-read the row, re-apply the change, and send the new ETag\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n const minimalResponse = {\n 204: {\n description: \"Written. `Prefer: return=minimal` was honoured, so there is no body\",\n headers: {\n \"Preference-Applied\": { schema: { type: \"string\" }, description: \"`return=minimal`\" }\n }\n }\n };\n\n // ── POST /data/_batch — writes across collections, one transaction ────\n //\n // Registered before the per-collection paths for the same reason the route\n // is: `_batch` is not a collection, and reading it as one would document a\n // table that does not exist.\n if ((collections || []).length > 0) {\n paths[\"/data/_batch\"] = {\n post: {\n tags: [\"Data\"],\n summary: \"Write across collections in one transaction\",\n description:\n \"All-or-nothing across collections — an order and its line items, a user and \" +\n \"their membership row. `/bulk` is one collection at a time, and sending the two \" +\n \"halves as separate requests is exactly the sequence that can half-succeed.\\n\\n\" +\n \"Operations run in order, each through the same pipeline its single-row route \" +\n \"uses: the same validation, callbacks and row-level security, as the same role. \" +\n \"An operation may name itself with `ref`, and a later one may stand \" +\n \"`{ \\\"$ref\\\": \\\"order.id\\\" }` wherever a value goes — in `values`, at any depth, \" +\n \"or as an `id`. Only backward references resolve.\\n\\n\" +\n \"Capped at the same number of entries as a bulk write, because one batch is one \" +\n \"transaction and holds its locks for the whole of it.\",\n operationId: \"batchWrite\",\n parameters: [\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this batch so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot tell a replay from a second batch, and \" +\n \"the whole batch is written twice.\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"operations\"],\n properties: {\n operations: {\n type: \"array\",\n items: { $ref: \"#/components/schemas/BatchOperation\" }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description:\n \"One entry per operation, in order: the written row for a create, update or \" +\n \"upsert, and `null` for a delete\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { type: \"object\", nullable: true } },\n meta: { type: \"object\", properties: { operations: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 400: {\n description:\n \"Malformed body, an unknown collection or field, an illegal field operation \" +\n \"or conflict target, a forward `$ref`, or more operations than the limit. \" +\n \"Nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 404: {\n description: \"An `update` or `delete` names a row that does not exist; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n schemas.FieldOperation = {\n type: \"object\",\n description:\n \"A change to the column's current value rather than the value to store. Exactly one \" +\n \"operator per field. Only on an update: an operation over a value that does not exist \" +\n \"yet is refused with a 400.\",\n properties: {\n $inc: { type: \"number\", description: \"Add to a `number` column; negative to subtract.\" },\n $push: { description: \"Append a value, or each of an array of values, to an `array` column.\" },\n $pull: { description: \"Remove every occurrence of a value from an `array` column.\" },\n $merge: { type: \"object\", description: \"Shallow-merge an object into a `map` column.\" }\n }\n };\n\n schemas.BatchOperation = {\n type: \"object\",\n required: [\"op\", \"collection\"],\n properties: {\n op: { type: \"string\", enum: [\"create\", \"update\", \"upsert\", \"delete\"] },\n collection: { type: \"string\", description: \"The collection slug this operation writes to.\" },\n id: {\n description:\n \"Required for `update` and `delete`. May instead be a reference marker — an \" +\n \"object whose single key is `$ref` and whose value is `<ref name>.<field>`, \" +\n \"e.g. `{ \\\"$ref\\\": \\\"order.id\\\" }` — naming a column of the row an earlier \" +\n \"operation wrote. (Described rather than declared as a schema: `$ref` is a \" +\n \"reserved word to every OpenAPI reader, and a property named `$ref` is read \" +\n \"as a reference and mangled.)\",\n oneOf: [{ type: \"string\" }, { type: \"integer\" }, { type: \"object\" }]\n },\n values: {\n type: \"object\",\n description:\n \"The row's fields. Values may be `$ref` markers; an `update` may also carry \" +\n \"field operations.\",\n additionalProperties: true\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"`upsert` only: the columns the conflict is matched on. Must carry a declared \" +\n \"uniqueness guarantee. Defaults to the primary key.\"\n },\n ref: {\n type: \"string\",\n description: \"Names this operation's result, so a later one can `$ref` its columns.\"\n }\n }\n };\n }\n\n // ── Collection routes ────────────────────────────────────────────────\n for (const collection of (collections || [])) {\n const schemaName = schemaNameFor(collection);\n const slug = collection.slug;\n\n tags.push({\n name: collection.name,\n description: collection.description || `CRUD operations for ${collection.name}`\n });\n\n // Build component schema for this collection\n schemas[schemaName] = buildCollectionSchema(collection, registeredSchemas);\n\n // Build an \"input\" schema (no read-only/auto fields like autoValue dates)\n schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);\n\n // The update body — same columns, no `required`. PATCH and PUT both\n // merge, so a field left out means \"unchanged\", not \"omitted by mistake\".\n schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);\n\n const dataPath = `/data/${slug}`;\n\n // ── GET /data/{slug}/count — How many rows match ──────────────\n //\n // Served for every collection since the route existed and described\n // here for the first time. A spec is what a generated client can see,\n // so an endpoint missing from it is an endpoint that client does not\n // have — and this is the one a paginating UI needs to know how many\n // pages there are.\n //\n // Registered before the list path so its literal segment cannot be read\n // as an `{id}`, which is the same ordering the router uses.\n paths[`${dataPath}/count`] = {\n get: {\n tags: [collection.name],\n summary: `Count ${collection.name}`,\n description:\n \"The number of rows the same filters would return, without returning them. \" +\n \"Takes the filter and search parameters of the list endpoint; `limit`, `offset` \" +\n \"and `orderBy` are not part of the question and are ignored.\",\n operationId: `count${schemaName}`,\n parameters: [\n ...listQueryParameters().filter(p => p.name === \"searchString\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"The number of matching rows\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"count\"],\n properties: {\n count: { type: \"integer\", description: \"Rows matching the filters\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug}/aggregate — count/sum/avg/min/max ────────\n //\n // Before the list path for the same reason `/count` is: a literal\n // segment that a generated client would otherwise be told is an `{id}`.\n paths[`${dataPath}/aggregate`] = {\n get: {\n tags: [collection.name],\n summary: `Aggregate ${collection.name}`,\n description:\n \"Aggregate values over the rows the same filters would return. Takes the filter and \" +\n \"search parameters of the list endpoint. Row-level security applies to the rows \" +\n \"being aggregated, so a caller who can read nothing counts nothing.\",\n operationId: `aggregate${schemaName}`,\n parameters: [\n {\n name: \"select\",\n in: \"query\",\n required: true,\n description:\n \"Comma-separated aggregates, e.g. `count()`, `sum(total)`, `avg(total),max(total)`. \" +\n \"Results are keyed `count`, `sum_total`, `avg_total` and so on.\",\n schema: { type: \"string\" },\n example: \"count(),sum(total)\"\n },\n {\n name: \"groupBy\",\n in: \"query\",\n required: false,\n description: \"Comma-separated fields to group by. Each grouped field is returned alongside the aggregates.\",\n schema: { type: \"string\" },\n example: \"status\"\n },\n ...listQueryParameters().filter(p => p.name === \"searchString\" || p.name === \"limit\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"One row per group, or a single row when `groupBy` is absent\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"data\"],\n properties: {\n data: {\n type: \"array\",\n items: { type: \"object\", additionalProperties: true }\n }\n }\n }\n }\n }\n },\n 501: { description: \"This backend's data driver does not implement aggregates\" },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug} — List entities ──────────────────────────\n paths[dataPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${collection.name}`,\n operationId: `list${schemaName}`,\n parameters: [\n ...listQueryParameters(),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"Paginated list of entities\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: { $ref: `#/components/schemas/${schemaName}` }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n },\n post: {\n tags: [collection.name],\n summary: `Create ${collection.singularName || collection.name}`,\n operationId: `create${schemaName}`,\n parameters: [\n {\n name: \"on_conflict\",\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to upsert on, turning the create into \" +\n \"INSERT ... ON CONFLICT DO UPDATE. They must carry a declared uniqueness \" +\n \"guarantee — `validation.unique`, a `unique` index, or the primary key — \" +\n \"or the request is refused with a 400 naming the targets that do exist. \" +\n \"Left off, this is a plain insert and a duplicate key still raises.\",\n example: \"email\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Input` }\n }\n }\n },\n responses: {\n 201: {\n description: \"Created entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n ...minimalResponse,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── Bulk: one transaction, all-or-nothing ─────────────────────\n //\n // These went undocumented while they existed, which is the same defect\n // the update verb had: an endpoint the server serves and the spec does\n // not mention cannot be reached by a generated client at all.\n const idempotencyHeader = {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this write so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot distinguish a replay from a second \" +\n \"genuine batch, and the whole batch is written twice. A key names one request: \" +\n \"re-send the identical request to replay its answer, and use a new key for a \" +\n \"different one.\"\n };\n\n const bulkErrors = {\n 400: {\n description: \"Malformed body, an unknown field, or more rows than the per-batch limit\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description:\n \"A request with the same Idempotency-Key is still in flight. Retry it: the \" +\n \"first attempt's result is replayed once it lands\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n };\n\n paths[`/data/${slug}/bulk`] = {\n post: {\n tags: [collection.name],\n summary: `Create many ${collection.name} in one transaction`,\n description:\n \"All-or-nothing: if any row is rejected none of them land, and the error names \" +\n \"the offending index. Every row still runs callbacks, relations and row-level \" +\n \"security. Capped server-side because one batch holds its locks for its whole \" +\n \"duration.\",\n operationId: `createMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"rows\"],\n properties: {\n rows: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}Input` } },\n upsert: {\n type: \"boolean\",\n description: \"Write each row as INSERT ... ON CONFLICT DO UPDATE.\"\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"The columns the conflict is matched on, instead of the primary key. \" +\n \"They must carry a declared uniqueness guarantee. Naming them without \" +\n \"`upsert: true` is a 400 rather than a silently ignored field.\"\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The written rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n ...bulkErrors\n }\n },\n patch: {\n tags: [collection.name],\n summary: `Update many ${collection.name} in one transaction`,\n description:\n \"Each entry names its row and the fields to change. `{ id, data }` rather than \" +\n \"flat rows carrying their own key, because on a table keyed on something other \" +\n \"than `id` a flat row cannot say whether a column is the address or a value to \" +\n \"write. An id matching no row fails the batch.\",\n operationId: `updateMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"updates\"],\n properties: {\n updates: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"id\", \"data\"],\n properties: {\n id: { type: \"string\", description: \"The row to update\" },\n data: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The updated rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n paths[`/data/${slug}/bulk/delete`] = {\n post: {\n tags: [collection.name],\n summary: `Delete many ${collection.name} in one transaction`,\n description:\n \"A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but \" +\n \"widely dropped by proxies and CDNs, and several generators ignore \" +\n \"`requestBody` on a DELETE operation — a generated client would send the \" +\n \"request with no ids at all. Takes ids rather than a filter: a mistyped \" +\n \"condition that empties a table cannot be reviewed at the call site the way \" +\n \"an explicit list can. `beforeDelete`/`afterDelete` fire per row.\",\n operationId: `deleteMany${schemaName}`,\n parameters: [idempotencyHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"ids\"],\n properties: {\n ids: {\n type: \"array\",\n items: { oneOf: [{ type: \"string\" }, { type: \"integer\" }] }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"How many rows were deleted\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n meta: { type: \"object\", properties: { deleted: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was deleted\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n // ── GET/PUT/DELETE /data/{slug}/{id} ──────────────────────────\n const entityPath = `/data/${slug}/{id}`;\n paths[entityPath] = {\n get: {\n tags: [collection.name],\n summary: `Get ${collection.singularName || collection.name} by ID`,\n operationId: `get${schemaName}ById`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n // The same two parameters the list route documents, and the\n // same code serves them — one row and a page of them go\n // through one pipeline, so anything true of `include` or\n // `fields` there is true here.\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load: comma-separated names or dotted paths \"\n + \"(`author,comments.author`, up to 3 hops), `*` for all one hop deep, or the \"\n + \"JSON form for per-relation `limit`/`where`/`orderBy`/`fields`. An unknown \"\n + \"name is a 400 UNKNOWN_RELATION.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return, as a SELECT projection. The primary key \"\n + \"always survives and `excludeFromApi` columns stay hidden.\",\n example: \"id,title\"\n }\n ],\n responses: {\n 200: {\n description: \"Entity found\",\n headers: {\n ETag: {\n schema: { type: \"string\" },\n description:\n \"This row's version. Send it back as `If-Match` on a later PATCH or \" +\n \"DELETE to have the write refused if the row has changed in between.\"\n }\n },\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n },\n patch: updateOperation(collection, schemaName, requireAuth, {\n ifMatchHeader,\n preferHeader,\n preconditionFailed,\n minimalResponse\n }),\n delete: {\n tags: [collection.name],\n summary: `Delete ${collection.singularName || collection.name}`,\n operationId: `delete${schemaName}`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n ifMatchHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this delete so a retry replays its answer. A delete replayed after \" +\n \"the first attempt committed would otherwise answer 404 — which an offline \" +\n \"queue reads as a permanent failure for a delete that in fact succeeded.\"\n }\n ],\n responses: {\n 204: { description: \"Deleted successfully\" },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...preconditionFailed,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n }\n\n // ── Subcollection routes ─────────────────────────────────────────────\n //\n // A second pass, after every collection's component schema exists. These\n // routes `$ref` the *target's* schema, and the first pass builds schemas in\n // array order — so doing this inline meant a subcollection whose target\n // appeared later in the list silently degraded to an untyped `object`.\n //\n // The names come from the *resolved* relations, not from the authored\n // `relations` array. `relationName` is optional at the authoring surface —\n // it defaults to the property key, or to the target's slug — so reading the\n // raw field skipped every relation that relied on the default, and missed\n // relations declared inline on a property entirely, since those are not in\n // the array. These are the same resolved names the nested-path router\n // matches, so the spec and the routes cannot drift apart.\n //\n // A to-one relation is left out. `posts/1/author` resolves, but it\n // addresses a single row, and documenting it as a paginated list would\n // describe a response shape the client never gets.\n for (const collection of (collections || [])) {\n const slug = collection.slug;\n const schemaName = schemaNameFor(collection);\n const relations = Object.values(resolveCollectionRelations(collection))\n .filter(isToMany);\n for (const relation of relations) {\n const relationName = relation.relationName;\n const targetCollection = relation.target();\n const targetSchema = schemaNameFor(targetCollection);\n\n const subPath = `/data/${slug}/{parentId}/${relationName}`;\n\n // Only add if the schema exists (target collection is also registered)\n paths[subPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,\n operationId: `list${schemaName}${toPascalCase(relationName)}`,\n parameters: [\n { name: \"parentId\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: `${collection.singularName || collection.name} ID` },\n // The nested list handler goes through the same\n // `parseQueryOptions` and the same fetch as the root\n // one, so it honours the same parameters. It documented\n // four of them.\n ...listQueryParameters()\n ],\n responses: {\n 200: {\n description: `List of related ${relationName}`,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: schemas[targetSchema]\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n }\n }\n\n return spec;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Is this property part of the shape the document may describe?\n *\n * One exclusion, and it is the document telling the truth about what the server\n * does: `excludeFromApi` is a server-side guarantee that the column \"is\n * stripped from every row the API serves, for every caller, including admins\n * and service keys\" — `stripExcluded` in the row pipeline enforces it. A schema\n * that lists such a column describes a field that is never present, and it\n * describes it to everyone: `/docs` is mounted on the app, not on the data\n * router, so it carries none of the auth middleware `{basePath}/data` does.\n * Every project scaffolded by `rebase init` published its `users` collection's\n * `passwordHash` and `emailVerificationToken` this way.\n *\n * A `relation` property used to be excluded here too, on the reasoning that the\n * property is virtual. It is — but the row is not empty where it stands: the\n * owning side carries a foreign key under a *wire* name (`authorId`), and a\n * read that includes the relation carries the target's row under the relation's\n * own name. Excluding both left `/api/docs` describing a strictly smaller\n * collection than `generated/sdk/database.types.ts` did for the same config.\n * Relations are now emitted by {@link emitRelationProperties}, under the same\n * keys and in the same order as the SDK's `Row`; the direct-property loops skip\n * them so the two passes cannot emit one key twice.\n *\n * Written as one predicate rather than a `continue` per loop because it kept\n * being fixed in one loop at a time: this is the same rule the SDK generator\n * applies to its `Row` type (`packages/codegen/src/generate-types.ts`).\n */\nfunction isDocumentedProperty(property: Property, direction: \"read\" | \"write\" = \"read\"): boolean {\n const access = effectiveAccess(property);\n return access?.[direction]?.length !== 0;\n}\n\n/**\n * The `x-rebase-access` annotation, or nothing for a field with no rule.\n *\n * A vendor extension rather than a schema keyword because OpenAPI has no way to\n * say \"this property is present for some callers\" — `readOnly` is about the\n * direction of a field, not about who. Generators ignore what they do not know,\n * so a client built from this spec still compiles; a *human* reading `/docs`, or\n * a gateway that wants to enforce the same rule at the edge, gets the role lists\n * verbatim. Emitted for the role case only: a field closed to everybody is\n * absent from the document entirely, which is a stronger statement.\n */\nfunction accessAnnotation(property: Property): Record<string, unknown> | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const annotation: Record<string, unknown> = {};\n if (access.read !== undefined) annotation.read = [...access.read];\n if (access.write !== undefined) annotation.write = [...access.write];\n return Object.keys(annotation).length > 0 ? annotation : undefined;\n}\n\n/** The sentence `x-rebase-access` deserves in prose, for a reader of `/docs`. */\nfunction accessDescription(property: Property): string | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const parts: string[] = [];\n const phrase = (roles: readonly string[]) =>\n roles.length === 0 ? \"nobody through the API\" : roles.map(r => `\\`${r}\\``).join(\", \") + \" (and `admin`)\";\n if (access.read !== undefined) parts.push(`readable by ${phrase(access.read)}`);\n if (access.write !== undefined) parts.push(`writable by ${phrase(access.write)}`);\n if (parts.length === 0) return undefined;\n return `Field access: ${parts.join(\"; \")}. A caller without the role does not receive the field at all — it is absent, not null.`;\n}\n\n/**\n * The keys `stripExcluded` deletes: the property name *and* its column name.\n *\n * Seeding the emitted set with both is what stops a foreign key derived from a\n * relation putting an `excludeFromApi` column back on the surface under its\n * other name. Same pair, same reason, as `excludedApiKeys` in the SDK\n * generator.\n */\nfunction excludedApiKeys(collection: CollectionConfig, direction: \"read\" | \"write\" = \"read\"): Set<string> {\n const excluded = new Set<string>();\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if (isDocumentedProperty(property as Property, direction)) continue;\n excluded.add(key);\n const columnName = (property as { columnName?: unknown }).columnName;\n if (typeof columnName === \"string\") excluded.add(columnName);\n }\n return excluded;\n}\n\n/**\n * The collection's primary key, as the key it is addressed by on the wire and\n * the property that declares it.\n *\n * `id` was a literal in three places — seeded before the read loop, assigned\n * after the input loop, inherited by the update schema from the input one — and\n * the two spellings disagreed: a declared `id: { type: \"number\" }` overwrote\n * the read seed and was overwritten by the input assignment, so the same field\n * was `integer` in `Post` and `string` in `PostInput`. One helper now answers\n * the question for all three.\n */\nfunction idPropertyEntry(collection: CollectionConfig | undefined): [string, Property] | undefined {\n for (const [key, property] of Object.entries(collection?.properties ?? {})) {\n if ((property as unknown as Record<string, unknown>)?.isId) return [key, property as Property];\n }\n return undefined;\n}\n\n/**\n * The schema of a primary key or of a foreign key pointing at one: the declared\n * property's own type, stripped of the field-level facts (description,\n * validation bounds) that belong to the column and not to a reference to it.\n *\n * Falls back to `string` for a collection that declares no primary key, which\n * is what every schema here assumed unconditionally before.\n */\nfunction idSchemaFor(collection: CollectionConfig | undefined): Record<string, unknown> {\n const declared = idPropertyEntry(collection);\n if (!declared) return { type: \"string\" };\n const converted = convertPropertyToSchema(declared[1]);\n const schema: Record<string, unknown> = { type: converted.type ?? \"string\" };\n if (converted.format) schema.format = converted.format;\n return schema;\n}\n\n/**\n * Relations resolve or they do not; a document is still owed for a collection\n * whose target thunk throws (a circular import, usually). The SDK generator\n * warns and carries on with no relation fields, and so does this — the\n * alternative is `/api/docs` 500ing for the whole project.\n */\nfunction resolveRelationsForDocument(collection: CollectionConfig): Record<string, ResolvedRelation> {\n try {\n return resolveCollectionRelations(collection);\n } catch {\n return {};\n }\n}\n\n/** Unwrap a target handed back as a module namespace — `() => import(\"./authors\")`. */\nfunction relationTarget(relation: ResolvedRelation): CollectionConfig | undefined {\n try {\n let target = relation.target() as CollectionConfig & { default?: CollectionConfig; __esModule?: boolean };\n if (target && (target.default || target.__esModule)) {\n target = (target.default ?? target) as typeof target;\n }\n return target;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The schema an *included* relation arrives as: the target's own row.\n *\n * `$ref` when the target is one of the collections this document describes, and\n * an open object when it is not — a dangling pointer makes Swagger UI render an\n * empty model and makes a strict generator abort, which is worse than a vague\n * one.\n */\nfunction includedRelationSchema(\n relation: ResolvedRelation,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const target = relationTarget(relation);\n const targetSchema = target ? schemaNameFor(target) : undefined;\n const item: Record<string, unknown> = targetSchema && registeredSchemas.has(targetSchema)\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" };\n return relation.cardinality === \"many\" ? { type: \"array\", items: item } : item;\n}\n\n/**\n * Emit a collection's relations onto a read schema: the foreign keys first,\n * then the relations themselves.\n *\n * The order and the keys are the SDK `Row`'s, deliberately — the parity test\n * next door compares the two key sets, and the only way that stays true is for\n * both to be derived the same way rather than kept in step by hand.\n *\n * A `belongsTo` reaches the wire under the *field* name of its local column\n * (`author_id` → `authorId`), which is what `fieldKeyForColumn` answers; a\n * relation addressed by the same name as its own foreign key is served as\n * either the scalar or the nested row depending on `include`, so it is\n * documented as both.\n */\nfunction emitRelationProperties(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n required: string[],\n emitted: Set<string>,\n registeredSchemas: ReadonlySet<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n for (const [relationKey, relation] of Object.entries(resolved)) {\n if (relation.kind !== \"belongsTo\" || !relation.localKey) continue;\n const fieldKey = fieldKeyForColumn(collection, relation.localKey);\n if (emitted.has(fieldKey)) continue;\n\n const foreignKey = idSchemaFor(relationTarget(relation));\n const shadowedByInclude = relationKey === fieldKey;\n properties[fieldKey] = shadowedByInclude\n ? { oneOf: [foreignKey, includedRelationSchema(relation, registeredSchemas)] }\n : { ...foreignKey, description: `Foreign key into \\`${relation.targetSlug}\\`` };\n emitted.add(fieldKey);\n\n if (isRelationRequired(collection, relation) && !shadowedByInclude) required.push(fieldKey);\n }\n\n for (const [key, relation] of Object.entries(resolved)) {\n if (emitted.has(key)) continue;\n properties[key] = includedRelationSchema(relation, registeredSchemas);\n emitted.add(key);\n }\n\n // A `relation` property whose relation did not resolve. Still a field of the\n // row, just not a precisely describable one.\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n if (emitted.has(key)) continue;\n properties[key] = { type: \"object\" };\n emitted.add(key);\n }\n}\n\n/**\n * Build the component schema for a collection (output / read shape).\n *\n * Every declared property except the ones {@link isDocumentedProperty} rules\n * out, plus the foreign keys and relations {@link emitRelationProperties} adds.\n */\nfunction buildCollectionSchema(\n collection: CollectionConfig,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n const properties: Record<string, unknown> = {\n [idKey]: { ...idSchemaFor(collection), description: \"Unique identifier\" }\n };\n const required: string[] = [idKey];\n const excluded = excludedApiKeys(collection);\n const emitted = new Set<string>(excluded);\n emitted.add(idKey);\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property)) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required && key !== idKey) {\n required.push(key);\n }\n }\n\n emitRelationProperties(collection, properties, required, emitted, registeredSchemas);\n annotateTenantField(collection, properties, \"read\");\n\n return {\n type: \"object\",\n required: required.length > 0 ? required : undefined,\n properties\n };\n}\n\n/**\n * Mark the tenant field, on whichever schema is being built.\n *\n * A vendor extension for the same reason `x-rebase-access` is one: OpenAPI has\n * no keyword for \"the server fills this in from who you are, and refuses a\n * value that is not yours\". `readOnly` is the closest and it is wrong — the\n * field *is* writable, by a caller sending their own tenant, and a bypass role\n * may send any. Generators ignore what they do not know, so a client built from\n * this document still compiles; a human reading `/docs`, or a gateway wanting\n * to enforce the same boundary at the edge, learns the field is special and\n * why.\n *\n * Applied after the property loops rather than inside `convertPropertyToSchema`\n * because tenancy is a fact about the *collection*, and that function is handed\n * a property with no idea which collection it came from.\n */\nfunction annotateTenantField(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n direction: \"read\" | \"write\"\n): void {\n const tenant = getTenantConfig(collection);\n if (!tenant) return;\n const schema = properties[tenant.field];\n if (!schema || typeof schema !== \"object\") return;\n\n const existing = (schema as { description?: unknown }).description;\n const sentence = direction === \"write\"\n ? \"The tenant this row belongs to. Omit it and the server stamps the tenant you are calling as; \" +\n \"send another tenant's and the write is refused with `TENANT_MISMATCH`. It cannot be changed \" +\n \"on an update (`TENANT_IMMUTABLE`).\"\n : \"The tenant this row belongs to. Rows of other tenants are not returned at all.\";\n\n properties[tenant.field] = {\n ...(schema as Record<string, unknown>),\n description: typeof existing === \"string\" && existing ? `${existing} — ${sentence}` : sentence,\n \"x-rebase-tenant\": true\n };\n}\n\n/**\n * The PATCH/PUT operation for `/data/{slug}/{id}`.\n *\n * Split out because both verbs serve it and they must not drift: the update\n * body is a **partial**, and describing it with the create schema was the bug\n * this replaces. `<Name>Input` marks every `validation.required` property as\n * required — correct for POST, wrong for an update, where omitting a field\n * means \"leave it alone\" rather than \"I forgot it\". A client generated from\n * that spec demanded fields the server does not, and a spec-validating gateway\n * would have rejected partial updates the server accepts.\n */\nfunction updateOperation(\n collection: CollectionConfig,\n schemaName: string,\n requireAuth: boolean,\n shared: {\n ifMatchHeader: Record<string, unknown>;\n preferHeader: Record<string, unknown>;\n preconditionFailed: Record<string, unknown>;\n minimalResponse: Record<string, unknown>;\n }\n): Record<string, unknown> {\n return {\n tags: [collection.name],\n summary: `Update ${collection.singularName || collection.name}`,\n description:\n \"Partial update: only the properties present in the body are written; the rest are left \" +\n \"unchanged.\\n\\n\" +\n \"A property's value may instead be a field operation — `{ \\\"views\\\": { \\\"$inc\\\": 1 } }`, \" +\n \"`{ \\\"tags\\\": { \\\"$push\\\": \\\"new\\\" } }`, `{ \\\"tags\\\": { \\\"$pull\\\": \\\"old\\\" } }`, \" +\n \"`{ \\\"meta\\\": { \\\"$merge\\\": { \\\"seen\\\": true } } }` — which is applied inside the \" +\n \"statement holding the row lock. That is the difference between a counter that is correct \" +\n \"under concurrency and one that silently loses increments, because expressing the same \" +\n \"change as a value means reading it first. `$inc` needs a `number` property, `$push`/`$pull` \" +\n \"an `array`, `$merge` a `map`; anything else is a 400. See the `FieldOperation` schema.\",\n operationId: `update${schemaName}`,\n parameters: [\n { name: \"id\", in: \"path\", required: true, schema: { type: \"string\" }, description: \"Entity ID\" },\n shared.ifMatchHeader,\n shared.preferHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this update so a retry replays its answer instead of applying the edit \" +\n \"again. A PATCH is not naturally idempotent — a field operation emphatically is \" +\n \"not — so a retry after a lost response applies it twice.\"\n }\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n },\n responses: {\n 200: {\n description: \"Updated entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: {\n description: \"Entity not found\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...shared.preconditionFailed,\n ...shared.minimalResponse,\n ...errorResponses(requireAuth)\n }\n };\n}\n\n/**\n * The update body: the create schema with `required` dropped.\n *\n * Derived rather than rebuilt so the two cannot describe different columns —\n * the only difference between creating and updating is which fields you must\n * supply, and that is exactly the one thing removed here.\n */\nfunction buildCollectionUpdateSchema(collection: CollectionConfig): Record<string, unknown> {\n const { required: _required, ...rest } = buildCollectionInputSchema(collection);\n return rest;\n}\n\n/**\n * Build an input schema (for POST/PUT) — excludes auto-generated fields.\n *\n * `excludeFromApi` columns are left out too, and the server now agrees: a write\n * naming one is refused (`write-validation.ts`), which is what the flag's name\n * and the generated SDK's own documentation always said. This comment used to\n * record that such a write was \"still accepted\" — the document was right and\n * the server was the thing that had not caught up.\n */\nfunction buildCollectionInputSchema(collection: CollectionConfig): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n // The write half, not the read half: a field readable by nobody but\n // writable by an `admin` belongs in the input body and not in the row, and\n // the two schemas used to share one exclusion set and so got both wrong.\n const excluded = excludedApiKeys(collection, \"write\");\n const emitted = new Set<string>(excluded);\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property, \"write\")) continue;\n\n // Skip auto-value date fields from the input schema\n if (property.type === \"date\" && property.autoValue) continue;\n\n // Skip auto-generated ID fields\n if (\"isId\" in property && property.isId && property.isId !== \"manual\" && property.isId !== true) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required) {\n required.push(key);\n }\n }\n\n // Allow explicit ID for create (optional). Typed from the declared primary\n // key rather than as a `string` literal: a serial `id` was `integer` on the\n // read schema and `string` here, for the same column, in every document the\n // generator has ever produced.\n if (!emitted.has(idKey)) {\n properties[idKey] = {\n ...idSchemaFor(collection),\n description: \"Optional: client-assigned ID. If omitted, the server generates one.\"\n };\n emitted.add(idKey);\n }\n\n // The two ways a write may name a `belongsTo` target, both of which the\n // server accepts and the generated SDK's `Insert` already offered: the\n // foreign key under its own wire name (`authorId`), and the relation\n // property (`author`), which the write transformer maps onto that column.\n // Neither reached the document, so the spec described a create that could\n // not set a relation at all.\n emitWritableRelations(collection, properties, emitted);\n annotateTenantField(collection, properties, \"write\");\n\n // The tenant field is never required on input, whatever the property\n // declares: the column is NOT NULL, and the server is what fills it.\n // Listing it would tell every generated client to demand a value its caller\n // is not supposed to compute.\n const tenantField = getTenantConfig(collection)?.field;\n const requiredOnInput = tenantField ? required.filter(key => key !== tenantField) : required;\n\n return {\n type: \"object\",\n required: requiredOnInput.length > 0 ? requiredOnInput : undefined,\n properties\n };\n}\n\n/**\n * The writable half of a collection's relations: `belongsTo` only.\n *\n * A to-many is not writable through the body — the server links rows through\n * the nested routes — so offering `tags: [...]` on a create would describe a\n * write that does nothing. Same rule, same reason, as `emitWritableRelations`\n * in the SDK generator, whose `Insert` type this mirrors key for key.\n *\n * Neither spelling is listed in `required`, even for a relation the collection\n * declares `validation: { required: true }` on: the two keys are alternatives,\n * and a schema naming both would tell a spec-validating gateway to reject a\n * create that the server accepts. (The SDK's `Insert` marks both non-optional\n * for the same relation, which is the same fact stated less carefully; a\n * document is the half that a gateway enforces.)\n */\nfunction emitWritableRelations(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n emitted: Set<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n const emit = (key: string, relation: ResolvedRelation): void => {\n if (emitted.has(key)) return;\n properties[key] = {\n ...idSchemaFor(relationTarget(relation)),\n description: `The \\`${relation.targetSlug}\\` row this belongs to.`\n };\n emitted.add(key);\n };\n\n for (const relation of Object.values(resolved)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n emit(fieldKeyForColumn(collection, relation.localKey), relation);\n }\n }\n\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const relation = findRelation(resolved, key);\n if (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n }\n}\n\n/**\n * Convert a Rebase Property to an OpenAPI 3.0 schema object.\n */\nfunction convertPropertyToSchema(property: Property): Record<string, unknown> {\n const schema = convertPropertyTypeToSchema(property);\n const annotation = accessAnnotation(property);\n if (!annotation) return schema;\n\n const sentence = accessDescription(property);\n return {\n ...schema,\n ...(sentence\n ? { description: schema.description ? `${schema.description} — ${sentence}` : sentence }\n : {}),\n \"x-rebase-access\": annotation\n };\n}\n\n/** The JSON Schema a property's *type* compiles to, before any access annotation. */\nfunction convertPropertyTypeToSchema(property: Property): Record<string, unknown> {\n const base: Record<string, unknown> = {};\n\n if (property.name) {\n base.description = property.name;\n }\n\n switch (property.type) {\n case \"string\": {\n const sp = property as StringProperty;\n base.type = \"string\";\n\n if (sp.enum) {\n const enumValues = resolveEnumValues(sp.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (sp.validation) {\n if (sp.validation.min !== undefined) base.minLength = sp.validation.min;\n if (sp.validation.max !== undefined) base.maxLength = sp.validation.max;\n if (sp.validation.length !== undefined) {\n base.minLength = sp.validation.length;\n base.maxLength = sp.validation.length;\n }\n if (sp.validation.matches !== undefined) {\n base.pattern = String(sp.validation.matches);\n }\n }\n\n if (sp.email) base.format = \"email\";\n if (sp.url) base.format = \"uri\";\n if (sp.storage) base.format = \"uri\";\n\n return base;\n }\n\n case \"number\": {\n const np = property as NumberProperty;\n // `isId` is on this list because the DDL generator puts it there:\n // a numeric primary key is `INTEGER GENERATED BY DEFAULT AS\n // IDENTITY` for `\"increment\"` and `INTEGER` for every other form\n // (`generate-postgres-ddl-logic.ts`), so the column the scaffold's\n // `posts.id` creates is an integer and the document called it a\n // `number` — which lets a generated client send `1.5` for a row id.\n const isInteger = np.validation?.integer\n || Boolean(np.isId)\n || np.columnType === \"integer\" || np.columnType === \"serial\"\n || np.columnType === \"bigserial\" || np.columnType === \"bigint\";\n base.type = isInteger ? \"integer\" : \"number\";\n\n if (np.enum) {\n const enumValues = resolveEnumValues(np.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (np.validation) {\n if (np.validation.min !== undefined) base.minimum = np.validation.min;\n if (np.validation.max !== undefined) base.maximum = np.validation.max;\n if (np.validation.moreThan !== undefined) {\n base.minimum = np.validation.moreThan;\n base.exclusiveMinimum = true;\n }\n if (np.validation.lessThan !== undefined) {\n base.maximum = np.validation.lessThan;\n base.exclusiveMaximum = true;\n }\n }\n\n return base;\n }\n\n case \"boolean\":\n base.type = \"boolean\";\n return base;\n\n case \"date\": {\n base.type = \"string\";\n if (property.mode === \"date\") {\n base.format = \"date\";\n } else {\n base.format = \"date-time\";\n }\n if (property.autoValue) {\n base.readOnly = true;\n base.description = (base.description || \"\") +\n (property.autoValue === \"on_create\" ? \" (Auto-set on creation)\" : \" (Auto-updated)\");\n }\n return base;\n }\n\n case \"geopoint\":\n base.type = \"object\";\n base.properties = {\n latitude: { type: \"number\" },\n longitude: { type: \"number\" }\n };\n base.required = [\"latitude\", \"longitude\"];\n return base;\n\n case \"reference\":\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Reference ID)\";\n return base;\n\n case \"array\": {\n const ap = property as ArrayProperty;\n base.type = \"array\";\n\n if (ap.oneOf) {\n // Discriminated union (e.g., content blocks)\n const typeField = ap.oneOf.typeField || \"type\";\n const valueField = ap.oneOf.valueField || \"value\";\n const variants: Record<string, unknown>[] = [];\n\n for (const [variantKey, variantProp] of Object.entries(ap.oneOf.properties)) {\n variants.push({\n type: \"object\",\n properties: {\n [typeField]: { type: \"string\",\nenum: [variantKey] },\n [valueField]: convertPropertyToSchema(variantProp)\n },\n required: [typeField, valueField]\n });\n }\n\n base.items = { oneOf: variants };\n } else if (ap.of) {\n if (Array.isArray(ap.of)) {\n base.items = { oneOf: ap.of.map(p => convertPropertyToSchema(p)) };\n } else {\n base.items = convertPropertyToSchema(ap.of);\n }\n } else {\n base.items = {};\n }\n\n if (ap.validation) {\n if (ap.validation.min !== undefined) base.minItems = ap.validation.min;\n if (ap.validation.max !== undefined) base.maxItems = ap.validation.max;\n }\n\n return base;\n }\n\n case \"map\": {\n const mp = property as MapProperty;\n base.type = \"object\";\n\n if (mp.properties) {\n const props: Record<string, unknown> = {};\n const req: string[] = [];\n\n for (const [key, subProp] of Object.entries(mp.properties)) {\n props[key] = convertPropertyToSchema(subProp);\n if (subProp.validation?.required) {\n req.push(key);\n }\n }\n\n base.properties = props;\n if (req.length > 0) base.required = req;\n } else if (mp.keyValue) {\n base.additionalProperties = true;\n }\n\n return base;\n }\n\n case \"vector\": {\n const vp = property as VectorProperty;\n base.type = \"array\";\n base.items = { type: \"number\" };\n base.description = (base.description || \"\") + ` (Vector(${vp.dimensions}))`;\n return base;\n }\n case \"binary\": {\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Binary/Base64)\";\n return base;\n }\n default:\n base.type = \"string\";\n return base;\n }\n}\n\n/**\n * Resolve EnumValues (array or record) into a flat array of enum values.\n */\nfunction resolveEnumValues(enumDef: Record<string | number, unknown> | Array<{ id: string | number }>): Array<string | number> {\n if (Array.isArray(enumDef)) {\n return enumDef.map(e => (typeof e === \"object\" && e !== null && \"id\" in e) ? e.id : e as string | number);\n }\n return Object.keys(enumDef).map(k => {\n // Preserve numeric keys as numbers\n const num = Number(k);\n return isNaN(num) ? k : num;\n });\n}\n\n/**\n * Build PostgREST-style filter parameters for a collection.\n * These are additional query parameters like `?status=eq.active&price=gte.100`.\n *\n * `excludeFromApi` columns are not offered: the server does filter on them, and\n * that is exactly the problem — a filter on a column no response can contain\n * answers questions about the value one row at a time, which is a worse\n * disclosure than the column name alone.\n */\nfunction buildFilterParameters(\n collection: CollectionConfig,\n reservedNames: ReadonlySet<string> = new Set()\n): Array<Record<string, unknown>> {\n const params: Array<Record<string, unknown>> = [];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (!isDocumentedProperty(property)) continue;\n // A `relation` property is not a column, so there is nothing to compare\n // against. The foreign key beside it is filterable and is still not\n // offered here — a separate gap from the schema one, and one the query\n // layer has to answer first.\n if (property.type === \"relation\") continue;\n if (property.type === \"map\" || property.type === \"array\" || property.type === \"geopoint\") {\n continue;\n }\n // A column whose name a list parameter already owns is unfilterable\n // over the wire — see `reservedParameterNames`.\n if (reservedNames.has(key)) continue;\n\n params.push({\n name: key,\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n `Filter by \\`${key}\\`. Supports PostgREST operators: ` +\n \"`eq.value`, `neq.value`, `gt.value`, `gte.value`, `lt.value`, `lte.value`, \" +\n \"`in.(a,b,c)`, `nin.(a,b,c)`, `cs.value` (array-contains), `csa.(a,b)` (array-contains-any). \" +\n \"Plain values imply equality.\",\n example: property.type === \"string\" ? \"eq.active\" : property.type === \"number\" ? \"gte.100\" : undefined\n });\n }\n\n return params;\n}\n\n/**\n * Standard error responses included on every endpoint.\n */\nfunction errorResponses(requireAuth: boolean): Record<string, unknown> {\n const responses: Record<string, unknown> = {\n 400: {\n description: \"Bad request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 500: {\n description: \"Internal server error\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n if (requireAuth) {\n responses[401] = {\n description: \"Authentication required or invalid token\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n responses[403] = {\n description: \"Insufficient permissions\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n }\n\n return responses;\n}\n\n/**\n * Prefix a noun with \"a\" or \"an\" based on its leading sound.\n */\nfunction withIndefiniteArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? \"an\" : \"a\"} ${noun}`;\n}\n\n/**\n * The component-schema name for a collection — and the stem of every\n * `operationId` and `$ref` that mentions it.\n *\n * `toPascalCase` keeps ASCII letters and digits and nothing else, so a name\n * written in a script that has none of them — a Cyrillic or Japanese\n * `singularName`, which the docs' six locales make ordinary rather than exotic\n * — reduced to the empty string. The schema was then stored under `\"\"` and\n * every reference to it read `#/components/schemas/`, an unresolvable pointer:\n * Swagger UI renders the model empty and a strict generator fails outright. So\n * fall through the names until one survives, and keep a constant as the floor.\n *\n * Two collections whose names PascalCase identically still share one component;\n * that needs a disambiguation rule, not a fallback.\n */\nfunction schemaNameFor(collection: CollectionConfig): string {\n return toPascalCase(collection.singularName || \"\")\n || toPascalCase(collection.name || \"\")\n || toPascalCase(collection.slug || \"\")\n || \"Collection\";\n}\n\n/**\n * Convert a string to PascalCase for schema names.\n */\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .split(\" \")\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n"],"mappings":"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"}
|