@rebasepro/server-postgres 0.10.1-canary.b1e3dbf → 0.10.1-canary.ff9ccd6

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.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Bringing a database up to date with a bundle's collections, additively.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * A managed runtime boots someone else's compiled project against a database it
7
+ * has never seen. Auth tables are ensured at boot already, but collection tables
8
+ * were not created by anything: the platform ran the app and every `/api/data/*`
9
+ * request answered 500 on a missing relation. `rebase db push` cannot help — it
10
+ * is an Atlas-driven CLI command, and the runtime image ships no CLI.
11
+ *
12
+ * ## Why additive-only, forever
13
+ *
14
+ * This runs unattended, against a database with customers' data in it, with no
15
+ * human reading a diff. So it may only ever do things that cannot lose data:
16
+ * create a missing table, add a missing column, create a missing enum type.
17
+ *
18
+ * It will **never** drop a table or a column, narrow a type, or alter a
19
+ * constraint. A removed field leaves its column behind; a renamed field looks
20
+ * like an addition and the old column stays. That is the correct trade for an
21
+ * automated path — the alternative is an unattended process that can silently
22
+ * destroy a column, which is precisely the failure `db push` was hardened
23
+ * against. Destructive changes stay a deliberate, human-reviewed migration.
24
+ *
25
+ * Because of that, this is safe to run on every boot, and re-running it is a
26
+ * no-op.
27
+ */
28
+ import { type CollectionConfig } from "@rebasepro/types";
29
+ /**
30
+ * The subset of a database handle this needs: run a statement, get rows back.
31
+ *
32
+ * Deliberately parameterless. Everything here is DDL or catalogue reads keyed by
33
+ * schema name, and schema names are identifiers — they cannot be bound as
34
+ * parameters anyway. They are validated against {@link SAFE_IDENTIFIER} before
35
+ * they reach a statement, so a config that somehow carried a quote is refused
36
+ * rather than concatenated.
37
+ */
38
+ export interface Queryable {
39
+ query<T = unknown>(sql: string): Promise<{
40
+ rows: T[];
41
+ }>;
42
+ }
43
+ /** What the database currently has, as the planner needs it. */
44
+ export interface ExistingSchema {
45
+ /** `schema.table` → set of column names. */
46
+ tables: Map<string, Set<string>>;
47
+ /** `schema.typename` of every enum type that already exists. */
48
+ enums: Set<string>;
49
+ }
50
+ export interface EnsureAction {
51
+ kind: "create-enum" | "create-table" | "add-column";
52
+ /** Qualified target, for logging: `public.posts` or `public.posts.title`. */
53
+ target: string;
54
+ sql: string;
55
+ }
56
+ export interface EnsurePlan {
57
+ actions: EnsureAction[];
58
+ /** Every statement, in dependency order. Empty when the schema is current. */
59
+ statements: string[];
60
+ }
61
+ /**
62
+ * Decide what to add. Pure — the caller supplies what exists and runs the result.
63
+ *
64
+ * Ordering matters and is deliberate: enum types before the tables and columns
65
+ * that reference them, tables before the columns added to other tables (a new
66
+ * table may be the target of a relation), and nothing is emitted twice.
67
+ */
68
+ export declare function planCollectionSchemaEnsure(collections: CollectionConfig[], existing: ExistingSchema): EnsurePlan;
69
+ /** Read what the database has, for the schemas the collections live in. */
70
+ export declare function readExistingSchema(client: Queryable, schemas: string[]): Promise<ExistingSchema>;
71
+ /**
72
+ * Bring the database up to date. Returns what it did.
73
+ *
74
+ * Each statement runs on its own rather than in one transaction: they are all
75
+ * independently safe and idempotent, and a single failure (an enum label that
76
+ * cannot be added, say) should not roll back the tables that were created fine.
77
+ * The error is surfaced with the statement that caused it.
78
+ */
79
+ export declare function ensureCollectionTables(client: Queryable, collections: CollectionConfig[], log?: (message: string) => void): Promise<EnsurePlan>;
@@ -1,4 +1,7 @@
1
- import { CollectionConfig } from "@rebasepro/types";
1
+ import { CollectionConfig, Property } from "@rebasepro/types";
2
+ export declare const resolveColumnName: (propName: string, prop?: Property | null) => string;
3
+ export declare const isIdProperty: (propName: string, prop: Property, collection: CollectionConfig) => boolean;
4
+ export declare const getSqlColumnType: (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]) => string;
2
5
  export declare const generatePostgresDdl: (collections: CollectionConfig[], options?: {
3
6
  includePolicies?: boolean;
4
7
  }) => Promise<string>;
