@rebasepro/server 0.17.3 → 0.18.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/LICENSE +0 -1
- package/README.md +5 -1
- package/bin/rebase-server.js +16 -1
- package/dist/{GCSStorageController-Bl7nmhdv.js → GCSStorageController-ZXPoNqW3.js} +21 -10
- package/dist/GCSStorageController-ZXPoNqW3.js.map +1 -0
- package/dist/{S3StorageController-CWvvrRpF.js → S3StorageController-5pAXyv31.js} +24 -10
- package/dist/S3StorageController-5pAXyv31.js.map +1 -0
- package/dist/admin-roles-vYdp_Pil.js +36 -0
- package/dist/admin-roles-vYdp_Pil.js.map +1 -0
- package/dist/{admin_block-BX6YULvJ.js → admin_block-0Xu0r6eZ.js} +4 -1
- package/dist/admin_block-0Xu0r6eZ.js.map +1 -0
- package/dist/api/errors.d.ts +37 -0
- package/dist/api/live-schema-routes.d.ts +14 -0
- package/dist/api/logs-routes.d.ts +8 -0
- package/dist/api/rest/api-generator.d.ts +33 -0
- package/dist/api/root-error-handler.d.ts +62 -0
- package/dist/api/schema-drift.d.ts +39 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/{ast-schema-editor-CvkYOvri.js → ast-schema-editor-CNgFJ3NF.js} +23 -7
- package/dist/ast-schema-editor-CNgFJ3NF.js.map +1 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +12 -0
- package/dist/auth/auth-hooks.d.ts +13 -2
- package/dist/auth/exclude-auth-secrets.d.ts +14 -0
- package/dist/auth/jwt.d.ts +22 -1
- package/dist/auth/mfa-routes.d.ts +10 -1
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/registration-policy.d.ts +27 -0
- package/dist/auth/rls-scope.d.ts +9 -1
- package/dist/auth/routes.d.ts +8 -1
- package/dist/auth/seed-admin.d.ts +73 -0
- package/dist/auth/session-routes.d.ts +10 -0
- package/dist/{auth-DkbzMZUS.js → auth-BRiOuyq8.js} +283 -198
- package/dist/auth-BRiOuyq8.js.map +1 -0
- package/dist/{backup-CRdZkA6c.js → backup-C8P6Cl3G.js} +2 -2
- package/dist/{backup-CRdZkA6c.js.map → backup-C8P6Cl3G.js.map} +1 -1
- package/dist/boot/boot.d.ts +18 -0
- package/dist/boot/bundle.d.ts +29 -6
- package/dist/boot/ddl-bootstrap.d.ts +2 -0
- package/dist/boot/env.d.ts +30 -1
- package/dist/boot/fetch-bundle.d.ts +24 -1
- package/dist/boot/options.d.ts +0 -8
- package/dist/boot/provision.d.ts +58 -18
- package/dist/boot/resource-adapters.d.ts +11 -1
- package/dist/boot/resource-resolvers.d.ts +96 -0
- package/dist/boot/sources.d.ts +27 -20
- package/dist/collections/validate-config.d.ts +29 -0
- package/dist/{contract-routes-CHPVKTZW.js → contract-routes-CusnEB5h.js} +24 -9
- package/dist/{contract-routes-CHPVKTZW.js.map → contract-routes-CusnEB5h.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +7 -1
- package/dist/cron/cron-scheduler.d.ts +48 -2
- package/dist/{cron-loader-d9WMFENB.js → cron-loader-DnmIePn_.js} +23 -4
- package/dist/cron-loader-DnmIePn_.js.map +1 -0
- package/dist/{cron-routes-DSC-kZXI.js → cron-routes-D3x2ydMa.js} +9 -6
- package/dist/cron-routes-D3x2ydMa.js.map +1 -0
- package/dist/{cron-scheduler-BB82dWuU.js → cron-scheduler-BpPKpl4i.js} +148 -28
- package/dist/cron-scheduler-BpPKpl4i.js.map +1 -0
- package/dist/{cron-store-KeN8rN63.js → cron-store-CCQXwgVL.js} +5 -5
- package/dist/{cron-store-KeN8rN63.js.map → cron-store-CCQXwgVL.js.map} +1 -1
- package/dist/{ddl-bootstrap-5YZCZ8qk.js → ddl-bootstrap-CfNvxMuK.js} +26 -2
- package/dist/{ddl-bootstrap-5YZCZ8qk.js.map → ddl-bootstrap-CfNvxMuK.js.map} +1 -1
- package/dist/email/index.d.ts +1 -0
- package/dist/email/unconfigured.d.ts +20 -0
- package/dist/env.d.ts +25 -0
- package/dist/errors-HjfaPlvY.js +385 -0
- package/dist/errors-HjfaPlvY.js.map +1 -0
- package/dist/{function-loader-LLdmBFoL.js → function-loader-xnbDAPfa.js} +9 -3
- package/dist/{function-loader-LLdmBFoL.js.map → function-loader-xnbDAPfa.js.map} +1 -1
- package/dist/function-routes-gQ0EShVG.js +267 -0
- package/dist/function-routes-gQ0EShVG.js.map +1 -0
- package/dist/functions/define-function.d.ts +6 -3
- package/dist/functions/function-routes.d.ts +8 -4
- package/dist/functions/index.js +128 -23
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-B2pL3X8b.js → history-recorder-B1FwXx9J.js} +3 -3
- package/dist/{history-recorder-B2pL3X8b.js.map → history-recorder-B1FwXx9J.js.map} +1 -1
- package/dist/{history-store-BUJ-37Sm.js → history-store-LHXaQywp.js} +2 -2
- package/dist/{history-store-BUJ-37Sm.js.map → history-store-LHXaQywp.js.map} +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.es.js +16229 -7676
- package/dist/index.es.js.map +1 -1
- package/dist/init.d.ts +33 -13
- package/dist/{jobs-XXXcZrmj.js → jobs-DkkD9mPV.js} +5 -5
- package/dist/{jobs-XXXcZrmj.js.map → jobs-DkkD9mPV.js.map} +1 -1
- package/dist/{jwt-DNeOn39r.js → jwt-DkhXwMzR.js} +9 -314
- package/dist/jwt-DkhXwMzR.js.map +1 -0
- package/dist/keys-g8lbVC_o.js +230 -0
- package/dist/keys-g8lbVC_o.js.map +1 -0
- package/dist/{logger-DS03e908.js → logger-DO2PZc4i.js} +197 -18
- package/dist/logger-DO2PZc4i.js.map +1 -0
- package/dist/{logs-routes-DB72iQSr.js → logs-routes-CbsTpozn.js} +74 -6
- package/dist/logs-routes-CbsTpozn.js.map +1 -0
- package/dist/{openapi-generator-CpZp2Fai.js → openapi-generator-BIBbO1Tq.js} +221 -38
- package/dist/openapi-generator-BIBbO1Tq.js.map +1 -0
- package/dist/{proxy-CMymhnwG.js → proxy-Czngl3p9.js} +2 -2
- package/dist/{proxy-CMymhnwG.js.map → proxy-Czngl3p9.js.map} +1 -1
- package/dist/{query-parser-DqXNaBIH.js → query-parser-C68Q9EX4.js} +35 -6
- package/dist/{query-parser-DqXNaBIH.js.map → query-parser-C68Q9EX4.js.map} +1 -1
- package/dist/queues/runtime.d.ts +56 -0
- package/dist/{request-timeout-C8gkc-j7.js → request-timeout-DESvlfrS.js} +4 -6
- package/dist/request-timeout-DESvlfrS.js.map +1 -0
- package/dist/schema-edit/project-root.d.ts +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js → schema-editor-routes-CcZKh50q.js} +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js.map → schema-editor-routes-CcZKh50q.js.map} +1 -1
- package/dist/{schemas-DBxgjM9A.js → schemas-C3234HWE.js} +2746 -72
- package/dist/schemas-C3234HWE.js.map +1 -0
- package/dist/singleton.d.ts +6 -4
- package/dist/src-DHK4fHkw.js +1002 -0
- package/dist/src-DHK4fHkw.js.map +1 -0
- package/dist/{src-C9gKj23J.js → src-Dq-I3Ybx.js} +507 -54
- package/dist/src-Dq-I3Ybx.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +5 -0
- package/dist/storage/LocalStorageController.d.ts +14 -0
- package/dist/storage/S3StorageController.d.ts +8 -0
- package/dist/storage/image-transform.d.ts +35 -0
- package/dist/storage/keys.d.ts +23 -0
- package/dist/storage/policies.d.ts +22 -0
- package/dist/storage/routes.d.ts +9 -0
- package/dist/storage/tus-handler.d.ts +43 -1
- package/dist/storage/types.d.ts +28 -0
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/dev-port.d.ts +18 -0
- package/dist/utils/logger.d.ts +60 -0
- package/dist/utils/request-logger.d.ts +2 -1
- package/package.json +30 -24
- package/dist/GCSStorageController-Bl7nmhdv.js.map +0 -1
- package/dist/S3StorageController-CWvvrRpF.js.map +0 -1
- package/dist/admin_block-BX6YULvJ.js.map +0 -1
- package/dist/ast-schema-editor-CvkYOvri.js.map +0 -1
- package/dist/auth-DkbzMZUS.js.map +0 -1
- package/dist/cron-loader-d9WMFENB.js.map +0 -1
- package/dist/cron-routes-DSC-kZXI.js.map +0 -1
- package/dist/cron-scheduler-BB82dWuU.js.map +0 -1
- package/dist/errors-DBwpj9N8.js +0 -231
- package/dist/errors-DBwpj9N8.js.map +0 -1
- package/dist/function-routes-ClT6UQpD.js +0 -41
- package/dist/function-routes-ClT6UQpD.js.map +0 -1
- package/dist/jwt-DNeOn39r.js.map +0 -1
- package/dist/logger-DS03e908.js.map +0 -1
- package/dist/logs-routes-DB72iQSr.js.map +0 -1
- package/dist/openapi-generator-CpZp2Fai.js.map +0 -1
- package/dist/request-timeout-C8gkc-j7.js.map +0 -1
- package/dist/schemas-DBxgjM9A.js.map +0 -1
- package/dist/src-C9gKj23J.js.map +0 -1
- package/dist/src-CcOl05Oq.js +0 -552
- package/dist/src-CcOl05Oq.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-DHK4fHkw.js","names":[],"sources":["../../types/src/types/filter-operators.ts","../../types/src/types/data_source.ts","../../types/src/types/collections.ts","../../types/src/types/rls-functions.ts","../../types/src/types/resources.ts","../../types/src/types/storage_source.ts","../../types/src/types/resource_kinds.ts"],"sourcesContent":["/**\n * Canonical filter operators and REST wire-format mappings.\n *\n * `WhereFilterOp` is THE operator type used at every layer — from React\n * components through the SDK, server, and down to the database driver.\n *\n * PostgREST short-codes (`eq`, `gt`, `cs`, …) exist **only** at the\n * HTTP wire boundary, handled by `serializeFilter` / `deserializeFilter`\n * in `@rebasepro/common`.\n *\n * ┌──────────────────────┬───────────────┬──────────────────────────────┐\n * │ Canonical │ REST short │ Meaning │\n * ├──────────────────────┼───────────────┼──────────────────────────────┤\n * │ \"==\" │ \"eq\" │ Equal │\n * │ \"!=\" │ \"neq\" │ Not equal │\n * │ \">\" │ \"gt\" │ Greater than │\n * │ \">=\" │ \"gte\" │ Greater than or equal │\n * │ \"<\" │ \"lt\" │ Less than │\n * │ \"<=\" │ \"lte\" │ Less than or equal │\n * │ \"in\" │ \"in\" │ Value in list │\n * │ \"not-in\" │ \"nin\" │ Value not in list │\n * │ \"array-contains\" │ \"cs\" │ Array contains element │\n * │ \"array-contains-any\" │ \"csa\" │ Array contains any of │\n * │ \"like\" │ \"like\" │ SQL LIKE (case-sensitive) │\n * │ \"ilike\" │ \"ilike\" │ SQL ILIKE (case-insensitive) │\n * │ \"not-like\" │ \"nlike\" │ NOT LIKE (case-sensitive) │\n * │ \"not-ilike\" │ \"nilike\" │ NOT ILIKE (case-insensitive) │\n * │ \"is-null\" │ \"isnull\" │ Field IS NULL │\n * │ \"is-not-null\" │ \"notnull\" │ Field IS NOT NULL │\n * └──────────────────────┴───────────────┴──────────────────────────────┘\n *\n * Pattern matching (`like`/`ilike`) uses SQL wildcard syntax: `%` matches any\n * sequence of characters, `_` matches a single character. On MongoDB these are\n * translated to anchored regular expressions; Firestore has no native pattern\n * matching and rejects these operators (use `searchString` instead).\n *\n * @module\n */\n\n/**\n * Canonical sort representation: `[fieldName, direction]`.\n *\n * Used in `FindParams.orderBy`, `collection.sort`, and `FilterPreset.sort`.\n * The colon-string form (`\"field:direction\"`) exists only at the HTTP wire\n * boundary, handled by `serializeOrderBy` / `deserializeOrderBy` in\n * `@rebasepro/common`.\n *\n * @group Models\n */\nexport type OrderByTuple<Key extends string = string> = [Key, \"asc\" | \"desc\"];\n\n/**\n * One sort key, or several applied in order of significance.\n *\n * ```ts\n * orderBy: [\"created_at\", \"desc\"] // one key\n * orderBy: [[\"roles\", \"asc\"], [\"created_at\", \"desc\"]] // roles, then newest first\n * ```\n *\n * The two forms are told apart by whether the first element is itself an\n * array, so a single tuple never needs wrapping and every existing caller\n * keeps working unchanged. `normalizeOrderBy` in `@rebasepro/common` collapses\n * both to the list form, which is what every layer below the call site speaks.\n *\n * Ties on the last key are broken by the row id, so a multi-key sort is a\n * total order and pages over it neither repeat nor skip rows.\n *\n * @group Models\n */\nexport type OrderBySpec<Key extends string = string> =\n | OrderBySortTuple<Key>\n | OrderBySortTuple<Key>[];\n\n/**\n * A sort key: a field name, or an aggregate over a to-many relation.\n *\n * @group Models\n */\nexport type SortKey<Key extends string = string> = Key | RelationAggregateSort;\n\n/**\n * `[sortKey, direction]` — the authoring form of {@link OrderByTuple}, which\n * additionally accepts a {@link RelationAggregateSort} object.\n *\n * The object never reaches a driver: `normalizeOrderBy` in `@rebasepro/common`\n * encodes it to its string spelling on the way down, and everything below that\n * point speaks plain `OrderByTuple`. See {@link RelationAggregateSort} for why\n * the wire form is a string.\n *\n * @group Models\n */\nexport type OrderBySortTuple<Key extends string = string> = [SortKey<Key>, \"asc\" | \"desc\"];\n\n/**\n * The aggregate functions a relation sort can apply.\n *\n * Five, and no `array_agg`/`string_agg`: an aggregate used as a sort key has to\n * produce something with an order, and these are the ones that do.\n *\n * @group Models\n */\nexport type RelationAggregateFn = \"min\" | \"max\" | \"count\" | \"sum\" | \"avg\";\n\n/**\n * Order rows by an aggregate over the rows a to-many relation reaches —\n * \"candidates, oldest waiting first\", \"clients, busiest first\".\n *\n * ```ts\n * // The date of each candidate's earliest open application.\n * orderBy: [[{ relation: \"applications\", field: \"created_at\", agg: \"min\" }, \"asc\"]]\n *\n * // How many applications each candidate has.\n * orderBy: [[{ relation: \"applications\", agg: \"count\" }, \"desc\"]]\n * ```\n *\n * This is the half of a queue that cannot be worked around client-side. A\n * *filter* over a relation can be approximated by denormalising a flag onto the\n * row; an *ordering* cannot be approximated at all once the result set is\n * paged, because the client only ever holds one page and the page was chosen by\n * the wrong order.\n *\n * Rows the relation reaches nothing from sort last ascending and first\n * descending — the placement Postgres gives a `NULL`, stated rather than\n * inherited, because the keyset comparison behind cursor paging has to agree\n * with it exactly. Ties are broken by the row id, so the order is total and\n * paging over it neither repeats nor skips.\n *\n * Compiled by the driver into a correlated subquery, so it is subject to the\n * reader's own row-level security on the target table: a related row the reader\n * cannot see does not contribute to the aggregate. Offered only where\n * {@link DataSourceCapabilities.relationAggregateSorts} says the driver can\n * compile it.\n *\n * @group Models\n */\nexport interface RelationAggregateSort {\n /** The to-many relation to aggregate over, by its name on this collection. */\n relation: string;\n\n /** The aggregate to apply. */\n agg: RelationAggregateFn;\n\n /**\n * The column of the *target* to aggregate. Required by every function\n * except `count`, which counts the related rows themselves when it is\n * omitted — and counts the rows whose column is non-null when it is not.\n */\n field?: string;\n}\n\n/** The wire spelling of a {@link RelationAggregateSort}: `min(applications.created_at)`. */\nconst RELATION_AGGREGATE_SORT_PATTERN = /^(min|max|count|sum|avg)\\(([^().]+)(?:\\.([^()]+))?\\)$/;\n\n/**\n * A {@link RelationAggregateSort} as a single string — `min(applications.created_at)`,\n * `count(applications)`.\n *\n * The wire form is a string because every layer below the call site already is\n * one: `OrderByTuple` is `[string, direction]`, the REST parameter is\n * `?orderBy=key:direction`, the driver contract takes `orderBy?: string |\n * OrderByTuple[]`, and a cursor names its keys by string. `_score` established\n * the same pattern — a sort key that is not a column, spelled as one — and this\n * reuses it rather than widening five signatures to carry an object that would\n * be flattened at the end anyway.\n *\n * SQL's own spelling, so the key reads as what it compiles to. Neither `:` nor\n * `,` appears in it, which is what keeps it safe in the colon-delimited wire\n * shorthand.\n *\n * @group Models\n */\nexport function encodeRelationAggregateSort(sort: RelationAggregateSort): string {\n return `${sort.agg}(${sort.relation}${sort.field ? `.${sort.field}` : \"\"})`;\n}\n\n/**\n * Read the string spelling back, or `undefined` if it is not one.\n *\n * `undefined` rather than a throw: this is asked of *every* sort key to find\n * out which kind it is, and an ordinary column name is not an error.\n *\n * @group Models\n */\nexport function parseRelationAggregateSort(key: string): RelationAggregateSort | undefined {\n const match = RELATION_AGGREGATE_SORT_PATTERN.exec(key);\n if (!match) return undefined;\n const [, agg, relation, field] = match;\n // `min()` and friends have nothing to aggregate without a column, and a\n // key that parses to a half-built sort would resolve to no expression and\n // be dropped — leaving the rows unsorted while the caller believes\n // otherwise. `count` is the one function that means something on its own.\n if (!field && agg !== \"count\") return undefined;\n return { agg: agg as RelationAggregateFn, relation, ...(field && { field }) };\n}\n\n/** Is this sort key the object form rather than a field name? */\nexport function isRelationAggregateSort(key: unknown): key is RelationAggregateSort {\n return typeof key === \"object\" && key !== null &&\n typeof (key as RelationAggregateSort).relation === \"string\" &&\n typeof (key as RelationAggregateSort).agg === \"string\";\n}\n\n/** A sort key in the single-string form every layer below the call site speaks. */\nexport function sortKeyToString(key: SortKey): string {\n return isRelationAggregateSort(key) ? encodeRelationAggregateSort(key) : key;\n}\n\n/**\n * Canonical filter operators supported across all database backends.\n * Each DB driver translates these to its native query format.\n *\n * @group Models\n */\nexport type WhereFilterOp =\n | \"<\"\n | \"<=\"\n | \"==\"\n | \"!=\"\n | \">=\"\n | \">\"\n | \"array-contains\"\n | \"in\"\n | \"not-in\"\n | \"array-contains-any\"\n | \"like\"\n | \"ilike\"\n | \"not-like\"\n | \"not-ilike\"\n | \"is-null\"\n | \"is-not-null\";\n\n/**\n * Used to define filters applied in collections.\n *\n * A single condition is a tuple `[operator, value]`.\n * Multiple conditions on the same field use an array of tuples.\n *\n * @example\n * // Single condition per field\n * { status: [\"==\", \"active\"], price: [\">=\", 9.99] }\n *\n * // Multiple conditions on one field\n * { age: [[\">=\", 18], [\"<\", 65]] }\n *\n * // Array operators\n * { role: [\"in\", [\"admin\", \"editor\"]] }\n * { tags: [\"array-contains\", \"featured\"] }\n *\n * // Pattern matching (SQL wildcards: % and _)\n * { name: [\"ilike\", \"%john%\"] }\n * { slug: [\"like\", \"post-%\"] }\n *\n * // Null checks (the value is ignored; `null` is conventional)\n * { deleted_at: [\"is-null\", null] }\n * { published_at: [\"is-not-null\", null] }\n *\n * @group Models\n */\nexport type FilterValues<Key extends string> =\n Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][]>>;\n\n/**\n * The field names a query may address on a row type: every column, plus a\n * dotted path reaching inside one — or *through a relation* to a column of the\n * related row.\n *\n * A dotted path is not checked at all, in either direction. That is a\n * deliberate loosening, and it is worth being exact about what it costs. The\n * root used to be checked: `\"meta.tag\"` required a `meta` column. It cannot\n * stay checked, because the other thing a dotted path now means is\n * `\"applications.status\"` — and `applications` is a *relation*, which comes\n * from the collection's `relations` and is not a column of `M` at all. There is\n * nothing in a generated row type that could validate one. `FindParams.include`\n * is `string[]` for exactly this reason and says so.\n *\n * So the guarantee moves rather than disappears: an unresolvable path is a 400\n * from the driver, not a silently dropped condition. See\n * `UnknownFilterFieldsMode` in `@rebasepro/server-postgres` — dropping a filter\n * key *widens* the read to every row, which is why that resolution fails\n * closed. A typo'd relation path is refused at runtime with the target\n * collection's real column list in the message.\n *\n * A JSON path — `metadata->>tier` — is admitted on the same terms and for the\n * same reason. It addresses a key *inside* a `json`/`jsonb` column, so nothing\n * in a generated row type describes it either; the driver resolves it and\n * refuses what it cannot. It has no dot, so the dotted branch above never\n * covered it, and every documented `?metadata->>tier=eq.gold` filter was a\n * compile error on a typed client while working perfectly over HTTP.\n *\n * Undotted keys are unaffected and still checked against `keyof M`.\n *\n * When `M` is left at its default `Record<string, unknown>`, `keyof M` is\n * `string` and this collapses to `string`, so every query stays permissive.\n * That is what keeps an untyped `createRebaseClient()` behaving exactly as it\n * did before the row type was threaded through.\n *\n * @group Models\n */\nexport type FieldPath<M extends Record<string, unknown> = Record<string, unknown>> =\n | Extract<keyof M, string>\n | NonColumnFieldPath;\n\n/**\n * A field key that is not a column: a relation path (`author.name`) or a JSON\n * path (`metadata->>tier`).\n *\n * The fluent builder needs this on its own, where `FindParams` does not. Its\n * `where(column, operator, value)` types the value against `M[column]`, which\n * only means something for a real column — so paths take a second overload\n * whose value is `unknown`. Keying that overload on the *shape* of a path,\n * rather than on \"everything that is not a column\", is what keeps a real column\n * with a wrong value type from falling through to it and being accepted: a\n * mistyped column name has neither a dot nor a `->>`, so it matches neither\n * overload and is still refused.\n *\n * @group Models\n */\nexport type NonColumnFieldPath =\n | `${string}.${string}`\n | `${string}->>${string}`;\n\n/**\n * Relaxed filter type that also accepts pre-serialized PostgREST strings.\n * **Internal only** — used at the wire-format boundary\n * (`serializeFilter` / `deserializeFilter` in `@rebasepro/common`).\n *\n * Application code, UI components, and SDK consumers should use\n * {@link FilterValues} instead.\n *\n * @internal\n */\nexport type WireFilterValues<Key extends string> =\n Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][] | string>>;\n\n/**\n * A pre-defined filter preset for quick access in the collection toolbar.\n * Users can select a preset to instantly apply a set of filters and\n * optionally a sort order.\n *\n * @group Models\n */\nexport interface FilterPreset<Key extends string = string> {\n /**\n * Display label shown in the preset menu.\n * If omitted, a summary is auto-generated from the filter keys.\n */\n label?: string;\n\n /**\n * The filter values to apply when this preset is selected.\n */\n filterValues: FilterValues<Key>;\n\n /**\n * Optional sort override to apply alongside the filter values.\n * One key, or several in order of significance.\n */\n sort?: OrderBySpec<Key>;\n}\n\n/**\n * PostgREST short-code operators. Wire format only — these never appear\n * in application code. Used by `serializeFilter`/`deserializeFilter`\n * in `@rebasepro/common`.\n */\nexport type RestFilterOp =\n | \"eq\" | \"neq\"\n | \"gt\" | \"gte\"\n | \"lt\" | \"lte\"\n | \"in\" | \"nin\"\n | \"cs\" | \"csa\"\n | \"like\" | \"ilike\"\n | \"nlike\" | \"nilike\"\n | \"isnull\" | \"notnull\";\n\n/** Maps canonical operators to their REST short-code equivalents. */\nexport const CANONICAL_TO_REST: Readonly<Record<WhereFilterOp, RestFilterOp>> = {\n \"==\": \"eq\",\n \"!=\": \"neq\",\n \">\": \"gt\",\n \">=\": \"gte\",\n \"<\": \"lt\",\n \"<=\": \"lte\",\n \"in\": \"in\",\n \"not-in\": \"nin\",\n \"array-contains\": \"cs\",\n \"array-contains-any\": \"csa\",\n \"like\": \"like\",\n \"ilike\": \"ilike\",\n \"not-like\": \"nlike\",\n \"not-ilike\": \"nilike\",\n \"is-null\": \"isnull\",\n \"is-not-null\": \"notnull\"\n};\n\n/** Maps REST short-code operators to their canonical equivalents. */\nexport const REST_TO_CANONICAL: Readonly<Record<RestFilterOp, WhereFilterOp>> = {\n \"eq\": \"==\",\n \"neq\": \"!=\",\n \"gt\": \">\",\n \"gte\": \">=\",\n \"lt\": \"<\",\n \"lte\": \"<=\",\n \"in\": \"in\",\n \"nin\": \"not-in\",\n \"cs\": \"array-contains\",\n \"csa\": \"array-contains-any\",\n \"like\": \"like\",\n \"ilike\": \"ilike\",\n \"nlike\": \"not-like\",\n \"nilike\": \"not-ilike\",\n \"isnull\": \"is-null\",\n \"notnull\": \"is-not-null\"\n};\n\n/**\n * Operators that test for null/not-null and therefore ignore their value.\n * Codecs normalize the value of these conditions to `null`.\n */\nexport const NULL_OPS: ReadonlySet<WhereFilterOp> = new Set<WhereFilterOp>([\n \"is-null\", \"is-not-null\"\n]);\n\n/**\n * Every canonical operator, in a stable order. Useful for engine capability\n * declarations ({@link DataSourceCapabilities.filterOperators}) and for\n * building operator subsets.\n * @group Models\n */\nexport const ALL_WHERE_FILTER_OPS: readonly WhereFilterOp[] = [\n \"<\", \"<=\", \"==\", \"!=\", \">=\", \">\",\n \"in\", \"not-in\",\n \"array-contains\", \"array-contains-any\",\n \"like\", \"ilike\", \"not-like\", \"not-ilike\",\n \"is-null\", \"is-not-null\"\n];\n\n/** All canonical operator strings for runtime validation. */\nconst CANONICAL_OPS: ReadonlySet<string> = new Set<WhereFilterOp>(ALL_WHERE_FILTER_OPS);\n\n/**\n * The REST table as a `Map`, because the key `toCanonicalOp` is handed comes\n * off the wire.\n *\n * Indexed as a plain object, every `Object.prototype` member answered:\n * `toCanonicalOp(\"valueOf\")` returned the inherited *function* as though it\n * were a `WhereFilterOp`, and every caller here treats a defined result as\n * \"known operator\". Same defect the REST codec's own lookup tables were\n * converted away from in `filter-dialect.ts`; this is the copy that survived\n * one package over, and it now sits under the operator validation the REST\n * parser does, which would otherwise have admitted `[\"constructor\", x]`.\n */\nconst REST_OP_LOOKUP: ReadonlyMap<string, WhereFilterOp> = new Map<string, WhereFilterOp>(\n Object.entries(REST_TO_CANONICAL) as [string, WhereFilterOp][]\n);\n\n/**\n * Resolve any operator string (canonical or REST short-code) to its\n * canonical `WhereFilterOp` form. Returns `undefined` for unknown operators.\n *\n * @example\n * toCanonicalOp(\"==\") // \"==\"\n * toCanonicalOp(\"eq\") // \"==\"\n * toCanonicalOp(\"cs\") // \"array-contains\"\n * toCanonicalOp(\"xyz\") // undefined\n */\nexport function toCanonicalOp(op: string): WhereFilterOp | undefined {\n if (CANONICAL_OPS.has(op)) return op as WhereFilterOp;\n return REST_OP_LOOKUP.get(op);\n}\n","import { ALL_WHERE_FILTER_OPS, WhereFilterOp } from \"./filter-operators\";\n\n/**\n * Describes the capabilities and features supported by a data source (driver).\n *\n * Each driver (Postgres, Firebase, MongoDB, etc.) declares which features it\n * supports. The admin uses this descriptor to:\n * - Show/hide editor tabs (e.g. Relations for SQL, Subcollections for Firebase)\n * - Filter the property type picker (e.g. `relation` for SQL, `reference` for Firebase)\n * - Toggle driver-specific form controls (e.g. `columnType` for SQL)\n *\n * @group Models\n */\nexport interface DataSourceCapabilities {\n /** Unique driver key (e.g. \"postgres\", \"firestore\", \"mongodb\") */\n key: string;\n\n /** Human-readable label for the UI (e.g. \"PostgreSQL\", \"Firebase / Firestore\") */\n label: string;\n\n // ── Feature flags ─────────────────────────────────────────────────\n /** Does this source support SQL-style relations (JOINs)? */\n supportsRelations: boolean;\n\n /** Does this source support nested subcollections? */\n supportsSubcollections: boolean;\n\n /** Does this source support Row Level Security policies? */\n supportsRLS: boolean;\n\n /** Does this source support document references (Firebase-style)? */\n supportsReferences: boolean;\n\n /** Does this source support SQL column type annotations? */\n supportsColumnTypes: boolean;\n\n /** Does this source support real-time listeners? */\n supportsRealtime: boolean;\n\n /**\n * Does this source store vectors natively?\n *\n * `VectorProperty` carries a `dimensions` and is pgvector-shaped. It was\n * the one driver-specific property kind with no flag to gate it, so unlike\n * every other field in this descriptor there was not even a runtime answer\n * to appeal to — a Firestore collection could declare an embedding column\n * and no driver would do anything with it.\n */\n supportsVectors: boolean;\n\n /**\n * Canonical filter operators this engine can execute.\n *\n * The admin UI intersects this set with the property-type defaults and\n * any per-property narrowing (`property.ui.filterOperators`) to decide\n * which operators to offer in filter fields — so an engine that cannot\n * run `ilike` (e.g. Firestore) never shows a \"Contains\" filter that\n * would throw at query time.\n */\n filterOperators: readonly WhereFilterOp[];\n\n /**\n * Relation kinds this engine's driver can compile into a filter.\n *\n * Only `belongsTo` puts a column on the row being filtered; the others are\n * answered with a correlated subquery over the junction or the target\n * table, which not every driver can build. An engine with no relations at\n * all declares none.\n *\n * The admin uses this to decide whether a relation column offers a filter\n * control. Offering one an engine cannot answer is not cosmetic: a driver\n * that drops the key it cannot resolve *widens* the read to every row, and\n * one that fails closed answers a control the admin itself put on screen\n * with a 400.\n *\n * Optional, so a third-party driver registered before this existed still\n * compiles. Omitted means {@link DEFAULT_FILTERABLE_RELATION_KINDS} — the\n * one kind that is a plain column comparison, which every relational\n * driver can do. The subquery kinds are a real capability and have to be\n * claimed rather than assumed: assuming them wrongly is the widening.\n */\n filterableRelationKinds?: readonly string[];\n\n /**\n * Can a filter address a *column of the related row* — `applications.status`\n * — rather than only the related row's id?\n *\n * A separate capability from {@link filterableRelationKinds} because it is\n * a separate subquery: the id filter stops at the junction, one of these\n * reaches the target table and compares one of its columns. A driver can\n * do the first and not the second.\n *\n * Optional and defaulting to **false**, for the reason the relation kinds\n * default narrow: an unclaimed capability that the admin assumes is there\n * produces a control whose query the driver answers by dropping the key —\n * and a dropped filter key widens the read to every row.\n *\n * Meaningless without {@link supportsRelations}; a driver with no relations\n * has nothing to reach through.\n */\n supportsRelationFieldFilters?: boolean;\n\n /**\n * Can a sort key be an aggregate over a to-many relation — \"oldest waiting\n * first\", \"busiest first\"?\n *\n * Compiled as a correlated scalar subquery in `ORDER BY`, which a document\n * store cannot express at all. Optional and defaulting to **false**.\n *\n * A wrongly claimed sort capability fails differently from a wrongly\n * claimed filter one, and worse in one respect: a driver that cannot\n * resolve the key drops the `ORDER BY` and answers 200 with rows in\n * whatever order the database pleased, which reads as a sorted list. Paging\n * over that repeats and skips rows.\n */\n relationAggregateSorts?: boolean;\n\n // ── Admin capability flags ───────────────────────────────────────\n /** Does this source support SQL admin operations (SQL editor, EXPLAIN, etc.)? */\n supportsSQLAdmin: boolean;\n\n /** Does this source support document admin operations (aggregation, stats)? */\n supportsDocumentAdmin: boolean;\n\n /** Does this source support schema admin (unmapped tables, table metadata)? */\n supportsSchemaAdmin: boolean;\n}\n\n/**\n * Subset of DataSourceCapabilities containing only feature flags.\n * Useful when you only need to check capabilities without UI metadata.\n * @group Models\n */\nexport type DataSourceFeatures = Omit<DataSourceCapabilities, \"key\" | \"label\">;\n\n/**\n * The default data-source key, used when a collection does not name a\n * `dataSource`. Shared by the frontend router and the backend driver\n * registry so both agree on \"the default database\".\n * @group Models\n */\nexport const DEFAULT_DATA_SOURCE_KEY = \"(default)\";\n\n/**\n * How the *frontend* reaches a data source.\n *\n * - `\"server\"` — through the Rebase backend (the `RebaseClient`). The backend\n * holds the actual database adapter and routes by data-source key. This is\n * the default and covers Postgres, MongoDB, and any other server-mediated\n * engine.\n * - `\"direct\"` — straight from the client to the external backend via its own\n * SDK driver (e.g. Firestore). The Rebase backend is not in the data path.\n * - `\"custom\"` — a developer-supplied {@link DataDriver}, transport unspecified.\n *\n * @group Models\n */\nexport type DataSourceTransport = \"server\" | \"direct\" | \"custom\";\n\n/**\n * Declarative definition of a data source — a named place data lives.\n *\n * Declared once and shared front and back: the frontend uses it to decide\n * transport (client vs direct driver), the backend uses the same `key` to\n * resolve a database adapter, and the editor derives capabilities from\n * `engine`. Collections reference a definition by its `key` via\n * `collection.dataSource`.\n *\n * @group Models\n */\nexport interface DataSourceDefinition {\n /**\n * Unique identifier for this data source. Collections point at it via\n * `dataSource`. Defaults to {@link DEFAULT_DATA_SOURCE_KEY}.\n */\n key: string;\n\n /**\n * The engine backing this data source (e.g. `\"postgres\"`, `\"mongodb\"`,\n * `\"firestore\"`, or a custom id). Determines the\n * {@link DataSourceCapabilities} surfaced in the editor.\n */\n engine: string;\n\n /**\n * How the frontend reaches this source. Optional — when omitted it is\n * inferred: `\"direct\"` if the definition carries a client-side driver,\n * `\"server\"` otherwise.\n */\n transport?: DataSourceTransport;\n\n /**\n * The physical database/schema/Firestore-database within the engine.\n * Threaded to drivers/adapters as the existing `databaseId` runtime\n * parameter. Defaults to the engine's own default.\n */\n databaseId?: string;\n\n /** Human-readable label for the UI. */\n label?: string;\n}\n\n/**\n * The resolved data source for a collection: the single source of truth that\n * the frontend router, backend registry, and editor all derive from.\n * Produced by `resolveDataSource(collection, registry)`.\n *\n * @group Models\n */\nexport interface ResolvedDataSource {\n /** Data-source key (routing key, shared front + back). */\n key: string;\n /** Engine backing the source (drives capabilities). */\n engine: string;\n /** Frontend transport. */\n transport: DataSourceTransport;\n /** Within-engine instance, if any (the `databaseId` runtime param). */\n databaseId?: string;\n /** Capabilities derived from {@link engine}. */\n capabilities: DataSourceCapabilities;\n}\n\n/**\n * Relation kinds assumed filterable when a driver does not say.\n *\n * `belongsTo` alone: its filter is a comparison on a column of the row being\n * filtered, the one shape that needs no query construction a driver might not\n * have. Everything else is a correlated subquery over another table.\n *\n * @group Models\n */\nexport const DEFAULT_FILTERABLE_RELATION_KINDS: readonly string[] = [\"belongsTo\"];\n\n// ── Built-in driver capabilities ─────────────────────────────────────\n\n/** @group Models */\nexport const POSTGRES_CAPABILITIES: DataSourceCapabilities = {\n key: \"postgres\",\n label: \"PostgreSQL\",\n supportsRelations: true,\n supportsSubcollections: false,\n supportsRLS: true,\n supportsReferences: false,\n supportsColumnTypes: true,\n supportsRealtime: true,\n supportsVectors: true,\n filterOperators: ALL_WHERE_FILTER_OPS,\n // `via` is absent: its join path is authored source → target with no\n // stated inverse, so the driver has nothing to reverse into a filter.\n filterableRelationKinds: [\"belongsTo\", \"manyToMany\", \"hasMany\", \"hasOne\"],\n supportsRelationFieldFilters: true,\n relationAggregateSorts: true,\n supportsSQLAdmin: true,\n supportsDocumentAdmin: false,\n supportsSchemaAdmin: true\n};\n\n/** @group Models */\nexport const FIREBASE_CAPABILITIES: DataSourceCapabilities = {\n key: \"firestore\",\n label: \"Firebase / Firestore\",\n supportsRelations: false,\n supportsSubcollections: true,\n supportsRLS: false,\n supportsReferences: true,\n supportsColumnTypes: false,\n supportsRealtime: true,\n supportsVectors: false,\n // Firestore has no SQL pattern matching — the driver throws on the LIKE\n // family, so the UI must never offer it.\n filterOperators: ALL_WHERE_FILTER_OPS.filter(op =>\n op !== \"like\" && op !== \"ilike\" && op !== \"not-like\" && op !== \"not-ilike\"),\n // No relations at all — a document store links by reference. Nothing to\n // reach through, so neither of the two relation-reaching features either.\n filterableRelationKinds: [],\n supportsRelationFieldFilters: false,\n relationAggregateSorts: false,\n supportsSQLAdmin: false,\n supportsDocumentAdmin: false,\n supportsSchemaAdmin: false\n};\n\n/** @group Models */\nexport const MONGODB_CAPABILITIES: DataSourceCapabilities = {\n key: \"mongodb\",\n label: \"MongoDB\",\n supportsRelations: false,\n supportsSubcollections: true,\n supportsRLS: false,\n supportsReferences: true,\n supportsColumnTypes: false,\n supportsRealtime: false,\n supportsVectors: false,\n filterOperators: ALL_WHERE_FILTER_OPS,\n filterableRelationKinds: [],\n supportsRelationFieldFilters: false,\n relationAggregateSorts: false,\n supportsSQLAdmin: false,\n supportsDocumentAdmin: true,\n supportsSchemaAdmin: true\n};\n\n/**\n * Fallback capabilities when the driver is unknown.\n * Enables everything so nothing is hidden unexpectedly.\n * @group Models\n */\nexport const DEFAULT_CAPABILITIES: DataSourceCapabilities = {\n key: \"(default)\",\n label: \"Default\",\n supportsRelations: true,\n supportsSubcollections: true,\n supportsRLS: true,\n supportsReferences: true,\n supportsColumnTypes: true,\n supportsRealtime: true,\n supportsVectors: true,\n filterOperators: ALL_WHERE_FILTER_OPS,\n // The exception to this descriptor's \"enable everything\" rule. The other\n // flags hide a tab or a picker when they are wrong; this one decides\n // whether a query is sent that an unknown driver may answer by dropping\n // the condition — which returns every row rather than none.\n filterableRelationKinds: DEFAULT_FILTERABLE_RELATION_KINDS,\n // Narrow for the same reason, and more sharply. An unknown driver that is\n // assumed to compile these answers by dropping the key: the filter widens\n // the read to every row, and the sort comes back unordered while looking\n // sorted. Both have to be claimed.\n supportsRelationFieldFilters: false,\n relationAggregateSorts: false,\n supportsSQLAdmin: true,\n supportsDocumentAdmin: true,\n supportsSchemaAdmin: true\n};\n\nconst CAPABILITIES_REGISTRY: Record<string, DataSourceCapabilities> = {\n postgres: POSTGRES_CAPABILITIES,\n firestore: FIREBASE_CAPABILITIES,\n mongodb: MONGODB_CAPABILITIES,\n \"(default)\": DEFAULT_CAPABILITIES\n};\n\n/**\n * Look up capabilities for a given engine key.\n * If `engine` is undefined or not found, returns `DEFAULT_CAPABILITIES`.\n * @group Models\n */\nexport function getDataSourceCapabilities(engine?: string): DataSourceCapabilities {\n if (!engine) return POSTGRES_CAPABILITIES; // postgres is the default engine\n return CAPABILITIES_REGISTRY[engine] ?? DEFAULT_CAPABILITIES;\n}\n\n/**\n * Register custom capabilities for a third-party driver.\n * @group Models\n */\nexport function registerDataSourceCapabilities(capabilities: DataSourceCapabilities): void {\n CAPABILITIES_REGISTRY[capabilities.key] = capabilities;\n}\n","import type { CollectionCallbacks } from \"./entity_callbacks\";\n\nimport type { EnumValues, Properties, PostgresProperties, FirebaseProperties, MongoProperties } from \"./properties\";\n\nimport type { User } from \"../users\";\nimport type { EmailSendResult } from \"../controllers/email\";\nimport type { Relation } from \"./relations\";\nimport type { SecurityRule } from \"./security_rules\";\nimport { getDataSourceCapabilities } from \"./data_source\";\nimport type { WhereFilterOp, FilterValues, FilterPreset } from \"./filter-operators\";\nimport type { SearchConfig } from \"./search\";\nimport type { CollectionIndex } from \"./indexes\";\n\n/**\n * Base interface containing all driver-agnostic collection properties.\n * Use {@link PostgresCollectionConfig} or {@link FirebaseCollectionConfig} for\n * driver-specific type safety, or {@link CollectionConfig} when you\n * need to handle any collection regardless of backend.\n *\n * @group Models\n */\nexport interface BaseCollectionConfig<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> {\n\n /**\n * The collection's identity. Required, and the value nearly everything else\n * keys on:\n *\n * - the REST path — `/api/data/<slug>`\n * - the SDK accessor — `client.data.<slug>` / `client.data.collection(\"<slug>\")`\n * - the admin panel's URL\n * - the target of a `reference` or `relation` property\n *\n * Conventionally kebab-case and plural (`blog-posts`). It is independent of\n * {@link table}: the slug is what callers say, the table is where the rows\n * live, and renaming one does not rename the other.\n *\n * Treat it as frozen once anything has shipped against it — changing a slug\n * changes every URL and every generated accessor at once.\n *\n * @example\n * defineCollection({\n * slug: \"blog-posts\", // /api/data/blog-posts, client.data.blogPosts\n * table: \"posts\",\n * properties: { … }\n * })\n */\n slug: string;\n\n /**\n * Name of the collection, typically plural.\n * E.g. `Products`, `Blog`\n */\n name: string;\n\n /**\n * Singular name of an entry in this collection\n * E.g. `Product`, `Blog entry`\n */\n singularName?: string;\n\n /**\n * Optional description of this view. You can use Markdown.\n */\n description?: string;\n\n /**\n * Child collections nested under entities of this collection.\n * Populated automatically during normalization from driver-specific fields\n * (e.g. Firebase `subcollections`, Postgres `relations` with many-cardinality).\n *\n * Custom drivers can set this directly to expose child collections to the UI.\n */\n childCollections?: () => CollectionConfig<Record<string, unknown>>[];\n\n\n /**\n * The data source this collection belongs to — the routing key shared by\n * the frontend router and the backend driver registry. It points at a\n * {@link DataSourceDefinition} registered on `<Rebase dataSources>` (front)\n * and `initializeRebaseBackend({ dataSources })` (back).\n *\n * If not specified, the default data source `\"(default)\"` is used, which\n * for a standard Rebase app is the server-mediated Postgres backend.\n *\n * @example\n * // Default data source (server-mediated Postgres)\n * { slug: \"products\" }\n *\n * // A direct-transport Firestore data source registered as \"analytics\"\n * { slug: \"events\", dataSource: \"analytics\" }\n *\n * // The same, spelled once — `defineCollection` accepts the handle\n * // `database(\"analytics\")` returned and records its key here.\n * import { analytics } from \"../resources\";\n * defineCollection({ slug: \"events\", dataSource: analytics, … })\n *\n * A string on the recorded collection, because a collection is data past\n * `defineCollection`: it serialises, it compares with `===`, and it reaches\n * the admin UI over the wire, none of which a handle survives.\n */\n dataSource?: string;\n\n /**\n * The database engine backing this collection (`\"postgres\"`, `\"firestore\"`,\n * `\"mongodb\"`, or a custom id).\n *\n * On concrete collection types ({@link PostgresCollectionConfig},\n * {@link FirebaseCollectionConfig}, {@link MongoDBCollectionConfig}) this is a literal\n * discriminant. On the base type it is optional and gets stamped\n * automatically during collection normalization from the registered\n * {@link DataSourceDefinition}.\n *\n * Prefer setting {@link dataSource} and letting the engine be resolved.\n */\n engine?: string;\n\n /**\n * Which database within the engine.\n * - For Firestore: The Firestore database ID (e.g., for multi-database projects)\n * - For PostgreSQL: Schema or database name\n * - For MongoDB: Database name\n *\n * If not specified, the default database of the engine is used. Resolved\n * from the collection's {@link DataSourceDefinition} when omitted here.\n */\n databaseId?: string;\n\n /**\n * Set of properties that compose a entity\n */\n properties: Properties;\n\n\n\n\n\n\n\n\n\n\n\n\n /**\n * Mark this collection as an authentication collection.\n * When true, this collection is used for user management, login, password hashing, and invitation flows.\n */\n auth?: boolean | AuthCollectionConfig;\n\n\n\n\n\n\n\n /**\n * Row-level authorization rules for this collection.\n *\n * Driver-agnostic on purpose, unlike `disableDefaultPolicies`, `table` and\n * `relations`, which are declared on {@link PostgresCollectionConfig} only.\n * The rules are a *contract* — who may read or write which rows — and each\n * engine enforces it its own way:\n *\n * - **Postgres** compiles them to real `CREATE POLICY` statements and lets\n * the database enforce them (see {@link PostgresCollectionConfig.securityRules},\n * which narrows this with the raw-SQL details).\n * - **MongoDB** translates them into a query filter it AND-s into every\n * read and write, honouring `access`, `ownerField`, `roles`, `mode` and\n * the `operation`/`operations` selectors, and making a best effort at raw\n * `using`/`withCheck` SQL.\n * - **Firestore** does not implement them at all; its own rules language is\n * evaluated by Google, not from here. `supportsRLS` on\n * {@link DataSourceCapabilities} reports which engines generate policies,\n * which is not the same question as whether an engine honours a rule.\n */\n securityRules?: readonly SecurityRule[];\n\n /**\n * This interface defines all the callbacks that can be used when a entity\n * is being created, updated or deleted.\n * Useful for adding your own logic or blocking the execution of the operation.\n */\n readonly callbacks?: CollectionCallbacks<M, USER>;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n /**\n * User id of the owner of this collection. This is used only by plugins, or if you\n * are writing custom code\n */\n ownerId?: string;\n\n /**\n * Arbitrary key-value metadata for external consumers.\n * Not interpreted by Rebase — passed through serialization unchanged.\n * Used by domain apps to store custom per-collection config.\n */\n metadata?: Record<string, unknown>;\n\n\n\n\n /**\n * If set to true, changes to the entity will be saved in a subcollection.\n * This prop has no effect if the history plugin is not enabled\n */\n history?: boolean;\n\n /**\n * Whether a write naming a field this collection does not declare is\n * rejected with a 400. Defaults to `true`.\n *\n * Set to `false` where a column really does exist that the config never\n * declared — populated by a trigger, or introspected rather than declared —\n * and callers need to write it. The column still has to exist: the driver\n * checks the key against the table's own columns whatever this is set to,\n * because a key with no column behind it is not passed to the database and\n * refused, it is dropped from the statement and answered 201.\n *\n * It does not let a typo through to Postgres for Postgres to judge. That is\n * what this flag was documented as doing, and no such judgment ever\n * happened.\n */\n strictWrites?: boolean;\n\n\n\n\n\n\n\n\n}\n\n// ── Driver-specific collection types ──────────────────────────────────\n\n/**\n * A collection backed by PostgreSQL (or any SQL database).\n * Adds support for SQL-style relations (JOINs) and Row Level Security.\n *\n * Use this type instead of {@link CollectionConfig} when you want\n * compile-time safety that only SQL-relevant fields appear.\n *\n * @group Models\n */\nexport interface PostgresCollectionConfig<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>\n extends BaseCollectionConfig<M, USER> {\n properties: PostgresProperties;\n\n /**\n * The database engine for this collection. For Postgres collections this\n * can be omitted (Postgres is the default) or set to `\"postgres\"`.\n */\n engine?: \"postgres\" | undefined;\n\n /**\n * The PostgreSQL table name for this collection.\n *\n * Optional: it defaults to `toSnakeCase(slug)`, which is what\n * `getTableName()` has always returned when it was absent. The type simply\n * demanded what the runtime already derived, so the smallest collection\n * anyone could write named its table twice —\n * `{ slug: \"todos\", table: \"todos\", … }` — and \"why do I write it twice\"\n * is the first question every evaluator asked.\n *\n * Set it only when the table name differs from the slug: an existing\n * database whose table is `blog_posts` while the URL should stay `posts`.\n *\n * Note that a **derived** name is still a real name, and nothing yet warns\n * when one moves. Foreign-key and junction column defaults are derived from\n * the *slug* rather than from this field, so renaming a slug re-derives\n * them on the next `db push` even where `table` is pinned.\n */\n table?: string;\n\n /**\n * The PostgreSQL schema name for this table.\n * E.g. \"public\", \"rebase\", \"auth\".\n * If not specified, \"public\" is used (or the default search path).\n */\n schema?: string;\n\n /**\n * For SQL databases, you can define the relations between collections here.\n * Relations describe JOINs, foreign keys, and junction tables.\n */\n relations?: Relation[];\n\n /**\n * Security rules for this collection (PostgreSQL Row Level Security).\n * When defined, the schema generator will enable RLS on the table and\n * create the corresponding PostgreSQL policies.\n *\n * Supports three levels of expressiveness:\n * 1. **Convenience shortcuts** — `ownerField`, `access`, `roles`\n * 2. **Raw SQL** — `using` and `withCheck` for full PostgreSQL power\n * 3. **Combined** — mix shortcuts with `roles` for common patterns\n *\n * The authenticated user context is available in raw SQL via:\n * - `rebase.uid()` — the current user's ID\n * - `rebase.roles()` — comma-separated app role IDs\n * - `rebase.jwt()` — full JWT claims as JSONB\n */\n securityRules?: readonly SecurityRule[];\n\n /**\n * Opt out of the framework's default Row Level Security policies.\n *\n * The schema generator automatically injects, for every collection, a\n * baseline SELECT policy granting the trusted server context and the\n * `admin` role read access (reads run under a restricted role, so RLS\n * default-denies without it). For auth collections it additionally injects\n * a self-read policy (`id = rebase.uid()`) and an admin-only write gate\n * (INSERT/UPDATE/DELETE require the `admin` role or the trusted server\n * context), making privileged columns such as `roles` safe by default.\n *\n * Author-defined `securityRules` are permissive and broaden access on top\n * of these defaults. Set this flag to `true` to remove the defaults\n * entirely and take full responsibility for the collection's RLS.\n *\n * @default false\n */\n disableDefaultPolicies?: boolean;\n\n /**\n * Opt in to Postgres full-text search for this collection.\n *\n * Omit it and `.search()` keeps its existing behaviour exactly — an\n * `ILIKE '%term%'` across top-level string properties. Declare it and the\n * collection gains one generated `tsvector` column and a GIN index, and\n * `.search()` compiles to a ranked `@@ websearch_to_tsquery` against them.\n *\n * Postgres-only, like {@link VectorProperty}: the block is rejected at boot\n * on other engines rather than silently ignored.\n *\n * @see SearchConfig\n */\n search?: SearchConfig;\n\n /**\n * Ordinary indexes on this collection's table.\n *\n * Collection-level, not per-property, because an index over two columns\n * has no single property to hang on and a partial index has none at all —\n * and because a second declaration site for the single-column case would\n * put the same object in two places. An index's identity is a column list\n * in an order; the single-column case is a degenerate one, not a special\n * one.\n *\n * `VectorProperty.index` stays where it is: an ANN structure is a property\n * of the column's type, not of a query.\n *\n * Postgres-only, like {@link SearchConfig}: refused on another engine\n * rather than silently ignored.\n */\n indexes?: readonly CollectionIndex<Extract<keyof M, string>>[];\n}\n\n/**\n * A collection backed by Firebase / Firestore.\n * Adds support for subcollections (nested document collections).\n *\n * Use this type instead of {@link CollectionConfig} when you want\n * compile-time safety that only Firestore-relevant fields appear.\n *\n * @group Models\n */\nexport interface FirebaseCollectionConfig<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>\n extends BaseCollectionConfig<M, USER> {\n /**\n * The database engine for this collection. Must be set to `\"firestore\"`.\n */\n engine: \"firestore\";\n\n /**\n * Set of properties that compose a entity.\n * Firestore collections support `reference` properties but not `relation`.\n */\n properties: FirebaseProperties;\n\n /**\n * The Firestore collection path to query. Defaults to `slug` if not set.\n * Use this when the Firestore path differs from the slug\n * (e.g., when a PostgreSQL collection already uses the same slug).\n *\n * @example\n * ```typescript\n * const fsCustomer: FirebaseCollectionConfig = {\n * slug: \"fs_customer\", // URL: /c/fs_customer\n * path: \"customer\", // Firestore path: customer\n * name: \"Customers (Firestore)\",\n * engine: \"firestore\",\n * properties: { ... }\n * };\n * ```\n */\n path?: string;\n\n /**\n * You can add subcollections to your entity in the same way you define the root\n * collections. The collections added here will be displayed when opening\n * the side dialog of a entity.\n */\n subcollections?: () => CollectionConfig<Record<string, unknown>>[];\n}\n\n/**\n * A collection backed by MongoDB.\n *\n * Use this type instead of {@link CollectionConfig} when you want\n * compile-time safety that only MongoDB-relevant fields appear.\n *\n * @group Models\n */\nexport interface MongoDBCollectionConfig<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>\n extends BaseCollectionConfig<M, USER> {\n\n /**\n * The database engine for this collection. Must be set to `\"mongodb\"`.\n */\n engine: \"mongodb\";\n\n /**\n * Set of properties that compose a entity.\n * MongoDB collections support `reference` properties but not `relation`.\n */\n properties: MongoProperties;\n\n /**\n * The MongoDB collection name to use. Defaults to `slug` if not set.\n * Use this when the MongoDB collection name differs from the slug\n * (e.g., when a PostgreSQL collection already uses the same slug).\n *\n * @example\n * ```typescript\n * const mongoCustomer: MongoDBCollectionConfig = {\n * slug: \"mongo_customer\", // URL: /c/mongo_customer\n * path: \"customer\", // MongoDB collection: customer\n * name: \"Customers (MongoDB)\",\n * engine: \"mongodb\",\n * properties: { ... }\n * };\n * ```\n */\n path?: string;\n}\n\n/**\n * A collection backed by any data source.\n * This is a discriminated union — use {@link PostgresCollectionConfig},\n * {@link FirebaseCollectionConfig}, or {@link MongoDBCollectionConfig} for\n * driver-specific type safety.\n *\n * @group Models\n */\nexport type CollectionConfig<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> =\n | PostgresCollectionConfig<M, USER>\n | FirebaseCollectionConfig<M, USER>\n | MongoDBCollectionConfig<M, USER>;\n\n/**\n * A collection of *any* row type.\n *\n * `CollectionConfig` is **invariant** in `M`: `callbacks` both consumes `M`\n * (`AfterReadProps<M>`) and produces it, so neither direction of assignment\n * holds. `CollectionConfig<SomeRow>` is therefore not assignable to a bare\n * `CollectionConfig`, whose `M` defaults to `Record<string, unknown>`.\n *\n * That matters wherever a collection is merely *referred to* rather than read\n * from. `defineCollection` returns a config whose `M` is inferred from the\n * properties — the whole point of it — so a field typed `() => CollectionConfig`\n * rejects every collection the builder produces, and `target: () => otherCollection`\n * (the documented way to point a relation at its other end) does not compile in\n * any project that uses the builder.\n *\n * `any` is deliberate and is what it is for here: these positions never read the\n * target's rows, they only identify which collection is meant, so there is no\n * type safety to preserve and invariance is pure obstruction.\n *\n * @group Models\n */\nexport type AnyCollectionConfig = CollectionConfig<any, any>;\n\n/**\n * Type guard for PostgreSQL collections.\n * Returns true if the collection uses the Postgres engine (or the default engine).\n *\n * Generic over the *input* type, and narrows by intersection rather than\n * replacement. Narrowing to a bare `PostgresCollectionConfig` discarded whatever\n * the caller actually had — most visibly the admin panel's view model, whose\n * flattened presentation fields vanished the moment a collection passed through\n * one of these guards.\n *\n * @group Models\n */\nexport function isPostgresCollectionConfig<C extends CollectionConfig<any, any>>(\n collection: C\n): collection is C & PostgresCollectionConfig<any, any> {\n return !collection.engine || collection.engine === \"postgres\";\n}\n\n/**\n * Narrows to the SQL collection fields — `table`, `relations`,\n * `disableDefaultPolicies` — by asking the engine's declared capabilities\n * rather than by naming Postgres.\n *\n * The two halves of this already existed and were never joined. The engine\n * split (`PostgresCollectionConfig` / `FirebaseCollectionConfig` /\n * `MongoDBCollectionConfig`) said which fields belong to which engine at the\n * type level; {@link DataSourceCapabilities} said the same thing at runtime,\n * down to a `supportsRelations` flag. So call sites guarded on the capability\n * and then read a field the base type had to declare for them — which is why\n * those fields were on the base, and why a MongoDB collection could be written\n * with a `table`.\n *\n * Prefer this over {@link isPostgresCollectionConfig} wherever the question is\n * \"does this collection live in a SQL table\", so a custom SQL engine\n * registered through `registerDataSourceCapabilities` is included.\n *\n * @group Models\n */\nexport function isRelationalCollectionConfig<C extends CollectionConfig<any, any>>(\n collection: C\n): collection is C & PostgresCollectionConfig<any, any> {\n return getDataSourceCapabilities(collection.engine).supportsRelations;\n}\n\n/**\n * Type guard for Firebase / Firestore collections.\n * @group Models\n */\nexport function isFirebaseCollectionConfig<C extends CollectionConfig<any, any>>(\n collection: C\n): collection is C & FirebaseCollectionConfig<any, any> {\n return collection.engine === \"firestore\";\n}\n\n/**\n * Type guard for MongoDB collections.\n * @group Models\n */\nexport function isMongoDBCollectionConfig<C extends CollectionConfig<any, any>>(\n collection: C\n): collection is C & MongoDBCollectionConfig<any, any> {\n return collection.engine === \"mongodb\";\n}\n\n/**\n * Returns the data path for a collection.\n * For Firestore or MongoDB collections with a `path`, returns that value;\n * otherwise falls back to `slug`.\n */\nexport function getCollectionDataPath<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(\n collection: CollectionConfig<M, USER>\n): string {\n if (isFirebaseCollectionConfig(collection) && collection.path) {\n return collection.path;\n }\n if (isMongoDBCollectionConfig(collection) && collection.path) {\n return collection.path;\n }\n return collection.slug;\n}\n\n/**\n * Reads a collection's driver-declared subcollections thunk (the `subcollections`\n * field) independent of engine identity, so engine-agnostic code doesn't have to\n * type-guard against a specific driver. Returns `undefined` when the collection\n * declares none.\n *\n * Pair with `getDataSourceCapabilities(engine).supportsSubcollections` to decide\n * whether the engine honours subcollections at all before reading them.\n * @group Models\n */\nexport function getDeclaredSubcollections<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(\n collection: CollectionConfig<M, USER>\n): (() => CollectionConfig<Record<string, unknown>>[]) | undefined {\n return (collection as FirebaseCollectionConfig<M, USER>).subcollections;\n}\n\n/**\n * Where the rows in an {@link EntityChildView} come from.\n *\n * The two are not the same thing, and conflating them is what made a Postgres\n * relation borrow Firestore's addressing:\n *\n * - `subcollection` is **containment**. The rows live under the parent; the\n * path is their identity, and they cannot exist without it. This is what\n * Firestore has natively.\n * - `relation` is a **link**. The rows are an ordinary collection, narrowed to\n * those the parent reaches. `owned` means the child carries the parent's\n * foreign key and belongs to it alone; `linked` means the row is shared\n * through a junction, so what the parent controls is the link, not the row.\n *\n * @group Models\n */\nexport type ChildViewSource =\n | { kind: \"subcollection\" }\n | {\n kind: \"relation\";\n relationKey: string;\n mode: \"owned\" | \"linked\";\n /**\n * Slug of the collection the rows actually live in.\n *\n * Distinct from the view's `key`, which is the relation. A `linked` view\n * needs both: the key addresses the parent's set, and this addresses the\n * whole collection to pick an existing row out of.\n */\n targetSlug: string;\n };\n\n/**\n * A list of rows rendered inside an entity view — the tab under a record.\n *\n * This is a *presentation* descriptor, which is the whole point: rendering a\n * related list as a tab used to require minting a child `CollectionConfig` with\n * its own slug, which dragged a URL grammar, a path resolver and a second\n * read/write pipeline along with it. A tab needs a key, a collection to list,\n * and to know where its rows come from.\n *\n * @group Models\n */\nexport interface EntityChildView<M extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Stable identifier for this view: the tab id and the path segment.\n *\n * For a relation this is the **relation key** — the name the backend\n * resolves a nested path segment by — not the target collection's slug.\n * Those differ whenever a relation is named, which is every inline relation\n * property, and the mismatch is why such a tab used to open onto an error.\n */\n key: string;\n\n /** The collection whose rows this view lists, with any overrides applied. */\n collection: CollectionConfig<M>;\n\n source: ChildViewSource;\n}\n\n\nexport type { WhereFilterOp, FilterValues, WireFilterValues, FilterPreset } from \"./filter-operators\";\n\n\nexport type InferCollectionConfigType<S extends CollectionConfig> = S extends CollectionConfig<infer M> ? M : never;\n\n/**\n * Configuration for authentication collections.\n *\n * Controls what happens when admins create users, reset passwords,\n * and which entity actions are auto-injected.\n *\n * Use `auth: true` as sugar for `{ enabled: true }` with all defaults.\n *\n * @example Override user creation\n * ```ts\n * auth: {\n * enabled: true,\n * onCreateUser: async (values, ctx) => {\n * const hash = await ctx.hashPassword(\"welcome123\");\n * return {\n * values: { ...values, passwordHash: hash, emailVerified: true },\n * temporaryPassword: \"welcome123\",\n * };\n * },\n * }\n * ```\n *\n * @example Disable the reset-password entity action\n * ```ts\n * auth: {\n * enabled: true,\n * actions: { resetPassword: false },\n * }\n * ```\n *\n * @group Models\n */\nexport interface AuthCollectionConfig {\n /** Set to true to mark this collection as the authentication collection. */\n enabled: boolean;\n\n /**\n * Called when an admin creates a user via the collection REST API.\n *\n * Default: generate password → hash → normalize email → save →\n * send invitation email (or return temp password if no email configured).\n *\n * Override to implement custom invitation flows, LDAP sync, etc.\n */\n onCreateUser?: (\n values: Record<string, unknown>,\n ctx: AuthCollectionContext\n ) => Promise<AuthCollectionCreateResult>;\n\n /**\n * Called when an admin resets a user's password via the admin panel.\n *\n * Default: generate reset token → send email (or generate + return temp password).\n * Override for custom reset flows.\n */\n onResetPassword?: (\n uid: string,\n ctx: AuthCollectionContext\n ) => Promise<AuthCollectionResetResult>;\n\n /**\n * Control which auth-specific entity actions are auto-injected.\n *\n * Default: `{ resetPassword: true }` — the framework auto-injects\n * the built-in `resetPasswordAction` into the collection's entity actions.\n *\n * Set to `false` to disable, or pass a custom `EntityAction` to replace the UI.\n *\n * The object form is an `EntityAction` from `@rebasepro/cms-types`, typed\n * here as `object` because it is a React component with admin controllers in\n * its props and nothing on the server reads it — only whether the built-in\n * action is injected, which is the boolean.\n */\n actions?: {\n resetPassword?: boolean | object;\n };\n}\n\n/**\n * Context provided to collection-level auth hooks.\n *\n * This is a simplified facade over the server internals —\n * it exposes only what's needed for custom auth flows without\n * coupling collection config to internal interfaces.\n *\n * @group Models\n */\nexport interface AuthCollectionContext {\n /** Hash a password using the configured algorithm (scrypt by default). */\n hashPassword: (password: string) => Promise<string>;\n /**\n * Send an email. Only available when email service is configured.\n *\n * Resolves with what the provider reported — the assigned Message-ID, most\n * usefully — so a hook that sends a message can store the id and later\n * thread a reply back to it. Callers that do not care may ignore it.\n */\n sendEmail?: (options: { to: string; subject: string; html: string; text?: string }) => Promise<EmailSendResult>;\n /** Whether the email service is configured and available. */\n emailConfigured: boolean;\n /** The app name from email config (for templates). */\n appName: string;\n /** The base URL for password reset links. */\n resetPasswordUrl: string;\n}\n\n/**\n * Result of a collection-level `onCreateUser` hook.\n * @group Models\n */\nexport interface AuthCollectionCreateResult {\n /** Processed values to persist (must include passwordHash, NOT raw password). */\n values: Record<string, unknown>;\n /** If set, shown to the admin in the creation result dialog. */\n temporaryPassword?: string;\n /** Whether an invitation email was sent. */\n invitationSent?: boolean;\n}\n\n/**\n * Result of a collection-level `onResetPassword` hook.\n * @group Models\n */\nexport interface AuthCollectionResetResult {\n /** If set, shown to the admin. */\n temporaryPassword?: string;\n /** Whether a reset email was sent. */\n invitationSent?: boolean;\n}\n","/**\n * The SQL helper functions RLS policies call, and the schema they live in.\n *\n * ## One schema, and it is ours\n *\n * Rebase creates exactly one schema in a project's database: `rebase`. These\n * three functions live in it alongside the framework's own tables, and that is\n * the whole contract — a reader can look at a database and know precisely which\n * namespace belongs to the framework and that nothing else was touched.\n *\n * It used to be two. `uid()`, `jwt()` and `roles()` sat in a schema called\n * `auth`, which is Supabase's name, chosen so that a developer who had written\n * Supabase RLS would recognise `auth.uid()`. The familiarity was real but the\n * name was not Rebase's to take, and taking it had a concrete cost: pointing\n * Rebase at a database that already had a Supabase `auth` schema meant\n * `CREATE OR REPLACE FUNCTION auth.uid() RETURNS text` against Supabase's\n * `RETURNS uuid`, which Postgres rejects outright —\n *\n * ERROR: cannot change return type of existing function\n * HINT: Use DROP FUNCTION auth.uid() first.\n *\n * — and the failure landed inside a catch-all that logged a warning and carried\n * on, leaving a database with auth tables, no helper functions, and policies\n * calling functions that did not exist. Under `rebase db migrate` the same\n * statements aborted the migration instead.\n *\n * `rebase.uid()` collides with nobody. A Supabase database keeps its `auth`\n * schema untouched and gains a `rebase` one, which is what a gradual migration\n * needs.\n *\n * ## Why functions at all, rather than inlining `current_setting`\n *\n * Because the indirection has already been spent once. `uid()` resolves\n * `app.uid` and falls back to the pre-rename `app.user_id`, so that during a\n * rolling deploy — old and new pods serving one database — both eras resolve\n * the principal. That was a single `CREATE OR REPLACE`. Inlined into policy\n * bodies it would have been a rewrite of every policy on every table.\n *\n * ## Why the name is not configurable\n *\n * A policy body is stored SQL: Postgres parses `USING (…)` once and keeps it, so\n * these strings are written into every policy in every database Rebase has\n * provisioned. Everything that reads policies back — the SQL-to-policy parser\n * behind the admin UI, the drift checker, `rls-check` — would have to know the\n * configured value to recognise its own output. One frozen name is the feature.\n */\n\n/** The schema Rebase owns. The only schema Rebase creates. */\nexport const REBASE_SCHEMA = \"rebase\";\n\n/**\n * The principal of the current request, as text, or NULL in the server context.\n *\n * Never NULL for a user request — an anonymous one carries\n * {@link ANONYMOUS_USER_ID} — which is what makes `IS NULL` a reliable test for\n * the trusted server plane and `IS NOT NULL` a tautology.\n */\nexport const RLS_UID_SQL = `${REBASE_SCHEMA}.uid()`;\n\n/** The request's roles as a comma-separated string, for `string_to_array`. */\nexport const RLS_ROLES_SQL = `${REBASE_SCHEMA}.roles()`;\n\n/**\n * Whether the caller is a GUEST — signed in through anonymous sign-in rather\n * than with an account.\n *\n * A different question from {@link ANONYMOUS_USER_ID}, and the two are easy to\n * confuse: that sentinel means \"no session at all\", while this means \"a session\n * with nobody behind it\". Anonymous sign-in mints a real user row with a real\n * uid, so before this reached the database the two kinds of caller were one\n * principal inside every policy.\n */\nexport const RLS_IS_ANONYMOUS_SQL = `${REBASE_SCHEMA}.is_anonymous()`;\n\n/** The request's JWT claims as `jsonb`, or `{}`. */\nexport const RLS_JWT_SQL = `${REBASE_SCHEMA}.jwt()`;\n\n/**\n * The pre-1.0 spellings, for recognising policies and hand-written SQL that\n * predate the move.\n *\n * Kept because policies outlive the server that wrote them: a database migrated\n * by an older release still holds `auth.uid()` in its policy bodies until the\n * next push or boot recompiles them, and anything that reads policies back has\n * to recognise both eras or report the framework's own output as foreign drift.\n * Also used to give a project whose `securityRules` contain raw `auth.uid()` a\n * message naming the replacement, instead of a parse failure.\n */\nexport const LEGACY_RLS_SCHEMA = \"auth\";\nexport const LEGACY_RLS_UID_SQL = `${LEGACY_RLS_SCHEMA}.uid()`;\nexport const LEGACY_RLS_ROLES_SQL = `${LEGACY_RLS_SCHEMA}.roles()`;\nexport const LEGACY_RLS_JWT_SQL = `${LEGACY_RLS_SCHEMA}.jwt()`;\n\n/**\n * Rewrites the pre-1.0 function calls in a fragment of policy SQL.\n *\n * Deliberately anchored on a word boundary and the schema qualifier, so a column\n * called `auth_uid` or a table named `auth` is left alone.\n */\nexport function rewriteLegacyRlsFunctions(sql: string): string {\n return sql.replace(\n /\\bauth\\.(uid|jwt|roles)\\s*\\(\\s*\\)/gi,\n (_match, fn: string) => `${REBASE_SCHEMA}.${fn.toLowerCase()}()`\n );\n}\n\n/** Whether a fragment of SQL still calls the pre-1.0 functions. */\nexport function usesLegacyRlsFunctions(sql: string): boolean {\n return /\\bauth\\.(uid|jwt|roles)\\s*\\(\\s*\\)/i.test(sql);\n}\n","/**\n * The resource graph: one declaration site for every named thing a project needs.\n *\n * ## The rule\n *\n * **Every named resource is declared with a constructor in config code.** A\n * database, a bucket, a topic and whatever kind comes next are all spelled the\n * same way, so \"where do I declare my second one\" has one answer instead of one\n * answer per kind.\n *\n * ```ts\n * export const main = database(\"main\");\n * export const media = bucket(\"media\", { transport: \"direct\" });\n * export const signups = topic<SignupEvent>(\"signups\");\n * ```\n *\n * ## Declaration is not binding\n *\n * A declaration says a resource *exists* and what shape it has. It never says\n * how to reach it — that is a property of the environment, not of the project,\n * and it differs between a laptop, a self-hosted box and a tenant in the cloud.\n * Binding lives in `@rebasepro/server`'s boot path, where each kind registers\n * the resolver that reads its environment variables, keyed off the logical\n * name declared here.\n *\n * This split is the whole point. Before it, storage topology was hand-written\n * into `rebase.json` while database topology lived in TypeScript, and the\n * boundary between them was a fact about what the control plane could read\n * before a build — a platform implementation detail that a developer had no way\n * to derive. Worse, storage could be declared in *both* places, and the merge\n * silently kept the JSON's engine and discarded the code's.\n *\n * ## Why a registry rather than a fixed union\n *\n * Kinds register themselves. Adding pub/sub, a cache or a search index must not\n * require editing a manifest schema, a validator and three switch statements —\n * that cost is exactly why the last two kinds ended up in different homes.\n */\n\n/** How a client reaches a resource. */\nexport type ResourceTransport =\n /** Through the backend. The default, and the only one that needs no client SDK. */\n | \"server\"\n /** A provider SDK talks to the resource directly; the backend is not in the path. */\n | \"direct\";\n\n/**\n * A resource kind, as registered.\n *\n * `engines` is an allowlist rather than documentation. An unrecognised engine\n * used to be a free string that passed every check and failed later, further\n * from the typo that caused it — `\"s2\"` for `\"s3\"` reached the runtime. Anything\n * genuinely outside the list is spelled `custom:<id>`, which says so at the call\n * site instead of looking like a typo.\n */\nexport interface ResourceKindSpec {\n /** The kind's name, as it appears in a declaration and in the graph. */\n kind: string;\n /**\n * Which definition of this kind this is. Bump it whenever anything else in\n * the spec changes.\n *\n * Two copies of this package can meet in one process — a published driver\n * inlines it into its dist, and the runtime image ships its own — and the\n * registry is shared between them on purpose. Without a revision the only\n * thing the registry can do with two specs that differ is refuse, and a\n * refusal at driver load is a pod that never boots: every bundle built with\n * a driver older than the change dies on the first image that carries it.\n * With one, the higher revision wins whichever copy loads first, and the\n * older copy is told so. Missing means 0, which is what every copy shipped\n * before revisions existed reports.\n *\n * Only copies that know about revisions honour them. A copy published\n * BEFORE they existed still compares the whole literal and throws, so a\n * kind that has shipped in such a copy cannot change its literal at all —\n * not even to add this field. Correct those kinds with `amendResourceKind`.\n */\n revision?: number;\n /** Engines this kind ships with. `custom:<id>` is always additionally valid. */\n engines: readonly string[];\n /** Used when a declaration names none. */\n defaultEngine: string;\n /**\n * Environment variable base names this kind binds from, in the order a\n * binder should try them. A resource keyed `analytics` reads\n * `<BASE>__ANALYTICS`; the default-keyed resource reads `<BASE>` unsuffixed,\n * so a single-resource project configured the obvious way declares nothing.\n */\n envBases: readonly string[];\n /**\n * The subset of `envBases` that matters for a given engine.\n *\n * The binder reads every base and takes whichever is set — harmless, and it\n * keeps binding tolerant. A GENERATOR cannot be that relaxed: `rebase eject\n * infra` writing S3_BUCKET, GCS_BUCKET, STORAGE_BUCKET and\n * STORAGE_PUBLIC_URL for a `local` bucket hands somebody four variables of\n * which three are noise, and a config file full of irrelevant keys is one\n * nobody reads carefully.\n *\n * Keyed by engine; an engine with no entry falls back to all of them, which\n * is the honest answer for one this package has never heard of.\n */\n envBasesByEngine?: Readonly<Record<string, readonly string[]>>;\n /** Option keys this kind accepts beyond the common ones, for validation. */\n optionKeys?: readonly string[];\n /**\n * Whether a project implicitly has one of these even when it declares\n * nothing. True for databases — a backend without one is not a backend —\n * and false for topics, where zero is the normal number.\n */\n implicitDefault?: boolean;\n}\n\n/** The key a resource takes when a project declares only one of its kind. */\nexport const DEFAULT_RESOURCE_KEY = \"(default)\";\n\n/** A declared resource, as it appears in the graph. */\nexport interface ResourceDeclaration {\n kind: string;\n /** Unique within its kind. What a binder looks up and what an env suffix is built from. */\n key: string;\n engine: string;\n transport: ResourceTransport;\n label?: string;\n /** Kind-specific options, validated against the kind's `optionKeys`. */\n options: Readonly<Record<string, unknown>>;\n /**\n * What in the project reaches this resource, as `<what>:<name>` — a\n * `collection:posts` routed to a database, a `property:posts.cover` stored\n * in a bucket, a `function:report` importing a handle.\n *\n * Recorded by the derive step, never by a constructor: a declaration says\n * a resource exists, and only a reader that has evaluated the rest of the\n * project can say who uses it. It is the map a host needs to split a\n * monolith into units later, and the map a console needs to answer \"what\n * breaks if I remove this\". Absent when nothing was recorded, which is\n * different from an empty list.\n */\n usedBy?: readonly string[];\n}\n\n/**\n * The value a constructor returns.\n *\n * Carries its own declaration so config code can hold it and pass it around,\n * and stringifies to its key so it drops into the places that still take one.\n * Collections name a data source by string today; a handle works there without\n * the collection API having to change, which keeps this a config redesign\n * rather than a rewrite of the data layer.\n */\nexport interface ResourceHandle extends ResourceDeclaration {\n toString(): string;\n}\n\nconst BRAND = Symbol.for(\"@rebasepro/types.resource\");\n\n/** Whether a value is a resource handle rather than a plain string key. */\nexport function isResourceHandle(value: unknown): value is ResourceHandle {\n return typeof value === \"object\" && value !== null && BRAND in value;\n}\n\n/**\n * A reference to a resource where a key is expected: the handle a constructor\n * returned, or the key spelled as a string.\n *\n * The handle is the point. `dataSource: analytics` is the same name spelled\n * once — rename the export and every use follows, jump-to-definition lands on\n * the declaration, and the derive step can record who uses what. The string\n * form stays because a key has to survive serialisation: the runtime and the\n * admin UI read collections as plain data, where a handle cannot travel.\n */\nexport type ResourceRef = string | ResourceHandle;\n\n/** The key a resource reference names, whether it is a handle or already a key. */\nexport function resourceKeyOf(ref: ResourceRef): string {\n return isResourceHandle(ref) ? ref.key : ref;\n}\n\n/**\n * Replace every resource handle inside a value with its key, deeply.\n *\n * Applied where authored config becomes data: `defineCollection`, the\n * collection loaders, the derive step. Past that point a collection is plain\n * data that serialises, compares with `===` and reaches the admin UI over the\n * wire, so a handle must not survive into it. Plain objects and arrays are\n * walked; anything else — a function, a Date, a class instance — is a leaf and\n * is returned as it is, which is what keeps callbacks and validators intact.\n */\nexport function resolveResourceRefs<T>(value: T): T {\n if (isResourceHandle(value)) return value.key as unknown as T;\n // Identity-preserving: a value with no handle inside comes back as the\n // same object, not a copy. Collections point at each other through\n // `target: () => authors`, and a loader that cloned every collection would\n // leave those closures returning the originals while everything else\n // held the copies. A collection that `defineCollection` already\n // normalised passes through here untouched.\n if (Array.isArray(value)) {\n let changed = false;\n const out = value.map(item => {\n const next = resolveResourceRefs(item);\n if (next !== item) changed = true;\n return next;\n });\n return (changed ? out : value) as T;\n }\n if (value !== null && typeof value === \"object\") {\n const proto = Object.getPrototypeOf(value);\n if (proto === Object.prototype || proto === null) {\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const next = resolveResourceRefs(v);\n if (next !== v) changed = true;\n out[k] = next;\n }\n return (changed ? out : value) as T;\n }\n }\n return value;\n}\n\n/**\n * The process-wide registry.\n *\n * Keyed off `globalThis` through a shared symbol rather than held in a module\n * local, because a module local is per *copy* of this package. A project that\n * ends up with two copies of `@rebasepro/types` — which a partially-linked\n * `node_modules` produces, and which has already caused a phantom\n * \"JWT secret not configured\" bug in this repo — would otherwise register into\n * one registry and read from the other, and see an empty graph with nothing\n * anywhere to explain it.\n *\n * `declarations` is that shared map, and stays shared: it is what a project\n * writes and what every copy has to be able to read.\n *\n * `kinds` is NOT, and the distinction is the whole point of `KINDS_KEY` below.\n */\ninterface Registry {\n /** Kinds this copy and its peers agree on — the versioned map. */\n kinds: Map<string, ResourceKindSpec>;\n /** Kinds written by a copy that predates the versioned map. Read-only here. */\n legacyKinds: Map<string, ResourceKindSpec>;\n declarations: Map<string, ResourceDeclaration>;\n}\n\nconst GLOBAL_KEY = Symbol.for(\"@rebasepro/types.resourceRegistry\");\n\n/**\n * Where kinds live, versioned — and why the version is in the symbol.\n *\n * Sharing one kinds map across copies means the copy that registers SECOND is\n * the one that runs the comparison. That copy is whatever the bundle happens to\n * carry, which for a driver is a build of this package frozen at its release —\n * so the rule enforced is the rule that shipped THEN, not the one written here.\n *\n * `revision` (390bb03cd, applied to `database` in 346df48e2) was supposed to\n * settle a disagreement between two copies, and it settles it only when the\n * copy doing the arithmetic knows what `revision` is. 0.17.0–0.17.3 do not:\n * they deep-equal the spec and throw. The runtime registers at import and a\n * driver is imported after it, so the old copy is always second, always the\n * judge, and always throws — verified by the bundle corpus on 2026-09-07,\n * which reported v0.17.3's message verbatim (\"Two packages cannot define the\n * same kind.\", no revision clause) while the runtime it ran on was 0.18.\n *\n * So copies that understand `revision` keep their kinds here, under a symbol\n * no released copy looks at, and the legacy map is left to whoever still wants\n * it. An old copy then registers into a map nobody contests, finds no existing\n * entry, and cannot throw — in any load order, which is what the previous fix\n * only claimed. Bumping this suffix again is how a future change to the\n * REGISTRATION PROTOCOL is made; a change to a kind's own definition is still\n * `revision`, among peers that share this map.\n */\nconst KINDS_KEY = Symbol.for(\"@rebasepro/types.resourceKinds.v2\");\n\nfunction registry(): Registry {\n const g = globalThis as unknown as Record<symbol, unknown>;\n let shared = g[GLOBAL_KEY] as { kinds: Map<string, ResourceKindSpec>; declarations: Map<string, ResourceDeclaration> } | undefined;\n if (!shared) {\n // `kinds` is created but never written by this copy: an older copy's\n // own `registry()` returns this object as-is once it exists, and would\n // throw on `undefined.get` if the property were absent.\n shared = { kinds: new Map(), declarations: new Map() };\n g[GLOBAL_KEY] = shared;\n }\n let kinds = g[KINDS_KEY] as Map<string, ResourceKindSpec> | undefined;\n if (!kinds) {\n kinds = new Map();\n g[KINDS_KEY] = kinds;\n }\n return { kinds, legacyKinds: shared.kinds, declarations: shared.declarations };\n}\n\n/**\n * Every kind visible to this copy: the versioned map, plus anything only a\n * legacy copy registered.\n *\n * The fallback is not for Rebase's own kinds — this copy defines all of those —\n * but for a third-party driver built against an older `@rebasepro/types` that\n * registers a kind of its own. Dropping it would make that kind invisible and\n * turn `declareResource` into \"unknown resource kind\" for something genuinely\n * registered.\n */\nfunction visibleKinds(): Map<string, ResourceKindSpec> {\n const { kinds, legacyKinds } = registry();\n if (legacyKinds.size === 0) return kinds;\n const merged = new Map(legacyKinds);\n for (const [k, v] of kinds) merged.set(k, v);\n return merged;\n}\n\n/**\n * Corrections this copy applies on top of a registered kind.\n *\n * Deliberately a module local — per COPY of this package — where the registry\n * above is deliberately shared. A published driver inlines this package into\n * its dist, and the copy it carries compares the shared registry's entry for a\n * kind against its own literal by `JSON.stringify` and throws if they differ\n * (see `registerResourceKind` before revisions existed). That code is in the\n * field and cannot be changed, so the registered literal of any kind that has\n * ever shipped is frozen: change one enumerable key and every bundle built with\n * an older driver dies at driver load on the next image. What a kind actually\n * binds can still be corrected — here, read through `resourceKind()` and\n * everything built on it, invisible to the older copy, which keeps binding the\n * way it did when it was published.\n */\ntype KindAmendment = Partial<Pick<ResourceKindSpec, \"envBases\" | \"envBasesByEngine\" | \"optionKeys\">>;\nconst amendments = new Map<string, KindAmendment>();\n\n/**\n * Correct a registered kind without touching its registered literal.\n *\n * Use this, never an edit to the literal, for a kind that has shipped in a\n * published package. The amendment applies to reads through this copy only.\n */\nexport function amendResourceKind(kind: string, amendment: KindAmendment): void {\n amendments.set(kind, { ...amendments.get(kind), ...amendment });\n}\n\n/** A registered kind as this copy sees it: the shared literal plus this copy's amendments. */\nfunction effectiveKind(spec: ResourceKindSpec): ResourceKindSpec {\n const amendment = amendments.get(spec.kind);\n return amendment ? { ...spec, ...amendment } : spec;\n}\n\n/** `kind:key`, the graph's primary key. */\nfunction declarationId(kind: string, key: string): string {\n return `${kind}:${key}`;\n}\n\n/**\n * Register a resource kind.\n *\n * Idempotent for an identical spec. When a spec for the same kind is already\n * registered and differs, the `revision` decides: the higher one is kept and\n * the other copy is warned about, in either load order. Two different specs at\n * the SAME revision are a genuine conflict — two packages defining one kind, or\n * a change that forgot to bump — and still throw.\n *\n * Both copies in that comparison are peers on `KINDS_KEY`, which is what makes\n * the rule enforceable: a copy old enough not to know `revision` writes to the\n * legacy map instead and never reaches this function's arithmetic. Registering\n * a kind an older copy already put in the legacy map is therefore not a\n * conflict — it is the ordinary case, and `visibleKinds` prefers this one.\n */\nexport function registerResourceKind(spec: ResourceKindSpec): void {\n const kinds = registry().kinds;\n const existing = kinds.get(spec.kind);\n if (!existing) {\n kinds.set(spec.kind, spec);\n return;\n }\n if (JSON.stringify(existing) === JSON.stringify(spec)) return;\n\n const have = existing.revision ?? 0;\n const incoming = spec.revision ?? 0;\n if (have === incoming) {\n throw new Error(\n `Resource kind \"${spec.kind}\" is already registered with a different definition at revision ${have}. ` +\n \"Two packages cannot define the same kind; a newer definition of the same kind must carry a higher `revision`.\"\n );\n }\n const [kept, dropped] = incoming > have ? [spec, existing] : [existing, spec];\n if (kept === spec) kinds.set(spec.kind, spec);\n // No logger below @rebasepro/server, and this runs in browsers too.\n console.warn(\n `[resources] Resource kind \"${spec.kind}\" is registered twice, at revisions ${dropped.revision ?? 0} and ` +\n `${kept.revision ?? 0}; keeping revision ${kept.revision ?? 0}. The older copy is usually @rebasepro/types ` +\n \"inlined in a driver built before the kind changed — rebuild the project with a current driver to remove it.\"\n );\n}\n\n/** Every registered kind, for validators and for `rebase doctor`. */\nexport function resourceKinds(): ResourceKindSpec[] {\n return [...visibleKinds().values()].map(effectiveKind);\n}\n\n/** One registered kind, or undefined. */\nexport function resourceKind(kind: string): ResourceKindSpec | undefined {\n const spec = visibleKinds().get(kind);\n return spec && effectiveKind(spec);\n}\n\n/** Options every kind accepts. */\nexport interface DeclareOptions {\n engine?: string;\n transport?: ResourceTransport;\n label?: string;\n [option: string]: unknown;\n}\n\nconst COMMON_OPTION_KEYS = [\"engine\", \"transport\", \"label\"] as const;\n\n/** Whether an engine is one the kind knows, or an explicit `custom:` opt-out. */\nexport function isValidEngine(spec: ResourceKindSpec, engine: string): boolean {\n return engine.startsWith(\"custom:\") || spec.engines.includes(engine);\n}\n\n/**\n * Declare a resource. The primitive every kind's constructor is built from.\n *\n * Redeclaring the same `kind:key` with a *different* shape throws rather than\n * merging. Merging is what the old storage path did, and it silently discarded\n * one of the two engines — a declaration accepted and then ignored, which is\n * the failure this whole model exists to remove. Redeclaring it identically is\n * fine: a config module evaluated twice must not be an error.\n */\nexport function declareResource(\n kind: string,\n key: string = DEFAULT_RESOURCE_KEY,\n options: DeclareOptions = {}\n): ResourceHandle {\n const spec = resourceKind(kind);\n if (!spec) {\n const known = [...visibleKinds().keys()].sort().join(\", \") || \"none\";\n throw new Error(\n `Unknown resource kind \"${kind}\". Registered kinds: ${known}. ` +\n \"Call registerResourceKind() before declaring one.\"\n );\n }\n\n if (!key || typeof key !== \"string\" || key.trim() === \"\") {\n throw new Error(`A ${kind} needs a non-empty key.`);\n }\n\n const engine = options.engine ?? spec.defaultEngine;\n if (!isValidEngine(spec, engine)) {\n throw new Error(\n `Unknown ${kind} engine \"${engine}\" for \"${key}\". ` +\n `Known engines: ${spec.engines.join(\", \")}. ` +\n `An engine this build does not ship is spelled \"custom:${engine}\", ` +\n \"which says so at the call site rather than failing later.\"\n );\n }\n\n const allowed = new Set<string>([...COMMON_OPTION_KEYS, ...(spec.optionKeys ?? [])]);\n const unknown = Object.keys(options).filter(k => !allowed.has(k));\n if (unknown.length > 0) {\n throw new Error(\n `Unknown option(s) on ${kind} \"${key}\": ${unknown.join(\", \")}. ` +\n `A ${kind} accepts: ${[...allowed].sort().join(\", \")}.`\n );\n }\n\n const extra: Record<string, unknown> = {};\n for (const k of spec.optionKeys ?? []) {\n if (options[k] !== undefined) extra[k] = options[k];\n }\n\n const declaration: ResourceDeclaration = {\n kind,\n key,\n engine,\n transport: options.transport ?? \"server\",\n ...(options.label !== undefined ? { label: options.label } : {}),\n options: Object.freeze(extra)\n };\n\n const id = declarationId(kind, key);\n const previous = registry().declarations.get(id);\n if (previous) {\n if (JSON.stringify(previous) !== JSON.stringify(declaration)) {\n throw new Error(\n `${kind} \"${key}\" is declared twice with different configuration. ` +\n \"Declare it once and export it — two declarations of one resource is \" +\n \"the ambiguity this model exists to remove, so it is refused rather \" +\n \"than merged.\"\n );\n }\n } else {\n registry().declarations.set(id, declaration);\n }\n\n const handle = {\n ...declaration,\n toString() { return key; },\n [BRAND]: true as const\n };\n return handle as ResourceHandle;\n}\n\n/** Every declared resource, in declaration order, optionally filtered by kind. */\nexport function declaredResources(kind?: string): ResourceDeclaration[] {\n const all = [...registry().declarations.values()];\n return kind ? all.filter(r => r.kind === kind) : all;\n}\n\n/**\n * Forget every declaration, keeping registered kinds.\n *\n * For tests and for a CLI that evaluates more than one project in a process.\n * Kinds survive because they are registered by module import, which will not\n * happen a second time.\n */\nexport function resetDeclaredResources(): void {\n registry().declarations.clear();\n}\n\n/**\n * The env-var suffix a resource's bindings use: `__ANALYTICS` for `analytics`,\n * and nothing at all for the default-keyed one.\n *\n * The default takes no suffix so that a project with one database configured\n * through plain `DATABASE_URL` keeps working having declared nothing — the\n * overwhelmingly common project must not have to say so.\n */\nexport function resourceEnvSuffix(key: string): string {\n if (key === DEFAULT_RESOURCE_KEY) return \"\";\n return `__${key.toUpperCase().replace(/[^A-Z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\")}`;\n}\n\n/**\n * Two resources of a kind whose keys differ but whose env suffixes do not.\n *\n * `media-files` and `media_files` both become `__MEDIA_FILES`, so one would\n * silently read the other's configuration. Returned rather than thrown so the\n * caller can report it with the rest of a validation pass.\n */\nexport function findEnvSuffixCollision(keys: readonly string[]): { a: string; b: string; suffix: string } | null {\n const seen = new Map<string, string>();\n for (const key of keys) {\n const suffix = resourceEnvSuffix(key);\n const previous = seen.get(suffix);\n if (previous !== undefined && previous !== key) return { a: previous, b: key, suffix };\n seen.set(suffix, key);\n }\n return null;\n}\n\n/**\n * The whole graph, as recorded in a manifest and read by a host.\n *\n * `version` is the graph format, not the project's. A host reading a graph it\n * does not understand must say so rather than provision half of it.\n */\nexport interface ResourceGraph {\n version: 1;\n resources: ResourceDeclaration[];\n}\n\n/** The current graph format version. */\nexport const RESOURCE_GRAPH_VERSION = 1 as const;\n\n/**\n * Build a graph from the current declarations, sorted for a stable diff.\n *\n * `usedBy` maps a `kind:key` id to the things that reach it. The derive step\n * supplies it after evaluating collections; a runtime building the graph at\n * boot passes nothing and gets declarations alone, which is all it binds from.\n */\nexport function buildResourceGraph(usedBy?: ReadonlyMap<string, readonly string[]>): ResourceGraph {\n const resources = declaredResources().slice().sort(\n (a, b) => a.kind.localeCompare(b.kind) || a.key.localeCompare(b.key)\n ).map(r => {\n const users = usedBy?.get(declarationId(r.kind, r.key));\n return users && users.length > 0 ? { ...r, usedBy: [...users].sort() } : r;\n });\n return { version: RESOURCE_GRAPH_VERSION, resources };\n}\n\n/** `kind:key`, the id `usedBy` maps are keyed by. Exported for the derive step. */\nexport function resourceId(kind: string, key: string): string {\n return declarationId(kind, key);\n}\n\n/**\n * The environment variables worth writing for a resource, given its engine.\n *\n * Falls back to every base the kind reads when the engine is unknown — a\n * `custom:` engine gets the full list rather than an empty one, because\n * guessing narrow would silently omit the variable it actually needs.\n */\nexport function envBasesForResource(declaration: ResourceDeclaration): readonly string[] {\n const spec = resourceKind(declaration.kind);\n if (!spec) return [];\n return spec.envBasesByEngine?.[declaration.engine] ?? spec.envBases;\n}\n","/**\n * Describes a named storage backend — a place files live.\n *\n * Declared once and shared front + back: the frontend uses it to decide\n * transport (HTTP proxy vs direct SDK), the backend uses the same `key`\n * to resolve a StorageController, and collection properties reference\n * a definition by its `key` via `StorageConfig.storageSource`.\n *\n * This mirrors the {@link DataSourceDefinition} pattern used for databases.\n *\n * @group Models\n */\n\n/**\n * The default storage source key, used when a property does not specify\n * a `storageSource`. Shared by the frontend and backend registries so\n * both agree on \"the default storage backend\".\n * @group Models\n */\nexport const DEFAULT_STORAGE_SOURCE_KEY = \"(default)\";\n\n/**\n * How the *frontend* reaches a storage backend.\n *\n * - `\"server\"` — through the Rebase backend REST API (`/api/storage`).\n * The backend holds the actual `StorageController` and routes by\n * storage-source key. This is the default and covers Local, S3, GCS,\n * and any other server-mediated engine.\n * - `\"direct\"` — straight from the client to the external backend via\n * its own SDK (e.g. Firebase Storage via `@firebase/storage`).\n * The Rebase backend is **not** in the upload/download path.\n *\n * @group Models\n */\nexport type StorageSourceTransport = \"server\" | \"direct\";\n\n/**\n * Declarative definition of a storage source — a named place files live.\n *\n * Declared once and shared front and back: the frontend uses it to decide\n * transport (client HTTP proxy vs direct provider SDK), the backend uses\n * the same `key` to resolve a `StorageController`, and collection\n * properties reference a definition by its `key` via\n * `StorageConfig.storageSource`.\n *\n * @group Models\n */\nexport interface StorageSourceDefinition {\n /**\n * Unique identifier for this storage source. Collection properties\n * point at it via `StorageConfig.storageSource`.\n * Defaults to {@link DEFAULT_STORAGE_SOURCE_KEY}.\n */\n key: string;\n\n /**\n * The engine backing this storage source (e.g. `\"local\"`, `\"s3\"`,\n * `\"gcs\"`, `\"firebase\"`, `\"azure\"`, or a custom id).\n */\n engine: string;\n\n /**\n * The credential set this source signs with, when several sources share one.\n *\n * ## What it is for\n *\n * Every binding a bucket needs is read per key — `S3_BUCKET__MEDIA`,\n * `S3_ACCESS_KEY_ID__MEDIA`, and so on. That is right for the bucket *name*,\n * which is different for every source by definition, and wrong for the\n * credentials, which usually are not: fifteen buckets on one MinIO install\n * meant fifteen copies of the same endpoint, access key and secret — ninety\n * variables where eighteen would do, and one key rotation became fifteen\n * paired edits where a single missed one fails at upload time with an opaque\n * signing error.\n *\n * Naming an account here lets the *account-scoped* bindings fall back to\n * `<BASE>__<ACCOUNT>` when no per-key value is set. The bucket name never\n * falls back: it is what distinguishes one source from another.\n *\n * ## Why it does not fall back to the bare variable\n *\n * A source with no `account` reads only its own suffixed names, exactly as\n * before — so every project that predates this is wire-identical. The\n * unsuffixed `S3_ACCESS_KEY_ID` belongs to the *default* source, and letting\n * a named bucket inherit it would mean a typo'd key silently signs with\n * another source's credentials. Two forms, both explicit, opt-in.\n */\n account?: string;\n\n /**\n * How the frontend reaches this storage. Defaults to `\"server\"`.\n *\n * When `\"direct\"`, the client uses a provider-specific SDK\n * (e.g. `@firebase/storage`) and the backend does not proxy\n * upload/download traffic for this source.\n */\n transport: StorageSourceTransport;\n\n /**\n * Serve unqualified uploads — a storage property naming no `storageSource`\n * — from this source.\n *\n * Declared, never inferred. A project with named buckets and no default\n * used to have one chosen for it by declaration order, with a warning, and\n * the choice differed between development and production because the\n * synthesized local default is dropped in production and the promotion was\n * not. Where the files land is the author's decision; boot now fails\n * without one.\n */\n default?: boolean;\n\n /** Human-readable label for the UI (e.g. \"Firebase Storage\", \"S3 Media\"). */\n label?: string;\n}\n\n/**\n * A resolved storage source: the single source of truth that the frontend\n * router and backend registry both derive from.\n *\n * @group Models\n */\nexport interface ResolvedStorageSource {\n /** Storage source key (routing key, shared front + back). */\n key: string;\n /** Engine backing the source. */\n engine: string;\n /** Frontend transport. */\n transport: StorageSourceTransport;\n /** Human-readable label. */\n label?: string;\n}\n\n/**\n * The environment-variable suffix for a storage or data source key.\n *\n * `\"\"` for the default source — so a single-bucket project keeps configuring\n * plain `S3_BUCKET` — and `__<KEY>` for every named one, uppercased with\n * non-alphanumerics collapsed to underscores: `media-cdn` → `S3_BUCKET__MEDIA_CDN`.\n *\n * The rule derives the variable name from the declared key rather than\n * discovering keys by scanning the environment. Scanning would have to guess how\n * `S3_BUCKET__MEDIA_CDN` splits into a key; deriving cannot be ambiguous, and a\n * typo surfaces as a missing source at boot instead of a silently ignored\n * variable.\n *\n * It lives in this package, with no dependencies, because four things must agree\n * on it exactly: the CLI (validating a build), the runtime (reading its own\n * environment), the control plane (writing a tenant's Secret), and the docs. A\n * second implementation of a naming convention is a second chance to disagree.\n *\n * @group Models\n */\nexport function storageEnvSuffix(key: string, defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY): string {\n if (!key || key === defaultKey) return \"\";\n const normalized = key\n .replace(/[^A-Za-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .toUpperCase();\n if (!normalized) {\n throw new Error(\n `Source key \"${key}\" cannot be turned into an environment variable name. ` +\n \"Use a key containing at least one letter or digit.\"\n );\n }\n return `__${normalized}`;\n}\n\n/**\n * Two distinct keys that collapse onto the same variable name, or `null`.\n *\n * `media-cdn` and `media_cdn` are different source keys but the same suffix, so\n * without this one of them silently reads the other's configuration. Returns the\n * offending pair rather than throwing, so each caller can raise it in its own\n * idiom — a `BundleError` at boot, a build failure in the CLI, a rejected deploy\n * in a control plane.\n *\n * @group Models\n */\nexport function findStorageSuffixCollision(\n keys: string[],\n defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY\n): { a: string; b: string; suffix: string } | null {\n const seen = new Map<string, string>();\n for (const key of keys) {\n const suffix = storageEnvSuffix(key, defaultKey);\n const existing = seen.get(suffix);\n if (existing !== undefined && existing !== key) {\n return { a: existing, b: key, suffix };\n }\n seen.set(suffix, key);\n }\n return null;\n}\n","/**\n * The kinds Rebase ships, and the constructors a project declares them with.\n *\n * Each kind is registered rather than hardcoded, so a fourth one arrives\n * without editing a manifest schema, a validator and a switch statement. That\n * cost is precisely why databases and buckets ended up declared in different\n * files with different rules — the cheapest thing to do was always to bolt the\n * new kind onto whichever home was nearest.\n *\n * A kind owns its engine list. `custom:<id>` is always accepted, so a build\n * that ships an engine this package has never heard of says so at the call site\n * instead of looking like a typo of one that exists.\n */\nimport {\n DEFAULT_RESOURCE_KEY,\n declareResource,\n declaredResources,\n amendResourceKind,\n registerResourceKind,\n type DeclareOptions,\n type ResourceDeclaration,\n type ResourceHandle\n} from \"./resources\";\nimport { DEFAULT_DATA_SOURCE_KEY, type DataSourceDefinition } from \"./data_source\";\nimport { DEFAULT_STORAGE_SOURCE_KEY, type StorageSourceDefinition } from \"./storage_source\";\n\n// ── database ─────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n // There is no single frozen literal for this kind, which is why it carries a\n // revision. 0.17.0 and 0.17.1 shipped `optionKeys: [\"databaseId\",\n // \"migrations\"]`; 0.17.2 added \"extensions\" to the literal itself, before the\n // rule against that existed. So two different objects are inlined in drivers\n // that are in the field, and no choice of literal can equal both: a runtime\n // that matched one threw `already registered with a different definition` at\n // the other and refused to boot. Two tenants crash-looped for six and a half\n // days on exactly that.\n //\n // `revision` is what resolves it. An older copy — whichever literal it\n // carries — is at revision 0, loses to this one, and warns instead of\n // throwing. Corrections still go in the amendment below; this number moves\n // only when the literal itself has to, and every published copy predating the\n // move is thereby handled.\n revision: 1,\n kind: \"database\",\n engines: [\"postgres\", \"mongodb\", \"firestore\", \"sqlite\"],\n defaultEngine: \"postgres\",\n envBases: [\"DATABASE_URL\", \"REBASE_DRIVER\", \"REBASE_DB_POOL_MAX\"],\n optionKeys: [\"databaseId\", \"migrations\", \"extensions\"],\n implicitDefault: true\n});\n// What a database actually binds from. The 0.17.3 list named two variables\n// the resolver never read and omitted five it does (25f1a97e3).\namendResourceKind(\"database\", {\n envBases: [\n \"DATABASE_URL\",\n \"DATABASE_READ_URL\",\n \"ADMIN_CONNECTION_STRING\",\n \"REBASE_DRIVER\",\n \"DB_POOL_MAX\",\n \"DB_POOL_IDLE_TIMEOUT\",\n \"DB_POOL_CONNECT_TIMEOUT\"\n ]\n});\n\n/** Options a database accepts beyond the common ones. */\nexport interface DatabaseOptions extends DeclareOptions {\n /**\n * The physical database or schema within the engine, when it differs from\n * the engine's own default. Threaded to drivers as `databaseId`.\n */\n databaseId?: string;\n /** Directory of migration files, relative to the config directory. */\n migrations?: string;\n /**\n * Server extensions Rebase may install on this database.\n *\n * A permission, not a request: naming one grants leave to run\n * `CREATE EXTENSION IF NOT EXISTS <name>`, and Rebase issues it only when\n * something in the schema actually needs it. Naming an extension nothing\n * needs installs nothing.\n *\n * It has to be said out loud because installing an extension is a decision\n * with a deployment behind it — the image has to ship the library, the role\n * has to be allowed to install it, and a managed provider has to have it on\n * an allow-list. Rebase cannot see any of that from inside the connection,\n * so the answer comes from whoever chose the database.\n *\n * Today `vector` is the one that matters: a `{ type: \"vector\" }` property\n * compiles to a `VECTOR(n)` column, which does not exist until pgvector is\n * installed. Without this, Rebase creates the column and lets Postgres\n * refuse, naming the option.\n *\n * ```ts\n * export const main = database({ extensions: [\"vector\"] });\n * ```\n *\n * `pg_trgm` and `unaccent` are not on this list and need no permission: a\n * `search` block installs them unasked, because they are contrib modules\n * present in every Postgres distribution. pgvector is a separate build that\n * a stock `postgres:18` does not carry.\n */\n extensions?: string[];\n}\n\n/** A database handle. Collections point at it via `dataSource`. */\nexport type DatabaseHandle = ResourceHandle;\n\n/**\n * Declare a database.\n *\n * ```ts\n * export const main = database(); // the default one\n * export const analytics = database(\"analytics\"); // reads DATABASE_URL__ANALYTICS\n * export const withPgv = database({ extensions: [\"vector\"] }); // the default one, configured\n * ```\n *\n * The third form exists because the default database has no name to pass, and\n * the alternative was `database(\"(default)\", { … })` — writing out an internal\n * sentinel to reach the options. A key is a string and options are an object,\n * so the two can never be confused for one another.\n */\nexport function database(options?: DatabaseOptions): DatabaseHandle;\nexport function database(key?: string, options?: DatabaseOptions): DatabaseHandle;\nexport function database(\n keyOrOptions: string | DatabaseOptions = DEFAULT_RESOURCE_KEY,\n options: DatabaseOptions = {}\n): DatabaseHandle {\n return typeof keyOrOptions === \"string\"\n ? declareResource(\"database\", keyOrOptions, options)\n : declareResource(\"database\", DEFAULT_RESOURCE_KEY, keyOrOptions);\n}\n\n/**\n * The extensions the project's databases gave Rebase leave to install.\n *\n * A flat union rather than a per-database answer, because the surfaces that ask\n * — `rebase db push` and the boot schema-ensure — drive one connection and\n * generate one `schema.sql` for every collection regardless of `dataSource`.\n * Splitting the permission by data source would be a distinction the rest of\n * that pipeline does not make, and a false precision is worse than none.\n *\n * Empty for a project that declared nothing, which is every project that has\n * not opted in — so this reads as a refusal by default, on purpose.\n */\nexport function declaredDatabaseExtensions(): readonly string[] {\n const names = new Set<string>();\n for (const declaration of declaredResources(\"database\")) {\n const declared = declaration.options.extensions;\n if (!Array.isArray(declared)) continue;\n for (const name of declared) {\n if (typeof name === \"string\" && name.trim()) names.add(name.trim());\n }\n }\n return [...names].sort();\n}\n\n// ── bucket ───────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n // FROZEN at the 0.17.3 literal, for the reason given on `database`.\n kind: \"bucket\",\n engines: [\"local\", \"s3\", \"gcs\", \"azure\", \"firebase\"],\n defaultEngine: \"local\",\n envBases: [\"S3_BUCKET\", \"GCS_BUCKET\", \"STORAGE_BUCKET\", \"STORAGE_PUBLIC_URL\"],\n envBasesByEngine: {\n local: [\"STORAGE_BUCKET\"],\n s3: [\"S3_BUCKET\", \"STORAGE_ENDPOINT\", \"STORAGE_REGION\", \"STORAGE_PUBLIC_URL\"],\n gcs: [\"GCS_BUCKET\", \"STORAGE_PUBLIC_URL\"],\n azure: [\"STORAGE_BUCKET\", \"STORAGE_PUBLIC_URL\"],\n firebase: [\"STORAGE_BUCKET\", \"STORAGE_PUBLIC_URL\"]\n },\n optionKeys: [\"publicRead\", \"prefix\", \"account\"],\n implicitDefault: false\n});\n// What a bucket actually binds from, per engine (25f1a97e3), plus `default`:\n// the registry no longer promotes a lone named bucket, so a project needs a way\n// to say which one serves unqualified uploads. The literal above is frozen, so\n// the new option key arrives here.\namendResourceKind(\"bucket\", {\n optionKeys: [\"publicRead\", \"prefix\", \"account\", \"default\"],\n envBases: [\n \"STORAGE_TYPE\",\n \"STORAGE_PATH\",\n \"S3_BUCKET\",\n \"S3_REGION\",\n \"S3_ACCESS_KEY_ID\",\n \"S3_SECRET_ACCESS_KEY\",\n \"S3_ENDPOINT\",\n \"S3_FORCE_PATH_STYLE\",\n \"GCS_BUCKET\",\n \"GCS_PROJECT_ID\",\n \"GCS_KEY_FILENAME\"\n ],\n envBasesByEngine: {\n local: [\"STORAGE_TYPE\", \"STORAGE_PATH\"],\n s3: [\n \"STORAGE_TYPE\",\n \"S3_BUCKET\",\n \"S3_REGION\",\n \"S3_ACCESS_KEY_ID\",\n \"S3_SECRET_ACCESS_KEY\",\n \"S3_ENDPOINT\",\n \"S3_FORCE_PATH_STYLE\"\n ],\n gcs: [\"STORAGE_TYPE\", \"GCS_BUCKET\", \"GCS_PROJECT_ID\", \"GCS_KEY_FILENAME\"],\n azure: [],\n firebase: []\n }\n});\n\n/** Options a bucket accepts beyond the common ones. */\nexport interface BucketOptions extends DeclareOptions {\n /**\n * Whether objects are world-readable by default.\n *\n * Declared rather than inferred from the engine, because the two have\n * disagreed before: a private object served through a cacheable public URL\n * is a data leak that nothing errors on.\n */\n publicRead?: boolean;\n /** Key prefix within the bucket, for sharing one bucket between sources. */\n prefix?: string;\n /**\n * Serve unqualified uploads — a storage property with no `storageSource` —\n * from this bucket.\n *\n * A project that declares only `bucket(\"media\")` has no default bucket, and\n * the registry used to promote the one it found with a warning. That is a\n * decision about where a user's files land, made by the framework, on the\n * strength of declaration order; it also produced two different\n * destinations either side of a deploy, because the synthesized local\n * default is dropped in production and the promotion is not. So it is now\n * a boot error, and this is one of the two ways to answer it — the other\n * being `bucket()`, which declares the default bucket itself.\n */\n default?: boolean;\n /**\n * The credential set this bucket signs with, when several share one.\n *\n * `bucket(\"media\", { engine: \"s3\", account: \"minio\" })` keeps reading its own\n * `S3_BUCKET__MEDIA` — the bucket name is what distinguishes one source from\n * another and never falls back — while the provider-level variables\n * (`S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_ENDPOINT`, `S3_REGION`,\n * `S3_FORCE_PATH_STYLE`) fall back to `__MINIO` when no per-key value is set.\n *\n * Fifteen buckets on one install go from ninety variables to eighteen, and\n * rotating the key becomes one edit. A per-bucket value still wins, so a\n * single source can move to another provider without breaking the rest off\n * their shared account.\n */\n account?: string;\n}\n\n/** A bucket handle. Storage properties point at it via `storageSource`. */\nexport type BucketHandle = ResourceHandle;\n\n/**\n * Declare a bucket.\n *\n * ```ts\n * export const uploads = bucket({ engine: \"s3\" }); // the default one\n * export const media = bucket(\"media\", { transport: \"direct\" });\n * ```\n *\n * `transport: \"direct\"` means a provider SDK talks to the bucket and the\n * backend is not in the upload path.\n *\n * The options-only form exists for the same reason `database`'s does: the\n * default bucket has no name to pass, and without it the only way to configure\n * one was `bucket(\"(default)\", { … })` — writing out an internal sentinel to\n * reach the options. Passing options where a key belongs used to throw \"a\n * bucket needs a non-empty key\", which names neither the mistake nor the fix.\n */\nexport function bucket(options?: BucketOptions): BucketHandle;\nexport function bucket(key?: string, options?: BucketOptions): BucketHandle;\nexport function bucket(\n keyOrOptions: string | BucketOptions = DEFAULT_RESOURCE_KEY,\n options: BucketOptions = {}\n): BucketHandle {\n return typeof keyOrOptions === \"string\"\n ? declareResource(\"bucket\", keyOrOptions, options)\n : declareResource(\"bucket\", DEFAULT_RESOURCE_KEY, keyOrOptions);\n}\n\n// ── topic ────────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n // FROZEN at the 0.17.3 literal, for the reason given on `database`.\n kind: \"topic\",\n // `jobs` is the durable local implementation: a topic fans out to one job\n // row per subscription, so each subscriber retries on its own schedule and\n // a failure is a row somebody can look at rather than a lost message.\n engines: [\"jobs\"],\n defaultEngine: \"jobs\",\n envBases: [\"REBASE_TOPIC_URL\"],\n optionKeys: [\"delivery\", \"maxAttempts\"],\n implicitDefault: false\n});\n// Nothing. A topic on the `jobs` engine is rows in the project's own database\n// and binds from no variable of its own. The literal above says\n// `REBASE_TOPIC_URL` — a name nothing in either repository read, which\n// `rebase status` then printed as a variable somebody could set — and it has to\n// keep saying it, because a driver ≤ 0.17.3 compares that object and throws.\n// The gate in `resource-env-bases.test.ts` covers every registered kind through\n// the amended view, so a phantom name fails a build rather than reaching a\n// developer.\namendResourceKind(\"topic\", { envBases: [] });\n\n/**\n * How hard the runtime tries to deliver.\n *\n * Only `at-least-once` is implemented, and it is the honest name for what a\n * retrying queue does: a handler must tolerate seeing the same event twice.\n * `at-most-once` is listed so a future transport can offer it without the\n * option changing shape, and is refused today rather than silently upgraded.\n */\nexport type TopicDelivery = \"at-least-once\" | \"at-most-once\";\n\n/** Options a topic accepts beyond the common ones. */\nexport interface TopicOptions extends DeclareOptions {\n delivery?: TopicDelivery;\n /** Attempts per subscription before a message is left failed. Default 5. */\n maxAttempts?: number;\n}\n\n/**\n * What a subscription does with an event.\n *\n * `attempt` counts from 1. Worth branching on: the first delivery and the\n * fourth are the same call, but the fourth is where it is worth logging loudly.\n */\nexport type TopicHandler<T> = (event: T, context: { attempt: number; topic: string; subscription: string }) => Promise<void> | void;\n\n/** A declared subscription, as recorded in the graph and wired at boot. */\nexport interface TopicSubscription<T = unknown> {\n topic: string;\n name: string;\n handler: TopicHandler<T>;\n maxAttempts?: number;\n}\n\n/**\n * What a topic publishes through.\n *\n * Installed by `@rebasepro/server` at boot. Absent — in the CLI evaluating\n * config to derive the graph, or in a unit test — publishing throws a message\n * naming the cause, rather than resolving and dropping the event. A publish\n * that silently does nothing is the failure mode a queue exists to prevent.\n */\nexport interface TopicRuntime {\n publish(topic: string, event: unknown): Promise<void>;\n}\n\nconst runtimeHolder: { current: TopicRuntime | null } = { current: null };\n\n/** Install the transport topics publish through. Called by the server at boot. */\nexport function setTopicRuntime(runtime: TopicRuntime | null): void {\n runtimeHolder.current = runtime;\n}\n\nconst subscriptions: TopicSubscription[] = [];\n\n/** Every declared subscription, for the worker to wire and the graph to record. */\nexport function declaredSubscriptions(topic?: string): TopicSubscription[] {\n return topic ? subscriptions.filter(s => s.topic === topic) : subscriptions.slice();\n}\n\n/** Forget declared subscriptions. For tests, alongside `resetDeclaredResources`. */\nexport function resetDeclaredSubscriptions(): void {\n subscriptions.length = 0;\n}\n\n/** A topic handle, carrying its payload type. */\nexport interface TopicHandle<T> extends ResourceHandle {\n /**\n * Publish an event.\n *\n * Resolves once the event is durably recorded for every subscription, not\n * once they have run. Enqueued inside a transaction that rolls back, it was\n * never published.\n */\n publish(event: T): Promise<void>;\n /**\n * Declare a subscription.\n *\n * The name is its identity: it is what the job row records, what a retry\n * counts against, and what a second subscription must not collide with.\n */\n subscription(name: string, handler: TopicHandler<T>, options?: { maxAttempts?: number }): void;\n}\n\n/**\n * Declare a topic.\n *\n * ```ts\n * export const signups = topic<{ userId: string }>(\"signups\");\n * signups.subscription(\"send-welcome\", async (event) => { … });\n * await signups.publish({ userId });\n * ```\n */\nexport function topic<T = unknown>(key: string, options: TopicOptions = {}): TopicHandle<T> {\n if (options.delivery === \"at-most-once\") {\n throw new Error(\n `Topic \"${key}\" asks for at-most-once delivery, which no shipped transport implements. ` +\n \"The durable queue behind topics retries, so it is at-least-once and a handler must \" +\n \"tolerate seeing an event twice. Refused rather than quietly given the other guarantee.\"\n );\n }\n const handle = declareResource(\"topic\", key, options);\n\n return {\n ...handle,\n toString() { return key; },\n async publish(event: T): Promise<void> {\n const runtime = runtimeHolder.current;\n if (!runtime) {\n throw new Error(\n `Cannot publish to topic \"${key}\": no topic runtime is installed. ` +\n \"Publishing works inside a running Rebase backend; this looks like config \" +\n \"being evaluated outside one (a build, a script, or a test without a harness).\"\n );\n }\n await runtime.publish(key, event);\n },\n subscription(name: string, handler: TopicHandler<T>, subOptions: { maxAttempts?: number } = {}): void {\n if (!name || name.trim() === \"\") {\n throw new Error(`A subscription on topic \"${key}\" needs a non-empty name.`);\n }\n if (subscriptions.some(s => s.topic === key && s.name === name)) {\n throw new Error(\n `Topic \"${key}\" already has a subscription named \"${name}\". ` +\n \"The name is what a job row records and what a retry counts against, so two \" +\n \"cannot share one.\"\n );\n }\n subscriptions.push({\n topic: key,\n name,\n handler: handler as TopicHandler<unknown>,\n ...(subOptions.maxAttempts !== undefined ? { maxAttempts: subOptions.maxAttempts } : {})\n });\n }\n } as TopicHandle<T>;\n}\n\n// ── cron ─────────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n kind: \"cron\",\n // The in-process scheduler, claiming each slot in `rebase.cron_claims` so\n // several instances of one deployment run a slot once. It is the only\n // engine because it is the only one that exists; an external scheduler\n // (a platform's cron, a Kubernetes CronJob) would be a second engine that\n // triggers the same handler over HTTP, and it can register itself.\n engines: [\"scheduler\"],\n defaultEngine: \"scheduler\",\n // Code, not configuration: a cron binds from no variable. It is in the\n // graph so a host knows a project's schedules BEFORE running it, which is\n // what lets a console show them and a placement decision read them.\n envBases: [],\n optionKeys: [\"schedule\", \"timezone\", \"description\", \"enabled\", \"timeoutSeconds\", \"catchUpWindowSeconds\"],\n implicitDefault: false\n});\n\n/** What a cron declaration records, beyond its handler. */\nexport interface CronResourceOptions extends DeclareOptions {\n /** Five-field cron expression, e.g. `0 3 * * *`. */\n schedule: string;\n /**\n * IANA zone the schedule is read in, e.g. `Europe/Madrid`.\n *\n * Without it the schedule is read in the process's own zone, which is\n * whatever the host happens to be set to — UTC in nearly every container,\n * the developer's own on a laptop. \"3 AM\" then means two different hours\n * either side of a deploy. Naming the zone makes the declaration mean one\n * thing everywhere.\n */\n timezone?: string;\n description?: string;\n enabled?: boolean;\n timeoutSeconds?: number;\n catchUpWindowSeconds?: number;\n}\n\n/**\n * Declare a cron, as the scheduler's `defineCron` does on its way through.\n *\n * Projects do not call this: `defineCron` in `@rebasepro/server` does, so a\n * cron file is both the handler and the declaration — one file, one name, and\n * the graph derived from it says what a host needs to know without evaluating\n * the handler. Exported so the derive step and the scheduler spell the\n * declaration identically.\n */\nexport function declareCron(name: string, options: CronResourceOptions): ResourceHandle {\n if (typeof options.schedule !== \"string\" || options.schedule.trim() === \"\") {\n throw new Error(`Cron \"${name}\" needs a schedule — a five-field cron expression such as \"0 3 * * *\".`);\n }\n return declareResource(\"cron\", name, options);\n}\n\n// ── function ─────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n kind: \"function\",\n // Mounted by this runtime at `/api/functions/<name>`. A host that runs a\n // function elsewhere — an edge runtime, say — is a second engine, and the\n // bundle's `portable` analysis already says which ones could move.\n engines: [\"http\"],\n defaultEngine: \"http\",\n envBases: [],\n optionKeys: [\"portable\", \"requires\", \"file\"],\n implicitDefault: false\n});\n\n/**\n * What a function declaration records.\n *\n * Recorded by the derive step from the bundler's static analysis rather than\n * by evaluating the function module: a function's handler is a Hono app that\n * only needs to exist at request time, and evaluating it at build time would\n * run its module-scope code in a process with none of its environment.\n */\nexport interface FunctionResourceOptions extends DeclareOptions {\n /** Path inside the project, so a host can point at the file. */\n file?: string;\n /** `false` when the source imports a Node built-in or a package that needs one. */\n portable?: boolean;\n /** Why it is not portable — one short phrase per reason. */\n requires?: string[];\n}\n\n/** Declare a function. Called by the derive step, not by projects. */\nexport function declareFunction(name: string, options: FunctionResourceOptions = {}): ResourceHandle {\n return declareResource(\"function\", name, options);\n}\n\n// ── queue ────────────────────────────────────────────────────────────────────\n\nregisterResourceKind({\n kind: \"queue\",\n // Same durable queue topics ride on: a row per job, claimed with\n // `FOR UPDATE SKIP LOCKED`, retried on a backoff, kept when it gives up.\n engines: [\"jobs\"],\n defaultEngine: \"jobs\",\n envBases: [],\n optionKeys: [\"maxAttempts\"],\n implicitDefault: false\n});\n\n/** Options a queue accepts beyond the common ones. */\nexport interface QueueOptions extends DeclareOptions {\n /** Attempts before a job is left failed. Default 5. */\n maxAttempts?: number;\n}\n\n/** What a queue's handler receives. `attempt` counts from 1. */\nexport type QueueHandler<T> = (\n payload: T,\n context: { attempt: number; queue: string; jobId: string }\n) => Promise<void> | void;\n\n/** Per-job options at enqueue time. */\nexport interface QueueEnqueueOptions {\n /** Earliest time the job may run. Defaults to now. */\n runAt?: Date;\n /** Attempts for this job, overriding the queue's. */\n maxAttempts?: number;\n}\n\n/**\n * What a queue enqueues through.\n *\n * Installed by `@rebasepro/server` at boot, alongside the topic runtime.\n * Absent — config evaluated by the CLI, a unit test — enqueueing throws with\n * the cause named, rather than resolving and dropping the job.\n */\nexport interface QueueRuntime {\n enqueue(queue: string, payload: unknown, options?: QueueEnqueueOptions): Promise<{ id: string }>;\n}\n\nconst queueRuntimeHolder: { current: QueueRuntime | null } = { current: null };\n\n/** Install the transport queues enqueue through. Called by the server at boot. */\nexport function setQueueRuntime(runtime: QueueRuntime | null): void {\n queueRuntimeHolder.current = runtime;\n}\n\n/** A queue's handler, as recorded for the worker to wire. */\nexport interface QueueConsumer<T = unknown> {\n queue: string;\n handler: QueueHandler<T>;\n}\n\nconst queueConsumers = new Map<string, QueueConsumer>();\n\n/** Every declared queue handler, for the worker to wire. */\nexport function declaredQueueConsumers(): QueueConsumer[] {\n return [...queueConsumers.values()];\n}\n\n/** Forget declared queue handlers. For tests, alongside `resetDeclaredResources`. */\nexport function resetDeclaredQueueConsumers(): void {\n queueConsumers.clear();\n}\n\n/** A queue handle, carrying its payload type. */\nexport interface QueueHandle<T> extends ResourceHandle {\n /**\n * Put a job on the queue.\n *\n * Resolves once the job is durably recorded, not once it has run. A row\n * insert, so enqueued inside a transaction that rolls back it was never\n * enqueued.\n */\n enqueue(payload: T, options?: QueueEnqueueOptions): Promise<{ id: string }>;\n /**\n * Declare the handler.\n *\n * One per queue: a queue is a work list with one consumer, which is what\n * separates it from a topic. Work that several things must react to is a\n * topic with several subscriptions.\n */\n handler(fn: QueueHandler<T>): void;\n}\n\n/**\n * Declare a queue.\n *\n * ```ts\n * export const thumbnails = queue<{ key: string }>(\"thumbnails\");\n * thumbnails.handler(async ({ key }) => { … });\n * await thumbnails.enqueue({ key }, { runAt: new Date(Date.now() + 60_000) });\n * ```\n *\n * The difference from a topic is the number of consumers: a queue has one, a\n * topic fans out to every subscription. Both ride on the durable job queue, so\n * declaring either turns it on.\n */\nexport function queue<T = unknown>(key: string, options: QueueOptions = {}): QueueHandle<T> {\n const handle = declareResource(\"queue\", key, options);\n\n return {\n ...handle,\n toString() { return key; },\n async enqueue(payload: T, enqueueOptions?: QueueEnqueueOptions): Promise<{ id: string }> {\n const runtime = queueRuntimeHolder.current;\n if (!runtime) {\n throw new Error(\n `Cannot enqueue on queue \"${key}\": no queue runtime is installed. ` +\n \"Enqueueing works inside a running Rebase backend; this looks like config \" +\n \"being evaluated outside one (a build, a script, or a test without a harness).\"\n );\n }\n return runtime.enqueue(key, payload, enqueueOptions);\n },\n handler(fn: QueueHandler<T>): void {\n if (queueConsumers.has(key)) {\n throw new Error(\n `Queue \"${key}\" already has a handler. A queue has exactly one consumer; ` +\n \"work that several things react to is a topic with several subscriptions.\"\n );\n }\n queueConsumers.set(key, { queue: key, handler: fn as QueueHandler<unknown> });\n }\n } as QueueHandle<T>;\n}\n\n// ── Handing declarations to the readers ──────────────────────────────────────\n\n/**\n * One declaration, as the data layer's definition.\n *\n * There is exactly one of these per kind, and everything that needs a\n * definition goes through it — the frontend, the managed runtime's boot path,\n * and an ejected project's own entrypoint. That is not tidiness: the mapping\n * used to exist twice, once here and once in `@rebasepro/server`'s\n * `graphToStorageSources`, and the two disagreed. The server's copy carried a\n * bucket's `account`; this one dropped it, so a bucket declared with shared\n * credentials resolved them on the managed runtime and resolved *nothing* in an\n * ejected backend — the source was skipped and every upload to it answered 501.\n *\n * A field-by-field map is one line away from that failure at all times, so\n * there is now one line to keep right instead of two to keep equal.\n */\nexport function resourceToDataSource(declaration: ResourceDeclaration): DataSourceDefinition {\n return {\n // The graph and the data layer spell \"the unnamed one\" identically\n // today, but they are separate constants and nothing stops them\n // drifting. Mapped explicitly so a divergence is a compile error rather\n // than a default database that silently fails to bind.\n key: declaration.key === DEFAULT_RESOURCE_KEY ? DEFAULT_DATA_SOURCE_KEY : declaration.key,\n engine: declaration.engine,\n transport: declaration.transport,\n ...(typeof declaration.options.databaseId === \"string\"\n ? { databaseId: declaration.options.databaseId }\n : {}),\n ...(declaration.label !== undefined ? { label: declaration.label } : {})\n };\n}\n\n/** One declaration, as the storage layer's definition. See {@link resourceToDataSource}. */\nexport function resourceToStorageSource(declaration: ResourceDeclaration): StorageSourceDefinition {\n return {\n key: declaration.key === DEFAULT_RESOURCE_KEY ? DEFAULT_STORAGE_SOURCE_KEY : declaration.key,\n engine: declaration.engine,\n transport: declaration.transport,\n // Carried, or the declaration's `account` is accepted at the call site\n // and lost on the way to the reader — a declared option that does\n // nothing, which is the exact failure this whole model exists to remove.\n ...(typeof declaration.options.account === \"string\"\n ? { account: declaration.options.account }\n : {}),\n ...(declaration.options.default === true ? { default: true } : {}),\n ...(declaration.label !== undefined ? { label: declaration.label } : {})\n };\n}\n\n/**\n * The declared databases, as definitions.\n *\n * Both the frontend and a project's own backend entrypoint read this. The\n * frontend needs to know which sources exist and how they are reached — a\n * `direct`-transport source is one the browser talks to itself — and it imports\n * the same config package the backend does. Without these it would mean writing\n * the list a second time, by hand, next to the declarations, which is precisely\n * the two-homes problem this model removed everywhere else.\n *\n * ```tsx\n * import \"../config/resources\"; // registers them\n * import { declaredDataSources, declaredStorageSources } from \"@rebasepro/types\";\n *\n * <Rebase dataSources={declaredDataSources()} storageSources={declaredStorageSources()} />\n * ```\n *\n * The import is what registers them, so a bundler that drops an unused module\n * would leave this empty — hence the side-effect import above rather than a\n * bare re-export.\n */\nexport function declaredDataSources(): DataSourceDefinition[] {\n return declaredResources(\"database\").map(resourceToDataSource);\n}\n\n/** The declared buckets, as definitions. */\nexport function declaredStorageSources(): StorageSourceDefinition[] {\n return declaredResources(\"bucket\").map(resourceToStorageSource);\n}\n"],"mappings":"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"}
|