@rebasepro/server-postgres 0.17.3-canary.gdd23447 → 0.18.0

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.
Files changed (149) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/backup-argv.d.ts +19 -0
  4. package/dist/branch-argv.d.ts +28 -0
  5. package/dist/branch-prune.d.ts +77 -0
  6. package/dist/cli-collections-path.d.ts +31 -0
  7. package/dist/cli-errors.d.ts +123 -0
  8. package/dist/cli-flags.d.ts +101 -0
  9. package/dist/cli-helpers.d.ts +94 -10
  10. package/dist/collection-index-C7LopMyX.js +247 -0
  11. package/dist/collection-index-C7LopMyX.js.map +1 -0
  12. package/dist/databasePoolManager.d.ts +27 -0
  13. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
  14. package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
  15. package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
  16. package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.es.js +19307 -3059
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
  21. package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
  22. package/dist/schema/column-type-drift.d.ts +79 -0
  23. package/dist/schema/doctor.d.ts +42 -0
  24. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  25. package/dist/schema/generate-next-step.d.ts +30 -0
  26. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  27. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  28. package/dist/schema/introspect-db-queries.d.ts +34 -0
  29. package/dist/security/rls-enforcement.d.ts +63 -2
  30. package/dist/services/BranchService.d.ts +86 -1
  31. package/dist/services/collection-helpers.d.ts +1 -1
  32. package/dist/services/realtimeService.d.ts +30 -2
  33. package/dist/services/row-pipeline.d.ts +12 -1
  34. package/dist/utils/pg-error-utils.d.ts +3 -1
  35. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  36. package/package.json +32 -27
  37. package/dist/backup-service-DCk7KhhL.js +0 -9044
  38. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  39. package/dist/collection-index-BRUg10H5.js +0 -5690
  40. package/dist/collection-index-BRUg10H5.js.map +0 -1
  41. package/dist/connection-GOKU3Hu5.js +0 -277
  42. package/dist/connection-GOKU3Hu5.js.map +0 -1
  43. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  44. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  45. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  46. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  47. package/dist/src-DiB5RP2Z.js +0 -541
  48. package/dist/src-DiB5RP2Z.js.map +0 -1
  49. package/dist/websocket-BZ4H5wUz.js +0 -8198
  50. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  51. package/src/PostgresAdapter.ts +0 -91
  52. package/src/PostgresBackendDriver.ts +0 -1857
  53. package/src/PostgresBootstrapper.ts +0 -1157
  54. package/src/auth/ensure-tables.ts +0 -896
  55. package/src/auth/schema-version.ts +0 -260
  56. package/src/auth/services.ts +0 -1610
  57. package/src/backup/backup-cli.ts +0 -520
  58. package/src/backup/backup-cron.ts +0 -212
  59. package/src/backup/backup-logic.ts +0 -93
  60. package/src/backup/backup-service.ts +0 -485
  61. package/src/backup/index.ts +0 -12
  62. package/src/backup/pg-tools.ts +0 -403
  63. package/src/backup/retention.ts +0 -86
  64. package/src/cli-errors.ts +0 -265
  65. package/src/cli-helpers.ts +0 -569
  66. package/src/cli-output.ts +0 -43
  67. package/src/cli.ts +0 -1339
  68. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  69. package/src/collections/buildRegistry.ts +0 -68
  70. package/src/collections/validate-relations.ts +0 -402
  71. package/src/connection.ts +0 -333
  72. package/src/data-transformer.ts +0 -845
  73. package/src/databasePoolManager.ts +0 -95
  74. package/src/history/HistoryService.ts +0 -254
  75. package/src/history/ensure-history-table.ts +0 -53
  76. package/src/index.ts +0 -19
  77. package/src/interfaces.ts +0 -60
  78. package/src/module-dir.ts +0 -7
  79. package/src/schema/atlas-argv.ts +0 -94
  80. package/src/schema/auth-schema.ts +0 -298
  81. package/src/schema/auth-users-columns.ts +0 -131
  82. package/src/schema/carved-out-migration.ts +0 -404
  83. package/src/schema/classify-change.ts +0 -436
  84. package/src/schema/collection-index.ts +0 -427
  85. package/src/schema/collections-schema-version.ts +0 -103
  86. package/src/schema/destructive-sql.ts +0 -94
  87. package/src/schema/doctor-cli.ts +0 -66
  88. package/src/schema/doctor-policy-checks.ts +0 -105
  89. package/src/schema/doctor.ts +0 -837
  90. package/src/schema/drizzle-ddl.ts +0 -33
  91. package/src/schema/dynamic-tables.test.ts +0 -302
  92. package/src/schema/dynamic-tables.ts +0 -293
  93. package/src/schema/ensure-collection-policies.ts +0 -198
  94. package/src/schema/ensure-collection-tables.test.ts +0 -518
  95. package/src/schema/ensure-collection-tables.ts +0 -1267
  96. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  97. package/src/schema/generate-drizzle-schema.ts +0 -143
  98. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  99. package/src/schema/generate-postgres-ddl.ts +0 -180
  100. package/src/schema/generate-schema-commit.ts +0 -267
  101. package/src/schema/generated-schema-staleness.ts +0 -284
  102. package/src/schema/introspect-db-constraints.ts +0 -385
  103. package/src/schema/introspect-db-inference.ts +0 -255
  104. package/src/schema/introspect-db-logic.ts +0 -1387
  105. package/src/schema/introspect-db-naming.ts +0 -15
  106. package/src/schema/introspect-db-project.ts +0 -78
  107. package/src/schema/introspect-db-queries.ts +0 -326
  108. package/src/schema/introspect-db-structure.ts +0 -670
  109. package/src/schema/introspect-db-types.ts +0 -56
  110. package/src/schema/introspect-db.ts +0 -257
  111. package/src/schema/introspect-runtime.test.ts +0 -260
  112. package/src/schema/introspect-runtime.ts +0 -327
  113. package/src/schema/non-sql-collections.test.ts +0 -131
  114. package/src/schema/rls-bootstrap-sql.ts +0 -288
  115. package/src/schema/search-column.ts +0 -643
  116. package/src/schema/test-schema.ts +0 -11
  117. package/src/schema/vector-index.ts +0 -491
  118. package/src/security/anonymous-grants.test.ts +0 -73
  119. package/src/security/policy-drift.test.ts +0 -435
  120. package/src/security/policy-drift.ts +0 -473
  121. package/src/security/rls-enforcement.ts +0 -505
  122. package/src/services/BranchService.ts +0 -326
  123. package/src/services/FetchService.ts +0 -2115
  124. package/src/services/PersistService.ts +0 -557
  125. package/src/services/RelationService.ts +0 -978
  126. package/src/services/RelationWriteService.ts +0 -653
  127. package/src/services/cdc/CdcListener.ts +0 -103
  128. package/src/services/cdc/junction-tables.ts +0 -91
  129. package/src/services/cdc/trigger-cdc.ts +0 -173
  130. package/src/services/channel-bus/ChannelBus.ts +0 -44
  131. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  132. package/src/services/channel-bus/index.ts +0 -114
  133. package/src/services/channel-history.ts +0 -424
  134. package/src/services/channel-presence.ts +0 -185
  135. package/src/services/collection-helpers.ts +0 -420
  136. package/src/services/dataService.ts +0 -256
  137. package/src/services/index.ts +0 -14
  138. package/src/services/junction-writes.ts +0 -295
  139. package/src/services/nested-path.ts +0 -145
  140. package/src/services/pg-notify-listener.ts +0 -151
  141. package/src/services/realtimeService.ts +0 -2545
  142. package/src/services/row-pipeline.ts +0 -240
  143. package/src/services/write-denial.ts +0 -55
  144. package/src/types.ts +0 -4
  145. package/src/utils/connection-string.ts +0 -58
  146. package/src/utils/drizzle-conditions.ts +0 -2743
  147. package/src/utils/pg-array-null-patch.ts +0 -42
  148. package/src/utils/pg-error-utils.ts +0 -391
  149. package/src/websocket.ts +0 -855
