@rebasepro/server-postgres 0.9.1-canary.ff338b5 → 0.10.1-canary.14e53ae

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 (70) hide show
  1. package/README.md +21 -0
  2. package/dist/PostgresBackendDriver.d.ts +18 -0
  3. package/dist/PostgresBootstrapper.d.ts +11 -1
  4. package/dist/auth/services.d.ts +93 -54
  5. package/dist/backup/backup-logic.d.ts +23 -0
  6. package/dist/backup/backup-service.d.ts +44 -2
  7. package/dist/backup/pg-tools.d.ts +41 -1
  8. package/dist/chunk-DSJWtz9O.js +40 -0
  9. package/dist/cli-helpers.d.ts +33 -1
  10. package/dist/ensure-collection-tables-CNlIONzj.js +304 -0
  11. package/dist/ensure-collection-tables-CNlIONzj.js.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.es.js +2090 -4741
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/schema/auth-bootstrap-sql.d.ts +1 -1
  16. package/dist/schema/auth-schema.d.ts +194 -24
  17. package/dist/schema/destructive-sql.d.ts +49 -0
  18. package/dist/schema/ensure-collection-tables.d.ts +79 -0
  19. package/dist/schema/generate-postgres-ddl-logic.d.ts +4 -1
  20. package/dist/schema/introspect-db-logic.d.ts +0 -5
  21. package/dist/schema/introspect-db-naming.d.ts +10 -0
  22. package/dist/security/policy-drift.d.ts +24 -0
  23. package/dist/security/rls-enforcement.d.ts +2 -2
  24. package/dist/services/cdc/CdcListener.d.ts +7 -14
  25. package/dist/services/channel-bus/ChannelBus.d.ts +29 -0
  26. package/dist/services/channel-bus/PostgresChannelBus.d.ts +111 -0
  27. package/dist/services/channel-bus/index.d.ts +55 -0
  28. package/dist/services/channel-history.d.ts +129 -0
  29. package/dist/services/channel-presence.d.ts +66 -0
  30. package/dist/services/pg-notify-listener.d.ts +47 -0
  31. package/dist/services/realtimeService.d.ts +183 -8
  32. package/dist/src-B0v4IKaI.js +329 -0
  33. package/dist/src-B0v4IKaI.js.map +1 -0
  34. package/dist/src-DmsRg8MR.js +4056 -0
  35. package/dist/src-DmsRg8MR.js.map +1 -0
  36. package/package.json +7 -31
  37. package/src/PostgresBackendDriver.ts +56 -5
  38. package/src/PostgresBootstrapper.ts +87 -1
  39. package/src/auth/ensure-tables.ts +187 -19
  40. package/src/auth/services.ts +309 -170
  41. package/src/backup/backup-cli.ts +60 -1
  42. package/src/backup/backup-cron.ts +24 -1
  43. package/src/backup/backup-logic.ts +62 -0
  44. package/src/backup/backup-service.ts +132 -13
  45. package/src/backup/pg-tools.ts +70 -2
  46. package/src/cli-helpers.ts +82 -27
  47. package/src/cli.ts +152 -6
  48. package/src/index.ts +4 -0
  49. package/src/schema/auth-bootstrap-sql.ts +7 -1
  50. package/src/schema/auth-schema.ts +53 -15
  51. package/src/schema/destructive-sql.ts +94 -0
  52. package/src/schema/ensure-collection-tables.test.ts +156 -0
  53. package/src/schema/ensure-collection-tables.ts +297 -0
  54. package/src/schema/generate-postgres-ddl-logic.ts +3 -3
  55. package/src/schema/introspect-db-inference.ts +1 -1
  56. package/src/schema/introspect-db-logic.ts +1 -10
  57. package/src/schema/introspect-db-naming.ts +15 -0
  58. package/src/schema/introspect-runtime.ts +1 -1
  59. package/src/security/policy-drift.test.ts +46 -0
  60. package/src/security/policy-drift.ts +70 -4
  61. package/src/security/rls-enforcement.ts +11 -5
  62. package/src/services/cdc/CdcListener.ts +27 -91
  63. package/src/services/channel-bus/ChannelBus.ts +44 -0
  64. package/src/services/channel-bus/PostgresChannelBus.ts +299 -0
  65. package/src/services/channel-bus/index.ts +123 -0
  66. package/src/services/channel-history.ts +378 -0
  67. package/src/services/channel-presence.ts +148 -0
  68. package/src/services/pg-notify-listener.ts +137 -0
  69. package/src/services/realtimeService.ts +581 -24
  70. package/src/websocket.ts +30 -11