@@ -0,0 +1,336 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ import "process";
3
+ __createRequire(import.meta.url);
4
+ //#region ../types/src/types/entities.ts
5
+ /**
6
+ * Class used to create a reference to a entity in a different path
7
+ */
8
+ var EntityRelation = class {
9
+ __type = "relation";
10
+ /**
11
+ * ID of the entity
12
+ */
13
+ id;
14
+ /**
15
+ * A string representing the path of the referenced document (relative
16
+ * to the root of the database).
17
+ */
18
+ path;
19
+ /**
20
+ * Pre-fetched data payload to eliminate N+1 queries.
21
+ * When present, clients can use this directly instead of fetching.
22
+ */
23
+ data;
24
+ constructor(id, path, data) {
25
+ this.id = id;
26
+ this.path = path;
27
+ this.data = data;
28
+ }
29
+ get pathWithId() {
30
+ return `${this.path}/${this.id}`;
31
+ }
32
+ isEntityReference() {
33
+ return false;
34
+ }
35
+ isEntityRelation() {
36
+ return true;
37
+ }
38
+ };
39
+ var Vector = class {
40
+ value;
41
+ constructor(value) {
42
+ this.value = value;
43
+ }
44
+ };
45
+ //#endregion
46
+ //#region ../types/src/types/filter-operators.ts
47
+ /** Maps REST short-code operators to their canonical equivalents. */
48
+ var REST_TO_CANONICAL = {
49
+ "eq": "==",
50
+ "neq": "!=",
51
+ "gt": ">",
52
+ "gte": ">=",
53
+ "lt": "<",
54
+ "lte": "<=",
55
+ "in": "in",
56
+ "nin": "not-in",
57
+ "cs": "array-contains",
58
+ "csa": "array-contains-any",
59
+ "like": "like",
60
+ "ilike": "ilike",
61
+ "nlike": "not-like",
62
+ "nilike": "not-ilike",
63
+ "isnull": "is-null",
64
+ "notnull": "is-not-null"
65
+ };
66
+ /**
67
+ * Operators that test for null/not-null and therefore ignore their value.
68
+ * Codecs normalize the value of these conditions to `null`.
69
+ */
70
+ var NULL_OPS = new Set(["is-null", "is-not-null"]);
71
+ /**
72
+ * Every canonical operator, in a stable order. Useful for engine capability
73
+ * declarations ({@link DataSourceCapabilities.filterOperators}) and for
74
+ * building operator subsets.
75
+ * @group Models
76
+ */
77
+ var ALL_WHERE_FILTER_OPS = [
78
+ "<",
79
+ "<=",
80
+ "==",
81
+ "!=",
82
+ ">=",
83
+ ">",
84
+ "in",
85
+ "not-in",
86
+ "array-contains",
87
+ "array-contains-any",
88
+ "like",
89
+ "ilike",
90
+ "not-like",
91
+ "not-ilike",
92
+ "is-null",
93
+ "is-not-null"
94
+ ];
95
+ /** All canonical operator strings for runtime validation. */
96
+ var CANONICAL_OPS = new Set(ALL_WHERE_FILTER_OPS);
97
+ /**
98
+ * Resolve any operator string (canonical or REST short-code) to its
99
+ * canonical `WhereFilterOp` form. Returns `undefined` for unknown operators.
100
+ *
101
+ * @example
102
+ * toCanonicalOp("==") // "=="
103
+ * toCanonicalOp("eq") // "=="
104
+ * toCanonicalOp("cs") // "array-contains"
105
+ * toCanonicalOp("xyz") // undefined
106
+ */
107
+ function toCanonicalOp(op) {
108
+ if (CANONICAL_OPS.has(op)) return op;
109
+ return REST_TO_CANONICAL[op];
110
+ }
111
+ //#endregion
112
+ //#region ../types/src/types/collections.ts
113
+ /**
114
+ * Type guard for PostgreSQL collections.
115
+ * Returns true if the collection uses the Postgres engine (or the default engine).
116
+ *
117
+ * Generic over the *input* type, and narrows by intersection rather than
118
+ * replacement. Narrowing to a bare `PostgresCollectionConfig` discarded whatever
119
+ * the caller actually had — most visibly the admin panel's view model, whose
120
+ * flattened presentation fields vanished the moment a collection passed through
121
+ * one of these guards.
122
+ *
123
+ * @group Models
124
+ */
125
+ function isPostgresCollectionConfig(collection) {
126
+ return !collection.engine || collection.engine === "postgres";
127
+ }
128
+ /**
129
+ * Reads a collection's driver-declared subcollections thunk (the `subcollections`
130
+ * field) independent of engine identity, so engine-agnostic code doesn't have to
131
+ * type-guard against a specific driver. Returns `undefined` when the collection
132
+ * declares none.
133
+ *
134
+ * Pair with `getDataSourceCapabilities(engine).supportsSubcollections` to decide
135
+ * whether the engine honours subcollections at all before reading them.
136
+ * @group Models
137
+ */
138
+ function getDeclaredSubcollections(collection) {
139
+ return collection.subcollections;
140
+ }
141
+ //#endregion
142
+ //#region ../types/src/types/policy.ts
143
+ /**
144
+ * The id a request without a logged-in user reports as `auth.uid()`.
145
+ *
146
+ * A user-context request always sets `app.uid`: blank would read back as
147
+ * `NULL`, and `NULL` is how the trusted server context is recognised, so an
148
+ * anonymous visitor would be promoted to server privileges. The driver
149
+ * therefore substitutes this sentinel at the single chokepoint where the GUC
150
+ * is set.
151
+ *
152
+ * The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
153
+ * tautology on the user path** — it is true for anonymous visitors too. Use
154
+ * {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
155
+ * in", and {@link policy.serverContext} to mean "the trusted server context".
156
+ *
157
+ * @group Models
158
+ */
159
+ var ANONYMOUS_USER_ID = "anonymous";
160
+ /** @group Models */
161
+ var policy = {
162
+ true: () => ({ kind: "true" }),
163
+ false: () => ({ kind: "false" }),
164
+ and: (...operands) => ({
165
+ kind: "and",
166
+ operands
167
+ }),
168
+ or: (...operands) => ({
169
+ kind: "or",
170
+ operands
171
+ }),
172
+ not: (operand) => ({
173
+ kind: "not",
174
+ operand
175
+ }),
176
+ compare: (left, op, right) => ({
177
+ kind: "compare",
178
+ op,
179
+ left,
180
+ right
181
+ }),
182
+ rolesOverlap: (roles) => ({
183
+ kind: "rolesOverlap",
184
+ roles
185
+ }),
186
+ rolesContain: (roles) => ({
187
+ kind: "rolesContain",
188
+ roles
189
+ }),
190
+ authenticated: () => ({ kind: "authenticated" }),
191
+ serverContext: () => ({ kind: "serverContext" }),
192
+ existsIn: (args) => ({
193
+ kind: "existsIn",
194
+ collection: args.collection,
195
+ where: args.where
196
+ }),
197
+ raw: (sql) => ({
198
+ kind: "raw",
199
+ sql
200
+ }),
201
+ field: (name) => ({
202
+ kind: "field",
203
+ name
204
+ }),
205
+ outerField: (name) => ({
206
+ kind: "outerField",
207
+ name
208
+ }),
209
+ literal: (value) => ({
210
+ kind: "literal",
211
+ value
212
+ }),
213
+ authUid: () => ({ kind: "authUid" }),
214
+ authRoles: () => ({ kind: "authRoles" })
215
+ };
216
+ //#endregion
217
+ //#region ../types/src/types/backend.ts
218
+ /**
219
+ * Type guard: does this admin support SQL operations?
220
+ * @group Admin
221
+ */
222
+ function isSQLAdmin(admin) {
223
+ return !!admin && typeof admin.executeSql === "function";
224
+ }
225
+ /**
226
+ * Type guard: does this admin support schema management?
227
+ * @group Admin
228
+ */
229
+ function isSchemaAdmin(admin) {
230
+ return !!admin && (typeof admin.fetchUnmappedTables === "function" || typeof admin.fetchTableMetadata === "function");
231
+ }
232
+ //#endregion
233
+ //#region ../types/src/types/channel_bus.ts
234
+ /**
235
+ * Whether `setting` is an already-constructed transport rather than a request
236
+ * for a built-in one.
237
+ *
238
+ * Structural rather than nominal so that an instance from a *different copy* of
239
+ * `@rebasepro/types` — an entirely normal outcome of a separately versioned
240
+ * transport package — is still recognised.
241
+ */
242
+ function isChannelBusInstance(setting) {
243
+ return typeof setting?.publish === "function";
244
+ }
245
+ //#endregion
246
+ //#region ../types/src/types/data_source.ts
247
+ /**
248
+ * The default data-source key, used when a collection does not name a
249
+ * `dataSource`. Shared by the frontend router and the backend driver
250
+ * registry so both agree on "the default database".
251
+ * @group Models
252
+ */
253
+ var DEFAULT_DATA_SOURCE_KEY = "(default)";
254
+ /** @group Models */
255
+ var POSTGRES_CAPABILITIES = {
256
+ key: "postgres",
257
+ label: "PostgreSQL",
258
+ supportsRelations: true,
259
+ supportsSubcollections: false,
260
+ supportsRLS: true,
261
+ supportsReferences: false,
262
+ supportsColumnTypes: true,
263
+ supportsRealtime: true,
264
+ filterOperators: ALL_WHERE_FILTER_OPS,
265
+ supportsSQLAdmin: true,
266
+ supportsDocumentAdmin: false,
267
+ supportsSchemaAdmin: true
268
+ };
269
+ /** @group Models */
270
+ var FIREBASE_CAPABILITIES = {
271
+ key: "firestore",
272
+ label: "Firebase / Firestore",
273
+ supportsRelations: false,
274
+ supportsSubcollections: true,
275
+ supportsRLS: false,
276
+ supportsReferences: true,
277
+ supportsColumnTypes: false,
278
+ supportsRealtime: true,
279
+ filterOperators: ALL_WHERE_FILTER_OPS.filter((op) => op !== "like" && op !== "ilike" && op !== "not-like" && op !== "not-ilike"),
280
+ supportsSQLAdmin: false,
281
+ supportsDocumentAdmin: false,
282
+ supportsSchemaAdmin: false
283
+ };
284
+ /** @group Models */
285
+ var MONGODB_CAPABILITIES = {
286
+ key: "mongodb",
287
+ label: "MongoDB",
288
+ supportsRelations: false,
289
+ supportsSubcollections: true,
290
+ supportsRLS: false,
291
+ supportsReferences: true,
292
+ supportsColumnTypes: false,
293
+ supportsRealtime: false,
294
+ filterOperators: ALL_WHERE_FILTER_OPS,
295
+ supportsSQLAdmin: false,
296
+ supportsDocumentAdmin: true,
297
+ supportsSchemaAdmin: true
298
+ };
299
+ /**
300
+ * Fallback capabilities when the driver is unknown.
301
+ * Enables everything so nothing is hidden unexpectedly.
302
+ * @group Models
303
+ */
304
+ var DEFAULT_CAPABILITIES = {
305
+ key: "(default)",
306
+ label: "Default",
307
+ supportsRelations: true,
308
+ supportsSubcollections: true,
309
+ supportsRLS: true,
310
+ supportsReferences: true,
311
+ supportsColumnTypes: true,
312
+ supportsRealtime: true,
313
+ filterOperators: ALL_WHERE_FILTER_OPS,
314
+ supportsSQLAdmin: true,
315
+ supportsDocumentAdmin: true,
316
+ supportsSchemaAdmin: true
317
+ };
318
+ var CAPABILITIES_REGISTRY = {
319
+ postgres: POSTGRES_CAPABILITIES,
320
+ firestore: FIREBASE_CAPABILITIES,
321
+ mongodb: MONGODB_CAPABILITIES,
322
+ "(default)": DEFAULT_CAPABILITIES
323
+ };
324
+ /**
325
+ * Look up capabilities for a given engine key.
326
+ * If `engine` is undefined or not found, returns `DEFAULT_CAPABILITIES`.
327
+ * @group Models
328
+ */
329
+ function getDataSourceCapabilities(engine) {
330
+ if (!engine) return POSTGRES_CAPABILITIES;
331
+ return CAPABILITIES_REGISTRY[engine] ?? DEFAULT_CAPABILITIES;
332
+ }
333
+ //#endregion
334
+ export { isSchemaAdmin as a, getDeclaredSubcollections as c, REST_TO_CANONICAL as d, toCanonicalOp as f, isSQLAdmin as i, isPostgresCollectionConfig as l, Vector as m, getDataSourceCapabilities as n, ANONYMOUS_USER_ID as o, EntityRelation as p, isChannelBusInstance as r, policy as s, DEFAULT_DATA_SOURCE_KEY as t, NULL_OPS as u };
335
+
336
+ //# sourceMappingURL=src-CBgtrPhJ.js.map