@@ -1,327 +0,0 @@
1
- /**
2
- * Runtime introspection — builds collections in memory from the live database.
3
- *
4
- * This is what makes BaaS mode work with zero configuration: instead of loading
5
- * collection files from disk, the server reads `information_schema` at boot and
6
- * derives a collection per table, so any database is served over REST without a
7
- * single config file.
8
- *
9
- * Distinct from `introspect-db.ts`, which runs the same queries but emits
10
- * TypeScript *source* for a developer to edit and commit (declared collections). The two
11
- * share the mapping helpers in `introspect-db-logic.ts` so a table is described
12
- * the same way whether it was generated or introspected.
13
- */
14
- import type { PostgresCollectionConfig } from "@rebasepro/types";
15
-
16
- import {
17
- TableRow,
18
- TableColumn,
19
- EnumValue,
20
- PrimaryKeyRow,
21
- ForeignKeyRow,
22
- TableMeta,
23
- buildTablesMap,
24
- buildEnumMap,
25
- identifyJoinTables,
26
- singularize,
27
- mapPgType,
28
- getIconForTable
29
- } from "./introspect-db-logic";
30
- import { humanize } from "./introspect-db-naming";
31
- import { firstFreeKey, toWireKey } from "@rebasepro/utils";
32
-
33
- export interface IntrospectedSchema {
34
- tablesMap: Map<string, TableMeta>;
35
- enumMap: Map<string, string[]>;
36
- joinTables: Set<string>;
37
- }
38
-
39
- /** Whether a table carries an authorization model of its own. */
40
- export interface TableRlsStatus {
41
- table: string;
42
- /** ALTER TABLE … ENABLE ROW LEVEL SECURITY has been run. */
43
- rlsEnabled: boolean;
44
- /** Policies attached to it. RLS enabled with none = nothing is visible. */
45
- policyCount: number;
46
- }
47
-
48
- /**
49
- * Read the RLS posture of each table in a schema.
50
- *
51
- * This is what decides whether baas mode may serve a table. A table with RLS
52
- * disabled has no authorization model: since every authenticated request runs
53
- * as `rebase_user`, and that role is granted DML on the schema, serving such a
54
- * table hands every row to every logged-in user.
55
- */
56
- export async function readRlsStatus(client: Queryable, pgSchema: string): Promise<Map<string, TableRlsStatus>> {
57
- const { rows } = await client.query<{ table: string; rls_enabled: boolean; policy_count: string | number }>(
58
- `SELECT c.relname AS table,
59
- c.relrowsecurity AS rls_enabled,
60
- (SELECT count(*) FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count
61
- FROM pg_class c
62
- JOIN pg_namespace n ON n.oid = c.relnamespace
63
- WHERE n.nspname = $1 AND c.relkind = 'r'`,
64
- [pgSchema]
65
- );
66
-
67
- return new Map(
68
- rows.map((r) => [
69
- r.table,
70
- { table: r.table, rlsEnabled: r.rls_enabled === true, policyCount: Number(r.policy_count ?? 0) }
71
- ])
72
- );
73
- }
74
-
75
- /** Minimal query surface — satisfied by pg.Client and pg.Pool alike. */
76
- export interface Queryable {
77
- query<R>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;
78
- }
79
-
80
- /**
81
- * Read tables, columns, enums, primary keys and foreign keys for a schema.
82
- * Mirrors the queries in introspect-db.ts.
83
- */
84
- export async function introspectSchema(client: Queryable, pgSchema: string): Promise<IntrospectedSchema> {
85
- const { rows: tables } = await client.query<TableRow>(
86
- `SELECT table_name
87
- FROM information_schema.tables
88
- WHERE table_schema = $1 AND table_type = 'BASE TABLE'
89
- AND table_name NOT LIKE 'drizzle_%'
90
- AND table_name NOT LIKE 'rebase_%'
91
- ORDER BY table_name`,
92
- [pgSchema]
93
- );
94
-
95
- const { rows: columns } = await client.query<TableColumn>(
96
- `SELECT
97
- c.table_name,
98
- c.column_name,
99
- c.data_type,
100
- c.udt_name,
101
- c.is_nullable,
102
- c.column_default,
103
- (SELECT a.atttypmod FROM pg_attribute a
104
- JOIN pg_class pc ON a.attrelid = pc.oid
105
- WHERE pc.relname = c.table_name
106
- AND a.attname = c.column_name
107
- AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod
108
- FROM information_schema.columns c
109
- WHERE c.table_schema = $1`,
110
- [pgSchema]
111
- );
112
-
113
- const { rows: enumValues } = await client.query<EnumValue>(
114
- `SELECT t.typname AS enum_name,
115
- e.enumlabel AS enum_value,
116
- e.enumsortorder AS sort_order
117
- FROM pg_type t
118
- JOIN pg_enum e ON t.oid = e.enumtypid
119
- JOIN pg_namespace n ON t.typnamespace = n.oid
120
- WHERE n.nspname = $1
121
- ORDER BY t.typname, e.enumsortorder`,
122
- [pgSchema]
123
- );
124
-
125
- const { rows: pks } = await client.query<PrimaryKeyRow>(
126
- `SELECT t.relname as table_name, a.attname as column_name
127
- FROM pg_index i
128
- JOIN pg_attribute a ON a.attrelid = i.indrelid
129
- AND a.attnum = ANY(i.indkey)
130
- JOIN pg_class t ON t.oid = i.indrelid
131
- JOIN pg_namespace n ON n.oid = t.relnamespace
132
- WHERE i.indisprimary AND n.nspname = $1`,
133
- [pgSchema]
134
- );
135
-
136
- const { rows: fks } = await client.query<ForeignKeyRow>(
137
- `SELECT
138
- tc.table_name,
139
- kcu.column_name,
140
- ccu.table_name AS foreign_table_name,
141
- ccu.column_name AS foreign_column_name
142
- FROM information_schema.table_constraints AS tc
143
- JOIN information_schema.key_column_usage AS kcu
144
- ON tc.constraint_name = kcu.constraint_name
145
- AND tc.table_schema = kcu.table_schema
146
- JOIN information_schema.constraint_column_usage AS ccu
147
- ON ccu.constraint_name = tc.constraint_name
148
- AND ccu.table_schema = tc.table_schema
149
- WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = $1`,
150
- [pgSchema]
151
- );
152
-
153
- const tablesMap = buildTablesMap(tables, columns, pks, fks);
154
- return {
155
- tablesMap,
156
- enumMap: buildEnumMap(enumValues),
157
- joinTables: identifyJoinTables(tablesMap)
158
- };
159
- }
160
-
161
- /** Derive the `isId` flavour for a primary-key column. */
162
- function idKindFor(col: TableColumn, propType: string): "uuid" | "increment" | true {
163
- if (col.data_type.toLowerCase() === "uuid") return "uuid";
164
- if (propType === "number") return "increment";
165
- return true;
166
- }
167
-
168
- function buildProperties(
169
- meta: TableMeta,
170
- enumMap: Map<string, string[]>
171
- ): Record<string, Record<string, unknown>> {
172
- const properties: Record<string, Record<string, unknown>> = {};
173
- const takenKeys = new Set<string>();
174
-
175
- for (const col of meta.columns) {
176
- const isPk = meta.pks.includes(col.column_name);
177
- // Foreign keys surface as relations below, unless they are also part of
178
- // the primary key, in which case the column itself must stay.
179
- const isFk = meta.fks.some((fk) => fk.column_name === col.column_name);
180
- if (isFk && !isPk) continue;
181
-
182
- const enumValues = enumMap.get(col.udt_name);
183
- const isEnum = col.data_type === "USER-DEFINED" && enumValues !== undefined;
184
- const isVector = col.udt_name === "vector";
185
- const propType = isEnum ? "string" : isVector ? "vector" : mapPgType(col.data_type);
186
-
187
- const property: Record<string, unknown> = {
188
- name: humanize(col.column_name),
189
- columnName: col.column_name,
190
- type: propType
191
- };
192
-
193
- // The wire name, with `columnName` above carrying the column. The two
194
- // are different names for different things — this used to serve the
195
- // column, so a runtime-introspected collection answered `user_id` while
196
- // every authored one beside it answered `displayName`.
197
- //
198
- // `user_id` and `userId` as two real columns camel-case to one key, so
199
- // the first free candidate wins and the second falls back to its own
200
- // column name, then to a numbered tail. Never dropped, and stable for a
201
- // given database: columns arrive in ordinal order.
202
- const key = firstFreeKey([toWireKey(col.column_name), col.column_name], takenKeys);
203
- takenKeys.add(key);
204
-
205
- if (isPk) {
206
- property.isId = idKindFor(col, propType);
207
- } else if (col.is_nullable === "NO" && col.column_default === null) {
208
- property.validation = { required: true };
209
- }
210
-
211
- if (isEnum && enumValues) {
212
- property.enum = enumValues.map((value) => ({ id: value, label: humanize(value) }));
213
- }
214
-
215
- properties[key] = property;
216
- }
217
-
218
- return properties;
219
- }
220
-
221
- /**
222
- * Owning relations, derived from this table's foreign keys — the same shape
223
- * `generateCollectionFile` writes into a collection file: a `relation` property
224
- * whose nested descriptor carries `kind`, a `target` thunk and the `localKey`.
225
- *
226
- * The shape is load-bearing, not cosmetic. `resolveCollectionRelations` reads
227
- * relations from `property.relation` and `resolveRelation` requires `target` to
228
- * be a thunk; this used to emit `target`/`cardinality`/`localKey` flat on the
229
- * property with the slug as a bare string, which satisfies neither. Nothing
230
- * threw — the resolver simply skipped every such property and reported that the
231
- * collection had no relations. So an introspected BaaS collection had its FK
232
- * columns removed from `properties` (they "surface as relations") and then no
233
- * resolvable relation to surface as, which is why writing the FK column
234
- * directly came back as `has no field 'product_id'`: `assertKnownWriteFields`
235
- * learns that column from the resolved relation's `localKey`.
236
- *
237
- * The thunk closes over the collections being built in this same pass rather
238
- * than importing a module, which is what a runtime introspection has instead of
239
- * generated files. It is called lazily, after the map is fully populated, so a
240
- * table may reference one introspected later.
241
- */
242
- function buildRelations(
243
- meta: TableMeta,
244
- slugByTable: Map<string, string>,
245
- collectionBySlug: Map<string, PostgresCollectionConfig>
246
- ): Record<string, Record<string, unknown>> {
247
- const relations: Record<string, Record<string, unknown>> = {};
248
-
249
- for (const fk of meta.fks) {
250
- const targetSlug = slugByTable.get(fk.foreign_table_name);
251
- if (!targetSlug) continue;
252
-
253
- // Strip the conventional _id suffix: author_id -> author
254
- let key = toWireKey(fk.column_name.replace(/_id$/, ""));
255
- if (meta.pks.includes(fk.column_name) && key === fk.column_name) {
256
- // The FK is also the PK and its name doesn't imply a relation (e.g.
257
- // "id"), so naming the relation after the column would collide with
258
- // the primary-key property.
259
- key = fk.foreign_table_name;
260
- }
261
-
262
- relations[key] = {
263
- name: humanize(key),
264
- type: "relation",
265
- relation: {
266
- kind: "belongsTo",
267
- target: () => collectionBySlug.get(targetSlug),
268
- localKey: fk.column_name
269
- }
270
- };
271
- }
272
-
273
- return relations;
274
- }
275
-
276
- /**
277
- * Turn an introspected schema into collections.
278
- *
279
- * Join tables are skipped: they carry no identity of their own and exist to
280
- * express a many-to-many edge between two other tables.
281
- */
282
- export function buildCollectionsFromSchema(
283
- { tablesMap, enumMap, joinTables }: IntrospectedSchema,
284
- pgSchema: string
285
- ): PostgresCollectionConfig[] {
286
- const slugByTable = new Map<string, string>();
287
- for (const tableName of tablesMap.keys()) {
288
- if (!joinTables.has(tableName)) slugByTable.set(tableName, tableName);
289
- }
290
-
291
- const collections: PostgresCollectionConfig[] = [];
292
- // Filled as we go; the relation thunks read it lazily, so a table may point
293
- // at one that has not been built yet at the moment its relation is created.
294
- const collectionBySlug = new Map<string, PostgresCollectionConfig>();
295
-
296
- for (const [tableName, meta] of tablesMap) {
297
- if (joinTables.has(tableName)) continue;
298
-
299
- const collectionName = humanize(tableName);
300
- const collection = {
301
- name: collectionName,
302
- singularName: singularize(collectionName),
303
- slug: tableName,
304
- table: tableName,
305
- schema: pgSchema,
306
- icon: getIconForTable(tableName),
307
- properties: {
308
- ...buildProperties(meta, enumMap),
309
- ...buildRelations(meta, slugByTable, collectionBySlug)
310
- }
311
- } as unknown as PostgresCollectionConfig;
312
-
313
- collections.push(collection);
314
- collectionBySlug.set(tableName, collection);
315
- }
316
-
317
- return collections;
318
- }
319
-
320
- /** Introspect the database and return ready-to-serve collections. */
321
- export async function introspectCollections(
322
- client: Queryable,
323
- pgSchema: string
324
- ): Promise<PostgresCollectionConfig[]> {
325
- const schema = await introspectSchema(client, pgSchema);
326
- return buildCollectionsFromSchema(schema, pgSchema);
327
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * A collection served by another engine is not this engine's to provision.
3
- *
4
- * Firestore and MongoDB collections are declared in the same
5
- * `config/collections` directory as the Postgres ones — that is what
6
- * `dataSource` routing is for — and every stage of the SQL toolchain used to
7
- * read "the collections" as all of them. So a Firestore collection got a
8
- * `pgTable` in the generated schema, a `CREATE TABLE` at boot, RLS policies,
9
- * and an entry in the `db push` include list, while the app went on reading its
10
- * documents from Firestore.
11
- *
12
- * The include list is the one that can lose data: a name on it is a name Atlas
13
- * is *allowed to drop*, so a Firestore collection called `exercises` removed a
14
- * real, unrelated `public.exercises` table's protection from the next
15
- * auto-approved `db push`.
16
- */
17
- import { describe, expect, it } from "@jest/globals";
18
- import type { CollectionConfig } from "@rebasepro/types";
19
-
20
- import { generateSchema } from "./generate-drizzle-schema-logic";
21
- import {
22
- generatePostgresDdl,
23
- generatePostgresPoliciesDdl,
24
- planCollectionPolicies
25
- } from "./generate-postgres-ddl-logic";
26
- import { planCollectionSchemaEnsure, type ExistingSchema } from "./ensure-collection-tables";
27
- import { getTableIncludesFromCollections } from "../cli-helpers";
28
-
29
- const sqlCollection = {
30
- name: "Customers",
31
- slug: "customers",
32
- properties: {
33
- id: { name: "ID", type: "string", isId: "uuid" },
34
- email: { name: "Email", type: "string" }
35
- },
36
- securityRules: [{ operation: "read", policy: "true" }]
37
- } as unknown as CollectionConfig;
38
-
39
- const firestoreCollection = {
40
- name: "Exercises",
41
- slug: "exercises",
42
- engine: "firestore",
43
- dataSource: "firestore",
44
- properties: {
45
- id: { name: "ID", type: "string" },
46
- title: { name: "Title", type: "string" },
47
- difficulty: {
48
- name: "Difficulty",
49
- type: "string",
50
- enum: [{ id: "easy", label: "Easy" }, { id: "hard", label: "Hard" }]
51
- }
52
- }
53
- } as unknown as CollectionConfig;
54
-
55
- const mongoCollection = {
56
- name: "Events",
57
- slug: "events",
58
- engine: "mongodb",
59
- properties: { id: { name: "ID", type: "string" } }
60
- } as unknown as CollectionConfig;
61
-
62
- /** Routed by `dataSource` alone — no `engine` field to read. */
63
- const dataSourceOnlyCollection = {
64
- name: "Articles",
65
- slug: "articles",
66
- dataSource: "firestore",
67
- properties: { id: { name: "ID", type: "string" } }
68
- } as unknown as CollectionConfig;
69
-
70
- const all = [sqlCollection, firestoreCollection, mongoCollection, dataSourceOnlyCollection];
71
-
72
- describe("the Drizzle schema generator", () => {
73
- it("emits a table for the Postgres collection only", async () => {
74
- const schema = await generateSchema(all);
75
-
76
- expect(schema).toContain("customers");
77
- expect(schema).not.toContain("exercises");
78
- expect(schema).not.toContain("events");
79
- expect(schema).not.toContain("articles");
80
- });
81
-
82
- it("does not emit enum types for a document store's fields", async () => {
83
- const schema = await generateSchema(all);
84
- expect(schema).not.toContain("difficulty");
85
- });
86
- });
87
-
88
- describe("the Postgres DDL generator", () => {
89
- it("creates no table for a non-SQL collection", async () => {
90
- const ddl = await generatePostgresDdl(all, { includePolicies: false });
91
-
92
- expect(ddl).toContain('CREATE TABLE "public"."customers"');
93
- expect(ddl).not.toContain("exercises");
94
- expect(ddl).not.toContain("events");
95
- });
96
-
97
- it("plans no RLS for a store that has none", () => {
98
- const policies = generatePostgresPoliciesDdl(all);
99
- expect(policies).not.toContain("exercises");
100
-
101
- const plans = planCollectionPolicies(all);
102
- expect(plans.map(p => p.qualified)).toEqual(["public.customers"]);
103
- });
104
- });
105
-
106
- describe("the boot-time schema ensure", () => {
107
- const empty = (): ExistingSchema => ({ tables: new Map(), enums: new Set() });
108
-
109
- it("creates nothing for collections another engine stores", () => {
110
- const plan = planCollectionSchemaEnsure(all, empty());
111
-
112
- expect(plan.actions.map(a => a.target)).toEqual(
113
- expect.arrayContaining(["public.customers"])
114
- );
115
- for (const action of plan.actions) {
116
- expect(action.target).not.toContain("exercises");
117
- expect(action.target).not.toContain("events");
118
- expect(action.target).not.toContain("articles");
119
- }
120
- });
121
- });
122
-
123
- describe("the db push include list", () => {
124
- it("does not claim a table for a Firestore collection", async () => {
125
- // Anything absent from this list is *excluded* from the declarative
126
- // apply, which is what protects it from being dropped.
127
- const includes = await getTableIncludesFromCollections(all);
128
-
129
- expect(includes).toEqual(["public.customers"]);
130
- });
131
- });