@rebasepro/server-postgres 0.19.1 → 0.19.2-canary.g08eed46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-ByLfM_Tt.js → BranchService-W3DMfcZZ.js} +2 -2
- package/dist/{BranchService-ByLfM_Tt.js.map → BranchService-W3DMfcZZ.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +81 -7
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/{backup-cli-oM9WQVlV.js → backup-cli-DSpQyqcG.js} +2 -2
- package/dist/{backup-cli-oM9WQVlV.js.map → backup-cli-DSpQyqcG.js.map} +1 -1
- package/dist/{backup-service-Br3tqwsy.js → backup-service-DA7a6SUV.js} +2 -2
- package/dist/{backup-service-Br3tqwsy.js.map → backup-service-DA7a6SUV.js.map} +1 -1
- package/dist/{cli-errors-DxN4yzdH.js → cli-errors-C3g_kHBw.js} +137 -18
- package/dist/cli-errors-C3g_kHBw.js.map +1 -0
- package/dist/cli-errors.d.ts +19 -0
- package/dist/cli-helpers.d.ts +47 -0
- package/dist/cli.js +233 -19
- package/dist/cli.js.map +1 -1
- package/dist/{collection-index-BHgT_TKd.js → collection-index-DdnsxiJ_.js} +14 -4
- package/dist/{collection-index-BHgT_TKd.js.map → collection-index-DdnsxiJ_.js.map} +1 -1
- package/dist/collections/validate-relations.d.ts +7 -0
- package/dist/column-plan-helpers-CpILzHJS.js +145 -0
- package/dist/column-plan-helpers-CpILzHJS.js.map +1 -0
- package/dist/{doctor-D7HWmGzs.js → doctor-D5SrGJ5P.js} +175 -61
- package/dist/doctor-D5SrGJ5P.js.map +1 -0
- package/dist/{ensure-collection-policies-2c2NV8zX.js → ensure-collection-policies-CHO0moXQ.js} +5 -5
- package/dist/{ensure-collection-policies-2c2NV8zX.js.map → ensure-collection-policies-CHO0moXQ.js.map} +1 -1
- package/dist/{ensure-collection-tables-D4wJzM4y.js → ensure-collection-tables-Bkvehxdm.js} +318 -312
- package/dist/ensure-collection-tables-Bkvehxdm.js.map +1 -0
- package/dist/{ensure-tables-CIojK-Rx.js → ensure-tables-CYMtuuOd.js} +2 -2
- package/dist/{ensure-tables-CIojK-Rx.js.map → ensure-tables-CYMtuuOd.js.map} +1 -1
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js +228 -0
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js.map +1 -0
- package/dist/{generate-drizzle-schema-CwEgVyjO.js → generate-drizzle-schema-vSK-VdYT.js} +3 -3
- package/dist/generate-drizzle-schema-vSK-VdYT.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js +475 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js.map +1 -0
- package/dist/{generated-schema-staleness-DQdfJl2A.js → generated-schema-staleness-DRQe2BpC.js} +81 -3
- package/dist/{generated-schema-staleness-DQdfJl2A.js.map → generated-schema-staleness-DRQe2BpC.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +23 -4
- package/dist/index.es.js +1991 -580
- package/dist/index.es.js.map +1 -1
- package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -1
- package/dist/plan-schema-Hgl62S-w.js +1531 -0
- package/dist/plan-schema-Hgl62S-w.js.map +1 -0
- package/dist/{policy-drift-kNJ6X2Uj.js → policy-drift-B0GDRh7_.js} +2 -2
- package/dist/{policy-drift-kNJ6X2Uj.js.map → policy-drift-B0GDRh7_.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js → rls-bootstrap-sql-BlzsOUtz.js} +111 -8
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js.map → rls-bootstrap-sql-BlzsOUtz.js.map} +1 -1
- package/dist/{rls-enforcement-DeYEjYLt.js → rls-enforcement-BV12vBwQ.js} +2 -1
- package/dist/rls-enforcement-BV12vBwQ.js.map +1 -0
- package/dist/schema/catalogue-schema.d.ts +97 -0
- package/dist/schema/collection-index.d.ts +9 -0
- package/dist/schema/column-plan-helpers.d.ts +125 -0
- package/dist/schema/config-relations.d.ts +32 -0
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/doctor.d.ts +9 -0
- package/dist/schema/dynamic-tables.d.ts +27 -6
- package/dist/schema/ensure-collection-tables.d.ts +31 -268
- package/dist/schema/generate-drizzle-schema-logic.d.ts +26 -8
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +84 -168
- package/dist/schema/generate-postgres-ddl.js +8 -2
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generated-column-conflicts.d.ts +84 -0
- package/dist/schema/generated-schema-diff.d.ts +56 -0
- package/dist/schema/generated-schema-staleness.d.ts +33 -0
- package/dist/schema/introspect-db-logic.d.ts +2 -0
- package/dist/schema/plan/diff-plan.d.ts +220 -0
- package/dist/schema/plan/plan-schema.d.ts +84 -0
- package/dist/schema/plan/render-ddl.d.ts +62 -0
- package/dist/schema/plan/render-drizzle.d.ts +31 -0
- package/dist/schema/plan/types.d.ts +410 -0
- package/dist/schema/plan/updated-at-trigger.d.ts +22 -0
- package/dist/schema/relation-names.d.ts +28 -0
- package/dist/schema/search-column.d.ts +9 -0
- package/dist/security/rls-enforcement.d.ts +17 -0
- package/dist/services/FetchService.d.ts +176 -36
- package/dist/services/PersistService.d.ts +17 -4
- package/dist/services/RelationService.d.ts +11 -3
- package/dist/services/collection-helpers.d.ts +16 -2
- package/dist/services/dataService.d.ts +29 -2
- package/dist/services/field-op-sql.d.ts +16 -0
- package/dist/services/field-viewer.d.ts +19 -0
- package/dist/services/realtimeService.d.ts +35 -1
- package/dist/services/row-pipeline.d.ts +29 -5
- package/dist/services/soft-delete.d.ts +73 -0
- package/dist/utils/pg-error-utils.d.ts +41 -1
- package/dist/{vector-index-DkWxm2de.js → vector-index-CMJKb3C9.js} +3 -14
- package/dist/{vector-index-DkWxm2de.js.map → vector-index-CMJKb3C9.js.map} +1 -1
- package/package.json +18 -10
- package/dist/cli-errors-DxN4yzdH.js.map +0 -1
- package/dist/doctor-D7HWmGzs.js.map +0 -1
- package/dist/ensure-collection-tables-D4wJzM4y.js.map +0 -1
- package/dist/generate-drizzle-schema-CwEgVyjO.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +0 -528
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js +0 -1312
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +0 -1
- package/dist/rls-enforcement-DeYEjYLt.js.map +0 -1
|
@@ -16,7 +16,8 @@ var collection_index_exports = /* @__PURE__ */ __exportAll({
|
|
|
16
16
|
indexFingerprint: () => indexFingerprint,
|
|
17
17
|
isRebaseIndexName: () => isRebaseIndexName,
|
|
18
18
|
renderPredicate: () => renderPredicate,
|
|
19
|
-
resolveIndexableColumn: () => resolveIndexableColumn
|
|
19
|
+
resolveIndexableColumn: () => resolveIndexableColumn,
|
|
20
|
+
sortIndexSpecs: () => sortIndexSpecs
|
|
20
21
|
});
|
|
21
22
|
/**
|
|
22
23
|
* A declaration that cannot become an index.
|
|
@@ -269,8 +270,17 @@ var buildCollectionIndexSpecs = (collection, resolveColumnName) => {
|
|
|
269
270
|
* collections, so leaving this in declaration order would make the artifact
|
|
270
271
|
* depend on the order files happened to load in.
|
|
271
272
|
*/
|
|
272
|
-
var buildCollectionIndexPlan = (collections, resolveColumnName) => collections.flatMap((collection) => buildCollectionIndexSpecs(collection, resolveColumnName))
|
|
273
|
+
var buildCollectionIndexPlan = (collections, resolveColumnName) => sortIndexSpecs(collections.flatMap((collection) => buildCollectionIndexSpecs(collection, resolveColumnName)));
|
|
274
|
+
/**
|
|
275
|
+
* The stable order every consumer of a set of index specs uses.
|
|
276
|
+
*
|
|
277
|
+
* Exported because the boot-time diff reads the specs off the schema plan
|
|
278
|
+
* rather than rebuilding them, and it has to reach the same order the CLI's
|
|
279
|
+
* exclude list and `schema.sql` do — a second sort written out beside this one
|
|
280
|
+
* is a second order the moment either is edited.
|
|
281
|
+
*/
|
|
282
|
+
var sortIndexSpecs = (specs) => [...specs].sort((a, b) => a.schema.localeCompare(b.schema) || a.table.localeCompare(b.table) || a.indexName.localeCompare(b.indexName));
|
|
273
283
|
//#endregion
|
|
274
|
-
export {
|
|
284
|
+
export { deriveIndexName as a, sortIndexSpecs as c, collection_index_exports as i, collectionIndexStatement as n, isRebaseIndexName as o, collectionIndexStatements as r, renderPredicate as s, buildCollectionIndexSpecs as t };
|
|
275
285
|
|
|
276
|
-
//# sourceMappingURL=collection-index-
|
|
286
|
+
//# sourceMappingURL=collection-index-DdnsxiJ_.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-index-BHgT_TKd.js","names":[],"sources":["../src/schema/collection-index.ts"],"sourcesContent":["/**\n * The one place a collection's `indexes:` block becomes `CREATE INDEX`.\n *\n * Like `search-column.ts` and `vector-index.ts`, this module exists so the DDL\n * generator and the boot-time ensure render the *same* specification rather\n * than describing the same index twice, differently.\n *\n * ## Why every form here is core Postgres\n *\n * `rebase db push` runs `atlas schema apply`, which materialises the desired\n * state in a bare scratch database to plan against. `--exclude` does not\n * suppress that replay, and `CREATE EXTENSION` cannot go in the file. So an\n * index wanting `gin_trgm_ops` or `vector_cosine_ops` would parse, plan, and\n * then fail against a database the author has never heard of. Those are\n * refused here instead, and redirected to the feature that owns them: trigram\n * search is `search:`, ANN is a `vector` property.\n *\n * Verified against atlas v1.2.3 and Postgres 18 before this was written: plain,\n * composite with `DESC NULLS LAST`, partial, unique, covering `INCLUDE`, `GIN`\n * and expression indexes all parse, apply, and re-plan clean. The Atlas\n * limitation that forced the search carve-out is that it will not parse a file\n * containing a function *definition* — a function *call* inside an index is\n * fine. That is why this module needs no carve-out and search did.\n *\n * ## Why the name carries a hash\n *\n * `CREATE INDEX IF NOT EXISTS` is a **name** check, not a definition check. A\n * readable name means a changed declaration keeps the old index and reports\n * success, forever. Hashing the index's *semantics* into its name makes a\n * redefinition a different object: the new one is built before the old one is\n * dropped, there is never a window with no index, and drift detection reduces\n * to a set difference over names.\n */\nimport type { CollectionConfig, CollectionIndex, IndexPredicate, Property, ResolvedRelation } from \"@rebasepro/types\";\nimport { isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { sha1Hex, truncateToBytes } from \"@rebasepro/utils\";\n\n/** Resolve a property key to its column name. Injected to avoid an import cycle. */\nexport type ResolveColumnName = (propName: string, prop?: Property | null) => string;\n\nexport type IndexMethod = \"btree\" | \"gin\" | \"brin\";\n\n/** A key column, with the ordering Postgres will actually apply. */\nexport interface ResolvedIndexKey {\n column: string;\n /** Always concrete. `btree` defaults ascending; unordered methods report `asc`. */\n direction: \"asc\" | \"desc\";\n /** Postgres's own default: `last` under `asc`, `first` under `desc`. */\n nulls: \"first\" | \"last\";\n}\n\n/** A predicate resolved onto column names, ready to render and to hash. */\nexport type ResolvedPredicate =\n | { column: string; op: \"=\" | \"!=\" | \"<\" | \"<=\" | \">\" | \">=\"; value: string | number | boolean }\n | { column: string; op: \"is null\" | \"is not null\" }\n | { column: string; op: \"in\"; value: readonly (string | number)[] }\n | { and: readonly ResolvedPredicate[] };\n\n/** One index, fully resolved. The only shape the renderers accept. */\nexport interface CollectionIndexSpec {\n schema: string;\n table: string;\n method: IndexMethod;\n unique: boolean;\n keys: ResolvedIndexKey[];\n include: string[];\n predicate: ResolvedPredicate | null;\n /** The author's one-line justification. Never enters the name. */\n reason: string;\n /** Derived by {@link deriveIndexName}. Frozen — see the module comment. */\n indexName: string;\n}\n\n/**\n * A declaration that cannot become an index.\n *\n * Thrown at build time, naming the collection and the array position, because\n * the alternative is a `CREATE INDEX` that fails during a push with a Postgres\n * error mentioning a column the author never wrote.\n */\nexport class CollectionIndexConfigError extends Error {\n readonly collectionSlug: string;\n readonly position: number;\n\n constructor(collectionSlug: string, position: number, message: string) {\n super(`${collectionSlug}.indexes[${position}]: ${message}`);\n this.name = \"CollectionIndexConfigError\";\n this.collectionSlug = collectionSlug;\n this.position = position;\n }\n}\n\n/** Postgres allows 32 key columns. See the doc comment on `on`. */\nexport const MAX_INDEX_KEYS = 5;\n\n/**\n * `_ix`/`_ux` plus `_` plus 7 hex — the part of the name that must always\n * survive truncation, and therefore is never inside the truncated portion.\n */\nconst NAME_SUFFIX_BYTES = 11;\n\n/**\n * Every Rebase-managed index name, and nothing else.\n *\n * The terminal `_ix_`/`_ux_` plus exactly seven lowercase hex characters is\n * what separates this scheme from every other producer in the codebase —\n * `_fkey`, `_gin`, `_trgm`, `_pkey`, `_key`, the vector distances, and the\n * `idx_` prefix auth uses. `_idx` was rejected as a tail because it is already\n * taken for real: `users_email_verification_token_idx` is byte-for-byte what a\n * naive `<table>_<column>_idx` derives on an auth-enabled `users` collection.\n *\n * Load-bearing for safety, not just tidiness. An index that does NOT match this\n * belongs to somebody else — a hand-written one, or one an introspected\n * database arrived with — and is excluded from the Atlas diff so the push\n * cannot drop it.\n */\nexport const isRebaseIndexName = (name: string): boolean => /_(?:ix|ux)_[0-9a-f]{7}$/.test(name);\n\nconst isOrderedMethod = (method: IndexMethod): boolean => method === \"btree\";\n\n/**\n * The parts of an index that decide what it *is*.\n *\n * A semantic projection, not the rendered statement — the same arrangement as\n * `getPolicyNameHash`, and for the same reason. A change to how this file\n * formats SQL (eliding a default `USING btree`, quoting differently, emitting\n * `NULLS LAST` explicitly) must not silently rename every index in every\n * deployed database. Hashing generator output would make every cosmetic edit a\n * fleet-wide DROP + CREATE.\n *\n * `reason` is deliberately absent: rewording a comment must not rebuild an\n * index. `nulls` is the *effective* placement, so writing Postgres's own\n * default down is a no-op rather than a redefinition.\n *\n * `v` is the only escape hatch, and it is expensive on purpose: bumping it\n * renames every index in the field.\n */\nexport const indexFingerprint = (spec: Omit<CollectionIndexSpec, \"indexName\">): string => sha1Hex(JSON.stringify({\n v: 1,\n s: spec.schema,\n t: spec.table,\n m: spec.method,\n u: spec.unique,\n k: spec.keys.map(k => [k.column, k.direction, k.nulls]),\n i: spec.include,\n w: spec.predicate\n})).substring(0, 7);\n\n/**\n * `<table>_<columns>_ix_<hash>`, or `_ux_` when unique.\n *\n * Truncation eats the readable head and never the hash. `toPostgresIdentifier`\n * truncates the whole string at 63 bytes, which on a hashed name would cut off\n * the one part that makes it unique — the failure already frozen into\n * `contracts/derived-names.txt`, where a foreign key is recorded with its\n * `_fkey` suffix truncated away, so a second foreign key on that table would\n * derive a byte-identical name.\n */\nexport const deriveIndexName = (spec: Omit<CollectionIndexSpec, \"indexName\">): string => {\n // Built suffix-first, so the two parts that carry meaning — the `_ix`/`_ux`\n // tag that {@link isRebaseIndexName} matches on, and the fingerprint — are\n // never in the string being truncated. Composing the whole name and then\n // trimming it to 63 loses both, silently: an 80-byte table name yields\n // `xxxx…xxx_610bb9e` with the tag gone, so the index stops being\n // recognisable as Rebase's and `db push` treats it as foreign forever.\n const tag = spec.unique ? \"ux\" : \"ix\";\n const suffix = `_${tag}_${indexFingerprint(spec)}`;\n const readable = `${spec.table}_${spec.keys.map(k => k.column).join(\"_\")}`;\n return `${truncateToBytes(readable, 63 - NAME_SUFFIX_BYTES)}${suffix}`;\n};\n\nconst quoteLiteral = (value: string | number | boolean): string => {\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"boolean\") return value ? \"TRUE\" : \"FALSE\";\n return `'${value.replace(/'/g, \"''\")}'`;\n};\n\n/** Render a resolved predicate as the body of a `WHERE` clause. */\nexport const renderPredicate = (predicate: ResolvedPredicate): string => {\n if (\"and\" in predicate) {\n // Always parenthesised. Postgres would apply the same precedence\n // without it, but the rendered SQL is read by people diffing a plan.\n return predicate.and.map(renderPredicate).join(\" AND \");\n }\n switch (predicate.op) {\n case \"is null\":\n case \"is not null\":\n return `\"${predicate.column}\" ${predicate.op.toUpperCase()}`;\n case \"in\":\n return `\"${predicate.column}\" IN (${predicate.value.map(quoteLiteral).join(\", \")})`;\n default:\n return `\"${predicate.column}\" ${predicate.op} ${quoteLiteral(predicate.value)}`;\n }\n};\n\n/**\n * The `CREATE INDEX` for one spec.\n *\n * `concurrently` is a parameter rather than a string replacement on the way\n * out. `search-column.ts` and `vector-index.ts` both reach for\n * `.replace(\"CREATE INDEX IF NOT EXISTS\", …)` instead, which silently does\n * nothing for a UNIQUE index — the rendered text is `CREATE UNIQUE INDEX …`\n * and the pattern never matches.\n */\nexport const collectionIndexStatement = (\n spec: CollectionIndexSpec,\n options: { concurrently?: boolean; ifNotExists?: boolean } = {}\n): string => {\n const unique = spec.unique ? \"UNIQUE \" : \"\";\n const concurrently = options.concurrently ? \"CONCURRENTLY \" : \"\";\n const ifNotExists = options.ifNotExists ? \"IF NOT EXISTS \" : \"\";\n const using = spec.method === \"btree\" ? \"\" : ` USING ${spec.method}`;\n\n const keys = spec.keys.map(k => {\n if (!isOrderedMethod(spec.method)) return `\"${k.column}\"`;\n const direction = k.direction === \"desc\" ? \" DESC\" : \"\";\n // Emitted only when it is not what the direction already implies, so\n // the rendered SQL matches what `pg_get_indexdef` reads back and a\n // re-plan finds no difference.\n const impliedNulls = k.direction === \"desc\" ? \"first\" : \"last\";\n const nulls = k.nulls === impliedNulls ? \"\" : ` NULLS ${k.nulls.toUpperCase()}`;\n return `\"${k.column}\"${direction}${nulls}`;\n }).join(\", \");\n\n const include = spec.include.length > 0\n ? ` INCLUDE (${spec.include.map(c => `\"${c}\"`).join(\", \")})`\n : \"\";\n const where = spec.predicate ? ` WHERE ${renderPredicate(spec.predicate)}` : \"\";\n\n return `CREATE ${unique}INDEX ${concurrently}${ifNotExists}\"${spec.indexName}\" ` +\n `ON \"${spec.schema}\".\"${spec.table}\"${using} (${keys})${include}${where};`;\n};\n\nexport const collectionIndexStatements = (\n specs: readonly CollectionIndexSpec[],\n options: { concurrently?: boolean; ifNotExists?: boolean } = {}\n): string[] => specs.map(spec => collectionIndexStatement(spec, options));\n\nconst relationOf = (\n collection: CollectionConfig,\n propKey: string\n): ResolvedRelation | undefined => resolveCollectionRelations(collection)[propKey];\n\n/**\n * The column a property key indexes.\n *\n * A `belongsTo` resolves to its `localKey` — `primaryCategory` becomes\n * `primary_category_id` — which is the case an index is most often wanted for\n * and the case where the property key and the column differ. Everything else\n * goes through `resolveColumnName`.\n *\n * The other relation kinds have no local column at all: the foreign key lives\n * on the target's table, or in a junction. Indexing them here is refused\n * rather than resolved to a column that does not exist.\n */\nexport const resolveIndexableColumn = (\n collection: CollectionConfig,\n propKey: string,\n resolveColumnName: ResolveColumnName,\n fail: (message: string) => never\n): string => {\n const relation = relationOf(collection, propKey);\n if (relation) {\n if (relation.kind === \"belongsTo\") return relation.localKey;\n fail(\n `\"${propKey}\" is a ${relation.kind} relation, which has no column on this table — ` +\n `the foreign key lives on \"${relation.targetSlug}\". Declare the index there.`\n );\n }\n\n const property = collection.properties?.[propKey] as Property | undefined;\n if (!property) {\n fail(`\"${propKey}\" is not a property of this collection.`);\n }\n return resolveColumnName(propKey, property);\n};\n\nconst resolvePredicate = (\n collection: CollectionConfig,\n predicate: IndexPredicate,\n resolveColumnName: ResolveColumnName,\n fail: (message: string) => never\n): ResolvedPredicate => {\n if (\"and\" in predicate) {\n return { and: predicate.and.map(p => resolvePredicate(collection, p, resolveColumnName, fail)) };\n }\n const column = resolveIndexableColumn(collection, predicate.prop, resolveColumnName, fail);\n switch (predicate.op) {\n case \"is null\":\n case \"is not null\":\n return { column, op: predicate.op };\n case \"in\": {\n const seen = new Set(predicate.value);\n if (seen.size !== predicate.value.length) {\n fail(`the \\`in\\` list for \"${predicate.prop}\" repeats a value, which changes nothing.`);\n }\n return { column, op: \"in\", value: [...predicate.value] };\n }\n default:\n return { column, op: predicate.op, value: predicate.value };\n }\n};\n\n/** The primary key columns of a collection, for the \"you already have this\" refusal. */\nconst primaryKeyColumns = (collection: CollectionConfig, resolveColumnName: ResolveColumnName): string[] =>\n Object.entries(collection.properties ?? {})\n .filter(([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId))\n .map(([key, prop]) => resolveColumnName(key, prop as Property));\n\n/**\n * Every index one collection declares, resolved and named.\n *\n * Throws {@link CollectionIndexConfigError} rather than dropping a bad entry:\n * an index that silently does not exist is the failure mode this whole feature\n * is here to remove.\n */\nexport const buildCollectionIndexSpecs = (\n collection: CollectionConfig,\n resolveColumnName: ResolveColumnName\n): CollectionIndexSpec[] => {\n if (!isPostgresCollectionConfig(collection)) return [];\n const declared = collection.indexes;\n if (!declared || declared.length === 0) return [];\n\n const slug = collection.slug ?? getTableName(collection);\n const schema = collection.schema ?? \"public\";\n const table = getTableName(collection);\n const pk = primaryKeyColumns(collection, resolveColumnName).sort().join(\",\");\n\n const specs: CollectionIndexSpec[] = [];\n const byName = new Map<string, number>();\n\n declared.forEach((index: CollectionIndex, position: number) => {\n const fail = (message: string): never => {\n throw new CollectionIndexConfigError(slug, position, message);\n };\n\n if (typeof index.reason !== \"string\" || index.reason.trim() === \"\") {\n fail(\"`reason` is required — see the doc comment. An index nobody can justify is one nobody can delete.\");\n }\n if (!Array.isArray(index.on) || index.on.length === 0) {\n fail(\"`on` must name at least one property.\");\n }\n if (index.on.length > MAX_INDEX_KEYS) {\n fail(`\\`on\\` has ${index.on.length} keys; the limit is ${MAX_INDEX_KEYS}. Payload columns belong in \\`include\\`.`);\n }\n\n const method: IndexMethod = index.using ?? \"btree\";\n const unique = method === \"btree\" && Boolean((index as { unique?: boolean }).unique);\n if (!isOrderedMethod(method)) {\n for (const key of index.on) {\n if (typeof key !== \"string\" && (\"direction\" in key || \"nulls\" in key)) {\n fail(`access method \"${method}\" does not support ASC/DESC or NULLS options.`);\n }\n }\n }\n\n const keys: ResolvedIndexKey[] = index.on.map(key => {\n const propKey = typeof key === \"string\" ? key : key.prop;\n const column = resolveIndexableColumn(collection, propKey, resolveColumnName, fail);\n const direction = (typeof key === \"string\" ? undefined : (key as { direction?: \"asc\" | \"desc\" }).direction) ?? \"asc\";\n const nulls = (typeof key === \"string\" ? undefined : (key as { nulls?: \"first\" | \"last\" }).nulls)\n ?? (direction === \"desc\" ? \"first\" : \"last\");\n return { column, direction, nulls };\n });\n\n const duplicateKey = keys.map(k => k.column).find((c, i, all) => all.indexOf(c) !== i);\n if (duplicateKey) fail(`\"${duplicateKey}\" appears twice in \\`on\\`.`);\n\n if (keys.map(k => k.column).sort().join(\",\") === pk && pk !== \"\") {\n fail(`this is the primary key — \"${table}_pkey\" already indexes exactly these columns.`);\n }\n\n const include = ((index as { include?: readonly string[] }).include ?? [])\n .map(propKey => resolveIndexableColumn(collection, propKey, resolveColumnName, fail));\n const overlap = include.find(c => keys.some(k => k.column === c));\n if (overlap) fail(`\"${overlap}\" is in both \\`on\\` and \\`include\\`; Postgres rejects the overlap.`);\n\n if (unique && keys.length === 1) {\n const propKey = typeof index.on[0] === \"string\" ? index.on[0] as string : (index.on[0] as { prop: string }).prop;\n const property = collection.properties?.[propKey] as { validation?: { unique?: boolean } } | undefined;\n if (property?.validation?.unique) {\n fail(\n `\"${propKey}\" already declares \\`validation.unique\\`, which compiles to an inline UNIQUE. ` +\n `Two declarations of one guarantee — remove one.`\n );\n }\n }\n\n const predicate = index.where\n ? resolvePredicate(collection, index.where, resolveColumnName, fail)\n : null;\n\n const withoutName = { schema, table, method, unique, keys, include, predicate, reason: index.reason };\n const indexName = deriveIndexName(withoutName);\n\n const clash = byName.get(indexName);\n if (clash !== undefined) {\n fail(`derives the same name as indexes[${clash}] — they are the same index declared twice.`);\n }\n byName.set(indexName, position);\n\n specs.push({ ...withoutName, indexName });\n });\n\n return specs;\n};\n\n/**\n * Every declared index across a set of collections, in a stable order.\n *\n * Sorted because the result reaches `schema.sql`, which `doctor` string-\n * compares against a regenerated copy — `generatePostgresDdl` does not sort its\n * collections, so leaving this in declaration order would make the artifact\n * depend on the order files happened to load in.\n */\nexport const buildCollectionIndexPlan = (\n collections: readonly CollectionConfig[],\n resolveColumnName: ResolveColumnName\n): CollectionIndexSpec[] =>\n collections\n .flatMap(collection => buildCollectionIndexSpecs(collection, resolveColumnName))\n .sort((a, b) =>\n a.schema.localeCompare(b.schema) ||\n a.table.localeCompare(b.table) ||\n a.indexName.localeCompare(b.indexName));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection-index-DdnsxiJ_.js","names":[],"sources":["../src/schema/collection-index.ts"],"sourcesContent":["/**\n * The one place a collection's `indexes:` block becomes `CREATE INDEX`.\n *\n * Like `search-column.ts` and `vector-index.ts`, this module exists so the DDL\n * generator and the boot-time ensure render the *same* specification rather\n * than describing the same index twice, differently.\n *\n * ## Why every form here is core Postgres\n *\n * `rebase db push` runs `atlas schema apply`, which materialises the desired\n * state in a bare scratch database to plan against. `--exclude` does not\n * suppress that replay, and `CREATE EXTENSION` cannot go in the file. So an\n * index wanting `gin_trgm_ops` or `vector_cosine_ops` would parse, plan, and\n * then fail against a database the author has never heard of. Those are\n * refused here instead, and redirected to the feature that owns them: trigram\n * search is `search:`, ANN is a `vector` property.\n *\n * Verified against atlas v1.2.3 and Postgres 18 before this was written: plain,\n * composite with `DESC NULLS LAST`, partial, unique, covering `INCLUDE`, `GIN`\n * and expression indexes all parse, apply, and re-plan clean. The Atlas\n * limitation that forced the search carve-out is that it will not parse a file\n * containing a function *definition* — a function *call* inside an index is\n * fine. That is why this module needs no carve-out and search did.\n *\n * ## Why the name carries a hash\n *\n * `CREATE INDEX IF NOT EXISTS` is a **name** check, not a definition check. A\n * readable name means a changed declaration keeps the old index and reports\n * success, forever. Hashing the index's *semantics* into its name makes a\n * redefinition a different object: the new one is built before the old one is\n * dropped, there is never a window with no index, and drift detection reduces\n * to a set difference over names.\n */\nimport type { CollectionConfig, CollectionIndex, IndexPredicate, Property, ResolvedRelation } from \"@rebasepro/types\";\nimport { isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { sha1Hex, truncateToBytes } from \"@rebasepro/utils\";\n\n/** Resolve a property key to its column name. Injected to avoid an import cycle. */\nexport type ResolveColumnName = (propName: string, prop?: Property | null) => string;\n\nexport type IndexMethod = \"btree\" | \"gin\" | \"brin\";\n\n/** A key column, with the ordering Postgres will actually apply. */\nexport interface ResolvedIndexKey {\n column: string;\n /** Always concrete. `btree` defaults ascending; unordered methods report `asc`. */\n direction: \"asc\" | \"desc\";\n /** Postgres's own default: `last` under `asc`, `first` under `desc`. */\n nulls: \"first\" | \"last\";\n}\n\n/** A predicate resolved onto column names, ready to render and to hash. */\nexport type ResolvedPredicate =\n | { column: string; op: \"=\" | \"!=\" | \"<\" | \"<=\" | \">\" | \">=\"; value: string | number | boolean }\n | { column: string; op: \"is null\" | \"is not null\" }\n | { column: string; op: \"in\"; value: readonly (string | number)[] }\n | { and: readonly ResolvedPredicate[] };\n\n/** One index, fully resolved. The only shape the renderers accept. */\nexport interface CollectionIndexSpec {\n schema: string;\n table: string;\n method: IndexMethod;\n unique: boolean;\n keys: ResolvedIndexKey[];\n include: string[];\n predicate: ResolvedPredicate | null;\n /** The author's one-line justification. Never enters the name. */\n reason: string;\n /** Derived by {@link deriveIndexName}. Frozen — see the module comment. */\n indexName: string;\n}\n\n/**\n * A declaration that cannot become an index.\n *\n * Thrown at build time, naming the collection and the array position, because\n * the alternative is a `CREATE INDEX` that fails during a push with a Postgres\n * error mentioning a column the author never wrote.\n */\nexport class CollectionIndexConfigError extends Error {\n readonly collectionSlug: string;\n readonly position: number;\n\n constructor(collectionSlug: string, position: number, message: string) {\n super(`${collectionSlug}.indexes[${position}]: ${message}`);\n this.name = \"CollectionIndexConfigError\";\n this.collectionSlug = collectionSlug;\n this.position = position;\n }\n}\n\n/** Postgres allows 32 key columns. See the doc comment on `on`. */\nexport const MAX_INDEX_KEYS = 5;\n\n/**\n * `_ix`/`_ux` plus `_` plus 7 hex — the part of the name that must always\n * survive truncation, and therefore is never inside the truncated portion.\n */\nconst NAME_SUFFIX_BYTES = 11;\n\n/**\n * Every Rebase-managed index name, and nothing else.\n *\n * The terminal `_ix_`/`_ux_` plus exactly seven lowercase hex characters is\n * what separates this scheme from every other producer in the codebase —\n * `_fkey`, `_gin`, `_trgm`, `_pkey`, `_key`, the vector distances, and the\n * `idx_` prefix auth uses. `_idx` was rejected as a tail because it is already\n * taken for real: `users_email_verification_token_idx` is byte-for-byte what a\n * naive `<table>_<column>_idx` derives on an auth-enabled `users` collection.\n *\n * Load-bearing for safety, not just tidiness. An index that does NOT match this\n * belongs to somebody else — a hand-written one, or one an introspected\n * database arrived with — and is excluded from the Atlas diff so the push\n * cannot drop it.\n */\nexport const isRebaseIndexName = (name: string): boolean => /_(?:ix|ux)_[0-9a-f]{7}$/.test(name);\n\nconst isOrderedMethod = (method: IndexMethod): boolean => method === \"btree\";\n\n/**\n * The parts of an index that decide what it *is*.\n *\n * A semantic projection, not the rendered statement — the same arrangement as\n * `getPolicyNameHash`, and for the same reason. A change to how this file\n * formats SQL (eliding a default `USING btree`, quoting differently, emitting\n * `NULLS LAST` explicitly) must not silently rename every index in every\n * deployed database. Hashing generator output would make every cosmetic edit a\n * fleet-wide DROP + CREATE.\n *\n * `reason` is deliberately absent: rewording a comment must not rebuild an\n * index. `nulls` is the *effective* placement, so writing Postgres's own\n * default down is a no-op rather than a redefinition.\n *\n * `v` is the only escape hatch, and it is expensive on purpose: bumping it\n * renames every index in the field.\n */\nexport const indexFingerprint = (spec: Omit<CollectionIndexSpec, \"indexName\">): string => sha1Hex(JSON.stringify({\n v: 1,\n s: spec.schema,\n t: spec.table,\n m: spec.method,\n u: spec.unique,\n k: spec.keys.map(k => [k.column, k.direction, k.nulls]),\n i: spec.include,\n w: spec.predicate\n})).substring(0, 7);\n\n/**\n * `<table>_<columns>_ix_<hash>`, or `_ux_` when unique.\n *\n * Truncation eats the readable head and never the hash. `toPostgresIdentifier`\n * truncates the whole string at 63 bytes, which on a hashed name would cut off\n * the one part that makes it unique — the failure already frozen into\n * `contracts/derived-names.txt`, where a foreign key is recorded with its\n * `_fkey` suffix truncated away, so a second foreign key on that table would\n * derive a byte-identical name.\n */\nexport const deriveIndexName = (spec: Omit<CollectionIndexSpec, \"indexName\">): string => {\n // Built suffix-first, so the two parts that carry meaning — the `_ix`/`_ux`\n // tag that {@link isRebaseIndexName} matches on, and the fingerprint — are\n // never in the string being truncated. Composing the whole name and then\n // trimming it to 63 loses both, silently: an 80-byte table name yields\n // `xxxx…xxx_610bb9e` with the tag gone, so the index stops being\n // recognisable as Rebase's and `db push` treats it as foreign forever.\n const tag = spec.unique ? \"ux\" : \"ix\";\n const suffix = `_${tag}_${indexFingerprint(spec)}`;\n const readable = `${spec.table}_${spec.keys.map(k => k.column).join(\"_\")}`;\n return `${truncateToBytes(readable, 63 - NAME_SUFFIX_BYTES)}${suffix}`;\n};\n\nconst quoteLiteral = (value: string | number | boolean): string => {\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"boolean\") return value ? \"TRUE\" : \"FALSE\";\n return `'${value.replace(/'/g, \"''\")}'`;\n};\n\n/** Render a resolved predicate as the body of a `WHERE` clause. */\nexport const renderPredicate = (predicate: ResolvedPredicate): string => {\n if (\"and\" in predicate) {\n // Always parenthesised. Postgres would apply the same precedence\n // without it, but the rendered SQL is read by people diffing a plan.\n return predicate.and.map(renderPredicate).join(\" AND \");\n }\n switch (predicate.op) {\n case \"is null\":\n case \"is not null\":\n return `\"${predicate.column}\" ${predicate.op.toUpperCase()}`;\n case \"in\":\n return `\"${predicate.column}\" IN (${predicate.value.map(quoteLiteral).join(\", \")})`;\n default:\n return `\"${predicate.column}\" ${predicate.op} ${quoteLiteral(predicate.value)}`;\n }\n};\n\n/**\n * The `CREATE INDEX` for one spec.\n *\n * `concurrently` is a parameter rather than a string replacement on the way\n * out. `search-column.ts` and `vector-index.ts` both reach for\n * `.replace(\"CREATE INDEX IF NOT EXISTS\", …)` instead, which silently does\n * nothing for a UNIQUE index — the rendered text is `CREATE UNIQUE INDEX …`\n * and the pattern never matches.\n */\nexport const collectionIndexStatement = (\n spec: CollectionIndexSpec,\n options: { concurrently?: boolean; ifNotExists?: boolean } = {}\n): string => {\n const unique = spec.unique ? \"UNIQUE \" : \"\";\n const concurrently = options.concurrently ? \"CONCURRENTLY \" : \"\";\n const ifNotExists = options.ifNotExists ? \"IF NOT EXISTS \" : \"\";\n const using = spec.method === \"btree\" ? \"\" : ` USING ${spec.method}`;\n\n const keys = spec.keys.map(k => {\n if (!isOrderedMethod(spec.method)) return `\"${k.column}\"`;\n const direction = k.direction === \"desc\" ? \" DESC\" : \"\";\n // Emitted only when it is not what the direction already implies, so\n // the rendered SQL matches what `pg_get_indexdef` reads back and a\n // re-plan finds no difference.\n const impliedNulls = k.direction === \"desc\" ? \"first\" : \"last\";\n const nulls = k.nulls === impliedNulls ? \"\" : ` NULLS ${k.nulls.toUpperCase()}`;\n return `\"${k.column}\"${direction}${nulls}`;\n }).join(\", \");\n\n const include = spec.include.length > 0\n ? ` INCLUDE (${spec.include.map(c => `\"${c}\"`).join(\", \")})`\n : \"\";\n const where = spec.predicate ? ` WHERE ${renderPredicate(spec.predicate)}` : \"\";\n\n return `CREATE ${unique}INDEX ${concurrently}${ifNotExists}\"${spec.indexName}\" ` +\n `ON \"${spec.schema}\".\"${spec.table}\"${using} (${keys})${include}${where};`;\n};\n\nexport const collectionIndexStatements = (\n specs: readonly CollectionIndexSpec[],\n options: { concurrently?: boolean; ifNotExists?: boolean } = {}\n): string[] => specs.map(spec => collectionIndexStatement(spec, options));\n\nconst relationOf = (\n collection: CollectionConfig,\n propKey: string\n): ResolvedRelation | undefined => resolveCollectionRelations(collection)[propKey];\n\n/**\n * The column a property key indexes.\n *\n * A `belongsTo` resolves to its `localKey` — `primaryCategory` becomes\n * `primary_category_id` — which is the case an index is most often wanted for\n * and the case where the property key and the column differ. Everything else\n * goes through `resolveColumnName`.\n *\n * The other relation kinds have no local column at all: the foreign key lives\n * on the target's table, or in a junction. Indexing them here is refused\n * rather than resolved to a column that does not exist.\n */\nexport const resolveIndexableColumn = (\n collection: CollectionConfig,\n propKey: string,\n resolveColumnName: ResolveColumnName,\n fail: (message: string) => never\n): string => {\n const relation = relationOf(collection, propKey);\n if (relation) {\n if (relation.kind === \"belongsTo\") return relation.localKey;\n fail(\n `\"${propKey}\" is a ${relation.kind} relation, which has no column on this table — ` +\n `the foreign key lives on \"${relation.targetSlug}\". Declare the index there.`\n );\n }\n\n const property = collection.properties?.[propKey] as Property | undefined;\n if (!property) {\n fail(`\"${propKey}\" is not a property of this collection.`);\n }\n return resolveColumnName(propKey, property);\n};\n\nconst resolvePredicate = (\n collection: CollectionConfig,\n predicate: IndexPredicate,\n resolveColumnName: ResolveColumnName,\n fail: (message: string) => never\n): ResolvedPredicate => {\n if (\"and\" in predicate) {\n return { and: predicate.and.map(p => resolvePredicate(collection, p, resolveColumnName, fail)) };\n }\n const column = resolveIndexableColumn(collection, predicate.prop, resolveColumnName, fail);\n switch (predicate.op) {\n case \"is null\":\n case \"is not null\":\n return { column, op: predicate.op };\n case \"in\": {\n const seen = new Set(predicate.value);\n if (seen.size !== predicate.value.length) {\n fail(`the \\`in\\` list for \"${predicate.prop}\" repeats a value, which changes nothing.`);\n }\n return { column, op: \"in\", value: [...predicate.value] };\n }\n default:\n return { column, op: predicate.op, value: predicate.value };\n }\n};\n\n/** The primary key columns of a collection, for the \"you already have this\" refusal. */\nconst primaryKeyColumns = (collection: CollectionConfig, resolveColumnName: ResolveColumnName): string[] =>\n Object.entries(collection.properties ?? {})\n .filter(([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId))\n .map(([key, prop]) => resolveColumnName(key, prop as Property));\n\n/**\n * Every index one collection declares, resolved and named.\n *\n * Throws {@link CollectionIndexConfigError} rather than dropping a bad entry:\n * an index that silently does not exist is the failure mode this whole feature\n * is here to remove.\n */\nexport const buildCollectionIndexSpecs = (\n collection: CollectionConfig,\n resolveColumnName: ResolveColumnName\n): CollectionIndexSpec[] => {\n if (!isPostgresCollectionConfig(collection)) return [];\n const declared = collection.indexes;\n if (!declared || declared.length === 0) return [];\n\n const slug = collection.slug ?? getTableName(collection);\n const schema = collection.schema ?? \"public\";\n const table = getTableName(collection);\n const pk = primaryKeyColumns(collection, resolveColumnName).sort().join(\",\");\n\n const specs: CollectionIndexSpec[] = [];\n const byName = new Map<string, number>();\n\n declared.forEach((index: CollectionIndex, position: number) => {\n const fail = (message: string): never => {\n throw new CollectionIndexConfigError(slug, position, message);\n };\n\n if (typeof index.reason !== \"string\" || index.reason.trim() === \"\") {\n fail(\"`reason` is required — see the doc comment. An index nobody can justify is one nobody can delete.\");\n }\n if (!Array.isArray(index.on) || index.on.length === 0) {\n fail(\"`on` must name at least one property.\");\n }\n if (index.on.length > MAX_INDEX_KEYS) {\n fail(`\\`on\\` has ${index.on.length} keys; the limit is ${MAX_INDEX_KEYS}. Payload columns belong in \\`include\\`.`);\n }\n\n const method: IndexMethod = index.using ?? \"btree\";\n const unique = method === \"btree\" && Boolean((index as { unique?: boolean }).unique);\n if (!isOrderedMethod(method)) {\n for (const key of index.on) {\n if (typeof key !== \"string\" && (\"direction\" in key || \"nulls\" in key)) {\n fail(`access method \"${method}\" does not support ASC/DESC or NULLS options.`);\n }\n }\n }\n\n const keys: ResolvedIndexKey[] = index.on.map(key => {\n const propKey = typeof key === \"string\" ? key : key.prop;\n const column = resolveIndexableColumn(collection, propKey, resolveColumnName, fail);\n const direction = (typeof key === \"string\" ? undefined : (key as { direction?: \"asc\" | \"desc\" }).direction) ?? \"asc\";\n const nulls = (typeof key === \"string\" ? undefined : (key as { nulls?: \"first\" | \"last\" }).nulls)\n ?? (direction === \"desc\" ? \"first\" : \"last\");\n return { column, direction, nulls };\n });\n\n const duplicateKey = keys.map(k => k.column).find((c, i, all) => all.indexOf(c) !== i);\n if (duplicateKey) fail(`\"${duplicateKey}\" appears twice in \\`on\\`.`);\n\n if (keys.map(k => k.column).sort().join(\",\") === pk && pk !== \"\") {\n fail(`this is the primary key — \"${table}_pkey\" already indexes exactly these columns.`);\n }\n\n const include = ((index as { include?: readonly string[] }).include ?? [])\n .map(propKey => resolveIndexableColumn(collection, propKey, resolveColumnName, fail));\n const overlap = include.find(c => keys.some(k => k.column === c));\n if (overlap) fail(`\"${overlap}\" is in both \\`on\\` and \\`include\\`; Postgres rejects the overlap.`);\n\n if (unique && keys.length === 1) {\n const propKey = typeof index.on[0] === \"string\" ? index.on[0] as string : (index.on[0] as { prop: string }).prop;\n const property = collection.properties?.[propKey] as { validation?: { unique?: boolean } } | undefined;\n if (property?.validation?.unique) {\n fail(\n `\"${propKey}\" already declares \\`validation.unique\\`, which compiles to an inline UNIQUE. ` +\n `Two declarations of one guarantee — remove one.`\n );\n }\n }\n\n const predicate = index.where\n ? resolvePredicate(collection, index.where, resolveColumnName, fail)\n : null;\n\n const withoutName = { schema, table, method, unique, keys, include, predicate, reason: index.reason };\n const indexName = deriveIndexName(withoutName);\n\n const clash = byName.get(indexName);\n if (clash !== undefined) {\n fail(`derives the same name as indexes[${clash}] — they are the same index declared twice.`);\n }\n byName.set(indexName, position);\n\n specs.push({ ...withoutName, indexName });\n });\n\n return specs;\n};\n\n/**\n * Every declared index across a set of collections, in a stable order.\n *\n * Sorted because the result reaches `schema.sql`, which `doctor` string-\n * compares against a regenerated copy — `generatePostgresDdl` does not sort its\n * collections, so leaving this in declaration order would make the artifact\n * depend on the order files happened to load in.\n */\nexport const buildCollectionIndexPlan = (\n collections: readonly CollectionConfig[],\n resolveColumnName: ResolveColumnName\n): CollectionIndexSpec[] =>\n sortIndexSpecs(collections.flatMap(collection => buildCollectionIndexSpecs(collection, resolveColumnName)));\n\n/**\n * The stable order every consumer of a set of index specs uses.\n *\n * Exported because the boot-time diff reads the specs off the schema plan\n * rather than rebuilding them, and it has to reach the same order the CLI's\n * exclude list and `schema.sql` do — a second sort written out beside this one\n * is a second order the moment either is edited.\n */\nexport const sortIndexSpecs = (specs: readonly CollectionIndexSpec[]): CollectionIndexSpec[] =>\n [...specs].sort((a, b) =>\n a.schema.localeCompare(b.schema) ||\n a.table.localeCompare(b.table) ||\n a.indexName.localeCompare(b.indexName));\n"],"mappings":"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"}
|
|
@@ -3,6 +3,13 @@ import { PostgresCollectionRegistry } from "./PostgresCollectionRegistry.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Check every relation against the schema it actually runs on, at boot.
|
|
5
5
|
*
|
|
6
|
+
* "Actually runs on" is now literal: the registry's tables are read back from
|
|
7
|
+
* `information_schema` (see `catalogue-schema.ts`), so this compares the
|
|
8
|
+
* collections against the database. It used to compare them against the
|
|
9
|
+
* committed `schema.generated.ts`, which made a *stale file* able to refuse a
|
|
10
|
+
* boot — the exact incident class from 0.13, where boot-ensure had already
|
|
11
|
+
* renamed the column and the file had not caught up.
|
|
12
|
+
*
|
|
6
13
|
* The tagged union made the *shape* of a relation impossible to get wrong: a
|
|
7
14
|
* `manyToMany` cannot carry a `foreignKeyOnTarget`, a to-many cannot carry a
|
|
8
15
|
* `localKey`. What it cannot know is whether any of the names are real —
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
__createRequire(import.meta.url);
|
|
3
|
+
import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
|
|
4
|
+
import { toSnakeCase } from "@rebasepro/utils";
|
|
5
|
+
//#region src/schema/column-plan-helpers.ts
|
|
6
|
+
var column_plan_helpers_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
assertSinglePrimaryKey: () => assertSinglePrimaryKey,
|
|
8
|
+
declaresEnumType: () => declaresEnumType,
|
|
9
|
+
enumLabelsOf: () => enumLabelsOf,
|
|
10
|
+
getPrimaryKeyName: () => getPrimaryKeyName,
|
|
11
|
+
getPrimaryKeyProp: () => getPrimaryKeyProp,
|
|
12
|
+
idColumnDefault: () => idColumnDefault,
|
|
13
|
+
isIdProperty: () => isIdProperty,
|
|
14
|
+
resolveColumnName: () => resolveColumnName
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the SQL column name for a property.
|
|
18
|
+
*
|
|
19
|
+
* Uses the explicit `columnName` when set (e.g. from introspection), falling
|
|
20
|
+
* back to `toSnakeCase(propName)` for manually-authored collections.
|
|
21
|
+
*/
|
|
22
|
+
var resolveColumnName = (propName, prop) => {
|
|
23
|
+
if (prop && "columnName" in prop && typeof prop.columnName === "string") return prop.columnName;
|
|
24
|
+
return toSnakeCase(propName);
|
|
25
|
+
};
|
|
26
|
+
/** Every property that carries a truthy `isId`, in declaration order. */
|
|
27
|
+
var idPropertyEntries = (collection) => Object.entries(collection.properties ?? {}).filter(([, prop]) => prop && typeof prop === "object" && "isId" in prop && Boolean(prop.isId));
|
|
28
|
+
/**
|
|
29
|
+
* Refuse a collection that declares two primary keys.
|
|
30
|
+
*
|
|
31
|
+
* Postgres allows a composite primary key; Rebase does not model one, and the
|
|
32
|
+
* three emitters each invented a different wrong answer for it. The Drizzle
|
|
33
|
+
* generator appended `.primaryKey()` to both columns (two primary keys, which
|
|
34
|
+
* drizzle-kit turns into an error at push time), the DDL generator wrote two
|
|
35
|
+
* inline `PRIMARY KEY` clauses in one `CREATE TABLE` (Postgres: "multiple
|
|
36
|
+
* primary keys for table are not allowed"), and boot-ensure created the table
|
|
37
|
+
* with the *first* id column and silently never added the second — so a managed
|
|
38
|
+
* tenant got a table missing a column its own collection reads.
|
|
39
|
+
*
|
|
40
|
+
* None of those is recoverable at runtime, and two of them fail long after the
|
|
41
|
+
* config was written. Refused here instead, where the collection has a name.
|
|
42
|
+
*/
|
|
43
|
+
var assertSinglePrimaryKey = (collection) => {
|
|
44
|
+
const ids = idPropertyEntries(collection);
|
|
45
|
+
if (ids.length < 2) return;
|
|
46
|
+
throw new Error(`Collection "${collection.slug ?? collection.name ?? "(unnamed)"}" marks ${ids.length} properties with \`isId\` (${ids.map(([name]) => `"${name}"`).join(", ")}). Composite primary keys are not supported: give exactly one property \`isId\`, and express the second key with \`indexes: [{ on: [...], unique: true, reason: "…" }]\`.`);
|
|
47
|
+
};
|
|
48
|
+
var getPrimaryKeyProp = (collection) => {
|
|
49
|
+
if (collection.properties) {
|
|
50
|
+
const idPropEntry = idPropertyEntries(collection)[0];
|
|
51
|
+
if (idPropEntry) {
|
|
52
|
+
const prop = idPropEntry[1];
|
|
53
|
+
const isUuid = prop.type === "string" && "isId" in prop && prop.isId === "uuid";
|
|
54
|
+
return {
|
|
55
|
+
name: idPropEntry[0],
|
|
56
|
+
type: prop.type === "number" ? "number" : "string",
|
|
57
|
+
isUuid
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const idProp = collection.properties?.["id"];
|
|
62
|
+
if (idProp?.type === "number") return {
|
|
63
|
+
name: "id",
|
|
64
|
+
type: "number",
|
|
65
|
+
isUuid: false
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
name: "id",
|
|
69
|
+
type: "string",
|
|
70
|
+
isUuid: idProp?.type === "string" && "isId" in idProp && idProp.isId === "uuid"
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
var getPrimaryKeyName = (collection) => getPrimaryKeyProp(collection).name;
|
|
74
|
+
var isIdProperty = (propName, prop, collection) => {
|
|
75
|
+
if ("isId" in prop && Boolean(prop.isId)) return true;
|
|
76
|
+
return idPropertyEntries(collection).length === 0 && propName === "id";
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The labels an `enum` declares, in order, whichever form it was written in.
|
|
80
|
+
*
|
|
81
|
+
* `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend
|
|
82
|
+
* the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`
|
|
83
|
+
* — so a record-form enum threw `p.enum.map is not a function` on a managed
|
|
84
|
+
* tenant, at boot, with no developer in the loop. The other two emitters each
|
|
85
|
+
* carried their own copy of the both-forms walk.
|
|
86
|
+
*
|
|
87
|
+
* An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`
|
|
88
|
+
* is not valid SQL, so every emitter "handled" it by skipping the type and then
|
|
89
|
+
* typing the column with it anyway: Drizzle referenced an enum variable it never
|
|
90
|
+
* declared (the generated file does not compile), the DDL named a type nothing
|
|
91
|
+
* creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a
|
|
92
|
+
* survivable action, so the boot died. There is no useful column at the end of
|
|
93
|
+
* any of those paths.
|
|
94
|
+
*/
|
|
95
|
+
var enumLabelsOf = (propName, prop, collection) => {
|
|
96
|
+
const values = prop.enum;
|
|
97
|
+
if (values === void 0 || values === null) return [];
|
|
98
|
+
const usable = (Array.isArray(values) ? values.map((entry) => String(entry !== null && typeof entry === "object" && "id" in entry ? entry.id : entry)) : Object.keys(values).map(String)).filter((label) => label.length > 0);
|
|
99
|
+
if (usable.length === 0) throw new Error(`Property "${propName}" of collection "${collection.slug ?? collection.name ?? "(unnamed)"}" declares an empty \`enum\`. A Postgres enum type needs at least one label — \`CREATE TYPE … AS ENUM ()\` is not valid SQL — so the column would reference a type nothing creates. List the values, or drop the \`enum\` for a plain column.`);
|
|
100
|
+
return usable;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Does this property compile to a Postgres enum *type*?
|
|
104
|
+
*
|
|
105
|
+
* Strings only. A `number` property with an `enum` used to create a type on all
|
|
106
|
+
* three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was
|
|
107
|
+
* orphaned the moment it was created and the values it listed were enforced by
|
|
108
|
+
* nothing. The dropdown in the panel is driven by the config, not the column, so
|
|
109
|
+
* nothing is lost by not creating it — and a type no column uses is one more
|
|
110
|
+
* object `db push` has to plan a DROP for.
|
|
111
|
+
*/
|
|
112
|
+
var declaresEnumType = (prop) => prop.type === "string" && Boolean(prop.enum);
|
|
113
|
+
/**
|
|
114
|
+
* Refuses `isId: "cuid"`.
|
|
115
|
+
*
|
|
116
|
+
* `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`
|
|
117
|
+
* function has ever been created — not by the generators, not by boot, not by
|
|
118
|
+
* any migration. So the column has never had a working default on Postgres: the
|
|
119
|
+
* first insert that relied on it failed with `function cuid() does not exist`.
|
|
120
|
+
* There is nothing to keep compatible.
|
|
121
|
+
*
|
|
122
|
+
* It is refused rather than implemented. Shipping a real `rebase.cuid()` means a
|
|
123
|
+
* new SQL function in the same carved-out class as the search helpers — excluded
|
|
124
|
+
* from Atlas by pattern, applied by boot, re-applied by push, checked by the
|
|
125
|
+
* doctor, and versioned across the fleet forever — to reproduce a monotonic id
|
|
126
|
+
* that `uuid` already provides and that a project wanting cuid's exact shape can
|
|
127
|
+
* write itself in one line of SQL.
|
|
128
|
+
*/
|
|
129
|
+
var idColumnDefault = (propName, prop, collection) => {
|
|
130
|
+
if (!("isId" in prop)) return void 0;
|
|
131
|
+
const isId = prop.isId;
|
|
132
|
+
if (isId === void 0 || isId === null || isId === true || isId === "manual" || isId === false) return void 0;
|
|
133
|
+
if (typeof isId !== "string") return void 0;
|
|
134
|
+
if (prop.type === "string" && isId === "uuid") return { kind: "uuid" };
|
|
135
|
+
if (prop.type === "number" && isId === "increment") return { kind: "identity" };
|
|
136
|
+
if (isId === "cuid") throw new Error(`Property "${propName}" of collection "${collection.slug ?? collection.name ?? "(unnamed)"}" uses \`isId: "cuid"\`, which Postgres cannot honour: the generated default calls a \`cuid()\` function that Rebase has never created, so every insert relying on it fails. Use \`isId: "uuid"\`, or give the strategy as SQL — \`\`isId: "sql\`my_id()\`"\`\` — and create that function in a migration.`);
|
|
137
|
+
return {
|
|
138
|
+
kind: "sql",
|
|
139
|
+
expression: isId.startsWith("sql`") && isId.endsWith("`") ? isId.substring(4, isId.length - 1) : isId
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
143
|
+
export { getPrimaryKeyName as a, isIdProperty as c, enumLabelsOf as i, resolveColumnName as l, column_plan_helpers_exports as n, getPrimaryKeyProp as o, declaresEnumType as r, idColumnDefault as s, assertSinglePrimaryKey as t };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=column-plan-helpers-CpILzHJS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-plan-helpers-CpILzHJS.js","names":[],"sources":["../src/schema/column-plan-helpers.ts"],"sourcesContent":["/**\n * The questions about a property that the schema planner asks first.\n *\n * What is the primary key, what is this column called, what does an id default\n * to, what labels does this enum have. `schema/plan/plan-schema.ts` is the only\n * caller — it is the only reader of a `Property` — and these live beside it\n * rather than inside it because they are also where the *refusals* are, and a\n * refusal wants to be reachable and testable on its own.\n *\n * They were extracted while there were still three emitters, each carrying its\n * own verbatim copy of all four rules until one of them was edited: the audit\n * that produced this module counted twelve open disagreements and found that a\n * third of the commits touching those files were re-synchronisations. The\n * emitters are renderers now and there is nothing left to keep in step, but the\n * questions are still questions about a property rather than about a plan, so\n * this is where they belong.\n *\n * Everything here is pure and synchronous, and it throws rather than guesses:\n * an enum with no values, a `cuid` id on Postgres and a second `isId` are all\n * configurations that produce a broken database in silence, so they are refused\n * where every path passes.\n */\nimport type {\n CollectionConfig,\n NumberProperty,\n Property,\n StringProperty\n} from \"@rebasepro/types\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Resolve the SQL column name for a property.\n *\n * Uses the explicit `columnName` when set (e.g. from introspection), falling\n * back to `toSnakeCase(propName)` for manually-authored collections.\n */\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n/** Every property that carries a truthy `isId`, in declaration order. */\nconst idPropertyEntries = (collection: CollectionConfig): [string, Property][] =>\n Object.entries(collection.properties ?? {}).filter(\n ([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)\n ) as [string, Property][];\n\n/**\n * Refuse a collection that declares two primary keys.\n *\n * Postgres allows a composite primary key; Rebase does not model one, and the\n * three emitters each invented a different wrong answer for it. The Drizzle\n * generator appended `.primaryKey()` to both columns (two primary keys, which\n * drizzle-kit turns into an error at push time), the DDL generator wrote two\n * inline `PRIMARY KEY` clauses in one `CREATE TABLE` (Postgres: \"multiple\n * primary keys for table are not allowed\"), and boot-ensure created the table\n * with the *first* id column and silently never added the second — so a managed\n * tenant got a table missing a column its own collection reads.\n *\n * None of those is recoverable at runtime, and two of them fail long after the\n * config was written. Refused here instead, where the collection has a name.\n */\nexport const assertSinglePrimaryKey = (collection: CollectionConfig): void => {\n const ids = idPropertyEntries(collection);\n if (ids.length < 2) return;\n throw new Error(\n `Collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" marks ${ids.length} properties ` +\n `with \\`isId\\` (${ids.map(([name]) => `\"${name}\"`).join(\", \")}). ` +\n \"Composite primary keys are not supported: give exactly one property `isId`, and express the \" +\n \"second key with `indexes: [{ on: [...], unique: true, reason: \\\"…\\\" }]`.\"\n );\n};\n\nexport const getPrimaryKeyProp = (collection: CollectionConfig): { name: string, type: \"string\" | \"number\", isUuid: boolean } => {\n if (collection.properties) {\n const idPropEntry = idPropertyEntries(collection)[0];\n if (idPropEntry) {\n const prop = idPropEntry[1];\n const isUuid = prop.type === \"string\" && \"isId\" in prop && (prop as unknown as StringProperty).isId === \"uuid\";\n return { name: idPropEntry[0], type: prop.type === \"number\" ? \"number\" : \"string\", isUuid };\n }\n }\n // Fallback: a collection that declares no `isId` gets an implicit `id`.\n const idProp = collection.properties?.[\"id\"] as unknown as Property | undefined;\n if (idProp?.type === \"number\") {\n return { name: \"id\", type: \"number\", isUuid: false };\n }\n const isUuid = idProp?.type === \"string\" && \"isId\" in idProp && (idProp as unknown as StringProperty).isId === \"uuid\";\n return { name: \"id\", type: \"string\", isUuid: isUuid ?? false };\n};\n\nexport const getPrimaryKeyName = (collection: CollectionConfig): string =>\n getPrimaryKeyProp(collection).name;\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // Only fall back to \"id\" when NO property is explicitly marked.\n return idPropertyEntries(collection).length === 0 && propName === \"id\";\n};\n\n/**\n * The labels an `enum` declares, in order, whichever form it was written in.\n *\n * `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend\n * the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`\n * — so a record-form enum threw `p.enum.map is not a function` on a managed\n * tenant, at boot, with no developer in the loop. The other two emitters each\n * carried their own copy of the both-forms walk.\n *\n * An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`\n * is not valid SQL, so every emitter \"handled\" it by skipping the type and then\n * typing the column with it anyway: Drizzle referenced an enum variable it never\n * declared (the generated file does not compile), the DDL named a type nothing\n * creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a\n * survivable action, so the boot died. There is no useful column at the end of\n * any of those paths.\n */\nexport const enumLabelsOf = (propName: string, prop: Property, collection: CollectionConfig): string[] => {\n const values = (prop as { enum?: unknown }).enum;\n if (values === undefined || values === null) return [];\n\n const labels = Array.isArray(values)\n ? values.map((entry: unknown) =>\n String(entry !== null && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? (entry as Record<string, unknown>).id\n : entry))\n : Object.keys(values as Record<string, unknown>).map(String);\n\n const usable = labels.filter(label => label.length > 0);\n if (usable.length === 0) {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"declares an empty `enum`. A Postgres enum type needs at least one label — \" +\n \"`CREATE TYPE … AS ENUM ()` is not valid SQL — so the column would reference a type \" +\n \"nothing creates. List the values, or drop the `enum` for a plain column.\"\n );\n }\n return usable;\n};\n\n/**\n * Does this property compile to a Postgres enum *type*?\n *\n * Strings only. A `number` property with an `enum` used to create a type on all\n * three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was\n * orphaned the moment it was created and the values it listed were enforced by\n * nothing. The dropdown in the panel is driven by the config, not the column, so\n * nothing is lost by not creating it — and a type no column uses is one more\n * object `db push` has to plan a DROP for.\n */\nexport const declaresEnumType = (prop: Property): boolean =>\n prop.type === \"string\" && Boolean((prop as StringProperty).enum);\n\n/**\n * The `DEFAULT` an id column carries, as the thing itself rather than a\n * rendered string — each emitter spells it differently.\n *\n * The three of them disagreed on all four strategies. The DDL generator wrote\n * `DEFAULT ${prop.isId}` verbatim, so the documented `` isId: \"sql`gen_id()`\" ``\n * reached Postgres with the template wrapper still on it — a syntax error in\n * `schema.sql`. Boot-ensure gave a default to `uuid` and to nothing else, so on\n * a managed tenant every other strategy produced a column with no default at\n * all, and the first insert that trusted the config failed on a NULL primary\n * key. Only the Drizzle generator stripped the wrapper.\n */\nexport type IdColumnDefault =\n /** `gen_random_uuid()` / `.defaultRandom()`. */\n | { kind: \"uuid\" }\n /** `GENERATED BY DEFAULT AS IDENTITY` / `.generatedByDefaultAsIdentity()` — part of the type, not a DEFAULT. */\n | { kind: \"identity\" }\n /** A raw SQL expression, wrapper already stripped. */\n | { kind: \"sql\"; expression: string };\n\n/**\n * Refuses `isId: \"cuid\"`.\n *\n * `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`\n * function has ever been created — not by the generators, not by boot, not by\n * any migration. So the column has never had a working default on Postgres: the\n * first insert that relied on it failed with `function cuid() does not exist`.\n * There is nothing to keep compatible.\n *\n * It is refused rather than implemented. Shipping a real `rebase.cuid()` means a\n * new SQL function in the same carved-out class as the search helpers — excluded\n * from Atlas by pattern, applied by boot, re-applied by push, checked by the\n * doctor, and versioned across the fleet forever — to reproduce a monotonic id\n * that `uuid` already provides and that a project wanting cuid's exact shape can\n * write itself in one line of SQL.\n */\nexport const idColumnDefault = (propName: string, prop: Property, collection: CollectionConfig): IdColumnDefault | undefined => {\n if (!(\"isId\" in prop)) return undefined;\n const isId = (prop as { isId?: unknown }).isId;\n // `true` and `\"manual\"` both mean \"the application supplies it\".\n if (isId === undefined || isId === null || isId === true || isId === \"manual\" || isId === false) return undefined;\n if (typeof isId !== \"string\") return undefined;\n\n if (prop.type === \"string\" && (isId as StringProperty[\"isId\"]) === \"uuid\") return { kind: \"uuid\" };\n if (prop.type === \"number\" && (isId as NumberProperty[\"isId\"]) === \"increment\") return { kind: \"identity\" };\n\n if (isId === \"cuid\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"uses `isId: \\\"cuid\\\"`, which Postgres cannot honour: the generated default calls a `cuid()` \" +\n \"function that Rebase has never created, so every insert relying on it fails. \" +\n \"Use `isId: \\\"uuid\\\"`, or give the strategy as SQL — ``isId: \\\"sql`my_id()`\\\"`` — and create that \" +\n \"function in a migration.\"\n );\n }\n\n // Any other string is a raw SQL default. Written either bare\n // (`gen_random_uuid()::text`) or in the documented template form\n // (``sql`gen_random_uuid()::text` ``); the wrapper is markup, not SQL.\n const expression = isId.startsWith(\"sql`\") && isId.endsWith(\"`\")\n ? isId.substring(4, isId.length - 1)\n : isId;\n return { kind: \"sql\", expression };\n};\n"],"mappings":"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"}
|