@rebasepro/server-postgres 0.12.1-canary.gf5f1d39 → 0.13.1-canary.g18cfeb7

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 (112) hide show
  1. package/dist/PostgresBackendDriver.d.ts +49 -2
  2. package/dist/PostgresBootstrapper.d.ts +25 -1
  3. package/dist/auth/services.d.ts +21 -0
  4. package/dist/{src-BbFOPJ1S.js → auth-users-columns-Do9mw5Y5.js} +602 -200
  5. package/dist/auth-users-columns-Do9mw5Y5.js.map +1 -0
  6. package/dist/backup/backup-service.d.ts +10 -1
  7. package/dist/backup/pg-tools.d.ts +47 -0
  8. package/dist/backup-service-Bww-Lg0s.js +8999 -0
  9. package/dist/backup-service-Bww-Lg0s.js.map +1 -0
  10. package/dist/cli-helpers.d.ts +40 -1
  11. package/dist/connection-BuZ97wsr.js +250 -0
  12. package/dist/connection-BuZ97wsr.js.map +1 -0
  13. package/dist/connection.d.ts +42 -0
  14. package/dist/ensure-collection-policies-DMUdRQdM.js +57 -0
  15. package/dist/ensure-collection-policies-DMUdRQdM.js.map +1 -0
  16. package/dist/ensure-collection-tables-DSIxvLLD.js +694 -0
  17. package/dist/ensure-collection-tables-DSIxvLLD.js.map +1 -0
  18. package/dist/index.es.js +1701 -10167
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/policy-CPkCqVTz.js +105 -0
  21. package/dist/policy-CPkCqVTz.js.map +1 -0
  22. package/dist/rls-bootstrap-sql-Bpv3nUZo.js +244 -0
  23. package/dist/rls-bootstrap-sql-Bpv3nUZo.js.map +1 -0
  24. package/dist/schema/auth-schema.d.ts +83 -144
  25. package/dist/schema/auth-users-columns.d.ts +97 -0
  26. package/dist/schema/ensure-collection-policies.d.ts +60 -0
  27. package/dist/schema/ensure-collection-tables.d.ts +45 -3
  28. package/dist/schema/generate-drizzle-schema-logic.d.ts +1 -1
  29. package/dist/schema/generate-postgres-ddl-logic.d.ts +137 -3
  30. package/dist/schema/generated-schema-staleness.d.ts +39 -0
  31. package/dist/schema/introspect-db-constraints.d.ts +57 -0
  32. package/dist/schema/introspect-db-logic.d.ts +94 -5
  33. package/dist/schema/introspect-db-queries.d.ts +119 -0
  34. package/dist/schema/introspect-db-structure.d.ts +263 -0
  35. package/dist/schema/introspect-db-types.d.ts +11 -0
  36. package/dist/schema/rls-bootstrap-sql.d.ts +135 -0
  37. package/dist/security/rls-enforcement.d.ts +53 -2
  38. package/dist/services/FetchService.d.ts +14 -8
  39. package/dist/services/RelationService.d.ts +24 -1
  40. package/dist/services/channel-bus/index.d.ts +1 -7
  41. package/dist/services/collection-helpers.d.ts +36 -2
  42. package/dist/services/dataService.d.ts +5 -1
  43. package/dist/services/realtimeService.d.ts +25 -21
  44. package/dist/services/row-pipeline.d.ts +1 -1
  45. package/dist/{src-Zqwaw3P5.js → src-C_wvdMnl.js} +78 -145
  46. package/dist/src-C_wvdMnl.js.map +1 -0
  47. package/dist/utils/connection-string.d.ts +29 -0
  48. package/dist/utils/drizzle-conditions.d.ts +5 -4
  49. package/dist/utils/pg-error-utils.d.ts +35 -0
  50. package/dist/websocket-D0TBU3ia.js +530 -0
  51. package/dist/websocket-D0TBU3ia.js.map +1 -0
  52. package/package.json +17 -16
  53. package/src/PostgresAdapter.ts +21 -2
  54. package/src/PostgresBackendDriver.ts +169 -3
  55. package/src/PostgresBootstrapper.ts +233 -35
  56. package/src/auth/ensure-tables.ts +335 -81
  57. package/src/auth/services.ts +24 -2
  58. package/src/backup/backup-cli.ts +41 -2
  59. package/src/backup/backup-service.ts +38 -5
  60. package/src/backup/pg-tools.ts +96 -3
  61. package/src/cli-helpers.ts +92 -9
  62. package/src/cli.ts +219 -56
  63. package/src/collections/validate-relations.ts +136 -14
  64. package/src/connection.ts +73 -0
  65. package/src/data-transformer.ts +22 -6
  66. package/src/databasePoolManager.ts +5 -2
  67. package/src/history/ensure-history-table.ts +7 -0
  68. package/src/schema/auth-schema.ts +30 -19
  69. package/src/schema/auth-users-columns.ts +131 -0
  70. package/src/schema/doctor.ts +7 -5
  71. package/src/schema/ensure-collection-policies.ts +105 -0
  72. package/src/schema/ensure-collection-tables.test.ts +105 -9
  73. package/src/schema/ensure-collection-tables.ts +301 -44
  74. package/src/schema/generate-drizzle-schema-logic.ts +49 -7
  75. package/src/schema/generate-postgres-ddl-logic.ts +474 -27
  76. package/src/schema/generated-schema-staleness.ts +169 -0
  77. package/src/schema/introspect-db-constraints.ts +385 -0
  78. package/src/schema/introspect-db-inference.ts +18 -8
  79. package/src/schema/introspect-db-logic.ts +385 -71
  80. package/src/schema/introspect-db-queries.ts +326 -0
  81. package/src/schema/introspect-db-structure.ts +670 -0
  82. package/src/schema/introspect-db-types.ts +56 -0
  83. package/src/schema/introspect-db.ts +37 -80
  84. package/src/schema/introspect-runtime.test.ts +56 -8
  85. package/src/schema/introspect-runtime.ts +31 -9
  86. package/src/schema/non-sql-collections.test.ts +131 -0
  87. package/src/schema/rls-bootstrap-sql.ts +288 -0
  88. package/src/security/anonymous-grants.test.ts +4 -2
  89. package/src/security/policy-drift.test.ts +11 -3
  90. package/src/security/rls-enforcement.ts +141 -3
  91. package/src/services/BranchService.ts +71 -28
  92. package/src/services/FetchService.ts +100 -107
  93. package/src/services/PersistService.ts +34 -7
  94. package/src/services/RelationService.ts +249 -48
  95. package/src/services/channel-bus/index.ts +0 -9
  96. package/src/services/channel-history.ts +8 -0
  97. package/src/services/channel-presence.ts +6 -0
  98. package/src/services/collection-helpers.ts +69 -3
  99. package/src/services/dataService.ts +5 -1
  100. package/src/services/realtimeService.ts +38 -35
  101. package/src/services/row-pipeline.ts +1 -1
  102. package/src/utils/connection-string.ts +58 -0
  103. package/src/utils/drizzle-conditions.ts +31 -6
  104. package/src/utils/pg-error-utils.ts +65 -0
  105. package/src/websocket.ts +18 -9
  106. package/dist/chunk-DSJWtz9O.js +0 -40
  107. package/dist/ensure-collection-tables-CNTcZGvn.js +0 -304
  108. package/dist/ensure-collection-tables-CNTcZGvn.js.map +0 -1
  109. package/dist/schema/auth-bootstrap-sql.d.ts +0 -24
  110. package/dist/src-BbFOPJ1S.js.map +0 -1
  111. package/dist/src-Zqwaw3P5.js.map +0 -1
  112. package/src/schema/auth-bootstrap-sql.ts +0 -47
