@rebasepro/server-postgres 0.19.1 → 0.19.2-canary.g08eed46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-ByLfM_Tt.js → BranchService-W3DMfcZZ.js} +2 -2
- package/dist/{BranchService-ByLfM_Tt.js.map → BranchService-W3DMfcZZ.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +81 -7
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/{backup-cli-oM9WQVlV.js → backup-cli-DSpQyqcG.js} +2 -2
- package/dist/{backup-cli-oM9WQVlV.js.map → backup-cli-DSpQyqcG.js.map} +1 -1
- package/dist/{backup-service-Br3tqwsy.js → backup-service-DA7a6SUV.js} +2 -2
- package/dist/{backup-service-Br3tqwsy.js.map → backup-service-DA7a6SUV.js.map} +1 -1
- package/dist/{cli-errors-DxN4yzdH.js → cli-errors-C3g_kHBw.js} +137 -18
- package/dist/cli-errors-C3g_kHBw.js.map +1 -0
- package/dist/cli-errors.d.ts +19 -0
- package/dist/cli-helpers.d.ts +47 -0
- package/dist/cli.js +233 -19
- package/dist/cli.js.map +1 -1
- package/dist/{collection-index-BHgT_TKd.js → collection-index-DdnsxiJ_.js} +14 -4
- package/dist/{collection-index-BHgT_TKd.js.map → collection-index-DdnsxiJ_.js.map} +1 -1
- package/dist/collections/validate-relations.d.ts +7 -0
- package/dist/column-plan-helpers-CpILzHJS.js +145 -0
- package/dist/column-plan-helpers-CpILzHJS.js.map +1 -0
- package/dist/{doctor-D7HWmGzs.js → doctor-D5SrGJ5P.js} +175 -61
- package/dist/doctor-D5SrGJ5P.js.map +1 -0
- package/dist/{ensure-collection-policies-2c2NV8zX.js → ensure-collection-policies-CHO0moXQ.js} +5 -5
- package/dist/{ensure-collection-policies-2c2NV8zX.js.map → ensure-collection-policies-CHO0moXQ.js.map} +1 -1
- package/dist/{ensure-collection-tables-D4wJzM4y.js → ensure-collection-tables-Bkvehxdm.js} +318 -312
- package/dist/ensure-collection-tables-Bkvehxdm.js.map +1 -0
- package/dist/{ensure-tables-CIojK-Rx.js → ensure-tables-CYMtuuOd.js} +2 -2
- package/dist/{ensure-tables-CIojK-Rx.js.map → ensure-tables-CYMtuuOd.js.map} +1 -1
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js +228 -0
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js.map +1 -0
- package/dist/{generate-drizzle-schema-CwEgVyjO.js → generate-drizzle-schema-vSK-VdYT.js} +3 -3
- package/dist/generate-drizzle-schema-vSK-VdYT.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js +475 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js.map +1 -0
- package/dist/{generated-schema-staleness-DQdfJl2A.js → generated-schema-staleness-DRQe2BpC.js} +81 -3
- package/dist/{generated-schema-staleness-DQdfJl2A.js.map → generated-schema-staleness-DRQe2BpC.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +23 -4
- package/dist/index.es.js +1991 -580
- package/dist/index.es.js.map +1 -1
- package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -1
- package/dist/plan-schema-Hgl62S-w.js +1531 -0
- package/dist/plan-schema-Hgl62S-w.js.map +1 -0
- package/dist/{policy-drift-kNJ6X2Uj.js → policy-drift-B0GDRh7_.js} +2 -2
- package/dist/{policy-drift-kNJ6X2Uj.js.map → policy-drift-B0GDRh7_.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js → rls-bootstrap-sql-BlzsOUtz.js} +111 -8
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js.map → rls-bootstrap-sql-BlzsOUtz.js.map} +1 -1
- package/dist/{rls-enforcement-DeYEjYLt.js → rls-enforcement-BV12vBwQ.js} +2 -1
- package/dist/rls-enforcement-BV12vBwQ.js.map +1 -0
- package/dist/schema/catalogue-schema.d.ts +97 -0
- package/dist/schema/collection-index.d.ts +9 -0
- package/dist/schema/column-plan-helpers.d.ts +125 -0
- package/dist/schema/config-relations.d.ts +32 -0
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/doctor.d.ts +9 -0
- package/dist/schema/dynamic-tables.d.ts +27 -6
- package/dist/schema/ensure-collection-tables.d.ts +31 -268
- package/dist/schema/generate-drizzle-schema-logic.d.ts +26 -8
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +84 -168
- package/dist/schema/generate-postgres-ddl.js +8 -2
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generated-column-conflicts.d.ts +84 -0
- package/dist/schema/generated-schema-diff.d.ts +56 -0
- package/dist/schema/generated-schema-staleness.d.ts +33 -0
- package/dist/schema/introspect-db-logic.d.ts +2 -0
- package/dist/schema/plan/diff-plan.d.ts +220 -0
- package/dist/schema/plan/plan-schema.d.ts +84 -0
- package/dist/schema/plan/render-ddl.d.ts +62 -0
- package/dist/schema/plan/render-drizzle.d.ts +31 -0
- package/dist/schema/plan/types.d.ts +410 -0
- package/dist/schema/plan/updated-at-trigger.d.ts +22 -0
- package/dist/schema/relation-names.d.ts +28 -0
- package/dist/schema/search-column.d.ts +9 -0
- package/dist/security/rls-enforcement.d.ts +17 -0
- package/dist/services/FetchService.d.ts +176 -36
- package/dist/services/PersistService.d.ts +17 -4
- package/dist/services/RelationService.d.ts +11 -3
- package/dist/services/collection-helpers.d.ts +16 -2
- package/dist/services/dataService.d.ts +29 -2
- package/dist/services/field-op-sql.d.ts +16 -0
- package/dist/services/field-viewer.d.ts +19 -0
- package/dist/services/realtimeService.d.ts +35 -1
- package/dist/services/row-pipeline.d.ts +29 -5
- package/dist/services/soft-delete.d.ts +73 -0
- package/dist/utils/pg-error-utils.d.ts +41 -1
- package/dist/{vector-index-DkWxm2de.js → vector-index-CMJKb3C9.js} +3 -14
- package/dist/{vector-index-DkWxm2de.js.map → vector-index-CMJKb3C9.js.map} +1 -1
- package/package.json +18 -10
- package/dist/cli-errors-DxN4yzdH.js.map +0 -1
- package/dist/doctor-D7HWmGzs.js.map +0 -1
- package/dist/ensure-collection-tables-D4wJzM4y.js.map +0 -1
- package/dist/generate-drizzle-schema-CwEgVyjO.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +0 -528
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js +0 -1312
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +0 -1
- package/dist/rls-enforcement-DeYEjYLt.js.map +0 -1
|
@@ -1,42 +1,25 @@
|
|
|
1
|
-
import { CollectionConfig, Property, SecurityRule } from "@rebasepro/types";
|
|
2
|
-
export declare const resolveColumnName: (propName: string, prop?: Property | null) => string;
|
|
3
|
-
export declare const getPrimaryKeyProp: (collection: CollectionConfig) => {
|
|
4
|
-
name: string;
|
|
5
|
-
type: "string" | "number";
|
|
6
|
-
isUuid: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare const isNumericId: (collection: CollectionConfig) => boolean;
|
|
9
|
-
export declare const getPrimaryKeyName: (collection: CollectionConfig) => string;
|
|
10
|
-
export declare const isIdProperty: (propName: string, prop: Property, collection: CollectionConfig) => boolean;
|
|
11
|
-
type ResolveCollection = (slug: string) => CollectionConfig | undefined;
|
|
12
1
|
/**
|
|
13
|
-
* The
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* A required link is **`RESTRICT`**, not `CASCADE`. `NOT NULL` says the child
|
|
26
|
-
* cannot exist without a parent; it does not say deleting the parent should
|
|
27
|
-
* take the child with it. That second claim is a data-retention decision, and
|
|
28
|
-
* defaulting to it meant `onDelete` — a field nobody has to write — silently
|
|
29
|
-
* turned every `DELETE FROM authors` into a cascade through posts, comments and
|
|
30
|
-
* anything else that hung off them. `RESTRICT` fails the delete instead and
|
|
31
|
-
* names the constraint, so the author chooses `cascade` on purpose or clears
|
|
32
|
-
* the children first.
|
|
33
|
-
*
|
|
34
|
-
* Both DDL paths — the `CREATE TABLE` generator and {@link planRelationalColumns},
|
|
35
|
-
* which boot-ensure compares the live database against — call this, so a
|
|
36
|
-
* default that appears in one and not the other cannot make `db push` plan a
|
|
37
|
-
* constraint rewrite on every run.
|
|
2
|
+
* The SQL half of `rebase db generate`, as a plan and a renderer.
|
|
3
|
+
*
|
|
4
|
+
* There is no interpretation left in this file. `schema/plan/plan-schema.ts`
|
|
5
|
+
* reads the collections into a {@link SchemaPlan} and `schema/plan/render-ddl.ts`
|
|
6
|
+
* writes the files; what remains here is the public entry points those two are
|
|
7
|
+
* reached through, plus the smaller plans (`planRelationalColumns`,
|
|
8
|
+
* `planJunctionTables`, `planCollectionPolicies`) that other modules ask for as
|
|
9
|
+
* slices of the same plan.
|
|
10
|
+
*
|
|
11
|
+
* It used to hold `getSqlColumnType` and a `CREATE TABLE` walk with its own
|
|
12
|
+
* reading of relations, junctions, enums and search — one of three such
|
|
13
|
+
* readings. See `schema/plan/types.ts` for what that cost.
|
|
38
14
|
*/
|
|
39
|
-
|
|
15
|
+
import { CollectionConfig, SecurityRule } from "@rebasepro/types";
|
|
16
|
+
import { quoteSqlLiteral, defaultBelongsToOnDelete } from "./plan/plan-schema.js";
|
|
17
|
+
import { type DdlRenderOptions } from "./plan/render-ddl.js";
|
|
18
|
+
import type { ForeignKeyPlan } from "./plan/types.js";
|
|
19
|
+
import type { GeneratedColumnDependency } from "./generated-column-conflicts.js";
|
|
20
|
+
export { quoteSqlLiteral, defaultBelongsToOnDelete };
|
|
21
|
+
export type { ForeignKeyPlan };
|
|
22
|
+
type ResolveCollection = (slug: string) => CollectionConfig | undefined;
|
|
40
23
|
/**
|
|
41
24
|
* The individual SQL statements a single security rule compiles to: a
|
|
42
25
|
* `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete
|
|
@@ -48,20 +31,22 @@ export declare const defaultBelongsToOnDelete: (required: boolean | undefined) =
|
|
|
48
31
|
*/
|
|
49
32
|
export declare const generatePolicyStatements: (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection) => string[];
|
|
50
33
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
34
|
+
* `drizzle/schema.sql` — the desired state Atlas diffs the database against.
|
|
35
|
+
*
|
|
36
|
+
* Synchronous. It was `async` with nothing to await, which made every caller
|
|
37
|
+
* (the writer script, the doctor, the live editor, two gates) carry a promise
|
|
38
|
+
* for no reason and made the one place that forgot to await it silently compare
|
|
39
|
+
* `[object Promise]`.
|
|
57
40
|
*/
|
|
58
|
-
export declare const
|
|
59
|
-
|
|
41
|
+
export declare const generatePostgresDdl: (allCollections: CollectionConfig[], options?: DdlRenderOptions) => string;
|
|
42
|
+
/** `drizzle/policies.sql` — the RLS policies. Atlas has no model for a policy. */
|
|
43
|
+
export declare const generatePostgresPoliciesDdl: (allCollections: CollectionConfig[]) => string;
|
|
60
44
|
/**
|
|
61
|
-
*
|
|
45
|
+
* `drizzle/search.sql` — everything a `search` block needs, as a file Rebase
|
|
46
|
+
* applies itself.
|
|
62
47
|
*
|
|
63
|
-
* Search is the
|
|
64
|
-
* reasons
|
|
48
|
+
* Search is one of the parts of the schema Atlas does not own, for two
|
|
49
|
+
* independent reasons and either alone would be enough:
|
|
65
50
|
*
|
|
66
51
|
* 1. Its free tier refuses to *parse* a desired-state file that so much as
|
|
67
52
|
* contains a function — "functions and procedures are available to
|
|
@@ -73,24 +58,7 @@ export declare const getSqlColumnType: (propName: string, prop: Property, collec
|
|
|
73
58
|
* against, so a helper seeded there beforehand is gone by the time the plan
|
|
74
59
|
* is analysed. There is no hook to reinstate it.
|
|
75
60
|
*
|
|
76
|
-
*
|
|
77
|
-
* (`searchExcludePatterns`) and applied from here — the same arrangement the
|
|
78
|
-
* RLS policies already use, and for the same underlying reason.
|
|
79
|
-
*
|
|
80
|
-
* Ordered as it must run: extensions, then helpers, then the column whose
|
|
81
|
-
* expression calls them, then the index over that column. Every statement is
|
|
82
|
-
* `IF NOT EXISTS` / `OR REPLACE`, because this is replayed on every push and
|
|
83
|
-
* appended to migrations that run against databases at any stage of their
|
|
84
|
-
* life. Empty when nothing opted in — the caller writes no file then.
|
|
85
|
-
*
|
|
86
|
-
* The dev database Atlas plans against needs none of this. `searchExcludePatterns`
|
|
87
|
-
* removes the search column from the *inspected* state, so the state Atlas
|
|
88
|
-
* replays there never mentions the column and never calls a helper. Seeding the
|
|
89
|
-
* helpers into that database was tried and removed: measured on Atlas 1.2.3, a
|
|
90
|
-
* `schema apply --dry-run` produces byte-identical output seeded or not, and
|
|
91
|
-
* Atlas wipes the dev database before it plans anyway — so a seed could not have
|
|
92
|
-
* helped even where the excludes are missing, which is the case that really does
|
|
93
|
-
* fail with `function public.rebase_search_unaccent(text) does not exist`.
|
|
61
|
+
* Empty when nothing opted in — the caller writes no file then.
|
|
94
62
|
*/
|
|
95
63
|
export declare const generatePostgresSearchDdl: (allCollections: CollectionConfig[]) => string;
|
|
96
64
|
/**
|
|
@@ -99,103 +67,57 @@ export declare const generatePostgresSearchDdl: (allCollections: CollectionConfi
|
|
|
99
67
|
* Without these, a desired state that omits search reads to Atlas as an
|
|
100
68
|
* instruction to drop the column — taking the index and the whole search
|
|
101
69
|
* feature with it on the next push.
|
|
102
|
-
*
|
|
103
|
-
* Fully qualified, `schema.table.object`, matching the include list. The
|
|
104
|
-
* two-part form is what Atlas wants when the connection URL scopes it to one
|
|
105
|
-
* schema and is *silently ignored* otherwise: it reads `posts.search_vector`
|
|
106
|
-
* as a table named `search_vector` in a schema named `posts`, matches nothing,
|
|
107
|
-
* and reports no error for the pattern that never fired.
|
|
108
70
|
*/
|
|
109
71
|
export declare const searchExcludePatterns: (allCollections: CollectionConfig[]) => string[];
|
|
110
72
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Vector is
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* dies with `type "vector" does not exist`. Seeding the extension beforehand
|
|
121
|
-
* does not survive the clean (measured: present before the run, gone after),
|
|
122
|
-
* putting `CREATE EXTENSION` in `schema.sql` is refused as an Atlas Pro
|
|
123
|
-
* feature, and `--exclude` alone cannot help because Atlas still parses and
|
|
124
|
-
* applies the whole file.
|
|
125
|
-
*
|
|
126
|
-
* So the column, its ANN indexes and the extension are excluded from Atlas's
|
|
127
|
-
* view ({@link vectorExcludePatterns}) and applied from here — the same
|
|
128
|
-
* arrangement search and the RLS policies already use.
|
|
129
|
-
*
|
|
130
|
-
* `CREATE EXTENSION` appears only when the project's database named `vector` in
|
|
131
|
-
* its `extensions` — see `DatabaseOptions.extensions`. Everything else is
|
|
132
|
-
* emitted either way: a database that already has pgvector installed by hand
|
|
133
|
-
* needs no permission from anyone, and one that does not gets the column
|
|
134
|
-
* refused by Postgres with a message this repo makes name the opt-in.
|
|
135
|
-
*
|
|
136
|
-
* Ordered as it must run: the extension, then the drift guard, then the column
|
|
137
|
-
* whose type needs the extension, then the indexes over that column. Every
|
|
138
|
-
* statement is `IF NOT EXISTS`, because this is replayed on every push and
|
|
139
|
-
* appended to migrations that run against databases at any stage of their life.
|
|
140
|
-
* Empty when no collection declares a vector — the caller writes no file then.
|
|
73
|
+
* `drizzle/vector.sql` — the `vector` columns, their ANN indexes and, when a
|
|
74
|
+
* database declared it, the extension.
|
|
75
|
+
*
|
|
76
|
+
* Vector is out of Atlas's hands for a narrower reason than search: not that
|
|
77
|
+
* Atlas refuses to parse the file, but that it cannot *execute* it. To compute
|
|
78
|
+
* a desired state Atlas materialises `schema.sql` in a dev database, that
|
|
79
|
+
* database is created empty, and Atlas empties it again at the start of every
|
|
80
|
+
* run — so `VECTOR(384)` resolves against a database that structurally cannot
|
|
81
|
+
* have pgvector, and every push dies with `type "vector" does not exist`.
|
|
141
82
|
*/
|
|
142
83
|
export declare const generatePostgresVectorDdl: (allCollections: CollectionConfig[], options?: {
|
|
143
84
|
extensions?: readonly string[];
|
|
144
85
|
}) => string;
|
|
145
|
-
/**
|
|
146
|
-
* Glob patterns telling Atlas to leave the vector column and its ANN indexes
|
|
147
|
-
* alone.
|
|
148
|
-
*
|
|
149
|
-
* Without these, a desired state that omits the column reads to Atlas as an
|
|
150
|
-
* instruction to drop it, taking the embeddings and the indexes with it on the
|
|
151
|
-
* next push.
|
|
152
|
-
*
|
|
153
|
-
* Fully qualified, `schema.table.object`, for the reason spelled out on
|
|
154
|
-
* {@link searchExcludePatterns}: the two-part form matches nothing and reports
|
|
155
|
-
* no error, which turns a protection into a `DROP COLUMN`.
|
|
156
|
-
*/
|
|
86
|
+
/** @see searchExcludePatterns */
|
|
157
87
|
export declare const vectorExcludePatterns: (allCollections: CollectionConfig[]) => string[];
|
|
158
|
-
export declare const generatePostgresDdl: (allCollections: CollectionConfig[], options?: {
|
|
159
|
-
includePolicies?: boolean;
|
|
160
|
-
includeSearch?: boolean;
|
|
161
|
-
includeVector?: boolean;
|
|
162
|
-
}) => Promise<string>;
|
|
163
|
-
/** The RLS statements one declared collection's table needs, ready to run. */
|
|
164
88
|
/**
|
|
165
|
-
*
|
|
89
|
+
* `drizzle/triggers.sql` — the `BEFORE UPDATE` triggers behind
|
|
90
|
+
* `autoValue: "on_update"`, and the one function they share.
|
|
166
91
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
92
|
+
* A trigger is a function plus a binding, so it is carved out of Atlas's view
|
|
93
|
+
* for the first of the two reasons search is. Empty when no property declares
|
|
94
|
+
* `on_update` — the caller writes no file then, and removes a stale one.
|
|
170
95
|
*/
|
|
171
|
-
export
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/** Bare table name, no schema prefix. */
|
|
175
|
-
table: string;
|
|
176
|
-
column: string;
|
|
177
|
-
targetSchema: string;
|
|
178
|
-
targetTable: string;
|
|
179
|
-
targetColumn: string;
|
|
180
|
-
sql: string;
|
|
181
|
-
}
|
|
96
|
+
export declare const generatePostgresTriggersDdl: (allCollections: CollectionConfig[]) => string;
|
|
97
|
+
/** @see searchExcludePatterns */
|
|
98
|
+
export declare const triggerExcludePatterns: (allCollections: CollectionConfig[]) => string[];
|
|
182
99
|
/** A column a `relation` or `reference` property owns on its own table. */
|
|
183
100
|
export interface RelationalColumnPlan {
|
|
184
101
|
schema: string;
|
|
185
102
|
/** Bare table name, no schema prefix. */
|
|
186
103
|
table: string;
|
|
187
104
|
column: string;
|
|
188
|
-
/** Postgres type, exactly as the DDL
|
|
105
|
+
/** Postgres type, exactly as the DDL renderer declares it. */
|
|
189
106
|
type: string;
|
|
107
|
+
/** `validation.required` on the property that owns the link. */
|
|
108
|
+
required: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* True when a declared property emits this column and the relation
|
|
111
|
+
* contributes only the constraint — `postId` with `columnName: "post_id"`
|
|
112
|
+
* beside a `belongsTo` on `post_id`.
|
|
113
|
+
*/
|
|
114
|
+
columnOwnedByProperty?: boolean;
|
|
190
115
|
/** Absent when the target collection is not part of this bundle. */
|
|
191
116
|
foreignKey?: ForeignKeyPlan;
|
|
192
117
|
/**
|
|
193
118
|
* What this column would have been called before `generateForeignKeyName`
|
|
194
|
-
* learned to singularize
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* Carried so the boot-time ensure can notice a database provisioned under
|
|
198
|
-
* the old rule. It is never used to name anything.
|
|
119
|
+
* learned to singularize. Carried so the boot-time ensure can notice a
|
|
120
|
+
* database provisioned under the old rule; never used to name anything.
|
|
199
121
|
*/
|
|
200
122
|
legacyColumn?: string;
|
|
201
123
|
}
|
|
@@ -217,25 +139,17 @@ export interface JunctionTablePlan {
|
|
|
217
139
|
* The FK columns the declared collections own — one entry per `relation`
|
|
218
140
|
* (`belongsTo` side) or `reference` property.
|
|
219
141
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* always followed; on a managed tenant nothing follows, so a table arrived
|
|
224
|
-
* without the column its own collection reads and wrote 400 on every insert.
|
|
225
|
-
*
|
|
226
|
-
* A relation whose target is not in the bundle yields no column at all (the
|
|
227
|
-
* generator returns early on an unresolvable target); a `reference` whose target
|
|
228
|
-
* is unknown yields the column without a constraint. Both mirror the generator
|
|
229
|
-
* exactly — a divergence here is a schema fork between boot and `db push`.
|
|
142
|
+
* A view over the schema plan. It was a second walk of the collections beside
|
|
143
|
+
* the `CREATE TABLE` one, which is how boot-ensure came to add every relation
|
|
144
|
+
* column bare while `db push` made the same column `NOT NULL`.
|
|
230
145
|
*/
|
|
231
146
|
export declare const planRelationalColumns: (allCollections: CollectionConfig[]) => RelationalColumnPlan[];
|
|
232
147
|
/**
|
|
233
148
|
* The junction tables a bundle's many-to-many relations imply.
|
|
234
149
|
*
|
|
235
|
-
* Derived from
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* signed-in user, which is why the two must ship together.
|
|
150
|
+
* Derived from the same plan the derived RLS comes from, so a table created
|
|
151
|
+
* here always has policies planned for it — a junction with row-level security
|
|
152
|
+
* left off is readable and writable by every signed-in user.
|
|
239
153
|
*/
|
|
240
154
|
export declare const planJunctionTables: (allCollections: CollectionConfig[]) => JunctionTablePlan[];
|
|
241
155
|
export interface CollectionPolicyPlan {
|
|
@@ -251,20 +165,22 @@ export interface CollectionPolicyPlan {
|
|
|
251
165
|
policyStatements: string[];
|
|
252
166
|
}
|
|
253
167
|
/**
|
|
254
|
-
* The per-table RLS plan for the
|
|
255
|
-
* statements — what the managed runtime applies at boot so a freshly
|
|
168
|
+
* The per-table RLS plan for the declared collections and their junctions, as
|
|
169
|
+
* executable statements — what the managed runtime applies at boot so a freshly
|
|
256
170
|
* provisioned tenant database serves data instead of 401ing every read.
|
|
257
171
|
*
|
|
258
|
-
*
|
|
259
|
-
* `
|
|
260
|
-
* derived junction rules), so boot and `db push` produce identical policies from
|
|
261
|
-
* identical collections.
|
|
262
|
-
*
|
|
263
|
-
* Junction tables are included, and have to be: boot creates them now
|
|
264
|
-
* ({@link planJunctionTables}), and a junction with RLS left off is readable and
|
|
265
|
-
* writable by every signed-in user. A junction whose table is still absent is
|
|
266
|
-
* skipped by the applier, not planned away here.
|
|
172
|
+
* The same policies `policies.sql` carries, from the same plan, so boot and
|
|
173
|
+
* `db push` produce identical RLS from identical collections.
|
|
267
174
|
*/
|
|
268
175
|
export declare const planCollectionPolicies: (allCollections: CollectionConfig[]) => CollectionPolicyPlan[];
|
|
269
|
-
|
|
270
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The `generated column → column it reads` edges a project's `search` blocks
|
|
178
|
+
* imply, derived from the collections alone.
|
|
179
|
+
*
|
|
180
|
+
* The same edges `queryGeneratedColumnDependencies` reads out of `pg_depend`,
|
|
181
|
+
* but available where no database is: `rebase db generate` writes a migration
|
|
182
|
+
* that will run somewhere else, later, against a database it cannot inspect.
|
|
183
|
+
* A spec's `fields[].column` is the physical column each search field starts
|
|
184
|
+
* at, which is exactly what the generated expression depends on.
|
|
185
|
+
*/
|
|
186
|
+
export declare const searchColumnDependencies: (allCollections: CollectionConfig[]) => GeneratedColumnDependency[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { a as generatePostgresVectorDdl, i as
|
|
3
|
+
import { a as generatePostgresVectorDdl, i as generatePostgresTriggersDdl, n as generatePostgresPoliciesDdl, r as generatePostgresSearchDdl, t as generatePostgresDdl } from "../generate-postgres-ddl-logic-CR2xcS7e.js";
|
|
4
4
|
import { n as outError, t as out } from "../cli-output-CNdMql-L.js";
|
|
5
5
|
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
6
6
|
import { declaredDatabaseExtensions } from "@rebasepro/types";
|
|
@@ -49,7 +49,7 @@ var runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
49
49
|
let collections = await loadCollectionsFromDirectory(resolvedPath);
|
|
50
50
|
if (!collections || !Array.isArray(collections)) collections = [];
|
|
51
51
|
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
52
|
-
const ddlContent =
|
|
52
|
+
const ddlContent = generatePostgresDdl(collections, {
|
|
53
53
|
includePolicies: false,
|
|
54
54
|
includeSearch: false,
|
|
55
55
|
includeVector: false
|
|
@@ -57,6 +57,7 @@ var runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
57
57
|
const policiesContent = generatePostgresPoliciesDdl(collections);
|
|
58
58
|
const searchContent = generatePostgresSearchDdl(collections);
|
|
59
59
|
const vectorContent = generatePostgresVectorDdl(collections, { extensions: declaredDatabaseExtensions() });
|
|
60
|
+
const triggersContent = generatePostgresTriggersDdl(collections);
|
|
60
61
|
if (outputPath) {
|
|
61
62
|
const outputDir = path.dirname(outputPath);
|
|
62
63
|
await promises.mkdir(outputDir, { recursive: true });
|
|
@@ -75,6 +76,11 @@ var runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
75
76
|
await promises.writeFile(vectorPath, vectorContent);
|
|
76
77
|
out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);
|
|
77
78
|
} else if (fs$1.existsSync(vectorPath)) await promises.rm(vectorPath);
|
|
79
|
+
const triggersPath = path.join(outputDir, "triggers.sql");
|
|
80
|
+
if (triggersContent) {
|
|
81
|
+
await promises.writeFile(triggersPath, triggersContent);
|
|
82
|
+
out(`✅ PostgreSQL trigger DDL generated successfully at ${triggersPath}`);
|
|
83
|
+
} else if (fs$1.existsSync(triggersPath)) await promises.rm(triggersPath);
|
|
78
84
|
} else {
|
|
79
85
|
out("✅ PostgreSQL DDL generated successfully.");
|
|
80
86
|
out(String(ddlContent));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-postgres-ddl.js","names":[],"sources":["../../src/schema/generate-postgres-ddl.ts","../../src/schema/bin/generate-postgres-ddl.ts"],"sourcesContent":["import { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport {\n generatePostgresDdl,\n generatePostgresPoliciesDdl,\n generatePostgresSearchDdl,\n generatePostgresVectorDdl\n} from \"./generate-postgres-ddl-logic\";\nimport { CollectionConfig, declaredDatabaseExtensions } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { out, outError } from \"../cli-output\";\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 * This generator runs as its own tsx subprocess and loads only the collections\n * directory, so nothing else in it would ever import the resources module — and\n * a permission that reads as absent because nobody loaded it is the worst shape\n * this could take: the push succeeds, `vector.sql` quietly omits the\n * `CREATE EXTENSION`, and the failure lands one statement later looking like a\n * 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\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n try {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n\n const resolvedPath = path.resolve(collectionsFilePath);\n\n // `<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 // Shared with the runtime and the doctor: what gets generated here must\n // be exactly what the server serves, including directory-level defaults.\n let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);\n\n\n if (!collections || !Array.isArray(collections)) {\n collections = [];\n }\n\n // Sort collections by slug alphabetically to ensure deterministic DDL generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\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 = await 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\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 } 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\n } catch (error) {\n outError(`Error generating DDL schema: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n // Step 1 of `rebase db push` is this process, and its exit code is the\n // only thing the push reads. Printing and exiting 0 made a resources\n // module that does not evaluate — the single most common cause here —\n // report `✓ rebase db push completed successfully` while Atlas applied\n // whatever `drizzle/schema.sql` happened to be on disk, sometimes\n // minutes old. With no prior file the same run died inside Atlas on\n // `stat drizzle/schema.sql: no such file or directory`, which hides the\n // real cause two screens up.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating DDL schema...`);\n // A watch session outlives a bad edit: the cause is already on\n // stderr, and the next save is the retry. Only the one-shot run\n // below turns a failure into an exit code.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // `runGeneration` has already printed the cause and set the exit code; the\n // handler is here so the last thing on the terminal is that message rather\n // than Node's unhandled-rejection dump of the same stack.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n","/**\n * The built entry point for `generate-postgres-ddl`.\n *\n * `generate-postgres-ddl.ts` guards its own `main()` with\n * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports\n * the module and importing the driver must not run the generator. That guard is\n * correct when the file IS the process entry — which is how it runs from source\n * under tsx — and silently false once the module is bundled: the executed file\n * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`\n * is not the stub's path. The generator then did nothing, exited 0, and the\n * caller reported success over an unwritten schema.\n *\n * So the built path gets an entry that says what it means.\n */\nimport { main } from \"../generate-postgres-ddl\";\n\nmain().catch(() => {\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generate-postgres-ddl.js","names":[],"sources":["../../src/schema/generate-postgres-ddl.ts","../../src/schema/bin/generate-postgres-ddl.ts"],"sourcesContent":["import { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport {\n generatePostgresDdl,\n generatePostgresPoliciesDdl,\n generatePostgresSearchDdl,\n generatePostgresTriggersDdl,\n generatePostgresVectorDdl\n} from \"./generate-postgres-ddl-logic\";\nimport { CollectionConfig, declaredDatabaseExtensions } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { out, outError } from \"../cli-output\";\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 * This generator runs as its own tsx subprocess and loads only the collections\n * directory, so nothing else in it would ever import the resources module — and\n * a permission that reads as absent because nobody loaded it is the worst shape\n * this could take: the push succeeds, `vector.sql` quietly omits the\n * `CREATE EXTENSION`, and the failure lands one statement later looking like a\n * 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\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n try {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n\n const resolvedPath = path.resolve(collectionsFilePath);\n\n // `<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 // Shared with the runtime and the doctor: what gets generated here must\n // be exactly what the server serves, including directory-level defaults.\n let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);\n\n\n if (!collections || !Array.isArray(collections)) {\n collections = [];\n }\n\n // Sort collections by slug alphabetically to ensure deterministic DDL generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\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 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\n } catch (error) {\n outError(`Error generating DDL schema: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n // Step 1 of `rebase db push` is this process, and its exit code is the\n // only thing the push reads. Printing and exiting 0 made a resources\n // module that does not evaluate — the single most common cause here —\n // report `✓ rebase db push completed successfully` while Atlas applied\n // whatever `drizzle/schema.sql` happened to be on disk, sometimes\n // minutes old. With no prior file the same run died inside Atlas on\n // `stat drizzle/schema.sql: no such file or directory`, which hides the\n // real cause two screens up.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating DDL schema...`);\n // A watch session outlives a bad edit: the cause is already on\n // stderr, and the next save is the retry. Only the one-shot run\n // below turns a failure into an exit code.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // `runGeneration` has already printed the cause and set the exit code; the\n // handler is here so the last thing on the terminal is that message rather\n // than Node's unhandled-rejection dump of the same stack.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n","/**\n * The built entry point for `generate-postgres-ddl`.\n *\n * `generate-postgres-ddl.ts` guards its own `main()` with\n * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports\n * the module and importing the driver must not run the generator. That guard is\n * correct when the file IS the process entry — which is how it runs from source\n * under tsx — and silently false once the module is bundled: the executed file\n * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`\n * is not the stub's path. The generator then did nothing, exited 0, and the\n * caller reported success over an unwritten schema.\n *\n * So the built path gets an entry that says what it means.\n */\nimport { main } from \"../generate-postgres-ddl\";\n\nmain().catch(() => {\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** An operation on a column that PostgreSQL refuses while a dependant exists. */
|
|
2
|
+
export type ColumnMutationKind = "type" | "drop";
|
|
3
|
+
export interface ColumnMutation {
|
|
4
|
+
/** Absent when the plan did not qualify the table; matched loosely then. */
|
|
5
|
+
schema?: string;
|
|
6
|
+
table: string;
|
|
7
|
+
column: string;
|
|
8
|
+
kind: ColumnMutationKind;
|
|
9
|
+
}
|
|
10
|
+
/** One `generated column → column it reads` edge, as read from the catalogue. */
|
|
11
|
+
export interface GeneratedColumnDependency {
|
|
12
|
+
schema: string;
|
|
13
|
+
table: string;
|
|
14
|
+
/** The generated column. */
|
|
15
|
+
column: string;
|
|
16
|
+
/** A column its expression reads. */
|
|
17
|
+
dependsOn: string;
|
|
18
|
+
}
|
|
19
|
+
/** A generated column standing in the way of the plan, and why. */
|
|
20
|
+
export interface GeneratedColumnConflict {
|
|
21
|
+
schema: string;
|
|
22
|
+
table: string;
|
|
23
|
+
/** The generated column that has to go before the apply. */
|
|
24
|
+
column: string;
|
|
25
|
+
/** The mutated columns it reads, deduplicated and sorted. */
|
|
26
|
+
blocking: {
|
|
27
|
+
column: string;
|
|
28
|
+
kind: ColumnMutationKind;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every column an Atlas plan retypes or drops, with the table it belongs to.
|
|
33
|
+
*
|
|
34
|
+
* One statement carries many clauses — Atlas emits `ALTER TABLE "public"."posts"
|
|
35
|
+
* ALTER COLUMN "title" TYPE text, ALTER COLUMN "slug" TYPE text, …` — so the
|
|
36
|
+
* table is read once per statement and the clauses are scanned within it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseColumnMutations(planSql: string): ColumnMutation[];
|
|
39
|
+
/**
|
|
40
|
+
* The generated columns that block a plan: those reading a column it retypes
|
|
41
|
+
* or drops.
|
|
42
|
+
*
|
|
43
|
+
* A mutation with no schema matches on table name alone. That is deliberately
|
|
44
|
+
* loose — a bare `ALTER TABLE posts` resolves through `search_path` at
|
|
45
|
+
* execution time, and guessing wrong in the *permissive* direction costs a
|
|
46
|
+
* generated column that is rebuilt seconds later, while guessing wrong in the
|
|
47
|
+
* strict direction costs the whole push.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findGeneratedColumnConflicts(mutations: ColumnMutation[], dependencies: GeneratedColumnDependency[]): GeneratedColumnConflict[];
|
|
50
|
+
/**
|
|
51
|
+
* Split the conflicts into the ones Rebase can rebuild and the ones it cannot.
|
|
52
|
+
*
|
|
53
|
+
* `managed` is decided by the very list that hid the column from Atlas: a
|
|
54
|
+
* `schema.table.column` exclude pattern means Rebase generated the column and
|
|
55
|
+
* `search.sql` will put it back. Anything else is somebody's hand-written
|
|
56
|
+
* generated column — dropping it would destroy a definition Rebase has no copy
|
|
57
|
+
* of, so it is reported and the push stops instead.
|
|
58
|
+
*/
|
|
59
|
+
export declare function partitionByOwnership(conflicts: GeneratedColumnConflict[], excludePatterns: string[]): {
|
|
60
|
+
managed: GeneratedColumnConflict[];
|
|
61
|
+
foreign: GeneratedColumnConflict[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* `ALTER TABLE … DROP COLUMN …` for each conflict, in catalogue order.
|
|
65
|
+
*
|
|
66
|
+
* `ifExists` is for the migration path, where the statement is written now and
|
|
67
|
+
* runs later against a database nobody can inspect: on a fresh one the column
|
|
68
|
+
* does not exist yet (the migration is about to create the table), on a live
|
|
69
|
+
* one it does, and the same file has to survive both.
|
|
70
|
+
*/
|
|
71
|
+
export declare function dropGeneratedColumnStatements(conflicts: GeneratedColumnConflict[], opts?: {
|
|
72
|
+
ifExists?: boolean;
|
|
73
|
+
}): string[];
|
|
74
|
+
/**
|
|
75
|
+
* The preamble a migration needs when it retypes a column a search block reads.
|
|
76
|
+
*
|
|
77
|
+
* Prepended, because the drop has to precede the `ALTER COLUMN … TYPE` that
|
|
78
|
+
* PostgreSQL would otherwise refuse. The column comes back at the end of the
|
|
79
|
+
* same migration: `db generate` appends `search.sql`, whose
|
|
80
|
+
* `ADD COLUMN IF NOT EXISTS` rebuilds the column, its index and its stamp.
|
|
81
|
+
*/
|
|
82
|
+
export declare function migrationDropPreamble(conflicts: GeneratedColumnConflict[]): string;
|
|
83
|
+
/** `public.posts.search_vector`, the way every message here names one. */
|
|
84
|
+
export declare function describeConflict(conflict: GeneratedColumnConflict): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
+
import type { TableMeta } from "./introspect-db-logic.js";
|
|
3
|
+
export type SchemaDifferenceKind = "missing-table" | "missing-column" | "extra-column" | "type" | "nullability";
|
|
4
|
+
export interface SchemaDifference {
|
|
5
|
+
kind: SchemaDifferenceKind;
|
|
6
|
+
/** Bare table name, as both sides spell it. */
|
|
7
|
+
table: string;
|
|
8
|
+
/** The collection backed by that table, when one is. */
|
|
9
|
+
collection?: string;
|
|
10
|
+
/** The column, in database terms. */
|
|
11
|
+
column?: string;
|
|
12
|
+
/** The field it is served under — the property key, where there is one. */
|
|
13
|
+
property?: string;
|
|
14
|
+
/** What `schema.generated.ts` says. */
|
|
15
|
+
generated?: string;
|
|
16
|
+
/** What the database says. */
|
|
17
|
+
live?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The families a boot warning is worth raising over.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately coarse. `varchar(120)` and `text` are the same family because the
|
|
23
|
+
* runtime treats them identically and a warning about the difference would fire
|
|
24
|
+
* on every boot of a correct project — the widths still reach Postgres, which
|
|
25
|
+
* enforces them. What this is looking for is the kind of drift that changes what
|
|
26
|
+
* a value *is*: a number that became text, a timestamp that became a date, a
|
|
27
|
+
* column that stopped existing.
|
|
28
|
+
*/
|
|
29
|
+
export declare function typeFamily(sqlType: string | undefined): string;
|
|
30
|
+
/**
|
|
31
|
+
* Every way the generated module and the database disagree, for the tables both
|
|
32
|
+
* describe.
|
|
33
|
+
*
|
|
34
|
+
* One-directional in one respect only: a table the module has and the database
|
|
35
|
+
* does not is reported (that is the stale-file case), while a table the database
|
|
36
|
+
* has and the module does not is left alone — a database may hold anything, and
|
|
37
|
+
* the collections-vs-file question is `generated-schema-staleness`'s.
|
|
38
|
+
*/
|
|
39
|
+
export declare function diffGeneratedSchemaAgainstCatalogue(options: {
|
|
40
|
+
/** The `tables` export of the bundle's schema module. */
|
|
41
|
+
generated: Record<string, unknown> | undefined;
|
|
42
|
+
/** What was introspected for this data source, keyed by bare table name. */
|
|
43
|
+
catalogue: Map<string, TableMeta>;
|
|
44
|
+
/** Collections of this source, so a column can be named as a property. */
|
|
45
|
+
collections: CollectionConfig[];
|
|
46
|
+
}): SchemaDifference[];
|
|
47
|
+
/** One line, in the terms the reader has: collection, property, column. */
|
|
48
|
+
export declare function describeSchemaDifference(difference: SchemaDifference): string;
|
|
49
|
+
/**
|
|
50
|
+
* Report the differences, one warning each, and never throw.
|
|
51
|
+
*
|
|
52
|
+
* Per difference rather than one summary block on purpose: these are read out of
|
|
53
|
+
* a log aggregator as often as a terminal, and a single multi-line message
|
|
54
|
+
* cannot be filtered down to the one collection somebody is looking at.
|
|
55
|
+
*/
|
|
56
|
+
export declare function warnOnGeneratedSchemaDrift(differences: SchemaDifference[]): void;
|
|
@@ -93,3 +93,36 @@ export declare function staleVerdict(input: {
|
|
|
93
93
|
/** Whether `--fix` was given. */
|
|
94
94
|
fix: boolean;
|
|
95
95
|
}): StaleVerdict;
|
|
96
|
+
/** One exported declaration that differs between two generated schemas. */
|
|
97
|
+
export interface DeclarationDifference {
|
|
98
|
+
/** The exported symbol: a table, an enum, a relations block. */
|
|
99
|
+
name: string;
|
|
100
|
+
kind: "missing" | "unexpected" | "changed";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* What differs between the schema the current collections generate and the one
|
|
104
|
+
* on disk — by declaration, not by byte.
|
|
105
|
+
*
|
|
106
|
+
* `rebase doctor` used to whitespace-normalise both files and compare the
|
|
107
|
+
* strings, which answers "are these the same text" when the question is "does
|
|
108
|
+
* this file still describe the same schema". The difference is not academic:
|
|
109
|
+
* the answer was a single yes/no with nothing to act on, so a developer told
|
|
110
|
+
* their schema was stale had to regenerate and read the diff to find out what
|
|
111
|
+
* had changed.
|
|
112
|
+
*
|
|
113
|
+
* Comparing declarations answers the question the reader actually has. The
|
|
114
|
+
* generated file is a flat list of `export const`s — one per table, enum and
|
|
115
|
+
* relations block, each the rendering of a piece of the schema plan — and two
|
|
116
|
+
* files declaring the same symbols with the same bodies describe the same
|
|
117
|
+
* database whatever their formatting.
|
|
118
|
+
*
|
|
119
|
+
* Not a plan-to-plan comparison, and deliberately not: reading a plan back out
|
|
120
|
+
* of `schema.generated.ts` means interpreting drizzle-orm builder calls, which
|
|
121
|
+
* is a second interpreter — the exact thing this area was just rid of. The
|
|
122
|
+
* rendered declarations are the closest honest proxy, and the renderer is
|
|
123
|
+
* deterministic, so the only things normalised away are what it is free to vary
|
|
124
|
+
* independently of the plan: whitespace and its own comments.
|
|
125
|
+
*/
|
|
126
|
+
export declare function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[];
|
|
127
|
+
/** The differences, as the line a developer reads in `rebase doctor`. */
|
|
128
|
+
export declare function describeDeclarationDifferences(differences: DeclarationDifference[]): string;
|
|
@@ -18,6 +18,8 @@ export interface TableColumn {
|
|
|
18
18
|
ordinal_position?: number;
|
|
19
19
|
/** `"ALWAYS"` for a generated column, `"NEVER"` otherwise. */
|
|
20
20
|
is_generated?: string;
|
|
21
|
+
/** The `GENERATED ALWAYS AS (…)` expression, when `is_generated` says so. */
|
|
22
|
+
generation_expression?: string | null;
|
|
21
23
|
/** `"YES"` for an identity column. */
|
|
22
24
|
is_identity?: string;
|
|
23
25
|
/** `"ALWAYS"` or `"BY DEFAULT"` on an identity column. */
|