@@ -21,4 +21,4 @@
21
21
  * `DROP SCHEMA "rebase" CASCADE` on the next diff. The `auth` schema is safe
22
22
  * because the generated schema.sql declares it.
23
23
  */
24
- export declare const AUTH_BOOTSTRAP_SQL = "-- Auth schema + RLS helper functions (required by the policies below)\nCREATE SCHEMA IF NOT EXISTS auth;\n\nCREATE OR REPLACE FUNCTION auth.uid() RETURNS text AS $$\n SELECT NULLIF(current_setting('app.user_id', true), '');\n$$ LANGUAGE sql STABLE;\n\nCREATE OR REPLACE FUNCTION auth.jwt() RETURNS jsonb AS $$\n SELECT COALESCE(\n NULLIF(current_setting('app.jwt', true), ''),\n '{}'\n )::jsonb;\n$$ LANGUAGE sql STABLE;\n\nCREATE OR REPLACE FUNCTION auth.roles() RETURNS text AS $$\n SELECT COALESCE(NULLIF(current_setting('app.user_roles', true), ''), '');\n$$ LANGUAGE sql STABLE;\n";
24
+ export declare const AUTH_BOOTSTRAP_SQL = "-- Auth schema + RLS helper functions (required by the policies below)\nCREATE SCHEMA IF NOT EXISTS auth;\n\n-- Falls back to the pre-rename `app.user_id` so a database that has taken the\n-- new schema but is still served by an older backend keeps resolving the\n-- principal. Drop the COALESCE once no such deployment remains.\nCREATE OR REPLACE FUNCTION auth.uid() RETURNS text AS $$\n SELECT COALESCE(\n NULLIF(current_setting('app.uid', true), ''),\n NULLIF(current_setting('app.user_id', true), '')\n );\n$$ LANGUAGE sql STABLE;\n\nCREATE OR REPLACE FUNCTION auth.jwt() RETURNS jsonb AS $$\n SELECT COALESCE(\n NULLIF(current_setting('app.jwt', true), ''),\n '{}'\n )::jsonb;\n$$ LANGUAGE sql STABLE;\n\nCREATE OR REPLACE FUNCTION auth.roles() RETURNS text AS $$\n SELECT COALESCE(NULLIF(current_setting('app.user_roles', true), ''), '');\n$$ LANGUAGE sql STABLE;\n";
@@ -232,6 +232,23 @@ export declare function createAuthSchema(usersSchemaName?: string): {
232
232
  }, {}, {
233
233
  $type: Record<string, unknown>;
234
234
  }>;
235
+ tokensValidAfter: import("drizzle-orm/pg-core").PgColumn<{
236
+ name: "tokens_valid_after";
237
+ tableName: "users";
238
+ dataType: "date";
239
+ columnType: "PgTimestamp";
240
+ data: Date;
241
+ driverParam: string;
242
+ notNull: false;
243
+ hasDefault: false;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: undefined;
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {}>;
235
252
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
236
253
  name: "created_at";
237
254
  tableName: "users";
@@ -290,8 +307,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
290
307
  identity: undefined;
291
308
  generated: undefined;
292
309
  }, {}, {}>;
