@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g19596ef
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/README.md +16 -0
- package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
- package/dist/BranchService-UgKa416x.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +187 -13
- package/dist/PostgresBootstrapper.d.ts +101 -2
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +78 -14
- package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
- package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
- package/dist/backup/backup-cli.d.ts +75 -2
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
- package/dist/backup-cli-yiKd1GsC.js.map +1 -0
- package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
- package/dist/backup-service-AZWc2wvI.js.map +1 -0
- package/dist/boot-ddl-B9ZmBJ75.js +105 -0
- package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
- package/dist/cli-console-DGee3Zl7.js +225 -0
- package/dist/cli-console-DGee3Zl7.js.map +1 -0
- package/dist/cli-console.d.ts +39 -0
- package/dist/cli-errors-oVUuD8k0.js +2180 -0
- package/dist/cli-errors-oVUuD8k0.js.map +1 -0
- package/dist/cli-errors.d.ts +133 -9
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
- package/dist/cli-flags-DnPHXjRH.js.map +1 -0
- package/dist/cli-flags.d.ts +15 -0
- package/dist/cli-helpers.d.ts +88 -34
- package/dist/cli-output.d.ts +16 -28
- package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
- package/dist/cli-prompt-egtNKkZl.js.map +1 -0
- package/dist/cli-prompt.d.ts +19 -0
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli-scratch-database.d.ts +33 -8
- package/dist/cli.js +826 -230
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
- package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
- package/dist/connection-C938nR3E.js.map +1 -0
- package/dist/connection.d.ts +13 -2
- package/dist/data-transformer.d.ts +0 -8
- package/dist/databasePoolManager-DZgnBOgj.js +119 -0
- package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
- package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
- package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
- package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
- package/dist/doctor-CUAAOFZm.js.map +1 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
- package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
- package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
- package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
- package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
- package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
- package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
- package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
- package/dist/generate-files-OoRJLEdk.js +199 -0
- package/dist/generate-files-OoRJLEdk.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
- package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
- package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
- package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
- package/dist/generated-sql.d.ts +34 -0
- package/dist/history/HistoryService.d.ts +64 -5
- package/dist/history/ensure-history-table.d.ts +63 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +139 -16738
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
- package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
- package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
- package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
- package/dist/open-rebase-CAjWpPjs.js +20895 -0
- package/dist/open-rebase-CAjWpPjs.js.map +1 -0
- package/dist/open-rebase.d.ts +94 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
- package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
- package/dist/plan-schema-CsNgK0qi.js.map +1 -0
- package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
- package/dist/policy-drift-BKC50y2p.js.map +1 -0
- package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
- package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
- package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
- package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
- package/dist/render-ddl-CrfEe__B.js.map +1 -0
- package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
- package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
- package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
- package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
- package/dist/schema/atlas-exclude.d.ts +42 -0
- package/dist/schema/auth-schema.d.ts +172 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/carved-out-migration.d.ts +5 -4
- package/dist/schema/classify-change.d.ts +29 -1
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +38 -21
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +50 -2
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +53 -1
- package/dist/schema/ensure-collection-policies.d.ts +95 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generate-schema-commit.d.ts +12 -0
- package/dist/schema/generated-schema-diff.d.ts +7 -0
- package/dist/schema/introspect-db-logic.d.ts +63 -0
- package/dist/schema/introspect-db-queries.d.ts +21 -2
- package/dist/schema/introspect-db-search.d.ts +22 -0
- package/dist/schema/introspect-db-storage.d.ts +83 -0
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +29 -21
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-baseline.d.ts +33 -0
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +127 -1
- package/dist/schema/plan/plan-schema.d.ts +16 -8
- package/dist/schema/plan/render-ddl.d.ts +8 -1
- package/dist/schema/plan/types.d.ts +45 -11
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/schema/undeclarable-constraints.d.ts +59 -0
- package/dist/search-column-BM-GV6vH.js +442 -0
- package/dist/search-column-BM-GV6vH.js.map +1 -0
- package/dist/security/policy-drift.d.ts +1 -1
- package/dist/security/rls-enforcement.d.ts +39 -4
- package/dist/services/BranchService.d.ts +29 -1
- package/dist/services/FetchService.d.ts +63 -12
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +106 -3
- package/dist/services/cdc/CdcListener.d.ts +26 -10
- package/dist/services/cdc/identity-columns.d.ts +19 -0
- package/dist/services/cdc/trigger-cdc.d.ts +180 -18
- package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
- package/dist/services/channel-bus/index.d.ts +1 -1
- package/dist/services/channel-history.d.ts +32 -5
- package/dist/services/collection-helpers.d.ts +26 -1
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +95 -6
- package/dist/services/realtimeService.d.ts +467 -53
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +85 -0
- package/dist/services/soft-delete.d.ts +12 -0
- package/dist/services/sql-script.d.ts +275 -0
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +22 -0
- package/dist/services/write-transaction-scope.d.ts +15 -3
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DB6xJv3p.js +873 -0
- package/dist/source-DB6xJv3p.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/connection-string.d.ts +31 -0
- package/dist/utils/drizzle-conditions.d.ts +63 -3
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
- package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
- package/dist/websocket-DUiiwQd2.js +1632 -0
- package/dist/websocket-DUiiwQd2.js.map +1 -0
- package/dist/websocket.d.ts +63 -18
- package/package.json +7 -7
- package/dist/BranchService-DGPL6G_C.js.map +0 -1
- package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
- package/dist/arg-Dni7MzLB.js.map +0 -1
- package/dist/backup-cli-24v4OlSp.js.map +0 -1
- package/dist/backup-service-HQ9GC4tN.js.map +0 -1
- package/dist/cli-errors-B8qHg02P.js +0 -798
- package/dist/cli-errors-B8qHg02P.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-Cb2thZ8s.js.map +0 -1
- package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
- package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
- package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
- package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
- package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
- package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
- package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
- package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
- package/dist/plan-schema-C0fxM8dY.js.map +0 -1
- package/dist/policy-drift-DljYdrpW.js.map +0 -1
- package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
- package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
__createRequire(import.meta.url);
|
|
3
|
+
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
+
import { i as outError, n as describeFailure, r as out, t as chalk } from "./source-DB6xJv3p.js";
|
|
5
|
+
import { t as generateSchema } from "./generate-drizzle-schema-logic-BaT0H7Gq.js";
|
|
6
|
+
import { a as generatePostgresVectorDdl, i as generatePostgresTriggersDdl, n as generatePostgresPoliciesDdl, r as generatePostgresSearchDdl, t as generatePostgresDdl } from "./generate-postgres-ddl-logic-B4k9djAV.js";
|
|
7
|
+
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
8
|
+
import { declaredDatabaseExtensions } from "@rebasepro/types";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import * as fs$1 from "fs";
|
|
11
|
+
import { promises } from "fs";
|
|
12
|
+
import { pathToFileURL } from "url";
|
|
13
|
+
//#region src/schema/generate-next-step.ts
|
|
14
|
+
/**
|
|
15
|
+
* The line `rebase schema generate` ends on.
|
|
16
|
+
*
|
|
17
|
+
* Its own module because `generate-drizzle-schema.ts` uses `import.meta` to
|
|
18
|
+
* decide whether it was executed directly, which the driver's jest suite cannot
|
|
19
|
+
* load at all — so the one sentence in that file with a decision in it had no
|
|
20
|
+
* test and could not have one where it stood.
|
|
21
|
+
*
|
|
22
|
+
* Printed only by `rebase schema generate` itself. `db push` and `db generate`
|
|
23
|
+
* run the same generator as their first step, and inside them the line was
|
|
24
|
+
* wrong advice: `db push` was told to run `db generate`, and `db generate` to
|
|
25
|
+
* run itself.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* A command name, highlighted the way the rest of the driver's output is: with
|
|
29
|
+
* chalk, which drops the colour under `NO_COLOR`, when stdout is not a terminal,
|
|
30
|
+
* and in CI logs. The escapes used to be written by hand here, so
|
|
31
|
+
* `NO_COLOR=1 rebase schema generate > log` put `\x1b[1m\x1b[44m…` in the file.
|
|
32
|
+
*/
|
|
33
|
+
var command = (text) => chalk.bold.bgBlue.black(text);
|
|
34
|
+
/**
|
|
35
|
+
* What to do with the file that was just generated.
|
|
36
|
+
*
|
|
37
|
+
* Not the same answer on the managed development database: `rebase db generate`
|
|
38
|
+
* plans with Atlas, Atlas needs a second empty database to diff against, and
|
|
39
|
+
* PGlite serves exactly one — so the CLI refuses that command outright there.
|
|
40
|
+
* This line recommended it unconditionally, which meant every stock scaffold
|
|
41
|
+
* was told, by the tool itself, to run something the same tool would refuse.
|
|
42
|
+
*
|
|
43
|
+
* The kind arrives as `REBASE_DEV_DATABASE_KIND`, set by the CLI from the
|
|
44
|
+
* ordered resolution in `dev-db/resolve.ts`. It is a pure decision — nothing is
|
|
45
|
+
* started to make it — and the driver cannot make it for itself: on the managed
|
|
46
|
+
* path `DATABASE_URL` is a perfectly ordinary connection string to a Postgres
|
|
47
|
+
* on loopback. Absent (the driver run directly, outside the CLI) keeps the
|
|
48
|
+
* original wording.
|
|
49
|
+
*/
|
|
50
|
+
var nextStepAfterGenerate = () => {
|
|
51
|
+
if (process.env.REBASE_DEV_DATABASE_KIND === "managed") return `Start or restart ${command("rebase dev")} — boot applies your collections to the managed development database.`;
|
|
52
|
+
return `You can now run ${command("rebase db generate")} to generate the SQL migration files.`;
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/schema/generate-files.ts
|
|
56
|
+
var generate_files_exports = /* @__PURE__ */ __exportAll({
|
|
57
|
+
GenerationFailed: () => GenerationFailed,
|
|
58
|
+
writeDrizzleSchema: () => writeDrizzleSchema,
|
|
59
|
+
writePostgresDdl: () => writePostgresDdl
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* The two generators `rebase db push` runs as its step 1, as functions.
|
|
63
|
+
*
|
|
64
|
+
* They used to exist only as scripts, each its own `tsx` process: the driver
|
|
65
|
+
* (itself a `tsx` process) spawned one for the Drizzle schema and then one for
|
|
66
|
+
* the DDL, and each paid a Node start, tsx's loader and an import of the whole
|
|
67
|
+
* server package before reading a single collection — about 0.5 s apiece, a
|
|
68
|
+
* second per push. The driver process is one-shot, so a fresh module graph per
|
|
69
|
+
* generation bought nothing: the collections cannot change under it.
|
|
70
|
+
*
|
|
71
|
+
* The scripts remain (`generate-drizzle-schema.ts`, `generate-postgres-ddl.ts`)
|
|
72
|
+
* for `--watch`, which re-imports the collections on every save and does need a
|
|
73
|
+
* process of its own — Node's module cache would serve it the first version
|
|
74
|
+
* forever.
|
|
75
|
+
*
|
|
76
|
+
* Both functions print their own success lines and their own error, exactly as
|
|
77
|
+
* the scripts did, and throw {@link GenerationFailed} after printing — so a
|
|
78
|
+
* caller can tell "the generator said why" from "the generator never ran".
|
|
79
|
+
*/
|
|
80
|
+
/** Thrown once the generator has printed why it failed. */
|
|
81
|
+
var GenerationFailed = class extends Error {
|
|
82
|
+
constructor(message, options) {
|
|
83
|
+
super(message, options);
|
|
84
|
+
this.name = "GenerationFailed";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/** The collections, in slug order so the output does not depend on the directory's. */
|
|
88
|
+
async function loadSortedCollections(collectionsPath) {
|
|
89
|
+
const loaded = await loadCollectionsFromDirectory(path.resolve(collectionsPath));
|
|
90
|
+
const collections = Array.isArray(loaded) ? [...loaded] : [];
|
|
91
|
+
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
92
|
+
return collections;
|
|
93
|
+
}
|
|
94
|
+
/** `src/schema.generated.ts`, from the collections. */
|
|
95
|
+
async function writeDrizzleSchema(options) {
|
|
96
|
+
try {
|
|
97
|
+
const schemaContent = generateSchema(await loadSortedCollections(options.collectionsPath));
|
|
98
|
+
if (options.outputPath) {
|
|
99
|
+
await promises.mkdir(path.dirname(options.outputPath), { recursive: true });
|
|
100
|
+
await promises.writeFile(options.outputPath, schemaContent);
|
|
101
|
+
out(`✅ Drizzle schema generated successfully at ${options.outputPath}`);
|
|
102
|
+
} else {
|
|
103
|
+
out("✅ Drizzle schema generated successfully.");
|
|
104
|
+
out(String(schemaContent));
|
|
105
|
+
}
|
|
106
|
+
if (options.nextStep !== false) out(nextStepAfterGenerate());
|
|
107
|
+
} catch (error) {
|
|
108
|
+
outError(`Error generating schema: ${describeFailure(error)}`);
|
|
109
|
+
throw new GenerationFailed("Drizzle schema generation failed", { cause: error });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Where a project declares its resources, relative to the config directory. */
|
|
113
|
+
var RESOURCE_ENTRIES = [
|
|
114
|
+
"resources.ts",
|
|
115
|
+
"resources.js",
|
|
116
|
+
"resources/index.ts",
|
|
117
|
+
"resources/index.js"
|
|
118
|
+
];
|
|
119
|
+
/**
|
|
120
|
+
* Evaluate the project's `resources.ts`, so `declaredDatabaseExtensions()` can
|
|
121
|
+
* answer.
|
|
122
|
+
*
|
|
123
|
+
* The DDL generator loads only the collections directory, so nothing else in it
|
|
124
|
+
* would ever import the resources module — and a permission that reads as
|
|
125
|
+
* absent because nobody loaded it is the worst shape this could take: the push
|
|
126
|
+
* succeeds, `vector.sql` quietly omits the `CREATE EXTENSION`, and the failure
|
|
127
|
+
* lands one statement later looking like a database problem.
|
|
128
|
+
*
|
|
129
|
+
* Imported directly rather than through the config index, for the reason
|
|
130
|
+
* `boot/resource-loading.ts` gives: re-exporting from the index would work only
|
|
131
|
+
* for projects that remember to.
|
|
132
|
+
*
|
|
133
|
+
* A project with no resources module is the normal case and declares nothing.
|
|
134
|
+
* An import that *throws* is not swallowed — a resources module that does not
|
|
135
|
+
* evaluate is a configuration error, and treating it as "declared nothing"
|
|
136
|
+
* would turn it into a silently missing extension.
|
|
137
|
+
*/
|
|
138
|
+
async function loadDeclaredResources(configDir) {
|
|
139
|
+
const entry = RESOURCE_ENTRIES.map((name) => path.join(configDir, name)).find((candidate) => fs$1.existsSync(candidate));
|
|
140
|
+
if (!entry) return;
|
|
141
|
+
await import(pathToFileURL(entry).href);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `schema.sql` (Atlas's desired state) and, beside it, `policies.sql`,
|
|
145
|
+
* `search.sql`, `vector.sql` and `triggers.sql` — the parts Atlas does not own.
|
|
146
|
+
*/
|
|
147
|
+
async function writePostgresDdl(options) {
|
|
148
|
+
try {
|
|
149
|
+
const resolvedPath = path.resolve(options.collectionsPath);
|
|
150
|
+
await loadDeclaredResources(path.dirname(resolvedPath));
|
|
151
|
+
const collections = await loadSortedCollections(resolvedPath);
|
|
152
|
+
const ddlContent = generatePostgresDdl(collections, {
|
|
153
|
+
includePolicies: false,
|
|
154
|
+
includeSearch: false,
|
|
155
|
+
includeVector: false
|
|
156
|
+
});
|
|
157
|
+
const policiesContent = generatePostgresPoliciesDdl(collections);
|
|
158
|
+
const searchContent = generatePostgresSearchDdl(collections);
|
|
159
|
+
const vectorContent = generatePostgresVectorDdl(collections, { extensions: declaredDatabaseExtensions() });
|
|
160
|
+
const triggersContent = generatePostgresTriggersDdl(collections);
|
|
161
|
+
const outputPath = options.outputPath;
|
|
162
|
+
if (outputPath) {
|
|
163
|
+
const outputDir = path.dirname(outputPath);
|
|
164
|
+
await promises.mkdir(outputDir, { recursive: true });
|
|
165
|
+
await promises.writeFile(outputPath, ddlContent);
|
|
166
|
+
out(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);
|
|
167
|
+
const policiesPath = path.join(outputDir, "policies.sql");
|
|
168
|
+
await promises.writeFile(policiesPath, policiesContent);
|
|
169
|
+
out(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);
|
|
170
|
+
const searchPath = path.join(outputDir, "search.sql");
|
|
171
|
+
if (searchContent) {
|
|
172
|
+
await promises.writeFile(searchPath, searchContent);
|
|
173
|
+
out(`✅ PostgreSQL search DDL generated successfully at ${searchPath}`);
|
|
174
|
+
} else if (fs$1.existsSync(searchPath)) await promises.rm(searchPath);
|
|
175
|
+
const vectorPath = path.join(outputDir, "vector.sql");
|
|
176
|
+
if (vectorContent) {
|
|
177
|
+
await promises.writeFile(vectorPath, vectorContent);
|
|
178
|
+
out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);
|
|
179
|
+
} else if (fs$1.existsSync(vectorPath)) await promises.rm(vectorPath);
|
|
180
|
+
const triggersPath = path.join(outputDir, "triggers.sql");
|
|
181
|
+
if (triggersContent) {
|
|
182
|
+
await promises.writeFile(triggersPath, triggersContent);
|
|
183
|
+
out(`✅ PostgreSQL trigger DDL generated successfully at ${triggersPath}`);
|
|
184
|
+
} else if (fs$1.existsSync(triggersPath)) await promises.rm(triggersPath);
|
|
185
|
+
} else {
|
|
186
|
+
out("✅ PostgreSQL DDL generated successfully.");
|
|
187
|
+
out(String(ddlContent));
|
|
188
|
+
out("\n✅ PostgreSQL Policies DDL generated successfully.");
|
|
189
|
+
out(String(policiesContent));
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
outError(`Error generating DDL schema: ${describeFailure(error)}`);
|
|
193
|
+
throw new GenerationFailed("Postgres DDL generation failed", { cause: error });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { writeDrizzleSchema as n, writePostgresDdl as r, generate_files_exports as t };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=generate-files-OoRJLEdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-files-OoRJLEdk.js","names":[],"sources":["../src/schema/generate-next-step.ts","../src/schema/generate-files.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 * Printed only by `rebase schema generate` itself. `db push` and `db generate`\n * run the same generator as their first step, and inside them the line was\n * wrong advice: `db push` was told to run `db generate`, and `db generate` to\n * run itself.\n */\nimport chalk from \"chalk\";\n\n/**\n * A command name, highlighted the way the rest of the driver's output is: with\n * chalk, which drops the colour under `NO_COLOR`, when stdout is not a terminal,\n * and in CI logs. The escapes used to be written by hand here, so\n * `NO_COLOR=1 rebase schema generate > log` put `\\x1b[1m\\x1b[44m…` in the file.\n */\nconst command = (text: string): string => chalk.bold.bgBlue.black(text);\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 ${command(\"rebase dev\")} — boot applies your collections to the managed development database.`;\n }\n\n return `You can now run ${command(\"rebase db generate\")} 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 { CollectionConfig, declaredDatabaseExtensions } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport {\n generatePostgresDdl,\n generatePostgresPoliciesDdl,\n generatePostgresSearchDdl,\n generatePostgresTriggersDdl,\n generatePostgresVectorDdl\n} from \"./generate-postgres-ddl-logic\";\nimport { describeFailure, out, outError } from \"../cli-output\";\nimport { nextStepAfterGenerate } from \"./generate-next-step\";\n\n/**\n * The two generators `rebase db push` runs as its step 1, as functions.\n *\n * They used to exist only as scripts, each its own `tsx` process: the driver\n * (itself a `tsx` process) spawned one for the Drizzle schema and then one for\n * the DDL, and each paid a Node start, tsx's loader and an import of the whole\n * server package before reading a single collection — about 0.5 s apiece, a\n * second per push. The driver process is one-shot, so a fresh module graph per\n * generation bought nothing: the collections cannot change under it.\n *\n * The scripts remain (`generate-drizzle-schema.ts`, `generate-postgres-ddl.ts`)\n * for `--watch`, which re-imports the collections on every save and does need a\n * process of its own — Node's module cache would serve it the first version\n * forever.\n *\n * Both functions print their own success lines and their own error, exactly as\n * the scripts did, and throw {@link GenerationFailed} after printing — so a\n * caller can tell \"the generator said why\" from \"the generator never ran\".\n */\n\n/** Thrown once the generator has printed why it failed. */\nexport class GenerationFailed extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"GenerationFailed\";\n }\n}\n\nexport interface GenerateFileOptions {\n /** The collections directory (or single module), resolved against the cwd. */\n collectionsPath: string;\n /** Where to write. Absent: print to stdout instead. */\n outputPath?: string;\n /**\n * End on the line that says what to run next. Only `rebase schema\n * generate` itself wants it: inside `db push` it told the push to run\n * `db generate`, and inside `db generate` to run itself.\n */\n nextStep?: boolean;\n}\n\n/** The collections, in slug order so the output does not depend on the directory's. */\nasync function loadSortedCollections(collectionsPath: string): Promise<CollectionConfig[]> {\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 const loaded = await loadCollectionsFromDirectory(path.resolve(collectionsPath));\n // A copy: the loader's array is its caller's, and in-process the same\n // collections are read again by the steps after this one.\n const collections: CollectionConfig[] = Array.isArray(loaded) ? [...loaded] : [];\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\n return collections;\n}\n\n/** `src/schema.generated.ts`, from the collections. */\nexport async function writeDrizzleSchema(options: GenerateFileOptions): Promise<void> {\n try {\n const collections = await loadSortedCollections(options.collectionsPath);\n const schemaContent = generateSchema(collections);\n\n if (options.outputPath) {\n await fsPromises.mkdir(path.dirname(options.outputPath), { recursive: true });\n await fsPromises.writeFile(options.outputPath, schemaContent);\n out(`✅ Drizzle schema generated successfully at ${options.outputPath}`);\n } else {\n out(\"✅ Drizzle schema generated successfully.\");\n out(String(schemaContent));\n }\n\n if (options.nextStep !== false) out(nextStepAfterGenerate());\n } catch (error) {\n outError(`Error generating schema: ${describeFailure(error)}`);\n // `rebase db push` runs this as step 1 and must stop on it: going on\n // applied whatever `src/schema.generated.ts` was already there.\n throw new GenerationFailed(\"Drizzle schema generation failed\", { cause: error });\n }\n}\n\n/** Where a project declares its resources, relative to the config directory. */\nconst RESOURCE_ENTRIES = [\"resources.ts\", \"resources.js\", \"resources/index.ts\", \"resources/index.js\"];\n\n/**\n * Evaluate the project's `resources.ts`, so `declaredDatabaseExtensions()` can\n * answer.\n *\n * The DDL generator loads only the collections directory, so nothing else in it\n * would ever import the resources module — and a permission that reads as\n * absent because nobody loaded it is the worst shape this could take: the push\n * succeeds, `vector.sql` quietly omits the `CREATE EXTENSION`, and the failure\n * lands one statement later looking like a database problem.\n *\n * Imported directly rather than through the config index, for the reason\n * `boot/resource-loading.ts` gives: re-exporting from the index would work only\n * for projects that remember to.\n *\n * A project with no resources module is the normal case and declares nothing.\n * An import that *throws* is not swallowed — a resources module that does not\n * evaluate is a configuration error, and treating it as \"declared nothing\"\n * would turn it into a silently missing extension.\n */\nasync function loadDeclaredResources(configDir: string): Promise<void> {\n const entry = RESOURCE_ENTRIES\n .map(name => path.join(configDir, name))\n .find(candidate => fs.existsSync(candidate));\n if (!entry) return;\n await import(pathToFileURL(entry).href);\n}\n\n/**\n * `schema.sql` (Atlas's desired state) and, beside it, `policies.sql`,\n * `search.sql`, `vector.sql` and `triggers.sql` — the parts Atlas does not own.\n */\nexport async function writePostgresDdl(options: GenerateFileOptions): Promise<void> {\n try {\n const resolvedPath = path.resolve(options.collectionsPath);\n\n // `<config>/collections` is the path this is given, so the config\n // directory is its parent — the same relationship `rebase resources`\n // and the boot loader assume.\n await loadDeclaredResources(path.dirname(resolvedPath));\n\n const collections = await loadSortedCollections(resolvedPath);\n\n // `schema.sql` is Atlas's desired state, and it carries neither the RLS\n // policies, nor the search apparatus, nor the vector columns: Atlas\n // manages none of them, and in the search and vector cases cannot. All\n // three are written beside it and applied by the CLI in their own right.\n const ddlContent = generatePostgresDdl(collections, {\n includePolicies: false,\n includeSearch: false,\n includeVector: false\n });\n const policiesContent = generatePostgresPoliciesDdl(collections);\n const searchContent = generatePostgresSearchDdl(collections);\n const vectorContent = generatePostgresVectorDdl(collections, {\n extensions: declaredDatabaseExtensions()\n });\n const triggersContent = generatePostgresTriggersDdl(collections);\n\n const outputPath = options.outputPath;\n if (outputPath) {\n const outputDir = path.dirname(outputPath);\n await fsPromises.mkdir(outputDir, { recursive: true });\n await fsPromises.writeFile(outputPath, ddlContent);\n out(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);\n\n const policiesPath = path.join(outputDir, \"policies.sql\");\n await fsPromises.writeFile(policiesPath, policiesContent);\n out(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);\n\n // Removed when the last `search` block goes: the CLI applies this\n // file whenever it exists, and a stale one would keep re-creating\n // functions for collections that no longer want them.\n const searchPath = path.join(outputDir, \"search.sql\");\n if (searchContent) {\n await fsPromises.writeFile(searchPath, searchContent);\n out(`✅ PostgreSQL search DDL generated successfully at ${searchPath}`);\n } else if (fs.existsSync(searchPath)) {\n await fsPromises.rm(searchPath);\n }\n\n // Removed when the last `vector` property goes, for the same reason\n // `search.sql` is: the CLI applies whichever of these files exists,\n // and a stale one would keep re-adding a column the collections no\n // longer declare — which Atlas, no longer told to exclude it, would\n // then plan a DROP for on the very next push.\n const vectorPath = path.join(outputDir, \"vector.sql\");\n if (vectorContent) {\n await fsPromises.writeFile(vectorPath, vectorContent);\n out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);\n } else if (fs.existsSync(vectorPath)) {\n await fsPromises.rm(vectorPath);\n }\n\n // `autoValue: \"on_update\"`, on the same terms as the two above: a\n // trigger is a function plus a binding, which Atlas's free tier\n // will not parse, and a stale file would keep re-installing a\n // trigger for a column nothing declares any more.\n const triggersPath = path.join(outputDir, \"triggers.sql\");\n if (triggersContent) {\n await fsPromises.writeFile(triggersPath, triggersContent);\n out(`✅ PostgreSQL trigger DDL generated successfully at ${triggersPath}`);\n } else if (fs.existsSync(triggersPath)) {\n await fsPromises.rm(triggersPath);\n }\n } else {\n out(\"✅ PostgreSQL DDL generated successfully.\");\n out(String(ddlContent));\n out(\"\\n✅ PostgreSQL Policies DDL generated successfully.\");\n out(String(policiesContent));\n }\n } catch (error) {\n outError(`Error generating DDL schema: ${describeFailure(error)}`);\n // Step 1 of `rebase db push` is this, and a failure has to stop it.\n // Going on made a resources module that does not evaluate — the single\n // most common cause here — report `✓ rebase db push completed\n // successfully` while Atlas applied whatever `drizzle/schema.sql`\n // happened to be on disk, sometimes minutes old. With no prior file the\n // same run died inside Atlas on `stat drizzle/schema.sql: no such file\n // or directory`, which hides the real cause two screens up.\n throw new GenerationFailed(\"Postgres DDL generation failed\", { cause: error });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
__createRequire(import.meta.url);
|
|
3
|
+
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
+
import { n as planSchema } from "./plan-schema-CsNgK0qi.js";
|
|
5
|
+
import { a as renderPolicyStatements, c as renderSearchDdl, d as searchExcludePatternsOf, f as triggerExcludePatternsOf, i as renderPoliciesDdl, l as renderTriggersDdl, o as renderPostgresDdl, p as vectorExcludePatternsOf, r as renderPgType, u as renderVectorDdl } from "./render-ddl-CrfEe__B.js";
|
|
6
|
+
import "@rebasepro/types";
|
|
7
|
+
import "@rebasepro/common";
|
|
8
|
+
//#region src/schema/generate-postgres-ddl-logic.ts
|
|
9
|
+
var generate_postgres_ddl_logic_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
generatePostgresDdl: () => generatePostgresDdl,
|
|
11
|
+
generatePostgresPoliciesDdl: () => generatePostgresPoliciesDdl,
|
|
12
|
+
generatePostgresSearchDdl: () => generatePostgresSearchDdl,
|
|
13
|
+
generatePostgresTriggersDdl: () => generatePostgresTriggersDdl,
|
|
14
|
+
generatePostgresVectorDdl: () => generatePostgresVectorDdl,
|
|
15
|
+
planCollectionPolicies: () => planCollectionPolicies,
|
|
16
|
+
planJunctionTables: () => planJunctionTables,
|
|
17
|
+
searchColumnDependencies: () => searchColumnDependencies,
|
|
18
|
+
searchExcludePatterns: () => searchExcludePatterns,
|
|
19
|
+
triggerExcludePatterns: () => triggerExcludePatterns,
|
|
20
|
+
vectorExcludePatterns: () => vectorExcludePatterns
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* `.rebase/sql/schema.sql` — the desired state Atlas diffs the database against.
|
|
24
|
+
*
|
|
25
|
+
* Synchronous. It was `async` with nothing to await, which made every caller
|
|
26
|
+
* (the writer script, the doctor, the live editor, two gates) carry a promise
|
|
27
|
+
* for no reason and made the one place that forgot to await it silently compare
|
|
28
|
+
* `[object Promise]`.
|
|
29
|
+
*/
|
|
30
|
+
var generatePostgresDdl = (allCollections, options = {}) => renderPostgresDdl(planSchema(allCollections), options);
|
|
31
|
+
/** `.rebase/sql/policies.sql` — the RLS policies. Atlas has no model for a policy. */
|
|
32
|
+
var generatePostgresPoliciesDdl = (allCollections) => renderPoliciesDdl(planSchema(allCollections));
|
|
33
|
+
/**
|
|
34
|
+
* `.rebase/sql/search.sql` — everything a `search` block needs, as a file Rebase
|
|
35
|
+
* applies itself.
|
|
36
|
+
*
|
|
37
|
+
* Search is one of the parts of the schema Atlas does not own, for two
|
|
38
|
+
* independent reasons and either alone would be enough:
|
|
39
|
+
*
|
|
40
|
+
* 1. Its free tier refuses to *parse* a desired-state file that so much as
|
|
41
|
+
* contains a function — "functions and procedures are available to
|
|
42
|
+
* logged-in users only". A generated `tsvector` column cannot avoid one:
|
|
43
|
+
* `unaccent` is STABLE and jsonb flattening needs a set-returning function,
|
|
44
|
+
* so both have to be wrapped in an IMMUTABLE helper to be legal in a
|
|
45
|
+
* generated column at all.
|
|
46
|
+
* 2. Even with the file accepted, Atlas *wipes* the dev database it diffs
|
|
47
|
+
* against, so a helper seeded there beforehand is gone by the time the plan
|
|
48
|
+
* is analysed. There is no hook to reinstate it.
|
|
49
|
+
*
|
|
50
|
+
* Empty when nothing opted in — the caller writes no file then.
|
|
51
|
+
*/
|
|
52
|
+
var generatePostgresSearchDdl = (allCollections) => renderSearchDdl(planSchema(allCollections));
|
|
53
|
+
/**
|
|
54
|
+
* Glob patterns telling Atlas to leave the search column and its index alone.
|
|
55
|
+
*
|
|
56
|
+
* Without these, a desired state that omits search reads to Atlas as an
|
|
57
|
+
* instruction to drop the column — taking the index and the whole search
|
|
58
|
+
* feature with it on the next push.
|
|
59
|
+
*/
|
|
60
|
+
var searchExcludePatterns = (allCollections) => searchExcludePatternsOf(planSchema(allCollections));
|
|
61
|
+
/**
|
|
62
|
+
* `.rebase/sql/vector.sql` — the `vector` columns, their ANN indexes and, when a
|
|
63
|
+
* database declared it, the extension.
|
|
64
|
+
*
|
|
65
|
+
* Vector is out of Atlas's hands for a narrower reason than search: not that
|
|
66
|
+
* Atlas refuses to parse the file, but that it cannot *execute* it. To compute
|
|
67
|
+
* a desired state Atlas materialises `schema.sql` in a dev database, that
|
|
68
|
+
* database is created empty, and Atlas empties it again at the start of every
|
|
69
|
+
* run — so `VECTOR(384)` resolves against a database that structurally cannot
|
|
70
|
+
* have pgvector, and every push dies with `type "vector" does not exist`.
|
|
71
|
+
*/
|
|
72
|
+
var generatePostgresVectorDdl = (allCollections, options = {}) => renderVectorDdl(planSchema(allCollections, { databaseExtensions: options.extensions }));
|
|
73
|
+
/** @see searchExcludePatterns */
|
|
74
|
+
var vectorExcludePatterns = (allCollections) => vectorExcludePatternsOf(planSchema(allCollections));
|
|
75
|
+
/**
|
|
76
|
+
* `.rebase/sql/triggers.sql` — the `BEFORE UPDATE` triggers behind
|
|
77
|
+
* `autoValue: "on_update"`, and the one function they share.
|
|
78
|
+
*
|
|
79
|
+
* A trigger is a function plus a binding, so it is carved out of Atlas's view
|
|
80
|
+
* for the first of the two reasons search is. Empty when no property declares
|
|
81
|
+
* `on_update` — the caller writes no file then, and removes a stale one.
|
|
82
|
+
*/
|
|
83
|
+
var generatePostgresTriggersDdl = (allCollections) => renderTriggersDdl(planSchema(allCollections));
|
|
84
|
+
/** @see searchExcludePatterns */
|
|
85
|
+
var triggerExcludePatterns = (allCollections) => triggerExcludePatternsOf(planSchema(allCollections));
|
|
86
|
+
var planOf = (collections) => planSchema(collections);
|
|
87
|
+
/**
|
|
88
|
+
* The junction tables a bundle's many-to-many relations imply.
|
|
89
|
+
*
|
|
90
|
+
* Derived from the same plan the derived RLS comes from, so a table created
|
|
91
|
+
* here always has policies planned for it — a junction with row-level security
|
|
92
|
+
* left off is readable and writable by every signed-in user.
|
|
93
|
+
*/
|
|
94
|
+
var planJunctionTables = (allCollections) => planOf(allCollections).tables.filter((table) => table.kind === "junction").map((table) => ({
|
|
95
|
+
schema: table.schema,
|
|
96
|
+
table: table.table,
|
|
97
|
+
columns: table.columns.map((c) => ({
|
|
98
|
+
name: c.column,
|
|
99
|
+
type: renderPgType(c.type),
|
|
100
|
+
legacyName: c.legacyColumn
|
|
101
|
+
})),
|
|
102
|
+
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(", ")}));`,
|
|
103
|
+
foreignKeys: table.columns.map((c) => c.foreignKey).filter((fk) => fk !== void 0)
|
|
104
|
+
}));
|
|
105
|
+
/**
|
|
106
|
+
* The per-table RLS plan for the declared collections and their junctions, as
|
|
107
|
+
* executable statements — what the managed runtime applies at boot so a freshly
|
|
108
|
+
* provisioned tenant database serves data instead of 401ing every read.
|
|
109
|
+
*
|
|
110
|
+
* The same policies `policies.sql` carries, from the same plan, so boot and
|
|
111
|
+
* `db push` produce identical RLS from identical collections.
|
|
112
|
+
*/
|
|
113
|
+
var planCollectionPolicies = (allCollections) => {
|
|
114
|
+
const plan = planOf(allCollections);
|
|
115
|
+
return [...plan.tables.filter((t) => t.kind === "collection"), ...plan.tables.filter((t) => t.kind === "junction")].map((table) => ({
|
|
116
|
+
schema: table.schema,
|
|
117
|
+
table: table.table,
|
|
118
|
+
qualified: table.qualified,
|
|
119
|
+
enableRls: `ALTER TABLE "${table.schema}"."${table.table}" ENABLE ROW LEVEL SECURITY;`,
|
|
120
|
+
policyStatements: table.policies.flatMap((policy) => renderPolicyStatements(table, policy))
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The `generated column → column it reads` edges a project's `search` blocks
|
|
125
|
+
* imply, derived from the collections alone.
|
|
126
|
+
*
|
|
127
|
+
* The same edges `queryGeneratedColumnDependencies` reads out of `pg_depend`,
|
|
128
|
+
* but available where no database is: `rebase db generate` writes a migration
|
|
129
|
+
* that will run somewhere else, later, against a database it cannot inspect.
|
|
130
|
+
* A spec's `fields[].column` is the physical column each search field starts
|
|
131
|
+
* at, which is exactly what the generated expression depends on.
|
|
132
|
+
*/
|
|
133
|
+
var searchColumnDependencies = (allCollections) => {
|
|
134
|
+
const dependencies = [];
|
|
135
|
+
for (const table of planOf(allCollections).tables) {
|
|
136
|
+
const spec = table.search;
|
|
137
|
+
if (!spec) continue;
|
|
138
|
+
const generated = [spec.column, ...spec.fuzzy ? [spec.fuzzy.column] : []];
|
|
139
|
+
const sources = new Set(spec.fields.map((field) => field.column));
|
|
140
|
+
for (const column of generated) for (const dependsOn of sources) dependencies.push({
|
|
141
|
+
schema: table.schema,
|
|
142
|
+
table: table.table,
|
|
143
|
+
column,
|
|
144
|
+
dependsOn
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return dependencies;
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
export { generatePostgresVectorDdl as a, planJunctionTables as c, generatePostgresTriggersDdl as i, generatePostgresPoliciesDdl as n, generate_postgres_ddl_logic_exports as o, generatePostgresSearchDdl as r, planCollectionPolicies as s, generatePostgresDdl as t };
|
|
151
|
+
|
|
152
|
+
//# sourceMappingURL=generate-postgres-ddl-logic-B4k9djAV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-postgres-ddl-logic-B4k9djAV.js","names":[],"sources":["../src/schema/generate-postgres-ddl-logic.ts"],"sourcesContent":["/**\n * The SQL half of `rebase db generate`, as a plan and a renderer.\n *\n * There is no interpretation left in this file. `schema/plan/plan-schema.ts`\n * reads the collections into a {@link SchemaPlan} and `schema/plan/render-ddl.ts`\n * writes the files; what remains here is the public entry points those two are\n * reached through, plus the smaller plans (`planRelationalColumns`,\n * `planJunctionTables`, `planCollectionPolicies`) that other modules ask for as\n * slices of the same plan.\n *\n * It used to hold `getSqlColumnType` and a `CREATE TABLE` walk with its own\n * reading of relations, junctions, enums and search — one of three such\n * readings. See `schema/plan/types.ts` for what that cost.\n */\nimport { CollectionConfig, SecurityRule, isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { planSchema, compileSecurityRule, quoteSqlLiteral, defaultBelongsToOnDelete } from \"./plan/plan-schema\";\nimport {\n renderPgType,\n renderPoliciesDdl,\n renderPolicyStatements,\n renderPostgresDdl,\n renderSearchDdl,\n renderTriggersDdl,\n renderVectorDdl,\n searchExcludePatternsOf,\n triggerExcludePatternsOf,\n vectorExcludePatternsOf,\n type DdlRenderOptions\n} from \"./plan/render-ddl\";\nimport type { ForeignKeyPlan, SchemaPlan } from \"./plan/types\";\nimport type { GeneratedColumnDependency } from \"./generated-column-conflicts\";\n\nexport { quoteSqlLiteral, defaultBelongsToOnDelete };\nexport type { ForeignKeyPlan };\n\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\n/**\n * The individual SQL statements a single security rule compiles to: a\n * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete\n * statement (terminated by `;`, no trailing newline).\n *\n * This is the primitive the boot-time RLS applier runs one statement at a time\n * (the runtime's DB handle speaks the extended query protocol, which forbids\n * multiple commands in one execute), while `db push` writes the joined string.\n */\nexport const generatePolicyStatements = (\n collection: CollectionConfig,\n rule: SecurityRule,\n resolveCollection: ResolveCollection\n): string[] => {\n const table = {\n schema: isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\",\n table: getTableName(collection)\n };\n return compileSecurityRule(collection, rule, resolveCollection, false)\n .flatMap(policy => renderPolicyStatements(table, policy));\n};\n\n/**\n * `.rebase/sql/schema.sql` — the desired state Atlas diffs the database against.\n *\n * Synchronous. It was `async` with nothing to await, which made every caller\n * (the writer script, the doctor, the live editor, two gates) carry a promise\n * for no reason and made the one place that forgot to await it silently compare\n * `[object Promise]`.\n */\nexport const generatePostgresDdl = (\n allCollections: CollectionConfig[],\n options: DdlRenderOptions = {}\n): string => renderPostgresDdl(planSchema(allCollections), options);\n\n/** `.rebase/sql/policies.sql` — the RLS policies. Atlas has no model for a policy. */\nexport const generatePostgresPoliciesDdl = (allCollections: CollectionConfig[]): string =>\n renderPoliciesDdl(planSchema(allCollections));\n\n/**\n * `.rebase/sql/search.sql` — everything a `search` block needs, as a file Rebase\n * applies itself.\n *\n * Search is one of the parts of the schema Atlas does not own, for two\n * independent reasons and either alone would be enough:\n *\n * 1. Its free tier refuses to *parse* a desired-state file that so much as\n * contains a function — \"functions and procedures are available to\n * logged-in users only\". A generated `tsvector` column cannot avoid one:\n * `unaccent` is STABLE and jsonb flattening needs a set-returning function,\n * so both have to be wrapped in an IMMUTABLE helper to be legal in a\n * generated column at all.\n * 2. Even with the file accepted, Atlas *wipes* the dev database it diffs\n * against, so a helper seeded there beforehand is gone by the time the plan\n * is analysed. There is no hook to reinstate it.\n *\n * Empty when nothing opted in — the caller writes no file then.\n */\nexport const generatePostgresSearchDdl = (allCollections: CollectionConfig[]): string =>\n renderSearchDdl(planSchema(allCollections));\n\n/**\n * Glob patterns telling Atlas to leave the search column and its index alone.\n *\n * Without these, a desired state that omits search reads to Atlas as an\n * instruction to drop the column — taking the index and the whole search\n * feature with it on the next push.\n */\nexport const searchExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n searchExcludePatternsOf(planSchema(allCollections));\n\n/**\n * `.rebase/sql/vector.sql` — the `vector` columns, their ANN indexes and, when a\n * database declared it, the extension.\n *\n * Vector is out of Atlas's hands for a narrower reason than search: not that\n * Atlas refuses to parse the file, but that it cannot *execute* it. To compute\n * a desired state Atlas materialises `schema.sql` in a dev database, that\n * database is created empty, and Atlas empties it again at the start of every\n * run — so `VECTOR(384)` resolves against a database that structurally cannot\n * have pgvector, and every push dies with `type \"vector\" does not exist`.\n */\nexport const generatePostgresVectorDdl = (\n allCollections: CollectionConfig[],\n options: { extensions?: readonly string[] } = {}\n): string => renderVectorDdl(planSchema(allCollections, { databaseExtensions: options.extensions }));\n\n/** @see searchExcludePatterns */\nexport const vectorExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n vectorExcludePatternsOf(planSchema(allCollections));\n\n/**\n * `.rebase/sql/triggers.sql` — the `BEFORE UPDATE` triggers behind\n * `autoValue: \"on_update\"`, and the one function they share.\n *\n * A trigger is a function plus a binding, so it is carved out of Atlas's view\n * for the first of the two reasons search is. Empty when no property declares\n * `on_update` — the caller writes no file then, and removes a stale one.\n */\nexport const generatePostgresTriggersDdl = (allCollections: CollectionConfig[]): string =>\n renderTriggersDdl(planSchema(allCollections));\n\n/** @see searchExcludePatterns */\nexport const triggerExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n triggerExcludePatternsOf(planSchema(allCollections));\n\n// ── Slices of the plan other modules ask for ─────────────────────────────────\n\n/** A column a `relation` or `reference` property owns on its own table. */\nexport interface RelationalColumnPlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n column: string;\n /** Postgres type, exactly as the DDL renderer declares it. */\n type: string;\n /** `validation.required` on the property that owns the link. */\n required: boolean;\n /**\n * True when a declared property emits this column and the relation\n * contributes only the constraint — `postId` with `columnName: \"post_id\"`\n * beside a `belongsTo` on `post_id`.\n */\n columnOwnedByProperty?: boolean;\n /** Absent when the target collection is not part of this bundle. */\n foreignKey?: ForeignKeyPlan;\n /**\n * What this column would have been called before `generateForeignKeyName`\n * learned to singularize. Carried so the boot-time ensure can notice a\n * database provisioned under the old rule; never used to name anything.\n */\n legacyColumn?: string;\n}\n\n/** The table behind a many-to-many `through` relation. */\nexport interface JunctionTablePlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n columns: { name: string; type: string; legacyName?: string }[];\n /** Both endpoint columns plus the composite primary key. */\n createTable: string;\n foreignKeys: ForeignKeyPlan[];\n}\n\nconst planOf = (collections: CollectionConfig[]): SchemaPlan => planSchema(collections);\n\n/**\n * The FK columns the declared collections own — one entry per `relation`\n * (`belongsTo` side) or `reference` property.\n *\n * A view over the schema plan. It was a second walk of the collections beside\n * the `CREATE TABLE` one, which is how boot-ensure came to add every relation\n * column bare while `db push` made the same column `NOT NULL`.\n */\nexport const planRelationalColumns = (allCollections: CollectionConfig[]): RelationalColumnPlan[] => {\n const plan = planOf(allCollections);\n const out: RelationalColumnPlan[] = [];\n for (const table of plan.tables) {\n if (table.kind !== \"collection\") continue;\n for (const column of table.columns) {\n if (column.source.kind !== \"relation\" && column.source.kind !== \"reference\") continue;\n out.push({\n schema: table.schema,\n table: table.table,\n column: column.column,\n type: renderPgType(column.type),\n required: !column.nullable,\n columnOwnedByProperty: column.columnOwnedByProperty,\n legacyColumn: column.legacyColumn,\n foreignKey: column.foreignKey\n });\n }\n }\n return out;\n};\n\n/**\n * The junction tables a bundle's many-to-many relations imply.\n *\n * Derived from the same plan the derived RLS comes from, so a table created\n * here always has policies planned for it — a junction with row-level security\n * left off is readable and writable by every signed-in user.\n */\nexport const planJunctionTables = (allCollections: CollectionConfig[]): JunctionTablePlan[] =>\n planOf(allCollections).tables\n .filter(table => table.kind === \"junction\")\n .map(table => ({\n schema: table.schema,\n table: table.table,\n columns: table.columns.map(c => ({\n name: c.column,\n type: renderPgType(c.type),\n legacyName: c.legacyColumn\n })),\n createTable:\n `CREATE TABLE IF NOT EXISTS \"${table.schema}\".\"${table.table}\" (` +\n table.columns.map(c => `\"${c.column}\" ${renderPgType(c.type)} NOT NULL`).join(\", \") +\n `, PRIMARY KEY (${table.primaryKey.map(c => `\"${c}\"`).join(\", \")}));`,\n foreignKeys: table.columns.map(c => c.foreignKey).filter((fk): fk is ForeignKeyPlan => fk !== undefined)\n }));\n\nexport interface CollectionPolicyPlan {\n /** The table's schema (e.g. `public`, `rebase`). */\n schema: string;\n /** The bare table name, no schema prefix. */\n table: string;\n /** `schema.table` — matches the keys `readExistingSchema` returns. */\n qualified: string;\n /** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */\n enableRls: string;\n /** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */\n policyStatements: string[];\n}\n\n/**\n * The per-table RLS plan for the declared collections and their junctions, as\n * executable statements — what the managed runtime applies at boot so a freshly\n * provisioned tenant database serves data instead of 401ing every read.\n *\n * The same policies `policies.sql` carries, from the same plan, so boot and\n * `db push` produce identical RLS from identical collections.\n */\nexport const planCollectionPolicies = (allCollections: CollectionConfig[]): CollectionPolicyPlan[] => {\n const plan = planOf(allCollections);\n // Collections first, then the junctions no collection declares.\n const ordered = [\n ...plan.tables.filter(t => t.kind === \"collection\"),\n ...plan.tables.filter(t => t.kind === \"junction\")\n ];\n return ordered.map(table => ({\n schema: table.schema,\n table: table.table,\n qualified: table.qualified,\n enableRls: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ENABLE ROW LEVEL SECURITY;`,\n policyStatements: table.policies.flatMap(policy => renderPolicyStatements(table, policy))\n }));\n};\n\n/**\n * The `generated column → column it reads` edges a project's `search` blocks\n * imply, derived from the collections alone.\n *\n * The same edges `queryGeneratedColumnDependencies` reads out of `pg_depend`,\n * but available where no database is: `rebase db generate` writes a migration\n * that will run somewhere else, later, against a database it cannot inspect.\n * A spec's `fields[].column` is the physical column each search field starts\n * at, which is exactly what the generated expression depends on.\n */\nexport const searchColumnDependencies = (\n allCollections: CollectionConfig[]\n): GeneratedColumnDependency[] => {\n const dependencies: GeneratedColumnDependency[] = [];\n for (const table of planOf(allCollections).tables) {\n const spec = table.search;\n if (!spec) continue;\n const generated = [spec.column, ...(spec.fuzzy ? [spec.fuzzy.column] : [])];\n const sources = new Set(spec.fields.map(field => field.column));\n for (const column of generated) {\n for (const dependsOn of sources) {\n dependencies.push({ schema: table.schema, table: table.table, column, dependsOn });\n }\n }\n }\n return dependencies;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,IAAa,uBACT,gBACA,UAA4B,CAAC,MACpB,kBAAkB,WAAW,cAAc,GAAG,OAAO;;AAGlE,IAAa,+BAA+B,mBACxC,kBAAkB,WAAW,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;AAqBhD,IAAa,6BAA6B,mBACtC,gBAAgB,WAAW,cAAc,CAAC;;;;;;;;AAS9C,IAAa,yBAAyB,mBAClC,wBAAwB,WAAW,cAAc,CAAC;;;;;;;;;;;;AAatD,IAAa,6BACT,gBACA,UAA8C,CAAC,MACtC,gBAAgB,WAAW,gBAAgB,EAAE,oBAAoB,QAAQ,WAAW,CAAC,CAAC;;AAGnG,IAAa,yBAAyB,mBAClC,wBAAwB,WAAW,cAAc,CAAC;;;;;;;;;AAUtD,IAAa,+BAA+B,mBACxC,kBAAkB,WAAW,cAAc,CAAC;;AAGhD,IAAa,0BAA0B,mBACnC,yBAAyB,WAAW,cAAc,CAAC;AAyCvD,IAAM,UAAU,gBAAgD,WAAW,WAAW;;;;;;;;AAuCtF,IAAa,sBAAsB,mBAC/B,OAAO,cAAc,CAAC,CAAC,OAClB,QAAO,UAAS,MAAM,SAAS,UAAU,CAAC,CAC1C,KAAI,WAAU;CACX,QAAQ,MAAM;CACd,OAAO,MAAM;CACb,SAAS,MAAM,QAAQ,KAAI,OAAM;EAC7B,MAAM,EAAE;EACR,MAAM,aAAa,EAAE,IAAI;EACzB,YAAY,EAAE;CAClB,EAAE;CACF,aACI,+BAA+B,MAAM,OAAO,KAAK,MAAM,MAAM,OAC7D,MAAM,QAAQ,KAAI,MAAK,IAAI,EAAE,OAAO,IAAI,aAAa,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,KAAK,IAAI,IAClF,kBAAkB,MAAM,WAAW,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;CACrE,aAAa,MAAM,QAAQ,KAAI,MAAK,EAAE,UAAU,CAAC,CAAC,QAAQ,OAA6B,OAAO,KAAA,CAAS;AAC3G,EAAE;;;;;;;;;AAuBV,IAAa,0BAA0B,mBAA+D;CAClG,MAAM,OAAO,OAAO,cAAc;CAMlC,OAAO,CAHH,GAAG,KAAK,OAAO,QAAO,MAAK,EAAE,SAAS,YAAY,GAClD,GAAG,KAAK,OAAO,QAAO,MAAK,EAAE,SAAS,UAAU,CAE7C,CAAA,CAAQ,KAAI,WAAU;EACzB,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,WAAW,MAAM;EACjB,WAAW,gBAAgB,MAAM,OAAO,KAAK,MAAM,MAAM;EACzD,kBAAkB,MAAM,SAAS,SAAQ,WAAU,uBAAuB,OAAO,MAAM,CAAC;CAC5F,EAAE;AACN;;;;;;;;;;;AAYA,IAAa,4BACT,mBAC8B;CAC9B,MAAM,eAA4C,CAAC;CACnD,KAAK,MAAM,SAAS,OAAO,cAAc,CAAC,CAAC,QAAQ;EAC/C,MAAM,OAAO,MAAM;EACnB,IAAI,CAAC,MAAM;EACX,MAAM,YAAY,CAAC,KAAK,QAAQ,GAAI,KAAK,QAAQ,CAAC,KAAK,MAAM,MAAM,IAAI,CAAC,CAAE;EAC1E,MAAM,UAAU,IAAI,IAAI,KAAK,OAAO,KAAI,UAAS,MAAM,MAAM,CAAC;EAC9D,KAAK,MAAM,UAAU,WACjB,KAAK,MAAM,aAAa,SACpB,aAAa,KAAK;GAAE,QAAQ,MAAM;GAAQ,OAAO,MAAM;GAAO;GAAQ;EAAU,CAAC;CAG7F;CACA,OAAO;AACX"}
|
package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
3
|
+
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
4
|
import { getTableName, relationalCollections, resolveCollectionRelations } from "@rebasepro/common";
|
|
5
5
|
import { generateForeignKeyName, legacyForeignKeyName } from "@rebasepro/utils";
|
|
6
6
|
//#region src/schema/generated-schema-staleness.ts
|
|
@@ -236,4 +236,4 @@ function describeDeclarationDifferences(differences) {
|
|
|
236
236
|
//#endregion
|
|
237
237
|
export { describeDeclarationDifferences as n, generated_schema_staleness_exports as r, compareGeneratedDeclarations as t };
|
|
238
238
|
|
|
239
|
-
//# sourceMappingURL=generated-schema-staleness-
|
|
239
|
+
//# sourceMappingURL=generated-schema-staleness-BWN_PsMi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-schema-staleness-Di9c4ZxN.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /**\n * How the file differs from what the current collections generate.\n *\n * The check this command's own summary promises — \"generated schema files\n * that no longer match the collections\" — and for a long time the one it did\n * not make. Staleness was computed from {@link findLegacyForeignKeyNames}\n * alone, which finds one specific historical defect: a junction column named\n * the way a previous release derived it. Every other way a file can fall\n * behind its collections — a table added, a column retyped, an enum label\n * dropped from the offer — left the command printing \"Nothing stale\" about a\n * file that regenerating visibly changes.\n *\n * `projects_database_mode` is the one that found this. The shared database\n * tier was retired, the label came off the collection, and `schema stale`\n * called the file a match for weeks while every `pnpm run build` silently\n * rewrote it.\n *\n * Optional so a caller that has not loaded the collections — the only way to\n * know what they would generate — reports what it does know rather than\n * claiming a clean bill it never checked.\n */\n differences?: DeclarationDifference[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n // Both checks, and both reported. They answer different questions and a file\n // can fail either: `stale` names a specific historical defect with a\n // specific remedy, `differences` says the file is simply not what the\n // collections now generate. Reporting only the first is what let this\n // command pass a file that had visibly moved.\n const problems: string[] = [];\n if (input.stale.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale)\n );\n }\n const differences = input.differences ?? [];\n if (differences.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} is not what the current collections generate.`,\n ` Differs in: ${describeDeclarationDifferences(differences)}.`\n );\n }\n\n if (problems.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\"\", ...problems, \"\"];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n //\n // The first sentence is the renamed-column one and belongs only to\n // the legacy foreign-key finding. Printed unconditionally it told a\n // reader whose enum label had simply moved that a column had been\n // renamed at boot, which sends them looking for a rename that never\n // happened. The consequence below is the same either way.\n ...(input.stale.length > 0\n ? [\" The database column has already been renamed at boot.\"]\n : []),\n \" The server reads the database, so it will still start — but `db push`, Atlas, `eject` \"\n + \"and any code importing this file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n if (!have.has(name)) differences.push({ name, kind: \"missing\" });\n else if (have.get(name) !== body) differences.push({ name, kind: \"changed\" });\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/** The differences, as the line a developer reads in `rebase doctor`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (differs)`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generated-schema-staleness-BWN_PsMi.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /**\n * How the file differs from what the current collections generate.\n *\n * The check this command's own summary promises — \"generated schema files\n * that no longer match the collections\" — and for a long time the one it did\n * not make. Staleness was computed from {@link findLegacyForeignKeyNames}\n * alone, which finds one specific historical defect: a junction column named\n * the way a previous release derived it. Every other way a file can fall\n * behind its collections — a table added, a column retyped, an enum label\n * dropped from the offer — left the command printing \"Nothing stale\" about a\n * file that regenerating visibly changes.\n *\n * `projects_database_mode` is the one that found this. The shared database\n * tier was retired, the label came off the collection, and `schema stale`\n * called the file a match for weeks while every `pnpm run build` silently\n * rewrote it.\n *\n * Optional so a caller that has not loaded the collections — the only way to\n * know what they would generate — reports what it does know rather than\n * claiming a clean bill it never checked.\n */\n differences?: DeclarationDifference[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n // Both checks, and both reported. They answer different questions and a file\n // can fail either: `stale` names a specific historical defect with a\n // specific remedy, `differences` says the file is simply not what the\n // collections now generate. Reporting only the first is what let this\n // command pass a file that had visibly moved.\n const problems: string[] = [];\n if (input.stale.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale)\n );\n }\n const differences = input.differences ?? [];\n if (differences.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} is not what the current collections generate.`,\n ` Differs in: ${describeDeclarationDifferences(differences)}.`\n );\n }\n\n if (problems.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\"\", ...problems, \"\"];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n //\n // The first sentence is the renamed-column one and belongs only to\n // the legacy foreign-key finding. Printed unconditionally it told a\n // reader whose enum label had simply moved that a column had been\n // renamed at boot, which sends them looking for a rename that never\n // happened. The consequence below is the same either way.\n ...(input.stale.length > 0\n ? [\" The database column has already been renamed at boot.\"]\n : []),\n \" The server reads the database, so it will still start — but `db push`, Atlas, `eject` \"\n + \"and any code importing this file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n if (!have.has(name)) differences.push({ name, kind: \"missing\" });\n else if (have.get(name) !== body) differences.push({ name, kind: \"changed\" });\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/** The differences, as the line a developer reads in `rebase doctor`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (differs)`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** The directory, relative to the working directory. POSIX, because it is also a URL path. */
|
|
2
|
+
export declare const GENERATED_SQL_DIR = ".rebase/sql";
|
|
3
|
+
/**
|
|
4
|
+
* `schema.sql` as Atlas takes it. Relative on purpose, like the
|
|
5
|
+
* `file://drizzle/migrations` beside it: Atlas joins the URL's host and path, so
|
|
6
|
+
* `.rebase` is read as the first path segment, measured against the pinned 1.2.3.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DESIRED_STATE_URL = "file://.rebase/sql/schema.sql";
|
|
9
|
+
/** The files the DDL generator writes, all of them derived from the collections. */
|
|
10
|
+
export declare const GENERATED_SQL_FILES: readonly ["schema.sql", "policies.sql", "search.sql", "vector.sql", "triggers.sql"];
|
|
11
|
+
/** The absolute directory, for the working directory the command runs in. */
|
|
12
|
+
export declare function generatedSqlDir(cwd?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Get the directory ready to be written, and retire the copies an older release
|
|
15
|
+
* committed.
|
|
16
|
+
*
|
|
17
|
+
* The directory ignores itself, rather than relying on the project's
|
|
18
|
+
* `.gitignore`: a scaffold ignores `.rebase/`, but a project created before it
|
|
19
|
+
* did would otherwise start committing these files from their new home.
|
|
20
|
+
*
|
|
21
|
+
* The old copies in `drizzle/` are deleted, not left behind. Nothing writes them
|
|
22
|
+
* any more, so from the first push after an upgrade they only fall further
|
|
23
|
+
* behind — and a stale `policies.sql` in the repository is exactly what someone
|
|
24
|
+
* reviewing the project's RLS would read.
|
|
25
|
+
*
|
|
26
|
+
* With `retire: false` (`db push --dry-run`, which writes nothing of the
|
|
27
|
+
* project's) the old copies are only found, not removed.
|
|
28
|
+
*
|
|
29
|
+
* @returns the old files it removed — or, with `retire: false`, would remove —
|
|
30
|
+
* relative to the working directory.
|
|
31
|
+
*/
|
|
32
|
+
export declare function prepareGeneratedSqlDir(cwd?: string, options?: {
|
|
33
|
+
retire?: boolean;
|
|
34
|
+
}): string[];
|