@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-drizzle-schema-logic-Dfyf_MRu.js","names":[],"sources":["../src/schema/plan/render-drizzle.ts","../src/schema/generate-drizzle-schema-logic.ts"],"sourcesContent":["/**\n * `schema.generated.ts`, rendered from a {@link SchemaPlan}.\n *\n * This file is what a developer running drizzle-kit themselves diffs against,\n * and what the runtime's query builder is typed by. It has to *compile*, which\n * nothing in this repo checked until `generated-schema-compiles.test.ts`: six\n * ordinary inputs used to produce a file TypeScript rejects, and the class the\n * bigint incident already taught is that a file which will not build gets\n * hand-patched and then sits stale until a security fix misses production.\n *\n * Two structural rules keep that from coming back:\n *\n * - **The import list is derived from what was emitted.** Every builder is\n * recorded as it is used ({@link BuilderUses}), and the header is written\n * last, from that set. It used to be a fixed roster plus three\n * property-scanning heuristics, so `columnType: \"uuid\"` on a plain string\n * emitted `uuid(…)` and imported nothing.\n * - **Nothing here reads a `Property`.** Types, defaults, keys and constraints\n * all arrive decided. The Drizzle generator disagreeing with the DDL one is\n * what left `geopoint` with a database column and no Drizzle key.\n */\nimport type { ColumnPlan, ForeignKeyPlan, PgType, PolicyPlan, RelationPlan, SchemaPlan, TablePlan } from \"./types\";\nimport { renderPredicate } from \"../collection-index\";\n\n/** What the generated file may leave out. */\nexport interface DrizzleRenderOptions {\n /**\n * `false` strips the `pgPolicy(...)` calls. RLS is still enabled on every\n * table regardless — a bare table must default-deny, not fail open.\n */\n policies?: boolean;\n}\n\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A string literal for the generated file.\n *\n * Column names, table names and enum values are all written in as literals and\n * none of them is constrained to be quote-free: a Postgres identifier only has\n * to be quoted, and `O'Brien` is an ordinary enum value. Interpolating them raw\n * ended the literal early.\n */\nconst quote = (value: string): string => JSON.stringify(value);\n\n/** An object key: verbatim when it is an identifier, quoted otherwise. */\nconst propKey = (name: string): string => (JS_IDENTIFIER.test(name) ? name : quote(name));\n\n/**\n * A property access on a generated table variable.\n *\n * `users.full name` is not an expression; `users[\"full name\"]` is, and Drizzle\n * treats the two identically.\n */\nconst member = (object: string, key: string): string =>\n (JS_IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${quote(key)}]`);\n\n/**\n * The drizzle-orm builders this file emitted, collected as they are written.\n * The only way to use a builder is to record it here.\n */\ntype BuilderUses = Set<string>;\n\n/** Record a builder and hand back its name, so a call site cannot use one silently. */\nconst needs = (uses: BuilderUses, builder: string): string => {\n uses.add(builder);\n return builder;\n};\n\n/**\n * Wraps a compiled SQL clause in a Drizzle `sql\\`...\\`` template literal.\n *\n * The clause is SQL being written into a TypeScript file, so it has to survive\n * being read back as a template literal. Three characters do not:\n *\n * - `` ` `` closes the template early, and the rest of the clause becomes code.\n * - `${` opens an interpolation — the file stops compiling, or worse, compiles\n * against whatever identifier happens to be in scope.\n * - `\\` is an escape, and Drizzle's `sql` tag reads the *cooked* strings, not\n * `.raw`. So a policy written as `email ~ '^admin\\.user@corp\\.com$'` reached\n * the database as `^admin.user@corp.com$`, where every `\\.` now matches any\n * character. A `USING` clause is a security boundary and that one silently\n * widened it.\n */\nconst wrapSql = (clause: string): string =>\n `sql\\`${clause.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\")}\\``;\n\n/** The pg-core builder call for a tagged type, minus the column name. */\nconst builderFor = (type: PgType, column: string, uses: BuilderUses): string => {\n const name = quote(column);\n switch (type.kind) {\n case \"text\": return `${needs(uses, \"text\")}(${name})`;\n case \"varchar\": return `${needs(uses, \"varchar\")}(${name}, { length: ${type.length} })`;\n case \"char\": return `${needs(uses, \"char\")}(${name}, { length: ${type.length} })`;\n case \"uuid\": return `${needs(uses, \"uuid\")}(${name})`;\n // The enum variable is declared at the top of the same file; it is not\n // a pg-core import.\n case \"enum\": return `${type.varName}(${name})`;\n case \"smallint\": return `${needs(uses, \"smallint\")}(${name})`;\n case \"integer\": return `${needs(uses, \"integer\")}(${name})`;\n // `bigint` and `bigserial` are the only pg-core builders that *require*\n // a config argument: without `mode`, drizzle cannot know whether to hand\n // back a `number` or a `bigint`, and the emitted call does not\n // typecheck. This is why `schema.generated.ts` drifted — regenerating it\n // produced a file that would not compile, so the bigint lines were\n // hand-patched, and every regeneration after that looked like a large,\n // alarming diff nobody wanted to ship.\n //\n // `number` rather than `bigint`: these are counters and byte totals that\n // every caller already treats as numbers.\n case \"bigint\": return `${needs(uses, \"bigint\")}(${name}, { mode: \"number\" })`;\n case \"bigserial\": return `${needs(uses, \"bigserial\")}(${name}, { mode: \"number\" })`;\n case \"smallserial\": return `${needs(uses, \"smallserial\")}(${name})`;\n case \"serial\": return `${needs(uses, \"serial\")}(${name})`;\n case \"real\": return `${needs(uses, \"real\")}(${name})`;\n case \"doublePrecision\": return `${needs(uses, \"doublePrecision\")}(${name})`;\n case \"numeric\":\n if (type.precision === undefined) return `${needs(uses, \"numeric\")}(${name})`;\n return type.scale === undefined\n ? `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision} })`\n : `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision}, scale: ${type.scale} })`;\n case \"boolean\": return `${needs(uses, \"boolean\")}(${name})`;\n case \"timestamptz\": return `${needs(uses, \"timestamp\")}(${name}, { withTimezone: true, mode: 'string' })`;\n case \"date\": return `${needs(uses, \"date\")}(${name}, { mode: 'string' })`;\n case \"time\": return `${needs(uses, \"time\")}(${name})`;\n case \"json\": return `${needs(uses, \"json\")}(${name})`;\n case \"jsonb\": return `${needs(uses, \"jsonb\")}(${name})`;\n case \"vector\": return `${needs(uses, \"vector\")}(${name}, { dimensions: ${type.dimensions} })`;\n case \"bytea\": return `${needs(uses, \"customType\")}({ dataType() { return 'bytea'; } })(${name})`;\n case \"tsvector\": return `${needs(uses, \"customType\")}({ dataType() { return 'tsvector'; } })(${name})`;\n // pg-core has no array *type*; `.array()` is a modifier on the element.\n case \"array\": return `${builderFor(type.of, column, uses)}.array()`;\n }\n};\n\n/**\n * The `.references(...)` clause a foreign key column carries.\n *\n * The `(): AnyPgColumn =>` annotation is not optional decoration and it is\n * emitted on every reference rather than only the self-referential ones. A\n * `belongsTo` pointing at its own table — a comment thread, a category tree —\n * produces `.references(() => posts.id)` inside the initializer of `posts`, and\n * TypeScript cannot infer a type for a `const` that appears in its own\n * initializer (TS7022). Drizzle documents the annotation as the fix; applying\n * it everywhere means the generated line does not depend on whether the author\n * happened to point the relation at another table.\n */\nconst referencesClause = (\n fk: ForeignKeyPlan,\n targetVar: string,\n targetKey: string,\n uses: BuilderUses\n): string => {\n needs(uses, \"type AnyPgColumn\");\n const parts = [\n fk.onUpdate ? `onUpdate: \"${fk.onUpdate.toLowerCase()}\"` : \"\",\n `onDelete: \"${fk.onDelete.toLowerCase()}\"`\n ].filter(Boolean);\n return `.references((): AnyPgColumn => ${member(targetVar, targetKey)}, { ${parts.join(\", \")} })`;\n};\n\n/** The declaration a column compiles to, without its object key. */\nconst renderColumn = (column: ColumnPlan, table: TablePlan, plan: SchemaPlan, uses: BuilderUses): string => {\n let out = builderFor(column.type, column.column, uses);\n\n if (column.generated) {\n return `${out}.generatedAlwaysAs(${wrapSql(column.generated.expression)})`;\n }\n\n // A junction endpoint spells `.notNull()` before its reference; every other\n // column spells it last. Both are the same constraint — this keeps the\n // generated file byte-stable across the move to one planner.\n const junctionKey = column.source.kind === \"junction-key\";\n if (junctionKey) out += \".notNull()\";\n\n if (column.default?.kind === \"identity\") out += \".generatedByDefaultAsIdentity()\";\n if (column.primaryKey) out += \".primaryKey()\";\n if (column.default && column.default.kind !== \"identity\") {\n const expression = column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n // `.defaultRandom()` is drizzle's own spelling of `gen_random_uuid()`,\n // and it exists **only on the uuid builder** — a `text` id whose\n // strategy happens to be spelled ``isId: \"sql`gen_random_uuid()`\"`` has\n // to take the generic form, which is what the file has always emitted\n // for it.\n out += column.type.kind === \"uuid\" && expression === \"gen_random_uuid()\"\n ? \".defaultRandom()\"\n : `.default(${wrapSql(expression)})`;\n }\n // A plain property that carries a relation's foreign key column gets the\n // constraint the relation would have emitted. `postId: { columnName:\n // \"post_id\" }` beside a `belongsTo` on `post_id` used to emit an integer\n // with no `.references()` at all, so that project got no foreign key on the\n // Drizzle side while `db push` created one.\n const foreignKey = column.foreignKey\n ?? table.columns.find(c => c.columnOwnedByProperty && c.column === column.column)?.foreignKey;\n if (foreignKey) {\n const target = plan.tables.find(t =>\n t.schema === foreignKey.targetSchema && t.table === foreignKey.targetTable);\n if (target) {\n const targetColumn = target.columns.find(c => c.column === foreignKey.targetColumn);\n out += referencesClause(foreignKey, target.varName, targetColumn?.key ?? foreignKey.targetColumn, uses);\n }\n }\n if (column.unique) out += \".unique()\";\n if (!column.nullable && !column.primaryKey && !junctionKey) out += \".notNull()\";\n return out;\n};\n\n/** The declared `indexes:` block, as Drizzle table extras. */\nconst indexExtras = (table: TablePlan, uses: BuilderUses): string[] =>\n table.indexes.map(spec => {\n const builder = spec.unique ? needs(uses, \"uniqueIndex\") : needs(uses, \"index\");\n // The spec holds COLUMN names; the generated table is keyed by field\n // name (`authorId` for `author_id`).\n const column = (name: string): string =>\n member(\"table\", table.columns.find(c => c.column === name)?.key ?? name);\n const keys = spec.keys.map(key => {\n if (spec.method !== \"btree\") return column(key.column);\n const direction = key.direction === \"desc\" ? \".desc()\" : \".asc()\";\n const nulls = key.nulls === \"first\" ? \".nullsFirst()\" : \".nullsLast()\";\n return `${column(key.column)}${direction}${nulls}`;\n });\n const on = spec.method === \"btree\"\n ? `.on(${keys.join(\", \")})`\n : `.using(${quote(spec.method)}, ${keys.join(\", \")})`;\n const where = spec.predicate ? `.where(sql\\`${renderPredicate(spec.predicate)}\\`)` : \"\";\n // drizzle-orm has no INCLUDE in its index builder (0.45), so a covering\n // index is emitted here as its key columns alone. The database still\n // gets the INCLUDE — `schema.sql` and boot-ensure both emit it.\n const covering = spec.include.length > 0\n ? ` // INCLUDE (${spec.include.join(\", \")}) — drizzle cannot express it; schema.sql does`\n : \"\";\n return ` ${builder}(${quote(spec.indexName)})${on}${where},${covering}`;\n });\n\nconst policyExtra = (policy: PolicyPlan, uses: BuilderUses): string => {\n needs(uses, \"pgPolicy\");\n const parts = [\n `as: \"${policy.mode}\"`,\n `for: \"${policy.operation}\"`,\n `to: [${policy.roles.map(r => `\"${r}\"`).join(\", \")}]`\n ];\n if (policy.using) parts.push(`using: ${wrapSql(policy.using)}`);\n if (policy.withCheck) parts.push(`withCheck: ${wrapSql(policy.withCheck)}`);\n return ` pgPolicy(${quote(policy.name)}, { ${parts.join(\", \")} }),`;\n};\n\nexport function renderDrizzleSchema(plan: SchemaPlan, options: DrizzleRenderOptions = {}): string {\n const withPolicies = options.policies !== false;\n const uses: BuilderUses = new Set();\n // The body is assembled first and the header composed around it, because\n // the header cannot be written until the body has said what it needs.\n let body = \"\";\n\n let schemaDeclarations = \"\";\n if (plan.declaredSchemas.length > 0) {\n needs(uses, \"pgSchema\");\n plan.declaredSchemas.forEach(schema => {\n schemaDeclarations += `export const ${schema}Schema = pgSchema(\"${schema}\");\\n`;\n });\n schemaDeclarations += \"\\n\";\n }\n\n const enumVars: string[] = [];\n for (const enumPlan of plan.enums) {\n // A collection with `schema: \"app\"` gets `CREATE TYPE \"app\".\"…\"` from\n // the DDL renderer, so the type has to be declared in the same schema\n // here — `pgEnum` is `public` and nothing else. Unqualified, the runtime\n // and drizzle-kit looked for a type that does not exist in `public`.\n const labels = enumPlan.labels.map(quote).join(\", \");\n const declaration = enumPlan.declaredSchema\n ? `${enumPlan.declaredSchema}Schema.enum(${quote(enumPlan.name)}, [${labels}])`\n : `${needs(uses, \"pgEnum\")}(${quote(enumPlan.name)}, [${labels}])`;\n body += `export const ${enumPlan.varName} = ${declaration};\\n`;\n if (!enumVars.includes(enumPlan.varName)) enumVars.push(enumPlan.varName);\n }\n body += \"\\n\";\n\n const tableVars: string[] = [];\n for (const table of plan.tables) {\n const creator = table.declaredSchema\n ? `${table.declaredSchema}Schema.table`\n : needs(uses, \"pgTable\");\n body += `export const ${table.varName} = ${creator}(\"${table.table}\", {\\n`;\n\n // The implicit `id` goes last here and first in the SQL file: the DDL\n // generator unshifts it so the key leads the `CREATE TABLE`, and this\n // one appends it after the generated search columns. Both describe the\n // same table; only the reading order differs.\n const ordered = [\n ...table.columns.filter(c => c.source.kind !== \"implicit-id\"),\n ...table.columns.filter(c => c.source.kind === \"implicit-id\")\n ].filter(c =>\n !c.columnOwnedByProperty\n // The auth columns a users collection does not itself declare —\n // `is_anonymous`, `tokens_valid_after`. `db push` emits them because\n // Atlas diffs the database against exactly what it is shown and\n // would otherwise plan a DROP; drizzle-kit creates no auth table, so\n // this file describes the collection as written and leaves auth's\n // own contract to the SQL side that owns it.\n && c.source.kind !== \"auth\");\n\n const lines = ordered.map(column => ` ${propKey(column.key)}: ${renderColumn(column, table, plan, uses)}`);\n // A junction's column list carries a trailing comma; a collection's does\n // not. Kept as the two files have always spelled them.\n body += table.kind === \"junction\"\n ? `${lines.join(\",\\n\")},\\n`\n : `${lines.join(\",\\n\")}\\n`;\n\n const extras: string[] = [];\n if (table.kind === \"junction\") {\n extras.push(` ${needs(uses, \"primaryKey\")}({ columns: [${table.primaryKey.map(c => member(\"table\", c)).join(\", \")}] }),`);\n } else {\n extras.push(...indexExtras(table, uses));\n }\n if (withPolicies) {\n extras.push(...table.policies.map(policy => policyExtra(policy, uses)));\n }\n\n if (extras.length > 0) {\n body += \"}, (table) => ([\\n\";\n body += `${extras.join(\"\\n\")}\\n`;\n body += \"])).enableRLS();\\n\\n\";\n } else {\n // No explicit policies and no declared indexes — RLS enabled with a\n // deny-all default (Postgres denies everything when RLS is on and no\n // permissive policies exist).\n body += \"}).enableRLS();\\n\\n\";\n }\n if (!tableVars.includes(table.varName)) tableVars.push(table.varName);\n }\n\n const relationVars: string[] = [];\n for (const table of plan.tables) {\n const entries = plan.relations.filter(r => r.tableVar === table.varName);\n if (entries.length === 0) continue;\n const rendered = entries.map(relation => renderRelation(table, relation));\n const varName = `${table.varName}Relations`;\n body += `export const ${varName} = drizzleRelations(${table.varName}, ({ one, many }) => ({\\n${rendered.join(\",\\n\")}\\n}));\\n\\n`;\n if (!relationVars.includes(varName)) relationVars.push(varName);\n }\n\n // ── The header, written last because the body decides what is in it ──────\n const imports = Array.from(uses).sort((a, b) =>\n a.replace(/^type /, \"\").localeCompare(b.replace(/^type /, \"\")));\n\n let out = \"// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\\n\\n\";\n out += `import { ${imports.join(\", \")} } from 'drizzle-orm/pg-core';\\n`;\n out += \"import { relations as drizzleRelations, sql } from 'drizzle-orm';\\n\\n\";\n out += schemaDeclarations;\n out += body;\n out += `export const tables = { ${tableVars.join(\", \")} };\\n`;\n out += `export const enums = { ${enumVars.join(\", \")} };\\n`;\n out += `export const relations = { ${relationVars.join(\", \")} };\\n\\n`;\n return out;\n}\n\nconst renderRelation = (table: TablePlan, relation: RelationPlan): string => {\n if (relation.kind === \"many\") {\n return ` ${quote(relation.key)}: many(${relation.targetVar}, { relationName: ${quote(relation.relationName!)} })`;\n }\n // A `hasOne` inverse has no `fields`/`references` to give: the foreign key\n // lives on the target. `one(target, { relationName })` is not a\n // `RelationConfig` (TS2345) *and* not something the runtime survives —\n // `createOne` reads `config.fields.reduce(...)` unconditionally — so a bare\n // `one(target)` is the documented FK-less form.\n if (!relation.fields) return ` ${quote(relation.key)}: one(${relation.targetVar})`;\n return ` ${quote(relation.key)}: one(${relation.targetVar}, {\\n` +\n ` fields: [${relation.fields.map(f => member(table.varName, f)).join(\", \")}],\\n` +\n ` references: [${relation.references!.map(r => member(relation.targetVar, r)).join(\", \")}],\\n` +\n ` relationName: ${quote(relation.relationName!)}\\n` +\n \" })\";\n};\n","/**\n * `schema.generated.ts`, the Drizzle half of `rebase db generate`.\n *\n * There is no interpretation left in this file. `schema/plan/plan-schema.ts`\n * reads the collections into a {@link SchemaPlan} and\n * `schema/plan/render-drizzle.ts` writes the TypeScript; this is the entry\n * point they are reached through.\n *\n * It used to hold `getDrizzleColumn` — one of three `switch (prop.type)`\n * statements, each with its own reading of relations, enums, search and\n * `validation.unique`, and each disagreeing with the other two about something.\n * See `schema/plan/types.ts`.\n */\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { relationalCollections, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { renderDrizzleSchema, type DrizzleRenderOptions } from \"./plan/render-drizzle\";\n\n/**\n * The generated Drizzle schema for a set of collections.\n *\n * Synchronous, and takes an options object. It was `async` with nothing to\n * await and a positional `stripPolicies` boolean — `generateSchema(c, true)`,\n * which reads as neither \"with policies\" nor \"without\".\n *\n * Sorted by slug here rather than in the writer script: the output is\n * order-dependent and `rebase doctor` regenerates it in memory to compare\n * against the file on disk, so with the sort in the writer only, a project\n * whose file order differed from its slug order was reported stale forever.\n */\nexport const generateSchema = (\n allCollections: CollectionConfig[],\n options: DrizzleRenderOptions = {}\n): string => {\n // A Firestore or MongoDB collection has no table to generate, and\n // generating one for it is not merely wasted output: `db push` would create\n // it, and `rebase doctor` would then report the store the collection\n // actually reads from as drift.\n const collections = sortCollectionsBySlug(relationalCollections(allCollections));\n return renderDrizzleSchema(planSchema(collections), options);\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { t as generateSchema } from "./generate-drizzle-schema-logic-
|
|
3
|
+
import { t as generateSchema } from "./generate-drizzle-schema-logic-Dfyf_MRu.js";
|
|
4
4
|
import { n as outError, t as out } from "./cli-output-CNdMql-L.js";
|
|
5
5
|
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
6
6
|
import { promises } from "fs";
|
|
@@ -73,7 +73,7 @@ var runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
73
73
|
let collections = await loadCollectionsFromDirectory(path.resolve(collectionsFilePath));
|
|
74
74
|
if (!collections || !Array.isArray(collections)) collections = [];
|
|
75
75
|
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
76
|
-
const schemaContent =
|
|
76
|
+
const schemaContent = generateSchema(collections);
|
|
77
77
|
if (outputPath) {
|
|
78
78
|
const outputDir = path.dirname(outputPath);
|
|
79
79
|
await promises.mkdir(outputDir, { recursive: true });
|
|
@@ -120,4 +120,4 @@ if (import.meta.url.endsWith(process.argv[1])) main().catch(() => {
|
|
|
120
120
|
//#endregion
|
|
121
121
|
export { main as t };
|
|
122
122
|
|
|
123
|
-
//# sourceMappingURL=generate-drizzle-schema-
|
|
123
|
+
//# sourceMappingURL=generate-drizzle-schema-vSK-VdYT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-drizzle-schema-vSK-VdYT.js","names":[],"sources":["../src/schema/generate-next-step.ts","../src/schema/generate-drizzle-schema.ts"],"sourcesContent":["/**\n * The line `rebase schema generate` ends on.\n *\n * Its own module because `generate-drizzle-schema.ts` uses `import.meta` to\n * decide whether it was executed directly, which the driver's jest suite cannot\n * load at all — so the one sentence in that file with a decision in it had no\n * test and could not have one where it stood.\n */\n\nexport const formatTerminalText = (text: string, options: {\n bold?: boolean;\n backgroundColor?: \"blue\" | \"green\" | \"red\" | \"yellow\" | \"cyan\" | \"magenta\";\n textColor?: \"white\" | \"black\" | \"red\" | \"green\" | \"yellow\" | \"blue\" | \"magenta\" | \"cyan\";\n} = {}): string => {\n let codes = \"\";\n if (options.bold) codes += \"\\x1b[1m\";\n if (options.backgroundColor) {\n const bgColors = {\n blue: \"\\x1b[44m\",\n green: \"\\x1b[42m\",\n red: \"\\x1b[41m\",\n yellow: \"\\x1b[43m\",\n cyan: \"\\x1b[46m\",\n magenta: \"\\x1b[45m\"\n } as const;\n codes += bgColors[options.backgroundColor];\n }\n if (options.textColor) {\n const textColors = {\n white: \"\\x1b[37m\",\n black: \"\\x1b[30m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\"\n } as const;\n codes += textColors[options.textColor];\n }\n return `${codes}${text}\\x1b[0m`;\n};\n\n/**\n * What to do with the file that was just generated.\n *\n * Not the same answer on the managed development database: `rebase db generate`\n * plans with Atlas, Atlas needs a second empty database to diff against, and\n * PGlite serves exactly one — so the CLI refuses that command outright there.\n * This line recommended it unconditionally, which meant every stock scaffold\n * was told, by the tool itself, to run something the same tool would refuse.\n *\n * The kind arrives as `REBASE_DEV_DATABASE_KIND`, set by the CLI from the\n * ordered resolution in `dev-db/resolve.ts`. It is a pure decision — nothing is\n * started to make it — and the driver cannot make it for itself: on the managed\n * path `DATABASE_URL` is a perfectly ordinary connection string to a Postgres\n * on loopback. Absent (the driver run directly, outside the CLI) keeps the\n * original wording.\n */\nexport const nextStepAfterGenerate = (): string => {\n if (process.env.REBASE_DEV_DATABASE_KIND === \"managed\") {\n return `Start or restart ${formatTerminalText(\"rebase dev\", {\n bold: true,\n backgroundColor: \"blue\",\n textColor: \"black\"\n })} — boot applies your collections to the managed development database.`;\n }\n\n return `You can now run ${formatTerminalText(\"rebase db generate\", {\n bold: true,\n backgroundColor: \"blue\",\n textColor: \"black\"\n })} to generate the SQL migration files.`;\n};\n","import { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { out, outError } from \"../cli-output\";\nimport { nextStepAfterGenerate } from \"./generate-next-step\";\n\n\n// --- Execution and Watch Logic ---\n\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n try {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n\n const resolvedPath = path.resolve(collectionsFilePath);\n\n // Shared with the runtime and the doctor: what gets generated here must\n // be exactly what the server serves, including directory-level defaults.\n let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);\n\n\n // If collections directory is empty but exists, or failed to find any, we still want to inject defaults\n if (!collections || !Array.isArray(collections)) {\n collections = [];\n }\n\n\n // Sort collections by slug alphabetically to ensure deterministic schema generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\n\n const schemaContent = generateSchema(collections);\n\n if (outputPath) {\n const outputDir = path.dirname(outputPath);\n await fsPromises.mkdir(outputDir, { recursive: true });\n await fsPromises.writeFile(outputPath, schemaContent);\n out(`✅ Drizzle schema generated successfully at ${outputPath}`);\n } else {\n out(\"✅ Drizzle schema generated successfully.\");\n out(String(schemaContent));\n }\n\n out(nextStepAfterGenerate());\n\n } catch (error) {\n outError(`Error generating schema: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n // The same contract its sibling `generate-postgres-ddl.ts` keeps, and\n // for the same reason: `rebase db push` runs both as step 1 and reads\n // nothing but their exit codes. Exiting 0 after printing an error made\n // the push apply whatever `src/schema.generated.ts` was already there.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-drizzle-schema.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating schema...`);\n // A watch session outlives a bad edit; the next save is the retry.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // The cause is already on stderr with the exit code set; this keeps Node\n // from printing the same stack again as an unhandled rejection.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,475 @@
|
|
|
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 { _ as searchHelperFunctions, a as triggerStatements, b as searchStampGuards, d as fuzzyColumnDefinition, g as searchExtensionStatements, h as searchColumnStamps, n as planSchema, p as searchColumnDefinition, r as quoteSqlLiteral, v as searchIndexNames, y as searchIndexStatements } from "./plan-schema-Hgl62S-w.js";
|
|
5
|
+
import { a as vectorDimensionGuard, c as vectorExtensionStatement, d as vectorIndexStatements, i as vectorColumnDefinition, l as vectorIndexNames, o as vectorExtensionDeclared, t as VECTOR_EXTENSION_OPT_IN } from "./vector-index-CMJKb3C9.js";
|
|
6
|
+
import { r as collectionIndexStatements } from "./collection-index-DdnsxiJ_.js";
|
|
7
|
+
import "@rebasepro/types";
|
|
8
|
+
import "@rebasepro/common";
|
|
9
|
+
//#region src/schema/plan/render-ddl.ts
|
|
10
|
+
/**
|
|
11
|
+
* The SQL files, rendered from a {@link SchemaPlan}.
|
|
12
|
+
*
|
|
13
|
+
* Four files, because Atlas owns exactly one of them:
|
|
14
|
+
*
|
|
15
|
+
* - `schema.sql` — the desired state Atlas diffs the database against.
|
|
16
|
+
* - `policies.sql` — the RLS policies. Atlas has no model for a policy.
|
|
17
|
+
* - `search.sql` — the generated `tsvector` column, its GIN index and the
|
|
18
|
+
* IMMUTABLE helpers its expression calls. Atlas's free tier refuses to
|
|
19
|
+
* *parse* a file containing a function, and it wipes the dev database it
|
|
20
|
+
* diffs against, so a helper seeded there is gone by the time the plan runs.
|
|
21
|
+
* - `vector.sql` — the `vector` columns and their ANN indexes. Atlas cannot
|
|
22
|
+
* *execute* these: it materialises `schema.sql` in a dev database that is
|
|
23
|
+
* created empty and emptied again on every run, so `VECTOR(384)` resolves
|
|
24
|
+
* against a database that structurally cannot have pgvector.
|
|
25
|
+
* - `triggers.sql` — `autoValue: "on_update"`, which is a function plus a
|
|
26
|
+
* binding, so it is out for the same reason search is.
|
|
27
|
+
*
|
|
28
|
+
* Everything not in `schema.sql` is excluded from Atlas's view by pattern
|
|
29
|
+
* (`…ExcludePatterns` below) and applied by Rebase — at push time by the CLI,
|
|
30
|
+
* at boot by the schema ensure. Without the excludes a desired state that omits
|
|
31
|
+
* an object reads to Atlas as an instruction to drop it.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* A {@link PgType} as Postgres spells it.
|
|
35
|
+
*
|
|
36
|
+
* The one place. `getSqlColumnType` and `getDrizzleColumn` each carried their
|
|
37
|
+
* own ladder and disagreed about `VARCHAR` widths, about `geopoint`, and about
|
|
38
|
+
* whether an `increment` id honours `columnType`.
|
|
39
|
+
*/
|
|
40
|
+
var renderPgType = (type) => {
|
|
41
|
+
switch (type.kind) {
|
|
42
|
+
case "text": return "TEXT";
|
|
43
|
+
case "varchar": return `VARCHAR(${type.length})`;
|
|
44
|
+
case "char": return `CHAR(${type.length})`;
|
|
45
|
+
case "uuid": return "UUID";
|
|
46
|
+
case "enum": return `"${type.schema}"."${type.name}"`;
|
|
47
|
+
case "smallint": return "SMALLINT";
|
|
48
|
+
case "integer": return "INTEGER";
|
|
49
|
+
case "bigint": return "BIGINT";
|
|
50
|
+
case "smallserial": return "SMALLSERIAL";
|
|
51
|
+
case "serial": return "SERIAL";
|
|
52
|
+
case "bigserial": return "BIGSERIAL";
|
|
53
|
+
case "real": return "REAL";
|
|
54
|
+
case "doublePrecision": return "DOUBLE PRECISION";
|
|
55
|
+
case "numeric":
|
|
56
|
+
if (type.precision === void 0) return "NUMERIC";
|
|
57
|
+
return type.scale === void 0 ? `NUMERIC(${type.precision})` : `NUMERIC(${type.precision}, ${type.scale})`;
|
|
58
|
+
case "boolean": return "BOOLEAN";
|
|
59
|
+
case "timestamptz": return "TIMESTAMP WITH TIME ZONE";
|
|
60
|
+
case "date": return "DATE";
|
|
61
|
+
case "time": return "TIME";
|
|
62
|
+
case "json": return "JSON";
|
|
63
|
+
case "jsonb": return "JSONB";
|
|
64
|
+
case "vector": return `VECTOR(${type.dimensions})`;
|
|
65
|
+
case "bytea": return "BYTEA";
|
|
66
|
+
case "tsvector": return "tsvector";
|
|
67
|
+
case "array": return `${renderPgType(type.of)}[]`;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Everything after the column name, in one order for every SQL emitter.
|
|
72
|
+
*
|
|
73
|
+
* `CREATE TABLE` and `ADD COLUMN` used to assemble this separately, which is
|
|
74
|
+
* how a required relation column came out `NOT NULL` after `db push` and
|
|
75
|
+
* nullable after a boot-ensure. `identity` is part of the type rather than a
|
|
76
|
+
* DEFAULT, which is why it is rendered here and not in the DEFAULT slot.
|
|
77
|
+
*/
|
|
78
|
+
var renderColumnDefinition = (column) => {
|
|
79
|
+
if (column.sqlDefinition) return column.sqlDefinition;
|
|
80
|
+
if (column.generated) return `${renderPgType(column.type)} GENERATED ALWAYS AS (${column.generated.expression}) STORED`;
|
|
81
|
+
let definition = renderPgType(column.type);
|
|
82
|
+
if (column.default?.kind === "identity") definition += " GENERATED BY DEFAULT AS IDENTITY";
|
|
83
|
+
if (column.primaryKey) definition += " PRIMARY KEY";
|
|
84
|
+
if (column.unique) definition += " UNIQUE";
|
|
85
|
+
if (column.default && column.default.kind !== "identity") definition += ` DEFAULT ${column.default.kind === "sql" ? column.default.expression : column.default.sql}`;
|
|
86
|
+
if (!column.nullable && !column.primaryKey) definition += " NOT NULL";
|
|
87
|
+
return definition;
|
|
88
|
+
};
|
|
89
|
+
/** `"col" TYPE …`, as it appears inside a `CREATE TABLE`. */
|
|
90
|
+
var renderColumn = (column) => `"${column.column}" ${renderColumnDefinition(column)}`;
|
|
91
|
+
/**
|
|
92
|
+
* One policy as its `DROP` / `CREATE` pair — each a complete statement.
|
|
93
|
+
*
|
|
94
|
+
* This is the primitive the boot-time RLS applier runs one statement at a time
|
|
95
|
+
* (the runtime's DB handle speaks the extended query protocol, which forbids
|
|
96
|
+
* multiple commands in one execute), while `db push` writes the joined string.
|
|
97
|
+
*/
|
|
98
|
+
var renderPolicyStatements = (table, policy) => {
|
|
99
|
+
const target = `"${table.schema}"."${table.table}"`;
|
|
100
|
+
let create = `CREATE POLICY "${policy.name}" ON ${target} AS ${policy.mode.toUpperCase()} FOR ${policy.operation.toUpperCase()} TO ${policy.roles.map((r) => `"${r}"`).join(", ")}`;
|
|
101
|
+
if (policy.using) create += ` USING (${policy.using})`;
|
|
102
|
+
if (policy.withCheck) create += ` WITH CHECK (${policy.withCheck})`;
|
|
103
|
+
return [`DROP POLICY IF EXISTS "${policy.name}" ON ${target};`, `${create};`];
|
|
104
|
+
};
|
|
105
|
+
/** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — every generated table is locked. */
|
|
106
|
+
var renderEnableRls = (table) => `ALTER TABLE "${table.schema}"."${table.table}" ENABLE ROW LEVEL SECURITY;`;
|
|
107
|
+
var statementsToDdl = (statements) => statements.map((s) => `${s}\n`).join("");
|
|
108
|
+
function renderPostgresDdl(plan, options = {}) {
|
|
109
|
+
const includePolicies = options.includePolicies !== false;
|
|
110
|
+
const includeSearch = options.includeSearch !== false;
|
|
111
|
+
const includeVector = options.includeVector !== false;
|
|
112
|
+
let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n\n";
|
|
113
|
+
plan.schemas.forEach((schema) => {
|
|
114
|
+
ddl += `CREATE SCHEMA IF NOT EXISTS "${schema}";\n`;
|
|
115
|
+
});
|
|
116
|
+
if (plan.schemas.length > 0) ddl += "\n";
|
|
117
|
+
const searchTables = plan.tables.filter((t) => t.search);
|
|
118
|
+
if (searchTables.length > 0) if (!includeSearch) ddl += "-- Full-text search support lives in `search.sql`, applied separately.\n\n";
|
|
119
|
+
else {
|
|
120
|
+
const extensions = Array.from(new Set(searchTables.flatMap((t) => searchExtensionStatements(t.search))));
|
|
121
|
+
const helpers = Array.from(new Set(searchTables.flatMap((t) => searchHelperFunctions(t.search))));
|
|
122
|
+
ddl += "-- Full-text search support (collections declaring a `search` block)\n";
|
|
123
|
+
extensions.forEach((s) => {
|
|
124
|
+
ddl += `${s}\n`;
|
|
125
|
+
});
|
|
126
|
+
if (extensions.length > 0) ddl += "\n";
|
|
127
|
+
helpers.forEach((s) => {
|
|
128
|
+
ddl += `${s}\n\n`;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (!includeVector && plan.tables.some((t) => t.vectorColumns.length > 0)) ddl += "-- Vector columns and their ANN indexes live in `vector.sql`, applied separately.\n\n";
|
|
132
|
+
if (plan.tables.some((t) => t.triggers.length > 0)) ddl += "-- `autoValue: \"on_update\"` triggers live in `triggers.sql`, applied separately.\n\n";
|
|
133
|
+
plan.enums.forEach((enumPlan) => {
|
|
134
|
+
ddl += `CREATE TYPE "${enumPlan.schema}"."${enumPlan.name}" AS ENUM (${enumPlan.labels.map(quoteSqlLiteral).join(", ")});\n`;
|
|
135
|
+
});
|
|
136
|
+
if (ddl.endsWith(";\n")) ddl += "\n";
|
|
137
|
+
const fkStatements = [];
|
|
138
|
+
const indexStatements = [];
|
|
139
|
+
const policyStatements = [];
|
|
140
|
+
for (const table of plan.tables) {
|
|
141
|
+
const emitted = table.columns.filter((column) => {
|
|
142
|
+
if (!includeVector && column.type.kind === "vector") return false;
|
|
143
|
+
if (!includeSearch && column.source.kind === "search") return false;
|
|
144
|
+
if (column.columnOwnedByProperty) return false;
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
ddl += `CREATE TABLE "${table.schema}"."${table.table}" (\n`;
|
|
148
|
+
const lines = emitted.map((column) => ` ${renderColumn(column)}`);
|
|
149
|
+
if (table.kind === "junction") lines.push(` PRIMARY KEY (${table.primaryKey.map((c) => `"${c}"`).join(", ")})`);
|
|
150
|
+
ddl += lines.join(",\n");
|
|
151
|
+
ddl += "\n);\n\n";
|
|
152
|
+
for (const column of table.columns) if (column.foreignKey) fkStatements.push(`${column.foreignKey.sql};`);
|
|
153
|
+
if (includeSearch && table.search) indexStatements.push(...searchIndexStatements(table.search));
|
|
154
|
+
if (includeVector && table.vector) {
|
|
155
|
+
indexStatements.push(...vectorIndexStatements(table.vector));
|
|
156
|
+
for (const skip of table.vector.skipped) indexStatements.push(`-- No ANN index on "${skip.schema}"."${skip.table}"."${skip.column}": ${skip.reason}`);
|
|
157
|
+
}
|
|
158
|
+
indexStatements.push(...collectionIndexStatements(table.indexes));
|
|
159
|
+
if (includePolicies) {
|
|
160
|
+
ddl += `${renderEnableRls(table)}\n`;
|
|
161
|
+
ddl += "\n";
|
|
162
|
+
for (const policy of table.policies) policyStatements.push(statementsToDdl(renderPolicyStatements(table, policy)));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (indexStatements.length > 0) {
|
|
166
|
+
ddl += "-- Indexes\n";
|
|
167
|
+
ddl += indexStatements.join("\n") + "\n\n";
|
|
168
|
+
}
|
|
169
|
+
if (fkStatements.length > 0) {
|
|
170
|
+
ddl += "-- Foreign Key Constraints\n";
|
|
171
|
+
ddl += fkStatements.join("\n") + "\n\n";
|
|
172
|
+
}
|
|
173
|
+
if (policyStatements.length > 0) {
|
|
174
|
+
ddl += "-- Row Level Security Policies\n";
|
|
175
|
+
ddl += policyStatements.join("");
|
|
176
|
+
ddl += "\n";
|
|
177
|
+
}
|
|
178
|
+
return ddl;
|
|
179
|
+
}
|
|
180
|
+
function renderPoliciesDdl(plan) {
|
|
181
|
+
let ddl = "-- This file contains RLS policies generated by Rebase. Applied separately from migrations.\n\n";
|
|
182
|
+
for (const table of plan.tables.filter((t) => t.kind === "collection")) {
|
|
183
|
+
ddl += `${renderEnableRls(table)}\n`;
|
|
184
|
+
ddl += "\n";
|
|
185
|
+
if (table.policies.length === 0) continue;
|
|
186
|
+
let announced;
|
|
187
|
+
for (const policy of table.policies) {
|
|
188
|
+
if (policy.injected && policy.ruleKey !== announced) {
|
|
189
|
+
ddl += "-- Injected by Rebase (not from this collection's securityRules).\n";
|
|
190
|
+
ddl += `-- Set \`disableDefaultPolicies: true\` on "${table.slug}" to drop these and own its RLS outright.\n`;
|
|
191
|
+
}
|
|
192
|
+
announced = policy.ruleKey;
|
|
193
|
+
ddl += statementsToDdl(renderPolicyStatements(table, policy));
|
|
194
|
+
}
|
|
195
|
+
ddl += "\n";
|
|
196
|
+
}
|
|
197
|
+
for (const table of plan.tables.filter((t) => t.kind === "junction")) {
|
|
198
|
+
ddl += `${renderEnableRls(table)}\n`;
|
|
199
|
+
ddl += "\n";
|
|
200
|
+
if (table.policies.length === 0) continue;
|
|
201
|
+
const declaringSlugs = (table.declaringSlugs ?? []).join("\", \"");
|
|
202
|
+
ddl += `-- Derived by Rebase for the junction "${table.table}" (no collection declares it).\n`;
|
|
203
|
+
ddl += "-- Reads require both endpoint rows to be visible; writes follow the update\n";
|
|
204
|
+
ddl += `-- rules of "${declaringSlugs}". Set \`disableDefaultPolicies: true\` on the\n`;
|
|
205
|
+
ddl += "-- declaring collection(s) to drop these and police the junction yourself.\n";
|
|
206
|
+
for (const policy of table.policies) ddl += statementsToDdl(renderPolicyStatements(table, policy));
|
|
207
|
+
ddl += "\n";
|
|
208
|
+
}
|
|
209
|
+
return ddl;
|
|
210
|
+
}
|
|
211
|
+
function renderSearchDdl(plan) {
|
|
212
|
+
const tables = plan.tables.filter((t) => t.search);
|
|
213
|
+
if (tables.length === 0) return "";
|
|
214
|
+
const extensions = Array.from(new Set(tables.flatMap((t) => searchExtensionStatements(t.search))));
|
|
215
|
+
const helpers = Array.from(new Set(tables.flatMap((t) => searchHelperFunctions(t.search))));
|
|
216
|
+
let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n";
|
|
217
|
+
ddl += "--\n";
|
|
218
|
+
ddl += "-- Full-text search for the collections declaring a `search` block.\n";
|
|
219
|
+
ddl += "-- Applied by Rebase, not by Atlas — see generatePostgresSearchDdl.\n\n";
|
|
220
|
+
extensions.forEach((s) => {
|
|
221
|
+
ddl += `${s}\n`;
|
|
222
|
+
});
|
|
223
|
+
if (extensions.length > 0) ddl += "\n";
|
|
224
|
+
helpers.forEach((s) => {
|
|
225
|
+
ddl += `${s}\n\n`;
|
|
226
|
+
});
|
|
227
|
+
for (const table of tables) {
|
|
228
|
+
const spec = table.search;
|
|
229
|
+
const target = `"${table.schema}"."${table.table}"`;
|
|
230
|
+
searchStampGuards(spec).forEach((s) => {
|
|
231
|
+
ddl += `${s}\n`;
|
|
232
|
+
});
|
|
233
|
+
ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${searchColumnDefinition(spec)};\n`;
|
|
234
|
+
const fuzzy = fuzzyColumnDefinition(spec);
|
|
235
|
+
if (fuzzy) ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${fuzzy};\n`;
|
|
236
|
+
searchColumnStamps(spec).forEach((s) => {
|
|
237
|
+
ddl += `${s.sql}\n`;
|
|
238
|
+
});
|
|
239
|
+
searchIndexStatements(spec).forEach((s) => {
|
|
240
|
+
ddl += `${s}\n`;
|
|
241
|
+
});
|
|
242
|
+
ddl += "\n";
|
|
243
|
+
}
|
|
244
|
+
return ddl;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Glob patterns telling Atlas to leave an object alone.
|
|
248
|
+
*
|
|
249
|
+
* Fully qualified, `schema.table.object`, matching the include list. The
|
|
250
|
+
* two-part form is what Atlas wants when the connection URL scopes it to one
|
|
251
|
+
* schema and is *silently ignored* otherwise: it reads `posts.search_vector` as
|
|
252
|
+
* a table named `search_vector` in a schema named `posts`, matches nothing, and
|
|
253
|
+
* reports no error for the pattern that never fired.
|
|
254
|
+
*/
|
|
255
|
+
var searchExcludePatternsOf = (plan) => {
|
|
256
|
+
const patterns = [];
|
|
257
|
+
for (const table of plan.tables) {
|
|
258
|
+
if (!table.search) continue;
|
|
259
|
+
for (const column of table.columns) {
|
|
260
|
+
if (column.source.kind !== "search") continue;
|
|
261
|
+
patterns.push(`${table.schema}.${table.table}.${column.column}`);
|
|
262
|
+
}
|
|
263
|
+
for (const index of searchIndexNames(table.search)) patterns.push(`${table.schema}.${table.table}.${index}`);
|
|
264
|
+
}
|
|
265
|
+
return patterns;
|
|
266
|
+
};
|
|
267
|
+
function renderVectorDdl(plan) {
|
|
268
|
+
const tables = plan.tables.filter((t) => t.vectorColumns.length > 0);
|
|
269
|
+
if (tables.length === 0) return "";
|
|
270
|
+
let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n";
|
|
271
|
+
ddl += "--\n";
|
|
272
|
+
ddl += "-- Vector columns and their ANN indexes, for the collections declaring a\n";
|
|
273
|
+
ddl += "-- `vector` property. Applied by Rebase, not by Atlas — see\n";
|
|
274
|
+
ddl += "-- generatePostgresVectorDdl.\n\n";
|
|
275
|
+
ddl += vectorExtensionDeclared(plan.options.databaseExtensions) ? `${vectorExtensionStatement()}\n\n` : `-- pgvector is not installed from here: no database declared it. To let Rebase
|
|
276
|
+
-- install it, add ${VECTOR_EXTENSION_OPT_IN} in config/resources.ts.\n-- Otherwise install it once by hand; the column below needs the type either way.
|
|
277
|
+
|
|
278
|
+
`;
|
|
279
|
+
for (const table of tables) {
|
|
280
|
+
const target = `"${table.schema}"."${table.table}"`;
|
|
281
|
+
for (const spec of table.vectorColumns) {
|
|
282
|
+
ddl += `${vectorDimensionGuard(spec)}\n`;
|
|
283
|
+
ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${vectorColumnDefinition(spec)};\n`;
|
|
284
|
+
}
|
|
285
|
+
if (table.vector) {
|
|
286
|
+
vectorIndexStatements(table.vector).forEach((s) => {
|
|
287
|
+
ddl += `${s}\n`;
|
|
288
|
+
});
|
|
289
|
+
for (const skip of table.vector.skipped) ddl += `-- No ANN index on "${skip.schema}"."${skip.table}"."${skip.column}": ${skip.reason}\n`;
|
|
290
|
+
}
|
|
291
|
+
ddl += "\n";
|
|
292
|
+
}
|
|
293
|
+
return ddl;
|
|
294
|
+
}
|
|
295
|
+
/** @see searchExcludePatternsOf */
|
|
296
|
+
var vectorExcludePatternsOf = (plan) => {
|
|
297
|
+
const patterns = [];
|
|
298
|
+
for (const table of plan.tables) {
|
|
299
|
+
const names = [...table.vectorColumns.map((spec) => spec.column), ...table.vector ? vectorIndexNames(table.vector) : []];
|
|
300
|
+
for (const name of names) patterns.push(`${table.schema}.${table.table}.${name}`);
|
|
301
|
+
}
|
|
302
|
+
return patterns;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* `autoValue: "on_update"`, as the file Rebase applies itself.
|
|
306
|
+
*
|
|
307
|
+
* Empty when nothing declares one — the caller writes no file then, and removes
|
|
308
|
+
* a stale one.
|
|
309
|
+
*/
|
|
310
|
+
function renderTriggersDdl(plan) {
|
|
311
|
+
const tables = plan.tables.filter((t) => t.triggers.length > 0);
|
|
312
|
+
if (tables.length === 0) return "";
|
|
313
|
+
let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n";
|
|
314
|
+
ddl += "--\n";
|
|
315
|
+
ddl += "-- `updated_at` triggers, for the properties declaring `autoValue: \"on_update\"`.\n";
|
|
316
|
+
ddl += "-- Applied by Rebase, not by Atlas — a trigger is a function plus a binding,\n";
|
|
317
|
+
ddl += "-- and Atlas's free tier refuses to parse a desired state containing one.\n\n";
|
|
318
|
+
for (const statement of plan.functions.filter(isTriggerFunction)) ddl += `${statement}\n\n`;
|
|
319
|
+
for (const table of tables) {
|
|
320
|
+
for (const trigger of table.triggers) triggerStatements(trigger).forEach((s) => {
|
|
321
|
+
ddl += `${s}\n`;
|
|
322
|
+
});
|
|
323
|
+
ddl += "\n";
|
|
324
|
+
}
|
|
325
|
+
return ddl;
|
|
326
|
+
}
|
|
327
|
+
var isTriggerFunction = (statement) => statement.includes("RETURNS trigger");
|
|
328
|
+
/** @see searchExcludePatternsOf */
|
|
329
|
+
var triggerExcludePatternsOf = (plan) => plan.tables.flatMap((table) => table.triggers.map((t) => `${table.schema}.${table.table}.${t.name}`));
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/schema/generate-postgres-ddl-logic.ts
|
|
332
|
+
var generate_postgres_ddl_logic_exports = /* @__PURE__ */ __exportAll({
|
|
333
|
+
generatePostgresDdl: () => generatePostgresDdl,
|
|
334
|
+
generatePostgresPoliciesDdl: () => generatePostgresPoliciesDdl,
|
|
335
|
+
generatePostgresSearchDdl: () => generatePostgresSearchDdl,
|
|
336
|
+
generatePostgresTriggersDdl: () => generatePostgresTriggersDdl,
|
|
337
|
+
generatePostgresVectorDdl: () => generatePostgresVectorDdl,
|
|
338
|
+
planCollectionPolicies: () => planCollectionPolicies,
|
|
339
|
+
planJunctionTables: () => planJunctionTables,
|
|
340
|
+
searchColumnDependencies: () => searchColumnDependencies,
|
|
341
|
+
searchExcludePatterns: () => searchExcludePatterns,
|
|
342
|
+
triggerExcludePatterns: () => triggerExcludePatterns,
|
|
343
|
+
vectorExcludePatterns: () => vectorExcludePatterns
|
|
344
|
+
});
|
|
345
|
+
/**
|
|
346
|
+
* `drizzle/schema.sql` — the desired state Atlas diffs the database against.
|
|
347
|
+
*
|
|
348
|
+
* Synchronous. It was `async` with nothing to await, which made every caller
|
|
349
|
+
* (the writer script, the doctor, the live editor, two gates) carry a promise
|
|
350
|
+
* for no reason and made the one place that forgot to await it silently compare
|
|
351
|
+
* `[object Promise]`.
|
|
352
|
+
*/
|
|
353
|
+
var generatePostgresDdl = (allCollections, options = {}) => renderPostgresDdl(planSchema(allCollections), options);
|
|
354
|
+
/** `drizzle/policies.sql` — the RLS policies. Atlas has no model for a policy. */
|
|
355
|
+
var generatePostgresPoliciesDdl = (allCollections) => renderPoliciesDdl(planSchema(allCollections));
|
|
356
|
+
/**
|
|
357
|
+
* `drizzle/search.sql` — everything a `search` block needs, as a file Rebase
|
|
358
|
+
* applies itself.
|
|
359
|
+
*
|
|
360
|
+
* Search is one of the parts of the schema Atlas does not own, for two
|
|
361
|
+
* independent reasons and either alone would be enough:
|
|
362
|
+
*
|
|
363
|
+
* 1. Its free tier refuses to *parse* a desired-state file that so much as
|
|
364
|
+
* contains a function — "functions and procedures are available to
|
|
365
|
+
* logged-in users only". A generated `tsvector` column cannot avoid one:
|
|
366
|
+
* `unaccent` is STABLE and jsonb flattening needs a set-returning function,
|
|
367
|
+
* so both have to be wrapped in an IMMUTABLE helper to be legal in a
|
|
368
|
+
* generated column at all.
|
|
369
|
+
* 2. Even with the file accepted, Atlas *wipes* the dev database it diffs
|
|
370
|
+
* against, so a helper seeded there beforehand is gone by the time the plan
|
|
371
|
+
* is analysed. There is no hook to reinstate it.
|
|
372
|
+
*
|
|
373
|
+
* Empty when nothing opted in — the caller writes no file then.
|
|
374
|
+
*/
|
|
375
|
+
var generatePostgresSearchDdl = (allCollections) => renderSearchDdl(planSchema(allCollections));
|
|
376
|
+
/**
|
|
377
|
+
* Glob patterns telling Atlas to leave the search column and its index alone.
|
|
378
|
+
*
|
|
379
|
+
* Without these, a desired state that omits search reads to Atlas as an
|
|
380
|
+
* instruction to drop the column — taking the index and the whole search
|
|
381
|
+
* feature with it on the next push.
|
|
382
|
+
*/
|
|
383
|
+
var searchExcludePatterns = (allCollections) => searchExcludePatternsOf(planSchema(allCollections));
|
|
384
|
+
/**
|
|
385
|
+
* `drizzle/vector.sql` — the `vector` columns, their ANN indexes and, when a
|
|
386
|
+
* database declared it, the extension.
|
|
387
|
+
*
|
|
388
|
+
* Vector is out of Atlas's hands for a narrower reason than search: not that
|
|
389
|
+
* Atlas refuses to parse the file, but that it cannot *execute* it. To compute
|
|
390
|
+
* a desired state Atlas materialises `schema.sql` in a dev database, that
|
|
391
|
+
* database is created empty, and Atlas empties it again at the start of every
|
|
392
|
+
* run — so `VECTOR(384)` resolves against a database that structurally cannot
|
|
393
|
+
* have pgvector, and every push dies with `type "vector" does not exist`.
|
|
394
|
+
*/
|
|
395
|
+
var generatePostgresVectorDdl = (allCollections, options = {}) => renderVectorDdl(planSchema(allCollections, { databaseExtensions: options.extensions }));
|
|
396
|
+
/** @see searchExcludePatterns */
|
|
397
|
+
var vectorExcludePatterns = (allCollections) => vectorExcludePatternsOf(planSchema(allCollections));
|
|
398
|
+
/**
|
|
399
|
+
* `drizzle/triggers.sql` — the `BEFORE UPDATE` triggers behind
|
|
400
|
+
* `autoValue: "on_update"`, and the one function they share.
|
|
401
|
+
*
|
|
402
|
+
* A trigger is a function plus a binding, so it is carved out of Atlas's view
|
|
403
|
+
* for the first of the two reasons search is. Empty when no property declares
|
|
404
|
+
* `on_update` — the caller writes no file then, and removes a stale one.
|
|
405
|
+
*/
|
|
406
|
+
var generatePostgresTriggersDdl = (allCollections) => renderTriggersDdl(planSchema(allCollections));
|
|
407
|
+
/** @see searchExcludePatterns */
|
|
408
|
+
var triggerExcludePatterns = (allCollections) => triggerExcludePatternsOf(planSchema(allCollections));
|
|
409
|
+
var planOf = (collections) => planSchema(collections);
|
|
410
|
+
/**
|
|
411
|
+
* The junction tables a bundle's many-to-many relations imply.
|
|
412
|
+
*
|
|
413
|
+
* Derived from the same plan the derived RLS comes from, so a table created
|
|
414
|
+
* here always has policies planned for it — a junction with row-level security
|
|
415
|
+
* left off is readable and writable by every signed-in user.
|
|
416
|
+
*/
|
|
417
|
+
var planJunctionTables = (allCollections) => planOf(allCollections).tables.filter((table) => table.kind === "junction").map((table) => ({
|
|
418
|
+
schema: table.schema,
|
|
419
|
+
table: table.table,
|
|
420
|
+
columns: table.columns.map((c) => ({
|
|
421
|
+
name: c.column,
|
|
422
|
+
type: renderPgType(c.type),
|
|
423
|
+
legacyName: c.legacyColumn
|
|
424
|
+
})),
|
|
425
|
+
createTable: `CREATE TABLE IF NOT EXISTS "${table.schema}"."${table.table}" (` + table.columns.map((c) => `"${c.column}" ${renderPgType(c.type)} NOT NULL`).join(", ") + `, PRIMARY KEY (${table.primaryKey.map((c) => `"${c}"`).join(", ")}));`,
|
|
426
|
+
foreignKeys: table.columns.map((c) => c.foreignKey).filter((fk) => fk !== void 0)
|
|
427
|
+
}));
|
|
428
|
+
/**
|
|
429
|
+
* The per-table RLS plan for the declared collections and their junctions, as
|
|
430
|
+
* executable statements — what the managed runtime applies at boot so a freshly
|
|
431
|
+
* provisioned tenant database serves data instead of 401ing every read.
|
|
432
|
+
*
|
|
433
|
+
* The same policies `policies.sql` carries, from the same plan, so boot and
|
|
434
|
+
* `db push` produce identical RLS from identical collections.
|
|
435
|
+
*/
|
|
436
|
+
var planCollectionPolicies = (allCollections) => {
|
|
437
|
+
const plan = planOf(allCollections);
|
|
438
|
+
return [...plan.tables.filter((t) => t.kind === "collection"), ...plan.tables.filter((t) => t.kind === "junction")].map((table) => ({
|
|
439
|
+
schema: table.schema,
|
|
440
|
+
table: table.table,
|
|
441
|
+
qualified: table.qualified,
|
|
442
|
+
enableRls: `ALTER TABLE "${table.schema}"."${table.table}" ENABLE ROW LEVEL SECURITY;`,
|
|
443
|
+
policyStatements: table.policies.flatMap((policy) => renderPolicyStatements(table, policy))
|
|
444
|
+
}));
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* The `generated column → column it reads` edges a project's `search` blocks
|
|
448
|
+
* imply, derived from the collections alone.
|
|
449
|
+
*
|
|
450
|
+
* The same edges `queryGeneratedColumnDependencies` reads out of `pg_depend`,
|
|
451
|
+
* but available where no database is: `rebase db generate` writes a migration
|
|
452
|
+
* that will run somewhere else, later, against a database it cannot inspect.
|
|
453
|
+
* A spec's `fields[].column` is the physical column each search field starts
|
|
454
|
+
* at, which is exactly what the generated expression depends on.
|
|
455
|
+
*/
|
|
456
|
+
var searchColumnDependencies = (allCollections) => {
|
|
457
|
+
const dependencies = [];
|
|
458
|
+
for (const table of planOf(allCollections).tables) {
|
|
459
|
+
const spec = table.search;
|
|
460
|
+
if (!spec) continue;
|
|
461
|
+
const generated = [spec.column, ...spec.fuzzy ? [spec.fuzzy.column] : []];
|
|
462
|
+
const sources = new Set(spec.fields.map((field) => field.column));
|
|
463
|
+
for (const column of generated) for (const dependsOn of sources) dependencies.push({
|
|
464
|
+
schema: table.schema,
|
|
465
|
+
table: table.table,
|
|
466
|
+
column,
|
|
467
|
+
dependsOn
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return dependencies;
|
|
471
|
+
};
|
|
472
|
+
//#endregion
|
|
473
|
+
export { generatePostgresVectorDdl as a, planJunctionTables as c, generatePostgresTriggersDdl as i, renderColumnDefinition as l, generatePostgresPoliciesDdl as n, generate_postgres_ddl_logic_exports as o, generatePostgresSearchDdl as r, planCollectionPolicies as s, generatePostgresDdl as t, renderPgType as u };
|
|
474
|
+
|
|
475
|
+
//# sourceMappingURL=generate-postgres-ddl-logic-CR2xcS7e.js.map
|