293
- userId: import("drizzle-orm/pg-core").PgColumn<{
294
- name: "user_id";
310
+ uid: import("drizzle-orm/pg-core").PgColumn<{
311
+ name: "uid";
295
312
  tableName: "refresh_tokens";
296
313
  dataType: "string";
297
314
  columnType: "PgUUID";
@@ -307,6 +324,23 @@ export declare function createAuthSchema(usersSchemaName?: string): {
307
324
  identity: undefined;
308
325
  generated: undefined;
309
326
  }, {}, {}>;
327
+ sessionId: import("drizzle-orm/pg-core").PgColumn<{
328
+ name: "session_id";
329
+ tableName: "refresh_tokens";
330
+ dataType: "string";
331
+ columnType: "PgUUID";
332
+ data: string;
333
+ driverParam: string;
334
+ notNull: true;
335
+ hasDefault: true;
336
+ isPrimaryKey: false;
337
+ isAutoincrement: false;
338
+ hasRuntimeDefault: false;
339
+ enumValues: undefined;
340
+ baseColumn: never;
341
+ identity: undefined;
342
+ generated: undefined;
343
+ }, {}, {}>;
310
344
  tokenHash: import("drizzle-orm/pg-core").PgColumn<{
311
345
  name: "token_hash";
312
346
  tableName: "refresh_tokens";
@@ -343,6 +377,57 @@ export declare function createAuthSchema(usersSchemaName?: string): {
343
377
  identity: undefined;
344
378
  generated: undefined;
345
379
  }, {}, {}>;
380
+ revoked: import("drizzle-orm/pg-core").PgColumn<{
381
+ name: "revoked";
382
+ tableName: "refresh_tokens";
383
+ dataType: "boolean";
384
+ columnType: "PgBoolean";
385
+ data: boolean;
386
+ driverParam: boolean;
387
+ notNull: true;
388
+ hasDefault: true;
389
+ isPrimaryKey: false;
390
+ isAutoincrement: false;
391
+ hasRuntimeDefault: false;
392
+ enumValues: undefined;
393
+ baseColumn: never;
394
+ identity: undefined;
395
+ generated: undefined;
396
+ }, {}, {}>;
397
+ rotatedAt: import("drizzle-orm/pg-core").PgColumn<{
398
+ name: "rotated_at";
399
+ tableName: "refresh_tokens";
400
+ dataType: "date";
401
+ columnType: "PgTimestamp";
402
+ data: Date;
403
+ driverParam: string;
404
+ notNull: false;
405
+ hasDefault: false;
406
+ isPrimaryKey: false;
407
+ isAutoincrement: false;
408
+ hasRuntimeDefault: false;
409
+ enumValues: undefined;
410
+ baseColumn: never;
411
+ identity: undefined;
412
+ generated: undefined;
413
+ }, {}, {}>;
414
+ sessionStartedAt: import("drizzle-orm/pg-core").PgColumn<{
415
+ name: "session_started_at";
416
+ tableName: "refresh_tokens";
417
+ dataType: "date";
418
+ columnType: "PgTimestamp";
419
+ data: Date;
420
+ driverParam: string;
421
+ notNull: true;
422
+ hasDefault: true;
423
+ isPrimaryKey: false;
424
+ isAutoincrement: false;
425
+ hasRuntimeDefault: false;
426
+ enumValues: undefined;
427
+ baseColumn: never;
428
+ identity: undefined;
429
+ generated: undefined;
430
+ }, {}, {}>;
346
431
  userAgent: import("drizzle-orm/pg-core").PgColumn<{
347
432
  name: "user_agent";
348
433
  tableName: "refresh_tokens";
@@ -422,8 +507,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
422
507
  identity: undefined;
423
508
  generated: undefined;
424
509
  }, {}, {}>;
425
- userId: import("drizzle-orm/pg-core").PgColumn<{
426
- name: "user_id";
510
+ uid: import("drizzle-orm/pg-core").PgColumn<{
511
+ name: "uid";
427
512
  tableName: "password_reset_tokens";
428
513
  dataType: "string";
429
514
  columnType: "PgUUID";
@@ -593,8 +678,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
593
678
  identity: undefined;
594
679
  generated: undefined;
595
680
  }, {}, {}>;
596
- userId: import("drizzle-orm/pg-core").PgColumn<{
597
- name: "user_id";
681
+ uid: import("drizzle-orm/pg-core").PgColumn<{
682
+ name: "uid";
598
683
  tableName: "user_identities";
599
684
  dataType: "string";
600
685
  columnType: "PgUUID";
@@ -723,8 +808,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
723
808
  identity: undefined;
724
809
  generated: undefined;
725
810
  }, {}, {}>;
726
- userId: import("drizzle-orm/pg-core").PgColumn<{
727
- name: "user_id";
811
+ uid: import("drizzle-orm/pg-core").PgColumn<{
812
+ name: "uid";
728
813
  tableName: "mfa_factors";
729
814
  dataType: "string";
730
815
  columnType: "PgUUID";
@@ -983,8 +1068,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
983
1068
  identity: undefined;
984
1069
  generated: undefined;
985
1070
  }, {}, {}>;
986
- userId: import("drizzle-orm/pg-core").PgColumn<{
987
- name: "user_id";
1071
+ uid: import("drizzle-orm/pg-core").PgColumn<{
1072
+ name: "uid";
988
1073
  tableName: "recovery_codes";
989
1074
  dataType: "string";
990
1075
  columnType: "PgUUID";
@@ -1077,8 +1162,8 @@ export declare function createAuthSchema(usersSchemaName?: string): {
1077
1162
  identity: undefined;
1078
1163
  generated: undefined;
1079
1164
  }, {}, {}>;
1080
- userId: import("drizzle-orm/pg-core").PgColumn<{
1081
- name: "user_id";
1165
+ uid: import("drizzle-orm/pg-core").PgColumn<{
1166
+ name: "uid";
1082
1167
  tableName: "magic_link_tokens";
1083
1168
  dataType: "string";
1084
1169
  columnType: "PgUUID";
@@ -1398,6 +1483,23 @@ export declare const users: import("drizzle-orm/pg-core").PgTableWithColumns<{
1398
1483
  }, {}, {
1399
1484
  $type: Record<string, unknown>;
1400
1485
  }>;
1486
+ tokensValidAfter: import("drizzle-orm/pg-core").PgColumn<{
1487
+ name: "tokens_valid_after";
1488
+ tableName: "users";
1489
+ dataType: "date";
1490
+ columnType: "PgTimestamp";
1491
+ data: Date;
1492
+ driverParam: string;
1493
+ notNull: false;
1494
+ hasDefault: false;
1495
+ isPrimaryKey: false;
1496
+ isAutoincrement: false;
1497
+ hasRuntimeDefault: false;
1498
+ enumValues: undefined;
1499
+ baseColumn: never;
1500
+ identity: undefined;
1501
+ generated: undefined;
1502
+ }, {}, {}>;
1401
1503
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
1402
1504
  name: "created_at";
1403
1505
  tableName: "users";
@@ -1456,8 +1558,8 @@ export declare const refreshTokens: import("drizzle-orm/pg-core").PgTableWithCol
1456
1558
  identity: undefined;
1457
1559
  generated: undefined;
1458
1560
  }, {}, {}>;
1459
- userId: import("drizzle-orm/pg-core").PgColumn<{
1460
- name: "user_id";
1561
+ uid: import("drizzle-orm/pg-core").PgColumn<{
1562
+ name: "uid";
1461
1563
  tableName: "refresh_tokens";
1462
1564
  dataType: "string";
1463
1565
  columnType: "PgUUID";
@@ -1473,6 +1575,23 @@ export declare const refreshTokens: import("drizzle-orm/pg-core").PgTableWithCol
1473
1575
  identity: undefined;
1474
1576
  generated: undefined;
1475
1577
  }, {}, {}>;
1578
+ sessionId: import("drizzle-orm/pg-core").PgColumn<{
1579
+ name: "session_id";
1580
+ tableName: "refresh_tokens";
1581
+ dataType: "string";
1582
+ columnType: "PgUUID";
1583
+ data: string;
1584
+ driverParam: string;
1585
+ notNull: true;
1586
+ hasDefault: true;
1587
+ isPrimaryKey: false;
1588
+ isAutoincrement: false;
1589
+ hasRuntimeDefault: false;
1590
+ enumValues: undefined;
1591
+ baseColumn: never;
1592
+ identity: undefined;
1593
+ generated: undefined;
1594
+ }, {}, {}>;
1476
1595
  tokenHash: import("drizzle-orm/pg-core").PgColumn<{
1477
1596
  name: "token_hash";
1478
1597
  tableName: "refresh_tokens";
@@ -1509,6 +1628,57 @@ export declare const refreshTokens: import("drizzle-orm/pg-core").PgTableWithCol
1509
1628
  identity: undefined;
1510
1629
  generated: undefined;
1511
1630
  }, {}, {}>;
1631
+ revoked: import("drizzle-orm/pg-core").PgColumn<{
1632
+ name: "revoked";
1633
+ tableName: "refresh_tokens";
1634
+ dataType: "boolean";
1635
+ columnType: "PgBoolean";
1636
+ data: boolean;
1637
+ driverParam: boolean;
1638
+ notNull: true;
1639
+ hasDefault: true;
1640
+ isPrimaryKey: false;
1641
+ isAutoincrement: false;
1642
+ hasRuntimeDefault: false;
1643
+ enumValues: undefined;
1644
+ baseColumn: never;
1645
+ identity: undefined;
1646
+ generated: undefined;
1647
+ }, {}, {}>;
1648
+ rotatedAt: import("drizzle-orm/pg-core").PgColumn<{
1649
+ name: "rotated_at";
1650
+ tableName: "refresh_tokens";
1651
+ dataType: "date";
1652
+ columnType: "PgTimestamp";
1653
+ data: Date;
1654
+ driverParam: string;
1655
+ notNull: false;
1656
+ hasDefault: false;
1657
+ isPrimaryKey: false;
1658
+ isAutoincrement: false;
1659
+ hasRuntimeDefault: false;
1660
+ enumValues: undefined;
1661
+ baseColumn: never;
1662
+ identity: undefined;
1663
+ generated: undefined;
1664
+ }, {}, {}>;
1665
+ sessionStartedAt: import("drizzle-orm/pg-core").PgColumn<{
1666
+ name: "session_started_at";
1667
+ tableName: "refresh_tokens";
1668
+ dataType: "date";
1669
+ columnType: "PgTimestamp";
1670
+ data: Date;
1671
+ driverParam: string;
1672
+ notNull: true;
1673
+ hasDefault: true;
1674
+ isPrimaryKey: false;
1675
+ isAutoincrement: false;
1676
+ hasRuntimeDefault: false;
1677
+ enumValues: undefined;
1678
+ baseColumn: never;
1679
+ identity: undefined;
1680
+ generated: undefined;
1681
+ }, {}, {}>;
1512
1682
  userAgent: import("drizzle-orm/pg-core").PgColumn<{
1513
1683
  name: "user_agent";
1514
1684
  tableName: "refresh_tokens";
@@ -1588,8 +1758,8 @@ export declare const passwordResetTokens: import("drizzle-orm/pg-core").PgTableW
1588
1758
  identity: undefined;
1589
1759
  generated: undefined;
1590
1760
  }, {}, {}>;
1591
- userId: import("drizzle-orm/pg-core").PgColumn<{
1592
- name: "user_id";
1761
+ uid: import("drizzle-orm/pg-core").PgColumn<{
1762
+ name: "uid";
1593
1763
  tableName: "password_reset_tokens";
1594
1764
  dataType: "string";
1595
1765
  columnType: "PgUUID";
@@ -1759,8 +1929,8 @@ export declare const userIdentities: import("drizzle-orm/pg-core").PgTableWithCo
1759
1929
  identity: undefined;
1760
1930
  generated: undefined;
1761
1931
  }, {}, {}>;
1762
- userId: import("drizzle-orm/pg-core").PgColumn<{
1763
- name: "user_id";
1932
+ uid: import("drizzle-orm/pg-core").PgColumn<{
1933
+ name: "uid";
1764
1934
  tableName: "user_identities";
1765
1935
  dataType: "string";
1766
1936
  columnType: "PgUUID";
@@ -1889,8 +2059,8 @@ export declare const mfaFactors: import("drizzle-orm/pg-core").PgTableWithColumn
1889
2059
  identity: undefined;
1890
2060
  generated: undefined;
1891
2061
  }, {}, {}>;
1892
- userId: import("drizzle-orm/pg-core").PgColumn<{
1893
- name: "user_id";
2062
+ uid: import("drizzle-orm/pg-core").PgColumn<{
2063
+ name: "uid";
1894
2064
  tableName: "mfa_factors";
1895
2065
  dataType: "string";
1896
2066
  columnType: "PgUUID";
@@ -2149,8 +2319,8 @@ export declare const recoveryCodes: import("drizzle-orm/pg-core").PgTableWithCol
2149
2319
  identity: undefined;
2150
2320
  generated: undefined;
2151
2321
  }, {}, {}>;
2152
- userId: import("drizzle-orm/pg-core").PgColumn<{
2153
- name: "user_id";
2322
+ uid: import("drizzle-orm/pg-core").PgColumn<{
2323
+ name: "uid";
2154
2324
  tableName: "recovery_codes";
2155
2325
  dataType: "string";
2156
2326
  columnType: "PgUUID";
@@ -2243,8 +2413,8 @@ export declare const magicLinkTokens: import("drizzle-orm/pg-core").PgTableWithC
2243
2413
  identity: undefined;
2244
2414
  generated: undefined;
2245
2415
  }, {}, {}>;
2246
- userId: import("drizzle-orm/pg-core").PgColumn<{
2247
- name: "user_id";
2416
+ uid: import("drizzle-orm/pg-core").PgColumn<{
2417
+ name: "uid";
2248
2418
  tableName: "magic_link_tokens";
2249
2419
  dataType: "string";
2250
2420
  columnType: "PgUUID";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pure helpers that classify an Atlas declarative-apply plan as destructive
3
+ * or not, and decide what `rebase db push` should do about it.
4
+ *
5
+ * `db push` runs `atlas schema apply` to make the live database match the
6
+ * generated `schema.sql`. Removing a collection field compiles to
7
+ * `DROP COLUMN`; renaming compiles to drop-then-add — either destroys data.
8
+ * We first run the apply with `--dry-run` to obtain the planned SQL, scan it
9
+ * here, and refuse to auto-approve anything destructive without an explicit
10
+ * opt-in.
11
+ *
12
+ * Everything in this file is side-effect free so it can be unit-tested
13
+ * without Atlas or a database.
14
+ */
15
+ /**
16
+ * Split a SQL script into individual statements, dropping blank lines and
17
+ * `--` comment lines. Deliberately simple: Atlas emits one plain statement
18
+ * per `;`, without string literals that contain semicolons in a schema DDL
19
+ * plan, so a naive split is safe and keeps this dependency-free.
20
+ */
21
+ export declare function splitSqlStatements(sql: string): string[];
22
+ export interface DestructiveStatement {
23
+ /** The offending statement (trimmed, without the trailing `;`). */
24
+ statement: string;
25
+ /** Which destructive operation it was flagged for, e.g. "DROP COLUMN". */
26
+ kind: string;
27
+ }
28
+ /**
29
+ * Scan an Atlas plan (the SQL printed by `schema apply --dry-run`) and return
30
+ * the statements that would destroy data. An empty array means the plan is
31
+ * safe to auto-approve.
32
+ */
33
+ export declare function detectDestructiveStatements(planSql: string): DestructiveStatement[];
34
+ export type PushDecision = "apply" | "confirm" | "refuse";
35
+ /**
36
+ * Decide how `db push` should proceed given the plan's destructiveness and
37
+ * the invocation context.
38
+ *
39
+ * - No destructive statements → `apply` (safe to auto-approve).
40
+ * - Destructive + `--allow-destructive` → `apply` (operator opted in).
41
+ * - Destructive + interactive TTY → `confirm` (prompt before applying).
42
+ * - Destructive + non-interactive → `refuse` (never silently drop data in
43
+ * CI / scripts / agents).
44
+ */
45
+ export declare function decidePushSafety(opts: {
46
+ destructiveCount: number;
47
+ allowDestructive: boolean;
48
+ interactive: boolean;
49
+ }): PushDecision;
@@ -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>;
@@ -32,11 +32,6 @@ export interface TableMeta {
32
32
  fks: ForeignKeyRow[];
33
33
  }
34
34
  export declare function singularize(word: string): string;
35
- /**
36
- * Convert a snake_case name to a human-readable Title Case label.
37
- * e.g. "created_at" -> "Created At", "customer_id" -> "Customer Id"
38
- */
39
- export declare function humanize(snakeName: string): string;
40
35
  /**
41
36
  * Convert a snake_case table name to a camelCase + "Collection" variable name.
42
37
  * e.g. "company_token" -> "companyTokenCollection"
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Naming helpers shared by the introspection modules. These live apart from
3
+ * `introspect-db-logic.ts` because the inference pass needs them too, and
4
+ * importing them from there would close a cycle back through this module.
5
+ */
6
+ /**
7
+ * Convert a snake_case name to a human-readable Title Case label.
8
+ * e.g. "created_at" -> "Created At", "customer_id" -> "Customer Id"
9
+ */
10
+ export declare function humanize(snakeName: string): string;
@@ -26,6 +26,14 @@ export interface PolicyRef {
26
26
  hasUsing: boolean;
27
27
  /** Whether a WITH CHECK clause is present at all (not what it says). */
28
28
  hasWithCheck: boolean;
29
+ /**
30
+ * The live clause text, when read from `pg_policies`. Present only for live
31
+ * policies (the expected side is parsed from DDL and does not carry it).
32
+ * Used solely for the insecure-tautology scan, not for divergence — Postgres
33
+ * rewrites this text, so it is not safe to diff against expected.
34
+ */
35
+ qual?: string | null;
36
+ withCheck?: string | null;
29
37
  }
30
38
  export interface PolicyDrift {
31
39
  /** Described by the collections, absent from the database. */
@@ -38,6 +46,22 @@ export interface PolicyDrift {
38
46
  actual: PolicyRef;
39
47
  differences: string[];
40
48
  }[];
49
+ /**
50
+ * A live policy whose expression is the known-permissive tautology
51
+ * `auth.uid() IS NOT NULL` — true for anonymous visitors too, because the
52
+ * user path coerces a blank id to the `'anonymous'` sentinel. This is what
53
+ * `policy.authenticated()` used to compile to, so a database pushed before
54
+ * that fix carries it, and neither the name, roles, command nor clause
55
+ * *presence* differs from the corrected policy — the only thing that changed
56
+ * is the expression text, which this checker otherwise (correctly) ignores.
57
+ * So it is the one drift that hides from every other check here.
58
+ *
59
+ * @see reason a sentence naming the clause and what to do.
60
+ */
61
+ insecure: {
62
+ policy: PolicyRef;
63
+ reason: string;
64
+ }[];
41
65
  }
42
66
  export interface Queryable {
43
67
  query<R>(text: string, values?: unknown[]): Promise<{
@@ -51,7 +51,7 @@ export interface ConnectionPosture {
51
51
  privileged: boolean;
52
52
  }
53
53
  export interface AuthContext {
54
- userId: string;
54
+ uid: string;
55
55
  /** Raw roles as carried on the user (strings or `{ id }` objects). */
56
56
  roles: unknown[];
57
57
  }
@@ -93,7 +93,7 @@ export declare function ensureAppRole(run: RawSqlRunner, schemas: string[]): Pro
93
93
  * SECURITY: this function is only ever called on the **user** path (the server
94
94
  * context uses the base/owner driver and never calls it). The default policies
95
95
  * treat `auth.uid() IS NULL` as the trusted server context, and `auth.uid()`
96
- * is `NULLIF(current_setting('app.user_id'), '')` — so an EMPTY user id would
96
+ * is `NULLIF(current_setting('app.uid'), '')` — so an EMPTY user id would
97
97
  * be read as NULL and silently escalate a user request to server privileges.
98
98
  * Coerce empty/blank ids to `ANONYMOUS_USER_ID` here, at the single chokepoint,
99
99
  * rather than trusting every caller (e.g. realtime subscription auth) to do it.
@@ -23,19 +23,14 @@ export declare function parseCdcPayload(payload: string): CdcChangeEvent | null;
23
23
  /**
24
24
  * Dedicated Postgres LISTEN client for database-level CDC.
25
25
  *
26
- * Mirrors the resilience of the RealtimeService cross-instance LISTEN client: a
27
- * standalone `pg.Client` (outside the Drizzle pool) that stays connected, and
28
- * transparently reconnects on error/disconnect. Each backend instance runs one,
29
- * so every instance observes every committed change regardless of which
30
- * instance (or external process) made the write.
26
+ * A {@link PgNotifyListener} — a connection outside the Drizzle pool that stays
27
+ * open and repairs itself — plus the parsing that turns a `rebase_cdc` payload
28
+ * into a change event. Each backend instance runs one, so every instance
29
+ * observes every committed change regardless of which instance (or external
30
+ * process) made the write.
31
31
  */
32
32
  export declare class CdcListener {
33
- private readonly connectionString;
34
- private readonly onEvent;
35
- private client?;
36
- private running;
37
- private reconnectTimer?;
38
- private static readonly RECONNECT_DELAY_MS;
33
+ private readonly listener;
39
34
  constructor(connectionString: string, onEvent: (event: CdcChangeEvent) => void | Promise<void>);
40
35
  /**
41
36
  * Connect and begin listening. Idempotent.
@@ -44,11 +39,9 @@ export declare class CdcListener {
44
39
  * established (or `LISTEN` is refused), this rejects so callers — notably
45
40
  * `REALTIME_CDC=auto` — can detect an unusable connection and fall back to
46
41
  * app-level realtime. Once the initial connection succeeds, later drops
47
- * self-heal via {@link scheduleReconnect}.
42
+ * self-heal in the background.
48
43
  */
49
44
  start(): Promise<void>;
50
45
  /** Stop listening and release the connection. */
51
46
  stop(): Promise<void>;
52
- private connect;
53
- private scheduleReconnect;
54
47
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Runtime pieces of the channel bus that are not the contract itself.
3
+ *
4
+ * The interface, the frame shape and the implementer's contract live in
5
+ * `@rebasepro/types` (`types/channel_bus.ts`), so a transport shipped as its own
6
+ * package — a Redis one, say — depends on the contract and not on this database
7
+ * adapter. They are re-exported here for convenience: code already importing
8
+ * from the adapter should not have to know where the types are declared.
9
+ */
10
+ import type { ChannelBusFrame } from "@rebasepro/types";
11
+ export type { ChannelBus, ChannelBusFrame, ChannelBusHandler, ChannelBusConfig, ChannelBusSetting } from "@rebasepro/types";
12
+ export { isChannelBusInstance } from "@rebasepro/types";
13
+ /**
14
+ * The default: no cross-instance delivery at all.
15
+ *
16
+ * This is what every deployment ran before the bus existed, and what a
17
+ * single-instance deployment should keep running — `publish` resolves without
18
+ * touching the network, so the broadcast path is the same handful of `ws.send`
19
+ * calls it always was.
20
+ */
21
+ export declare class MemoryChannelBus {
22
+ readonly kind: "memory";
23
+ readonly maxFrameBytes: number;
24
+ start(): Promise<void>;
25
+ publish(): Promise<void>;
26
+ stop(): Promise<void>;
27
+ }
28
+ /** Encoded size of a frame, for a transport's size check. */
29
+ export declare function frameByteLength(frame: ChannelBusFrame): number;