@@ -37,7 +37,7 @@ import { ensureHistoryTableExists } from "./history/ensure-history-table";
37
37
  import { patchPgArrayNullSafety } from "./utils/pg-array-null-patch";
38
38
  import { buildCollectionsFromSchema, introspectSchema, readRlsStatus } from "./schema/introspect-runtime";
39
39
  import { buildDrizzleTablesFromSchema, buildDrizzleRelationsFromSchema } from "./schema/dynamic-tables";
40
- import { detectConnectionPosture, ensureAppRole, validatePolicyPgRoles, warnOnAnonymousGrants, REBASE_USER_ROLE, type RawSqlRunner } from "./security/rls-enforcement";
40
+ import { detectConnectionPosture, ensureAppRole, validatePolicyPgRoles, warnOnAnonymousGrants, warnOnLegacyRlsFunctions, warnOnRoleSchemaCollision, REBASE_USER_ROLE, type RawSqlRunner } from "./security/rls-enforcement";
41
41
  import { provisionTriggerCdc, type CdcTableRef } from "./services/cdc/trigger-cdc";
42
42
  import { collectJunctionLinks } from "./services/cdc/junction-tables";
43
43
  import { createChannelBus, resolveChannelBusSetting } from "./services/channel-bus";
@@ -102,6 +102,40 @@ export interface PostgresDriverInternals {
102
102
 
103
103
  // Re-export from shared CLI error utilities
104
104
  import { isEconnrefused } from "./cli-errors";
105
+ import { classifyConnectFailure } from "./utils/pg-error-utils";
106
+
107
+ /**
108
+ * Which table name the boot-time drift check should look for, for one collection.
109
+ *
110
+ * A declared `table` IS the table name, not a hint to be second-guessed. This
111
+ * used to ask the registry whether it had indexed the declared name and fall
112
+ * back to the SLUG when it had not — but "the registry does not know this table"
113
+ * is exactly the condition the drift check exists to report, so the fallback
114
+ * fired precisely when it was most harmful.
115
+ *
116
+ * A collection with `slug: "usage-daily", table: "usage_daily"` was reported as
117
+ * missing table `usage-daily`: a name that does not exist, should never exist,
118
+ * and that nobody can find by looking. Worse, the remediation the caller prints
119
+ * says to run `rebase db push` — which would then CREATE that invented table
120
+ * beside the correct one, the same "second copy" hazard the misplaced-schema
121
+ * branch further down exists to prevent. Seen in production, where a correctly
122
+ * migrated database reported drift on every boot.
123
+ *
124
+ * The slug is used only when nothing was declared, which is the config shape
125
+ * where the slug genuinely is the table name.
126
+ *
127
+ * Exported for its own test: the caller needs a live pool and a real database,
128
+ * and this is the part that was wrong.
129
+ */
130
+ export function resolveDriftCheckName(
131
+ col: CollectionConfig,
132
+ registeredTableNames: string[]
133
+ ): string {
134
+ const declaredTable = isRelationalCollectionConfig(col) ? col.table : undefined;
135
+ return declaredTable
136
+ ?? registeredTableNames.find((k) => k === col.slug)
137
+ ?? col.slug;
138
+ }
105
139
 
106
140
  /**
107
141
  * Default PostgreSQL bootstrapper.
@@ -260,9 +294,45 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
260
294
  throw new Error(`Cannot connect to PostgreSQL at ${hostInfo}: connection refused. Is the database running?`);
261
295
  }
262
296
 
263
- // For other errors (timeouts, auth failures, etc.) warn but continue —
264
- // the pool may recover on subsequent attempts.
265
- logger.error("❌ Failed to connect to PostgreSQL", { error: err });
297
+ /*
298
+ * Everything else.
299
+ *
300
+ * Two problems with what used to happen here. First, the logged
301
+ * error was Drizzle's wrapper — `Failed query: SELECT 1` and a
302
+ * stack through drizzle internals — while the sentence that
303
+ * actually says what is wrong ("password authentication failed
304
+ * for user …", "database … does not exist") sits in `.cause`
305
+ * and was never printed. A developer with a typo in their
306
+ * DATABASE_URL got two walls of stack trace and no cause.
307
+ *
308
+ * Second, "continuing… the pool may recover" is only true for
309
+ * a transient fault. A wrong password or a missing database is
310
+ * settled: the next query fails the same way, so the process
311
+ * died seconds later anyway — after printing a reassurance.
312
+ * Those now fail here, where the message can be about the
313
+ * cause rather than about whichever query ran next.
314
+ */
315
+ const { fatal, reason, code } = classifyConnectFailure(err);
316
+ const detail = code ? ` [${code}]` : "";
317
+
318
+ if (fatal) {
319
+ logger.error(
320
+ `\n` +
321
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
322
+ ` ❌ PostgreSQL refused the connection\n` +
323
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
324
+ `\n` +
325
+ ` ${reason}${detail}\n` +
326
+ `\n` +
327
+ ` The server is reachable, so this is the credentials or the\n` +
328
+ ` database name in DATABASE_URL — check them in your .env.\n` +
329
+ `\n` +
330
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`
331
+ );
332
+ throw new Error(`PostgreSQL refused the connection: ${reason}${detail}`);
333
+ }
334
+
335
+ logger.error(`❌ Failed to connect to PostgreSQL: ${reason}${detail}`, { error: err });
266
336
  logger.warn("⚠️ Continuing without initial database verification. Drizzle/PG will attempt to connect on subsequent queries.");
267
337
  }
268
338
 
@@ -302,12 +372,22 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
302
372
  const res = await schemaAwareDb.execute(sql.raw(text));
303
373
  return (res.rows ?? []) as Record<string, unknown>[];
304
374
  };
375
+ // Said before anything else touches the schema: if the role and
376
+ // a schema share a name, unqualified SQL from any tool that does
377
+ // not pin `search_path` has been landing in the wrong place, and
378
+ // that is worth knowing before reading the drift report below.
379
+ await warnOnRoleSchemaCollision(runSql);
380
+
305
381
  const posture = await detectConnectionPosture(runSql);
306
382
  if (posture.privileged) {
307
383
  const collectionSchemas = registry.getCollections()
308
384
  .map((c) => (c as { schema?: string }).schema)
309
385
  .filter((s): s is string => typeof s === "string");
310
- await ensureAppRole(runSql, ["public", "rebase", "auth", ...collectionSchemas]);
386
+ // `auth` is deliberately absent: the RLS helpers moved into
387
+ // `rebase`, and granting USAGE on a schema Rebase does not
388
+ // own would, on a Supabase database, hand the end-user role
389
+ // access to theirs.
390
+ await ensureAppRole(runSql, ["public", "rebase", ...collectionSchemas]);
311
391
  driver.rlsUserRole = REBASE_USER_ROLE;
312
392
  realtimeService.rlsUserRole = REBASE_USER_ROLE;
313
393
  logger.info(`🔐 RLS enforcement active: authenticated requests run as "${REBASE_USER_ROLE}" (connection "${posture.role}" bypasses RLS: ${posture.superuser ? "superuser" : posture.bypassRLS ? "BYPASSRLS" : "table owner"})`);
@@ -336,6 +416,11 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
336
416
  // a rule that reads as "signed in only" but is true for every
337
417
  // caller grants the data away rather than hiding it.
338
418
  warnOnAnonymousGrants(registry.getCollections() as never);
419
+
420
+ // Raw policy SQL written against the pre-1.0 helper schema. It
421
+ // is rewritten on compile, so this is the only place the project
422
+ // is ever told the spelling moved.
423
+ warnOnLegacyRlsFunctions(registry.getCollections() as never);
339
424
  }
340
425
 
341
426
  // Ensure branch metadata table exists when branching is available
@@ -487,22 +572,28 @@ table: link.table });
487
572
  try {
488
573
  const registeredCollections = registry.getCollections();
489
574
  if (registeredCollections.length > 0) {
490
- const schemasToCheck = Array.from(new Set(
491
- registeredCollections.map(c => "schema" in c && c.schema ? c.schema : "public")
492
- ));
493
- const schemasList = schemasToCheck.map(s => `'${s}'`).join(",");
575
+ // Deliberately unfiltered by schema: a table that is missing
576
+ // from where the collection says it lives is very often
577
+ // sitting in another schema entirely (see the misplaced-table
578
+ // report below), and knowing *which* is the whole answer.
494
579
  const result = await schemaAwareDb.execute(sql.raw(`
495
580
  SELECT table_name, table_schema
496
581
  FROM information_schema.tables
497
- WHERE table_schema IN (${schemasList})
582
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
498
583
  AND table_type = 'BASE TABLE'
499
584
  `));
585
+ const tablesByName = new Map<string, string[]>();
586
+ for (const row of result.rows as Array<{ table_name: string; table_schema: string }>) {
587
+ const schemas = tablesByName.get(row.table_name) ?? [];
588
+ schemas.push(row.table_schema);
589
+ tablesByName.set(row.table_name, schemas);
590
+ }
500
591
  const dbTables = new Set(
501
592
  (result.rows as Array<{ table_name: string; table_schema: string }>).map(r =>
502
593
  r.table_schema === "public" ? r.table_name : `${r.table_schema}.${r.table_name}`
503
594
  )
504
595
  );
505
- const missing: Array<{ slug: string; table: string }> = [];
596
+ const missing: Array<{ slug: string; table: string; foundIn: string[] }> = [];
506
597
  for (const col of registeredCollections) {
507
598
  // Auth owns its table and creates it later in this same
508
599
  // boot (initializeAuth → ensureAuthTablesExist), so it is
@@ -513,41 +604,72 @@ table: link.table });
513
604
  if ((col as { auth?: { enabled?: boolean } }).auth?.enabled) continue;
514
605
 
515
606
  const schemaName = "schema" in col && col.schema ? col.schema : "public";
516
- const declaredTable = isRelationalCollectionConfig(col) ? col.table : undefined;
517
- const tableName = registry.hasTableForCollection(
518
- declaredTable ?? col.slug
519
- )
520
- ? (declaredTable ?? col.slug)
521
- : col.slug;
522
- // Resolve the actual table name the registry stored
523
- const resolvedTable = registry.getTableNames().find((k) =>
524
- k === tableName ||
525
- k === col.slug
526
- );
527
- const checkName = resolvedTable ?? tableName;
607
+ const checkName = resolveDriftCheckName(col, registry.getTableNames());
528
608
  const fullCheckName = schemaName === "public" ? checkName : `${schemaName}.${checkName}`;
529
609
  if (!dbTables.has(fullCheckName)) {
530
610
  // Report what was actually looked up: an unqualified
531
611
  // "users" sends people hunting for public.users.
532
612
  missing.push({ slug: col.slug,
533
- table: fullCheckName });
613
+ table: fullCheckName,
614
+ foundIn: (tablesByName.get(checkName) ?? []).filter(s => s !== schemaName) });
534
615
  }
535
616
  }
536
617
  if (missing.length > 0) {
537
618
  const lines = missing.map(
538
- m => ` • collection "${m.slug}" → table "${m.table}"`
619
+ m => ` • collection "${m.slug}" → table "${m.table}"` +
620
+ (m.foundIn.length > 0
621
+ ? ` — but a table of that name exists in ${m.foundIn.map(s => `"${s}"`).join(", ")}`
622
+ : "")
539
623
  );
624
+ // A table that exists under the same name in another
625
+ // schema is not ordinary drift: it is almost always this
626
+ // framework's own `search_path` hazard. Postgres resolves
627
+ // unqualified SQL through `"$user", public`, and this
628
+ // project creates a schema named `rebase` while every
629
+ // template names the database role `rebase` too — so an
630
+ // unqualified CREATE TABLE (a hand-written migration, the
631
+ // SQL editor, drizzle-kit) lands in `rebase`, and the
632
+ // runtime, which now pins `search_path=public`, cannot see
633
+ // it. Say so, because "missing table" sends people to
634
+ // re-run a push that will create a *second* copy.
635
+ const misplaced = missing.filter(m => m.foundIn.length > 0);
636
+ const misplacedHelp = misplaced.length === 0 ? [] : [
637
+ " Those tables exist — in the wrong schema. Unqualified SQL run by a",
638
+ " role whose name matches a schema (`rebase` is both, by default)",
639
+ " resolves through search_path's \"$user\" and creates there. Move them:",
640
+ ...misplaced.map(m =>
641
+ ` ALTER TABLE "${m.foundIn[0]}"."${m.table.split(".").pop()}" SET SCHEMA "${m.table.includes(".") ? m.table.split(".")[0] : "public"}";`
642
+ ),
643
+ " and qualify the SQL that created them, or pin search_path in DATABASE_URL",
644
+ " (`?options=-c%20search_path%3Dpublic`).",
645
+ ""
646
+ ];
647
+ // This runtime creates collection tables (and their RLS)
648
+ // at boot unless REBASE_MIGRATE_ON_BOOT=none, so a drift
649
+ // this late means that step was disabled, could not run,
650
+ // or failed — the guidance names a path for both a managed
651
+ // tenant (whose in-cluster database `pnpm db:push` cannot
652
+ // reach) and a self-host. Naming only the pnpm scripts, as
653
+ // this once did, told a managed operator to run a command
654
+ // that structurally cannot touch their database.
540
655
  logger.warn([
541
656
  "",
542
- "┌──────────────────────────────────────────────────────────────┐",
543
- "│ ⚠️ SCHEMA DRIFT — Missing tables in database │",
544
- "├──────────────────────────────────────────────────────────────┤",
545
- ...lines.map(l => `│ ${l.padEnd(60)}│`),
546
- "├──────────────────────────────────────────────────────────────┤",
547
- "│ Run one of: │",
548
- "│ pnpm db:push (dev — fast, no migration files) │",
549
- "│ pnpm db:migrate (prod — creates migration files) │",
550
- "└──────────────────────────────────────────────────────────────┘",
657
+ "⚠️ SCHEMA DRIFT — the database is missing tables this backend serves:",
658
+ ...lines,
659
+ "",
660
+ ...misplacedHelp,
661
+ " This runtime applies the collection schema at boot unless",
662
+ " REBASE_MIGRATE_ON_BOOT=none. Check the \"Collection schema\" / \"policies\"",
663
+ " log lines above — this drift means that step was off, skipped, or failed.",
664
+ " • Managed cloud: redeploy with REBASE_MIGRATE_ON_BOOT unset or",
665
+ " \"ensure\"; the runtime applies the schema to the tenant DB.",
666
+ " If the log above says the driver does not implement collection-table",
667
+ " creation, THIS driver is too old to do it. A driver is installed from",
668
+ " your bundle's dependencies, not supplied by the platform image, so a",
669
+ " newer runtime will not update it: bump \"@rebasepro/server-postgres\"",
670
+ " in your project's package.json and redeploy.",
671
+ " • Self-host: run `rebase db push` (dev) or `rebase db migrate` (prod)",
672
+ " against DATABASE_URL.",
551
673
  ""
552
674
  ].join("\n"));
553
675
  }
@@ -711,7 +833,83 @@ schemaHealthCheck: () => probeAuthSchema(db, resolveAuthSchema(authCollection))
711
833
  collections as Parameters<typeof ensureCollectionTables>[1],
712
834
  log
713
835
  );
714
- return { applied: plan.actions.length };
836
+ for (const failure of plan.failures) {
837
+ logger.warn(
838
+ `🔗 [schema] Could not add foreign key "${failure.target}" — the column exists and the ` +
839
+ `collection still serves, but rows are not policed by this constraint: ${failure.error}`
840
+ );
841
+ }
842
+ return { applied: plan.actions.length - plan.failures.length };
843
+ },
844
+
845
+ /**
846
+ * Apply the collections' RLS policies — ENABLE ROW LEVEL SECURITY and the
847
+ * `securityRules` compiled to `CREATE POLICY` — so a freshly provisioned
848
+ * database serves data instead of denying every user-context read.
849
+ *
850
+ * The companion to {@link ensureCollectionSchema}: that creates the
851
+ * tables, this makes them servable. Boot runs it *after* auth
852
+ * initialization, because the generated policies call `auth.uid()` /
853
+ * `auth.roles()`, and `CREATE POLICY` validates those functions exist.
854
+ *
855
+ * Runs through the same drizzle handle, one statement at a time (that
856
+ * handle speaks the extended query protocol, which rejects multi-command
857
+ * strings). Failures are per-table and non-fatal: a table left un-policed
858
+ * stays RLS-enabled, so it denies rather than leaks.
859
+ */
860
+ async ensureCollectionPolicies(
861
+ collections: unknown[],
862
+ driverResult: InitializedDriver,
863
+ log?: (message: string) => void
864
+ ): Promise<{ applied: number }> {
865
+ const internals = driverResult.internals as PostgresDriverInternals;
866
+ const { ensureCollectionPolicies } = await import("./schema/ensure-collection-policies");
867
+ const queryable = {
868
+ async query<T>(text: string): Promise<{ rows: T[] }> {
869
+ const result = await internals.db.execute(sql.raw(text));
870
+ const rows = (result as unknown as { rows?: T[] }).rows;
871
+ return { rows: rows ?? (Array.isArray(result) ? (result as T[]) : []) };
872
+ }
873
+ };
874
+ const outcome = await ensureCollectionPolicies(
875
+ queryable,
876
+ collections as CollectionConfig[],
877
+ log
878
+ );
879
+
880
+ for (const skip of outcome.skipped) {
881
+ logger.warn(`🔐 [rls] Policies not applied to "${skip.table}": ${skip.reason}`);
882
+ }
883
+ for (const failure of outcome.failures) {
884
+ logger.warn(
885
+ `🔐 [rls] Could not fully apply policies to "${failure.table}" — it stays locked (denies) until this is resolved: ${failure.error}`
886
+ );
887
+ }
888
+
889
+ // Retire the pre-1.0 `auth` schema now that the policies above no
890
+ // longer call into it. Deliberately after, and deliberately quiet:
891
+ // Postgres refuses to drop a function an RLS policy still
892
+ // references, so on a database where some table has not been
893
+ // recompiled yet this is expected to fail and succeed on a later
894
+ // boot. See DROP_LEGACY_AUTH_SCHEMA_SQL for the guards that keep it
895
+ // off a Supabase `auth` schema.
896
+ try {
897
+ const { dropLegacyAuthSchema } = await import("./schema/rls-bootstrap-sql");
898
+ await dropLegacyAuthSchema(
899
+ async (text) => {
900
+ const res = await internals.db.execute(sql.raw(text));
901
+ return (res.rows ?? []) as Record<string, unknown>[];
902
+ },
903
+ { info: (m) => logger.info(m), warn: (m) => logger.warn(m) }
904
+ );
905
+ } catch (err) {
906
+ logger.info(
907
+ "Left the legacy `auth` schema in place: " +
908
+ (err instanceof Error ? err.message : String(err))
909
+ );
910
+ }
911
+
912
+ return { applied: outcome.policiesApplied };
715
913
  },
716
914
 
717
915
  getAdmin(driverResult: InitializedDriver): DatabaseAdmin | undefined {