@rebasepro/server-postgres 0.25.0 → 0.26.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.
- package/dist/{BranchService-iXXZeVYD.js → BranchService-CWBnDhcv.js} +88 -12
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/backup/backup-cli.d.ts +40 -3
- package/dist/backup/backup-service.d.ts +11 -0
- package/dist/{backup-service-Bb39plam.js → backup-cli-PoRG-zEq.js} +777 -10
- package/dist/backup-cli-PoRG-zEq.js.map +1 -0
- package/dist/{boot-ddl-s_3Hkz94.js → boot-ddl-EsgqDkx0.js} +3 -3
- package/dist/{boot-ddl-s_3Hkz94.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-DQ_K-sdM.js → cli-console-f0k6_TsJ.js} +41 -9
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DwQnNATs.js → cli-errors-DsYyNYzd.js} +90 -11
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli-errors.d.ts +14 -0
- package/dist/cli-flags.d.ts +51 -0
- package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
- package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
- package/dist/cli.js +121 -271
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
- package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
- package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
- package/dist/connection-czvI7TZL.js.map +1 -0
- package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
- package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
- package/dist/db-command-line.d.ts +14 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Cis_HoGn.js → deferred-index-build-CqaOv1Xx.js} +8 -8
- package/dist/{deferred-index-build-Cis_HoGn.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-DvJH-IQG.js → doctor-DW_mlTzz.js} +186 -42
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-CCqayoUz.js → ensure-collection-policies-DLWgabQ2.js} +13 -10
- package/dist/ensure-collection-policies-DLWgabQ2.js.map +1 -0
- package/dist/{ensure-collection-tables-DvvImXDe.js → ensure-collection-tables-CFIRq8ni.js} +9 -9
- package/dist/{ensure-collection-tables-DvvImXDe.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-Dpgwx5sy.js → ensure-tables-4nA-tfwi.js} +5 -5
- package/dist/{ensure-tables-Dpgwx5sy.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-CUlUlRkO.js → enum-labels-kept-PcPW65xy.js} +3 -3
- package/dist/{enum-labels-kept-CUlUlRkO.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/{generate-drizzle-schema-C3EAP9HE.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-C3EAP9HE.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-ClmOUHXj.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-ClmOUHXj.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCeS4LrJ.js → generate-next-step-DgK5BlBn.js} +2 -2
- package/dist/{generate-next-step-DCeS4LrJ.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
- package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
- package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
- package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
- package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.es.js +288 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-CI5CU_n0.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-CI5CU_n0.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-Bd-hJk08.js → migrate-baseline-B-2Ef4Mv.js} +718 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js +89 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js.map +1 -0
- package/dist/{open-rebase-BKA7Nyoe.js → open-rebase-C8jQ6s5a.js} +324 -1047
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-Bv5n2PLn.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-Bv5n2PLn.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{plan-schema-CXOucCw-.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CXOucCw-.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-vm0BcvcE.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-vm0BcvcE.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-DIequ4bV.js → push-index-prebuild-UTvB5pqH.js} +4 -4
- package/dist/{push-index-prebuild-DIequ4bV.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-BqJut4F3.js → push-legacy-renames-BwaKi8xF.js} +4 -4
- package/dist/{push-legacy-renames-BqJut4F3.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-CvX8hiPb.js → push-policy-plan-DQ2tWceD.js} +5 -5
- package/dist/{push-policy-plan-CvX8hiPb.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{render-ddl-CQNwZdse.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CQNwZdse.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-BOHuHVlc.js} +43 -4
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +4 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +21 -0
- package/dist/schema/introspect-db.js +5 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/push-policy-plan.d.ts +5 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/BranchService.d.ts +20 -2
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{source-Bv5KPeIx.js → source-fZb92pN8.js} +2 -2
- package/dist/{source-Bv5KPeIx.js.map → source-fZb92pN8.js.map} +1 -1
- package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
- package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
- package/dist/{websocket-MC0zTBar.js → websocket-CKYc3V4N.js} +56 -14
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-iXXZeVYD.js.map +0 -1
- package/dist/backup-cli-B2CAn9fR.js +0 -647
- package/dist/backup-cli-B2CAn9fR.js.map +0 -1
- package/dist/backup-service-Bb39plam.js.map +0 -1
- package/dist/cli-console-DQ_K-sdM.js.map +0 -1
- package/dist/cli-errors-DwQnNATs.js.map +0 -1
- package/dist/cli-flags-D-b0qL9T.js +0 -206
- package/dist/cli-flags-D-b0qL9T.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-DvJH-IQG.js.map +0 -1
- package/dist/ensure-collection-policies-CCqayoUz.js.map +0 -1
- package/dist/open-rebase-BKA7Nyoe.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rls-enforcement-D7P4-ZYX.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/websocket-MC0zTBar.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rls-enforcement-D7P4-ZYX.js","names":[],"sources":["../src/security/rls-enforcement.ts"],"sourcesContent":["import { sql as drizzleSql, SQL } from \"drizzle-orm\";\nimport { ANONYMOUS_USER_ID, PolicyExpression, SecurityRule } from \"@rebasepro/types\";\nimport {\n AnonymousGrantRisk,\n findAnonymousGrants,\n REBASE_INTERNAL_TABLES,\n REBASE_USER_ROLE,\n revokeInternalTableAccess,\n securityRuleToConditions\n} from \"@rebasepro/common\";\nimport { REBASE_SCHEMA, usesLegacyRlsFunctions } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Unified RLS enforcement — the \"user context vs server context\" model.\n *\n * Every operation runs in one of two contexts:\n *\n * - **User context** — a request authenticated (or anonymous) via\n * `driver.withAuth(user)`. Runs as the restricted `rebase_user` role: a\n * non-owner, NOSUPERUSER, NOBYPASSRLS role, so Postgres RLS binds *every*\n * statement (SELECT, INSERT, UPDATE, DELETE). The collection's\n * `securityRules` are the whole authorization model; app-layer callbacks\n * are validation/side-effects, not a security boundary.\n *\n * - **Server context** — the base (owner) connection: auth flows, migrations,\n * and raw `rebase.sql`. As table owner it bypasses RLS. This is the trusted\n * plane, equivalent to Supabase's `service_role`.\n *\n * `rebase.dataAsAdmin` is **not** in it, despite the name. `init.ts` scopes\n * that driver with `withAuth(SERVICE_IDENTITY)`, so it arrives as user\n * context above — `rebase_user`, `app.uid = 'service'`, policies evaluated —\n * and clears the default policies through their admin arm rather than the\n * `rebase.uid() IS NULL` one.\n *\n * This module provides the three pieces:\n *\n * 1. {@link detectConnectionPosture} — is the connection subject to RLS at\n * all? (superuser / BYPASSRLS / table owner ⇒ no)\n * 2. {@link ensureAppRole} — idempotently provision `rebase_user` with\n * SELECT/INSERT/UPDATE/DELETE grants (+ default privileges so future\n * tables stay covered).\n * 3. {@link applyAuthContext} — per-transaction: set the `app.*` GUCs the\n * policies read (`rebase.uid()` etc.) and the transaction-local role\n * `rebase_user` so RLS binds, in one statement. Transaction-scoped, so it\n * composes with poolers.\n *\n * Provisioning runs from the framework's own bootstrap/migrate (which already\n * self-creates the `auth` schema and functions) — enforcement is default-on,\n * not an operator opt-in.\n */\n\n/**\n * The restricted role every authenticated (user-context) request runs as.\n *\n * Re-exported, not re-declared: the same name is needed by\n * `@rebasepro/common`'s internal-table revokes, and two spellings of a role name\n * fail as a silent no-op rather than an error.\n */\nexport { REBASE_USER_ROLE };\n\n/** Minimal SQL runner so callers can adapt drizzle or pg.Client. */\nexport type RawSqlRunner = (sqlText: string) => Promise<Record<string, unknown>[]>;\n\n/** Minimal transaction surface needed by {@link applyAuthContext}. */\nexport interface SqlTx {\n execute(query: SQL): Promise<unknown>;\n}\n\nexport interface ConnectionPosture {\n /** The connection's `current_user`. */\n role: string;\n superuser: boolean;\n bypassRLS: boolean;\n /**\n * Owns at least one user table — owners bypass non-FORCE RLS.\n *\n * \"Owns\" in Postgres's sense, not by name: a role that INHERITs the owner is\n * the owner for every ownership test the planner makes, including the one\n * that decides whether a policy applies at all.\n */\n ownsTables: boolean;\n /**\n * May create tables somewhere the server writes — and would then own them.\n *\n * The reason this is part of the posture rather than a separate question:\n * the posture is decided ONCE, at boot, and on a fresh database the\n * catalogue has nothing to report yet. The process that asks then goes on to\n * create every table in the schema and owns all of them for the rest of its\n * life. Asking only what is already there answers about the wrong instant.\n */\n canCreateTables: boolean;\n /** True when RLS would NOT constrain this connection. */\n privileged: boolean;\n}\n\nexport interface AuthContext {\n uid: string;\n /** Raw roles as carried on the user (strings or `{ id }` objects). */\n roles: unknown[];\n /**\n * Whether this session came from anonymous sign-in rather than an account.\n *\n * Anonymous sign-in mints a real user row and a real uid, so a guest was\n * indistinguishable from a registered account inside a policy: same\n * `rebase.uid()`, same default role. Every rule meaning \"a signed-in\n * person\" was therefore also a rule about anybody who had called\n * `POST /auth/anonymous`, which asks for no email, no password and no\n * agreement to anything.\n *\n * Optional, and absent reads as `false`: a caller that predates this — a\n * realtime subscription, a custom validator — keeps the behaviour it had\n * rather than having every one of its users reclassified as guests.\n */\n isAnonymous?: boolean;\n /**\n * The **custom** claims on the caller's token, as `rebase.jwt()` reports\n * them.\n *\n * This is how a policy reaches a fact about the session that is neither the\n * uid nor the roles — which, before this, it could not: `app.jwt` was built\n * from three fields the driver already had, so `rebase.jwt()`, documented\n * as \"full JWT claims as JSONB\", returned a three-key object that never had\n * a claim in it.\n *\n * Multi-tenancy's `claim` form is the first caller — `policy.authClaim(…)`\n * compiles to `rebase.jwt() ->> '<name>'`. Identity claims are stripped out\n * before they reach here (`AccessTokenPayload.claims`) and the identity\n * fields are written *over* them below, so nothing a hook put on a token\n * can assert a uid, roles or guest status.\n */\n claims?: Record<string, unknown>;\n}\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\n\n/** DML the user role holds on managed tables (RLS still filters per row). */\nconst USER_TABLE_PRIVILEGES = \"SELECT, INSERT, UPDATE, DELETE\";\n\n/**\n * Take every privilege the user role holds on one table back — what a table\n * gets when RLS cannot be switched on for it, so it is unreachable rather than\n * unprotected. Quoted exactly: a mixed-case adopted table (`\"User\"`) folded to\n * lower case names a table that does not exist.\n */\nexport const revokeUserRoleSql = (schema: string, table: string): string =>\n `REVOKE ALL PRIVILEGES ON ${quoteIdent(schema)}.${quoteIdent(table)} FROM ${quoteIdent(REBASE_USER_ROLE)}`;\n\n/**\n * Warn when the connection role shares its name with an existing schema.\n *\n * Postgres resolves unqualified names through `search_path`, which defaults to\n * `\"$user\", public` — and `$user` is the connection ROLE. When a schema of that\n * name exists it sits ahead of `public`, so every unqualified statement\n * silently operates on it instead:\n *\n * CREATE TABLE posts (...); -- you meant public.posts; you got <role>.posts\n *\n * Nothing errors. You get a second table of the same name in the wrong schema,\n * and reads that pin `public` cannot see it — which reads as \"missing table\" and\n * sends people to re-run a push that creates a *third* copy. The bootstrapper\n * has a whole branch dedicated to recognising the symptom after the fact.\n *\n * Rebase shipped straight into this: it creates a schema named `rebase` while\n * every template named the database role `rebase` too. The scaffold uses\n * `rebase_app` now, and every pool Rebase opens pins `search_path=public`\n * (`pinSearchPath`), which covers the paths the framework controls. This covers\n * the ones it does not — `psql`, `pg_dump`, drizzle-kit, a colleague's script,\n * a hand-written migration — because the hazard is a property of the two NAMES,\n * not of any one connection.\n *\n * A warning rather than a boot failure: the database works, the framework's own\n * traffic is pinned, and refusing to start over a naming choice a user may have\n * inherited would be worse than the risk.\n */\nexport async function warnOnRoleSchemaCollision(run: RawSqlRunner): Promise<void> {\n try {\n const rows = await run(`\n SELECT current_user AS role,\n EXISTS (\n SELECT 1 FROM pg_namespace n WHERE n.nspname = current_user\n ) AS collides\n `);\n if (rows[0]?.collides !== true) return;\n const role = String(rows[0]?.role ?? \"the connection role\");\n logger.warn(\n `⚠️ The database role \"${role}\" has the same name as a schema. Postgres resolves unqualified ` +\n `names through \\`search_path\\`, which defaults to \\`\"$user\", public\\` — so \"${role}\" is searched ` +\n `BEFORE public, and any unqualified \\`CREATE TABLE\\`/\\`SELECT\\` from a tool that does not pin the ` +\n `path (psql, pg_dump, drizzle-kit, a hand-written migration) silently lands in \"${role}\" instead. ` +\n `Rebase's own connections pin \\`search_path=public\\`, so the server is unaffected. To remove the ` +\n `hazard entirely, connect as a role whose name is not also a schema — the scaffold uses ` +\n `\"rebase_app\".`\n );\n } catch {\n // A diagnostic must never be the reason a boot fails.\n }\n}\n\n/**\n * Would RLS actually constrain this connection?\n *\n * Four ways the answer is no, and only two of them are obvious. Superuser and\n * BYPASSRLS announce themselves in `pg_roles`. The other two are about\n * ownership, and both used to read as \"no bypass here\":\n *\n * - **Ownership through membership.** The old query compared\n * `pg_tables.tableowner` to `current_user` as a string. Postgres does not: a\n * role that INHERITs the owner passes every ownership test the planner makes,\n * including the one that decides whether a policy applies. So a perfectly\n * ordinary \"app role that is a member of the owner role\" setup reported\n * unprivileged and bypassed every policy — permanently, since no restart\n * changes a string comparison's answer.\n *\n * - **Ownership that has not happened yet.** The posture is decided once, at\n * boot, and on a fresh database there is nothing in the catalogue to find. The\n * same process then creates every table and owns all of them. The first boot\n * of a new deployment therefore served every request unconstrained, the log\n * said RLS was fine, and the SECOND boot silently fixed it — which is the\n * worst possible shape for a security check, because the state that reproduces\n * it is gone by the time anyone looks. Asking \"may this role create tables\n * where the server provisions?\" answers about the process's whole life rather\n * than about one instant of it.\n *\n * `schemas` narrows the CREATE question to where the server actually writes.\n * Omitted, it asks about every schema the role can see, which is the safe\n * direction: over-reporting costs a role switch that was not strictly needed,\n * under-reporting costs the entire authorization model.\n */\nexport async function detectConnectionPosture(\n run: RawSqlRunner,\n schemas?: string[]\n): Promise<ConnectionPosture> {\n // Identifiers, and they reach SQL as literals in a catalogue comparison —\n // but they arrive from collection configs, so they are quoted as string\n // literals rather than interpolated as names, and a quote inside one is\n // escaped rather than trusted.\n const schemaFilter = schemas?.length\n ? `AND n.nspname IN (${Array.from(new Set(schemas))\n .map((s) => `'${s.replace(/'/g, \"''\")}'`)\n .join(\", \")})`\n : \"\";\n\n const rows = await run(`\n SELECT current_user AS role,\n r.rolsuper AS superuser,\n r.rolbypassrls AS bypassrls,\n EXISTS (\n SELECT 1 FROM pg_tables t\n WHERE pg_has_role(current_user, t.tableowner, 'USAGE')\n AND t.schemaname NOT IN ('pg_catalog', 'information_schema')\n ) AS owns_tables,\n EXISTS (\n SELECT 1 FROM pg_namespace n\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n AND n.nspname NOT LIKE 'pg\\\\_toast%'\n AND n.nspname NOT LIKE 'pg\\\\_temp%'\n ${schemaFilter}\n AND has_schema_privilege(current_user, n.oid, 'CREATE')\n ) AS can_create\n FROM pg_roles r\n WHERE r.rolname = current_user\n `);\n const row = rows[0] ?? {};\n const superuser = row.superuser === true;\n const bypassRLS = row.bypassrls === true;\n const ownsTables = row.owns_tables === true;\n const canCreateTables = row.can_create === true;\n return {\n role: String(row.role ?? \"unknown\"),\n superuser,\n bypassRLS,\n ownsTables,\n canCreateTables,\n privileged: superuser || bypassRLS || ownsTables || canCreateTables\n };\n}\n\n/**\n * Human-actionable instructions for when the connection cannot provision the\n * user role itself (no CREATEROLE and role not pre-created by the platform).\n */\nexport function appRoleSetupInstructions(connectionRole: string, schemas: string[]): string {\n const grants = schemas.map((s) =>\n `GRANT USAGE ON SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};\\n` +\n `GRANT ${USER_TABLE_PRIVILEGES} ON ALL TABLES IN SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};\\n` +\n `GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};`\n ).join(\"\\n\");\n return (\n `Rebase enforces row-level security by running authenticated requests as ` +\n `the restricted role \"${REBASE_USER_ROLE}\", but the connection role ` +\n `\"${connectionRole}\" bypasses RLS and cannot create that role itself.\\n` +\n `Run the following as a database administrator, then restart:\\n\\n` +\n `CREATE ROLE ${REBASE_USER_ROLE} NOLOGIN NOSUPERUSER NOBYPASSRLS NOINHERIT;\\n` +\n `GRANT ${REBASE_USER_ROLE} TO ${quoteIdent(connectionRole)};\\n` +\n grants\n );\n}\n\n/**\n * Idempotently provision the `rebase_user` role, membership for the current\n * connection role, and DML grants (+ default privileges for future tables)\n * on every existing schema in `schemas`.\n *\n * Split into privilege tiers so it works both when the connection is a\n * superuser (creates everything) and when the platform pre-created the role\n * and membership (e.g. CNPG `postInitApplicationSQL`) and the connection is\n * merely the table owner — owners can always run the grant tier themselves.\n *\n * RLS still filters every row: these grants only make the tables *reachable*\n * by the role; the policies decide which rows/commands actually pass.\n *\n * Throws with precise setup instructions when the role is missing and the\n * connection cannot create it.\n */\nexport async function ensureAppRole(run: RawSqlRunner, schemas: string[]): Promise<void> {\n const uniqueSchemas = Array.from(new Set(schemas.filter(Boolean)));\n\n // Tier 1 — role existence.\n const roleRows = await run(`SELECT 1 FROM pg_roles WHERE rolname = '${REBASE_USER_ROLE}'`);\n if (roleRows.length === 0) {\n try {\n await run(`CREATE ROLE ${REBASE_USER_ROLE} NOLOGIN NOSUPERUSER NOBYPASSRLS NOINHERIT`);\n } catch (err) {\n throw new Error(\n `Failed to create the \"${REBASE_USER_ROLE}\" role: ${err instanceof Error ? err.message : String(err)}\\n\\n` +\n appRoleSetupInstructions(\"current connection role\", uniqueSchemas)\n );\n }\n }\n\n // Tier 2 — membership, so a non-superuser connection may SET ROLE to it.\n const memberRows = await run(`\n SELECT (pg_has_role(current_user, '${REBASE_USER_ROLE}', 'MEMBER')\n OR (SELECT rolsuper FROM pg_roles WHERE rolname = current_user)) AS can_set,\n current_user AS role\n `);\n if (memberRows[0]?.can_set !== true) {\n try {\n await run(`GRANT ${REBASE_USER_ROLE} TO CURRENT_USER`);\n } catch (err) {\n throw new Error(\n `The connection role is not a member of \"${REBASE_USER_ROLE}\" and cannot grant itself membership: ` +\n `${err instanceof Error ? err.message : String(err)}\\n\\n` +\n appRoleSetupInstructions(String(memberRows[0]?.role ?? \"current connection role\"), uniqueSchemas)\n );\n }\n }\n\n // Tier 3 — grants.\n await ensureAppRoleGrants(run, uniqueSchemas);\n\n logger.debug(`🔐 [rls] User role \"${REBASE_USER_ROLE}\" provisioned (schemas: ${uniqueSchemas.join(\", \")})`);\n}\n\n/** A string literal for a catalogue read. Names come from config and constants, never from a request. */\nconst sqlLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\nconst textArray = (values: readonly string[]): string =>\n values.length === 0 ? \"ARRAY[]::text[]\" : `ARRAY[${values.map(sqlLiteral).join(\", \")}]::text[]`;\n\n/** What the grant tier would find already in place in one schema. */\ninterface SchemaGrants {\n /** `USAGE` on the schema, granted by its owner. */\n usage: boolean;\n /**\n * Every relation `GRANT … ON ALL TABLES` reaches already has all four DML\n * privileges from its owner — Rebase's own RLS-off tables aside, which the\n * sweep takes back right after and so never keep them.\n */\n tables: boolean;\n /** Every sequence already has `USAGE, SELECT` from its owner. */\n sequences: boolean;\n /** This role's default privileges in the schema already grant the table DML. */\n defaultTables: boolean;\n /** …and the sequence `USAGE, SELECT`. */\n defaultSequences: boolean;\n /**\n * Rebase's own RLS-off tables a `REVOKE` would still change: the user role\n * holds a privilege on them, or the table has never had an ACL written —\n * `NULL`, which a `REVOKE` (like the grant-and-revoke it replaces) turns\n * into the owner's explicit default. Same access either way; the same\n * catalogue only this way.\n */\n internalGranted: string[];\n}\n\n/**\n * The grant tier's state for every existing schema in `schemas`, in one read.\n *\n * A grant counts as present only as the statement this tier runs would record\n * it — grantor the object's owner, which is what a GRANT by the owner, a member\n * of the owning role or a superuser records — so skipping it leaves the ACL\n * exactly as running it would. Anything else reads as missing, and the\n * statement runs as it always did.\n */\nasync function readSchemaGrants(run: RawSqlRunner, schemas: string[]): Promise<Map<string, SchemaGrants>> {\n const internal = textArray(REBASE_INTERNAL_TABLES);\n const target = `(SELECT oid FROM pg_roles WHERE rolname = ${sqlLiteral(REBASE_USER_ROLE)})`;\n /** Distinct privileges `acl` gives the user role (from `grantor`, when given) out of `privileges`. */\n const held = (acl: string, privileges: string[], grantor?: string): string =>\n `(SELECT count(DISTINCT a.privilege_type) FROM aclexplode(${acl}) a WHERE a.grantee = ${target}` +\n `${grantor ? ` AND a.grantor = ${grantor}` : \"\"} AND a.privilege_type IN (${privileges.map(sqlLiteral).join(\", \")}))`;\n const DML = [\"SELECT\", \"INSERT\", \"UPDATE\", \"DELETE\"];\n const SEQ = [\"USAGE\", \"SELECT\"];\n const rows = await run(`\n SELECT n.nspname AS schema,\n ${held(\"n.nspacl\", [\"USAGE\"], \"n.nspowner\")} = 1 AS usage,\n NOT EXISTS (\n SELECT 1 FROM pg_class c\n WHERE c.relnamespace = n.oid AND c.relkind IN ('r', 'v', 'm', 'f', 'p')\n AND NOT (c.relname = ANY(${internal}) AND NOT c.relrowsecurity)\n AND ${held(\"c.relacl\", DML, \"c.relowner\")} < ${DML.length}\n ) AS tables,\n NOT EXISTS (\n SELECT 1 FROM pg_class c\n WHERE c.relnamespace = n.oid AND c.relkind = 'S'\n AND ${held(\"c.relacl\", SEQ, \"c.relowner\")} < ${SEQ.length}\n ) AS sequences,\n EXISTS (\n SELECT 1 FROM pg_default_acl d\n WHERE d.defaclrole = (SELECT oid FROM pg_roles WHERE rolname = current_user)\n AND d.defaclnamespace = n.oid AND d.defaclobjtype = 'r'\n AND ${held(\"d.defaclacl\", DML)} = ${DML.length}\n ) AS default_tables,\n EXISTS (\n SELECT 1 FROM pg_default_acl d\n WHERE d.defaclrole = (SELECT oid FROM pg_roles WHERE rolname = current_user)\n AND d.defaclnamespace = n.oid AND d.defaclobjtype = 'S'\n AND ${held(\"d.defaclacl\", SEQ)} = ${SEQ.length}\n ) AS default_sequences,\n ARRAY(\n SELECT c.relname::text FROM pg_class c\n WHERE c.relnamespace = n.oid AND c.relname = ANY(${internal}) AND NOT c.relrowsecurity\n AND (c.relacl IS NULL OR EXISTS (SELECT 1 FROM aclexplode(c.relacl) a WHERE a.grantee = ${target}))\n ORDER BY 1\n ) AS internal_granted\n FROM pg_namespace n\n WHERE n.nspname = ANY(${textArray(schemas)})\n `);\n const grants = new Map<string, SchemaGrants>();\n for (const row of rows) {\n const internalGranted = row.internal_granted;\n grants.set(String(row.schema), {\n usage: row.usage === true,\n tables: row.tables === true,\n sequences: row.sequences === true,\n defaultTables: row.default_tables === true,\n defaultSequences: row.default_sequences === true,\n internalGranted: Array.isArray(internalGranted)\n ? internalGranted.map(String)\n // A driver that hands arrays back as text: `{a,b}`.\n : String(internalGranted ?? \"\").replace(/^\\{|\\}$/g, \"\").split(\",\").filter(Boolean)\n });\n }\n return grants;\n}\n\n/**\n * The grant tier of {@link ensureAppRole} on its own: `USAGE` on each existing\n * schema in `schemas`, DML on its tables and `USAGE, SELECT` on its\n * sequences, the default privileges that cover what this role creates later,\n * and Rebase's own tables taken back — for a role and membership already\n * provisioned.\n *\n * Every statement is issued only when the catalogue says it would change\n * something. They used to run unconditionally, twice per boot (at connect, and\n * again in the final posture check): a `GRANT … ON ALL TABLES` rewrites every\n * table's `pg_class` row whether or not the ACL changes, which invalidates\n * every table's cached plans on every connection, and the internal-table sweep\n * ran 23 `REVOKE`s per schema. A boot against a database whose grants already\n * hold now reads one catalogue query and writes nothing; one that is missing\n * any of them runs exactly the statements it always ran.\n *\n * Table owners (the expected non-superuser posture) can always grant on their\n * own objects, so this needs no extra privilege.\n */\nexport async function ensureAppRoleGrants(run: RawSqlRunner, schemas: string[]): Promise<void> {\n const uniqueSchemas = Array.from(new Set(schemas.filter(Boolean)));\n const grants = await readSchemaGrants(run, uniqueSchemas);\n for (const schema of uniqueSchemas) {\n const state = grants.get(schema);\n // Not there (yet): a later call, once provisioning has created it, grants it.\n if (!state) continue;\n const s = quoteIdent(schema);\n if (!state.usage) await run(`GRANT USAGE ON SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n if (!state.tables) await run(`GRANT ${USER_TABLE_PRIVILEGES} ON ALL TABLES IN SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n if (!state.sequences) await run(`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n // Cover objects created later by the CURRENT role (the role that runs\n // migrations), so a migrate can never strand the user role.\n if (!state.defaultTables) {\n await run(`ALTER DEFAULT PRIVILEGES IN SCHEMA ${s} GRANT ${USER_TABLE_PRIVILEGES} ON TABLES TO ${REBASE_USER_ROLE}`);\n }\n if (!state.defaultSequences) {\n await run(`ALTER DEFAULT PRIVILEGES IN SCHEMA ${s} GRANT USAGE, SELECT ON SEQUENCES TO ${REBASE_USER_ROLE}`);\n }\n\n // The grants above are deliberately schema-wide — a project's own\n // collections may live in `rebase`, and future tables must be reachable\n // or a migration strands the role. Rebase's OWN tables are the exception:\n // refresh tokens, MFA secrets, API keys and the rest carry no RLS and no\n // row an end user should ever address. Taking the privilege back here\n // covers every table that already exists; each creator revokes on the\n // table it just made, for the boot that creates them for the first time.\n //\n // All of them right after the schema-wide grant, which has just handed\n // them out; otherwise only the ones the catalogue says still hold one.\n const tables = state.tables ? state.internalGranted : undefined;\n if (tables && tables.length === 0) continue;\n await revokeInternalTableAccess(async (text) => { await run(text); }, schema, {\n tables,\n onError: (table, error) => logger.warn(\n `🔐 [rls] Could not revoke \"${REBASE_USER_ROLE}\" access to \"${schema}\".\"${table}\" — ` +\n \"it stays reachable by authenticated requests: \" +\n (error instanceof Error ? error.message : String(error))\n )\n });\n }\n}\n\n/**\n * Rebase's own RLS-off tables in `schema` that a `revokeInternalTableAccess`\n * sweep would change — the user role holds a privilege on them, or their ACL\n * has never been written. One read; the sweep it narrows is one `DO` per\n * table name.\n */\nexport async function internalTablesGrantedToUserRole(run: RawSqlRunner, schema: string): Promise<string[]> {\n return (await readSchemaGrants(run, [schema])).get(schema)?.internalGranted ?? [];\n}\n\n/**\n * Apply the authenticated context to a transaction: the `app.*` GUCs that RLS\n * policies read via `rebase.uid()` / `rebase.roles()` / `rebase.jwt()`, and — when\n * `userRole` is set — the `LOCAL` role switch, in the same statement, so RLS\n * binds every statement in this transaction (reads *and* writes).\n *\n * GUCs are set with `is_local = true` and the role switch is `LOCAL`: both\n * reset at commit/rollback, so pooled connections are never polluted.\n *\n * Fails closed by construction: if the role switch errors, the transaction\n * aborts instead of proceeding privileged.\n *\n * SECURITY: this function is only ever called on the **user** path (the server\n * context uses the base/owner driver and never calls it). The default policies\n * treat `rebase.uid() IS NULL` as the trusted server context, and `rebase.uid()`\n * is `NULLIF(current_setting('app.uid'), '')` — so an EMPTY user id would\n * be read as NULL and silently escalate a user request to server privileges.\n * Coerce empty/blank ids to `ANONYMOUS_USER_ID` here, at the single chokepoint,\n * rather than trusting every caller (e.g. realtime subscription auth) to do it.\n * That sentinel is exported from `@rebasepro/types` because it leaks into rule\n * semantics: it is why `rebase.uid() IS NOT NULL` is true for anonymous requests.\n */\nexport async function applyAuthContext(tx: SqlTx, auth: AuthContext, userRole?: string): Promise<void> {\n const uid = typeof auth.uid === \"string\" && auth.uid.trim() !== \"\" ? auth.uid : ANONYMOUS_USER_ID;\n const normalizedRoles = auth.roles.map((r: unknown) =>\n typeof r === \"string\" ? r : (r as Record<string, unknown>)?.id ?? String(r)\n );\n // `app.user_id` is the pre-rename spelling, still written because policies\n // are data: a database provisioned before the rename holds rules compiled\n // to `current_setting('app.user_id')`, and those predicates would evaluate\n // to NULL — failing open or locking out — if we stopped setting it. Drop\n // the alias only once no live database carries a legacy policy.\n // A guest is a signed-in caller with no account behind them, and until this\n // the database could not tell the difference — see `rebase.is_anonymous()`.\n // Written as a string because that is what `set_config` takes.\n const isAnonymous = auth.isAnonymous === true ? \"true\" : \"false\";\n\n await tx.execute(drizzleSql`\n SELECT\n set_config('app.uid', ${uid}, true),\n set_config('app.user_id', ${uid}, true),\n set_config('app.user_roles', ${normalizedRoles.join(\",\")}, true),\n set_config('app.is_anonymous', ${isAnonymous}, true),\n set_config('app.jwt', ${JSON.stringify({\n // The caller's custom claims, FIRST, so the identity keys below\n // overwrite them rather than the other way round. Same ordering, and\n // the same reason, as `generateAccessToken`: a claims hook is a place\n // to add facts about a session, not the place its subject is decided.\n // `verifyAccessToken` already strips the identity claims out of this\n // object; the ordering is the second lock on the same door.\n ...(auth.claims ?? {}),\n sub: uid,\n roles: auth.roles,\n // In the claims too, so a policy reading `rebase.jwt()` — which is\n // how a rule reaches anything that is not uid or roles — sees the\n // same fact as `rebase.is_anonymous()`. Two sources that could\n // disagree would be worse than one.\n is_anonymous: auth.isAnonymous === true\n })}, true)${userRole\n // The role switch, in the same statement rather than a `SET LOCAL ROLE`\n // round trip of its own on every transaction. Postgres implements\n // `SET ROLE` as the `role` setting: `set_config('role', name, true)`\n // runs the same membership check, fails with the same SQLSTATE and\n // message (42501 \"permission denied to set role\", 22023 for an unknown\n // role — `isRoleSwitchingPermissionError` still reads it), takes the\n // name verbatim as the quoted identifier did, and resets at COMMIT and\n // ROLLBACK like `LOCAL`. Last in the list, after the GUCs; a refusal\n // fails the whole statement, so the transaction aborts rather than\n // running on as the connection owner. auth-context-one-statement.test.ts.\n ? drizzleSql`,\n set_config('role', ${userRole}, true)`\n : drizzleSql``}\n `);\n}\n\n/** Role names from other BaaS platforms that people reach for out of habit. */\nconst FOREIGN_CONVENTION_ROLES: Record<string, string> = {\n authenticated: \"Supabase\",\n anon: \"Supabase\",\n service_role: \"Supabase\"\n};\n\n/**\n * Warn about rules that read as \"signed-in users only\" but admit anonymous\n * callers — `rebase.uid() IS NOT NULL`, or a comparison against another\n * platform's magic user id such as `'anon'`.\n *\n * The sibling of {@link validatePolicyPgRoles}, for the more dangerous spelling\n * of the same habit. A foreign `pgRoles` value makes a policy unreachable and\n * the table reads empty — loud, and that guard throws. These do the opposite:\n * the rule compiles to a grant, and nothing looks wrong until the data is\n * already public.\n *\n * Warns rather than throws. Unlike an unreachable `pgRoles`, these rules are\n * serving traffic today: refusing to boot would take an app offline to report a\n * problem it already has, and on the read path it would take it offline\n * *because* its data was exposed. Rewriting the author's SQL is not an option\n * either — this is the escape hatch whose whole promise is that it means what it\n * says. So: say so, loudly, and leave the rule alone.\n */\nexport function warnOnAnonymousGrants(\n collections: { slug?: string; securityRules?: readonly SecurityRule[] }[]\n): void {\n // Grouped by the mistake, not by the rule: one habit typically repeats\n // across every collection an author wrote, and a per-rule list would repeat\n // the same paragraph dozens of times and get skimmed.\n const byRisk = new Map<string, { risk: AnonymousGrantRisk; sites: string[] }>();\n\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n const risks = [usingExpr, withCheckExpr]\n .filter((e): e is PolicyExpression => e !== null)\n .flatMap(findAnonymousGrants);\n\n for (const risk of risks) {\n const key = `${risk.pattern}:${risk.detail}`;\n const site = `${collection.slug ?? \"(unnamed)\"} → \"${rule.name ?? \"(unnamed rule)\"}\"`;\n const entry = byRisk.get(key) ?? { risk, sites: [] };\n if (!entry.sites.includes(site)) entry.sites.push(site);\n byRisk.set(key, entry);\n }\n }\n }\n\n if (byRisk.size === 0) return;\n\n const problems = [...byRisk.values()].map(({ risk, sites }) =>\n ` • ${risk.explanation}\\n ${sites.length} rule(s): ${sites.join(\", \")}`\n );\n\n logger.warn(\n `Security rules that read as a lockdown but grant access to anonymous requests. Every caller from a ` +\n `client carries a user id ('${ANONYMOUS_USER_ID}' when nobody is signed in), so these clauses are ` +\n `true for everyone:\\n\\n` +\n problems.join(\"\\n\\n\") + \"\\n\"\n );\n}\n\n/**\n * Name the collections whose raw policy SQL still calls the pre-1.0 helpers.\n *\n * The compiler rewrites `auth.uid()` to `rebase.uid()` on the way into the\n * database, so nothing is broken and no policy is wrong — which is exactly why\n * this has to be said out loud. A silent rewrite that works forever is not a\n * migration, it is a second supported spelling nobody wrote down, and the next\n * person to read those rules will copy the old one.\n *\n * Only `raw` expressions can carry it. Structured rules (`policy.authUid()`,\n * `policy.rolesOverlap(...)`) compile from the model and were never affected.\n */\nexport function warnOnLegacyRlsFunctions(\n collections: { slug?: string; securityRules?: readonly SecurityRule[] }[]\n): void {\n const sites: string[] = [];\n\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n const carriesLegacy = [usingExpr, withCheckExpr]\n .filter((e): e is PolicyExpression => e !== null)\n .some(containsLegacyRlsCall);\n if (!carriesLegacy) continue;\n\n const site = `${collection.slug ?? \"(unnamed)\"} → \"${rule.name ?? \"(unnamed rule)\"}\"`;\n if (!sites.includes(site)) sites.push(site);\n }\n }\n\n if (sites.length === 0) return;\n\n logger.warn(\n `These security rules call the pre-1.0 RLS helpers (\\`auth.uid()\\`, \\`auth.roles()\\`, \\`auth.jwt()\\`). ` +\n `They still work — the compiler rewrites them — but the functions now live in the \\`rebase\\` schema, ` +\n `and the \\`auth\\` one is Supabase's. Update the raw SQL in these rules to \\`${REBASE_SCHEMA}.uid()\\` ` +\n `and friends, or switch them to the structured helpers (\\`policy.authUid()\\`, \\`policy.rolesOverlap()\\`), ` +\n `which never had to be spelled by hand:\\n\\n` +\n sites.map(s => ` • ${s}`).join(\"\\n\") + \"\\n\"\n );\n}\n\n/** Whether any `raw` expression in the tree calls a pre-1.0 helper. */\nfunction containsLegacyRlsCall(expr: PolicyExpression): boolean {\n switch (expr.kind) {\n case \"raw\":\n return usesLegacyRlsFunctions(expr.sql);\n case \"and\":\n case \"or\":\n return expr.operands.some(containsLegacyRlsCall);\n case \"not\":\n return containsLegacyRlsCall(expr.operand);\n case \"existsIn\":\n return containsLegacyRlsCall(expr.where);\n default:\n return false;\n }\n}\n\n/**\n * Reject `pgRoles` that this server can never satisfy.\n *\n * `pgRoles` sets the `TO` clause of a generated policy, so a policy naming a\n * role the request never runs as simply never applies — and RLS then filters\n * every row. The table reads as empty, which is indistinguishable from having\n * no data, so the mistake survives review and ships.\n *\n * Requests run as `rebase_user`, so a policy is only reachable if it targets\n * `public` or a role `rebase_user` holds. Anything else is a configuration\n * error worth failing the boot for.\n */\nexport async function validatePolicyPgRoles(\n run: RawSqlRunner,\n collections: { slug?: string; securityRules?: readonly { name?: string; pgRoles?: readonly string[] }[] }[],\n /** The role requests actually run as: `rebase_user` when the connection is\n * privileged enough to switch, otherwise the connection role itself. */\n requestRole: string = REBASE_USER_ROLE\n): Promise<void> {\n const wanted = new Map<string, string[]>();\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n for (const role of rule.pgRoles ?? []) {\n if (role === \"public\") continue;\n wanted.set(role, [...(wanted.get(role) ?? []), collection.slug ?? \"(unnamed)\"]);\n }\n }\n }\n if (wanted.size === 0) return;\n\n const names = [...wanted.keys()].map((r) => `'${r.replace(/'/g, \"''\")}'`).join(\",\");\n const escapedRequestRole = requestRole.replace(/'/g, \"''\");\n const rows = await run(`\n SELECT r.rolname AS role,\n COALESCE(pg_has_role(to_regrole('${escapedRequestRole}'), r.oid, 'MEMBER'), false) AS reachable\n FROM pg_roles r\n WHERE r.rolname IN (${names})\n `);\n\n const reachable = new Map(rows.map((row) => [String(row.role), row.reachable === true]));\n const problems: string[] = [];\n\n for (const [role, slugs] of wanted) {\n if (reachable.get(role) === true) continue;\n\n const why = reachable.has(role)\n ? `\"${requestRole}\" is not a member of it`\n : \"no such role exists in this database\";\n const platform = FOREIGN_CONVENTION_ROLES[role];\n const hint = platform\n ? `\"${role}\" is a ${platform} convention, not a PostgreSQL role. Application roles belong in \\`roles: [\"${role === \"service_role\" ? \"admin\" : role}\"]\\`, which is checked inside the policy via rebase.roles().`\n : `Either grant it (GRANT ${role} TO ${requestRole}) or drop \\`pgRoles\\` so the policy targets \\`public\\`.`;\n\n problems.push(\n ` • pgRoles: [\"${role}\"] on ${slugs.join(\", \")} — ${why}.\\n ${hint}`\n );\n }\n\n if (problems.length > 0) {\n throw new Error(\n `Security rules target PostgreSQL roles this server cannot use. Requests run as ` +\n `\"${requestRole}\", so these policies would never apply and every row would be ` +\n `filtered out — the collections would look empty rather than error.\\n\\n` +\n problems.join(\"\\n\\n\") + \"\\n\"\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from "module";
|
|
2
|
-
const require = __createRequire(import.meta.url);
|
|
3
|
-
//#region \0rolldown/runtime.js
|
|
4
|
-
var __create = Object.create;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
-
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
11
|
-
var __exportAll = (all, no_symbols) => {
|
|
12
|
-
let target = {};
|
|
13
|
-
for (var name in all) __defProp(target, name, {
|
|
14
|
-
get: all[name],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
18
|
-
return target;
|
|
19
|
-
};
|
|
20
|
-
var __copyProps = (to, from, except, desc) => {
|
|
21
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
22
|
-
key = keys[i];
|
|
23
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
24
|
-
get: ((k) => from[k]).bind(null, key),
|
|
25
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return to;
|
|
29
|
-
};
|
|
30
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
31
|
-
value: mod,
|
|
32
|
-
enumerable: true
|
|
33
|
-
}) : target, mod));
|
|
34
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
|
|
35
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
36
|
-
throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
|
37
|
-
});
|
|
38
|
-
//#endregion
|
|
39
|
-
export { __toESM as i, __exportAll as n, __require as r, __commonJSMin as t };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-MC0zTBar.js","names":[],"sources":["../src/socket-opt-ins.ts","../src/websocket.ts"],"sourcesContent":["import { WebSocket } from \"ws\";\nimport { unref } from \"@rebasepro/utils\";\n\n/**\n * Behaviours a client opts into when it opens the socket.\n *\n * The client names them on the socket URL, `?rebase_caps=heartbeat,channel_seq,cancel_sql`,\n * and the server answers with a `hello` frame listing the ones it will honour:\n *\n * ```json\n * { \"type\": \"hello\", \"capabilities\": [\"heartbeat\", \"channel_seq\", \"cancel_sql\"], \"heartbeatMs\": 30000, \"clientId\": \"client_…\" }\n * ```\n *\n * Both halves are needed, so both directions of a version mismatch keep\n * working. An older client names none and gets no `hello`, no heartbeat and no\n * acknowledgement frames — nothing it would not recognise. An older server\n * ignores the query parameter and sends no `hello`, and a newer client that\n * hears none behaves as it always did.\n *\n * One list for both kinds: what the server sends (`heartbeat`, `channel_seq`)\n * and the frames it takes beyond the base protocol (`cancel_sql`). Named before\n * sign-in, so a socket that never signs in is served as one that does.\n *\n * - `heartbeat`: a `{ \"type\": \"heartbeat\" }` frame every `heartbeatMs`. A\n * browser cannot send a WebSocket ping or see one, so a connection that went\n * half-open — a NAT that forgot it, a laptop that slept — looks connected to\n * it forever. With these, two missed beats tell it to redial.\n * - `channel_seq`: on a retained channel, the sender of a broadcast gets\n * `{ \"type\": \"broadcast_ack\", \"channel\", \"seq\" }` where every other member\n * gets the message. Its own messages then leave no hole in the sequence it\n * watches, so a hole means a message it really missed — see\n * `RealtimeChannel` in the SDK.\n * - `cancel_sql`: the server takes `CANCEL_SQL { requestId }`, which stops a\n * console run (`EXECUTE_SQL`, `mode: \"script\"`) of the same socket. A client\n * sends it only to a server whose `hello` listed it.\n *\n * @module\n */\n\n/** The query parameter a client names its opt-ins in. */\nexport const SOCKET_OPT_IN_PARAM = \"rebase_caps\";\n\n/** Every behaviour this server can be asked for. */\nexport const SOCKET_OPT_INS = [\"heartbeat\", \"channel_seq\", \"cancel_sql\"] as const;\n\nexport type SocketOptIn = typeof SOCKET_OPT_INS[number];\n\n/** How often a socket that asked for heartbeats is sent one, by default. */\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;\n\nfunction isSocketOptIn(name: string): name is SocketOptIn {\n return (SOCKET_OPT_INS as readonly string[]).includes(name);\n}\n\n/**\n * What the client that opened this connection asked for, from its request\n * URL: `undefined` when it named nothing (a client that predates this, which\n * is sent no `hello`), otherwise the names this server honours — possibly\n * none.\n */\nexport function requestedOptIns(url: string | undefined): Set<SocketOptIn> | undefined {\n if (!url) return undefined;\n const query = url.indexOf(\"?\");\n if (query === -1) return undefined;\n const raw = new URLSearchParams(url.slice(query + 1)).get(SOCKET_OPT_IN_PARAM);\n if (raw === null) return undefined;\n return new Set(raw.split(\",\").map((name) => name.trim()).filter(isSocketOptIn));\n}\n\n/**\n * The `hello` frame for a client that asked for `optIns`. With `channel_seq`\n * it also names the socket's own id, the `senderId` its broadcasts carry in a\n * channel's history, so a client filling a hole from history can tell its own\n * messages (which it is never sent live) from the ones it missed.\n */\nexport function helloFrame(optIns: ReadonlySet<SocketOptIn>, heartbeatMs: number, clientId: string): string {\n return JSON.stringify({\n type: \"hello\",\n capabilities: [...optIns],\n ...(optIns.has(\"heartbeat\") ? { heartbeatMs } : {}),\n ...(optIns.has(\"channel_seq\") ? { clientId } : {})\n });\n}\n\nconst HEARTBEAT_FRAME = JSON.stringify({ type: \"heartbeat\" });\n\n/**\n * Send a heartbeat every `intervalMs` to each socket `sockets()` lists. One\n * timer for the whole server, unref'd so it never holds the process open.\n *\n * @returns Stops it.\n */\nexport function startHeartbeats(sockets: () => Iterable<WebSocket>, intervalMs: number): () => void {\n const timer = setInterval(() => {\n for (const ws of sockets()) {\n if (ws.readyState !== WebSocket.OPEN) continue;\n try {\n ws.send(HEARTBEAT_FRAME);\n } catch {\n // A socket closing under the send: its close handler cleans up.\n }\n }\n }, intervalMs);\n unref(timer);\n return () => clearInterval(timer);\n}\n","import type { SubscriptionAuthContext } from \"./services/realtimeService\";\nimport { BranchingUnsupportedError } from \"./services/BranchService\";\nimport { SQL_RESULT_BYTE_BUDGET, SqlResultTooLargeError } from \"./services/sql-script\";\nimport { PostgresBackendDriver, effectiveSqlRole, sqlRoleRanAs } from \"./PostgresBackendDriver\";\nimport { assertReadRequestReadable } from \"./services/read-field-access\";\nimport { isNestedPath, resolveNestedPath } from \"./services/nested-path\";\nimport type { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport type { CollectionConfig, DataDriver, DeleteProps, FetchCollectionProps, FetchOneProps, SaveProps, TableMetadata, BranchInfo, AuthAdapter, DataRateLimitCaller, RealtimeProvider, RealtimeSocketOptions, WebSocketMessage } from \"@rebasepro/types\";\nimport type { SqlScriptResult } from \"@rebasepro/types\";\nimport { deserializeFilter, redactSqlLiterals, UnknownFilterOperatorError } from \"@rebasepro/common\";\nimport { budgetSendBuffers, reapSilentSockets, terminateIfBacklogged } from \"./services/socket-liveness\";\nimport { DEFAULT_HEARTBEAT_INTERVAL_MS, helloFrame, requestedOptIns, startHeartbeats, type SocketOptIn } from \"./socket-opt-ins\";\nimport { ANONYMOUS_USER_ID, hasAdminRole, isSQLAdmin, isSchemaAdmin, resolveClientListLimit, ListLimitError, scopeGrants, scopesForRoles, type AccessModel, type AdminScope } from \"@rebasepro/types\";\nimport type { User } from \"@rebasepro/types\";\n\nimport { WebSocketServer, WebSocket, type RawData } from \"ws\";\nimport type { Server, IncomingMessage } from \"http\";\nimport type { Duplex } from \"stream\";\nimport { inspect } from \"util\";\nimport { extractUserFromToken, AccessTokenPayload, safeCompare, resolveRequireAuth, assertWriteRequestValid, assertFieldOpsValid, assertNoFieldOpsOnCreate, declaredErrorAnswer, RUNTIME_DEFAULT_MAX_BODY_SIZE, ApiError, resolveConflictTarget, assertNestedWriteAllowed, assertNoUserUpsert, assertUserCreationBodyValid, createUserThroughAuthCollection, type NestedWriteKind } from \"@rebasepro/server\";\nimport { logger, getAccessModel, decideImpersonation, isDatabaseUnreachable, DATABASE_UNAVAILABLE_MESSAGE, type ImpersonationCredential } from \"@rebasepro/server\";\n\n/**\n * What this socket calls on the realtime side, and all of it.\n *\n * One `RealtimeService`, or — on a project with several realtime-capable\n * sources — the routed composite over them (`createRoutedRealtimeService`).\n * The parameter used to be typed as the one implementation and the composite\n * was cast to it, so when `AUTHENTICATE` began calling `rescopeClient`, which\n * the composite never had, nothing failed until every multi-source sign-in\n * answered INTERNAL_ERROR. Declared here, by the caller, so what is handed in\n * is checked against what is called.\n */\nexport interface SocketRealtimeService {\n addClient(clientId: string, ws: WebSocket): void;\n rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void>;\n handleClientMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext): Promise<void> | void;\n /**\n * What the client opted into when it opened the socket (see\n * `socket-opt-ins.ts`). Optional: a service that sends nothing that\n * depends on one need not hear about them.\n */\n setClientOptIns?(clientId: string, optIns: ReadonlySet<SocketOptIn>): void;\n}\n\n/**\n * Narrow a bootstrapper's realtime provider to what the socket calls.\n *\n * The provider arrives typed as the engine-agnostic `RealtimeProvider`, which\n * declares none of the client-connection methods. Checked rather than cast, so\n * a provider missing one refuses at boot, naming it, instead of on a client's\n * first frame.\n */\nexport function socketRealtimeMissing(provider: RealtimeProvider): string[] {\n return ([\"addClient\", \"rescopeClient\", \"handleClientMessage\"] as const)\n .filter((method) => !(method in provider) || typeof Reflect.get(provider, method) !== \"function\");\n}\n\nexport function isSocketRealtimeService(provider: RealtimeProvider): provider is RealtimeProvider & SocketRealtimeService {\n return socketRealtimeMissing(provider).length === 0;\n}\n\n/** What a script run says besides its rows, which an `EXECUTE_SQL` answer carries as `result`. */\nfunction scriptDescription({ rows: _rows, ...description }: SqlScriptResult): Omit<SqlScriptResult, \"rows\"> {\n return description;\n}\n\n/** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */\ninterface WsAuthConfig {\n requireAuth?: boolean;\n jwtSecret?: string;\n /**\n * Same static server-to-server secret the HTTP middleware accepts. Without\n * it here, a service key authenticates over HTTP but not over the socket —\n * so any SDK client using one (scripts, cron, server-to-server) connects,\n * fails realtime auth with \"jwt malformed\", and silently gets no events.\n */\n serviceKey?: string;\n}\n\n/**\n * Normalized user identity for WebSocket sessions.\n */\ninterface WsUserIdentity {\n uid: string;\n roles: string[];\n isAdmin: boolean;\n /**\n * What this session may do, when its credential narrows its person — an\n * API key. Absent for a person's own session, who holds what their roles\n * hold.\n */\n scopes?: string[];\n /**\n * Whether this session is a guest — anonymous sign-in rather than an\n * account. Read from the token, because a socket has no request to look\n * anything up on and every refetch it triggers needs the same principal the\n * initial HTTP fetch had. Required, so every way of signing in has to say:\n * the adapter path left it out, and its guests were accounts.\n */\n isAnonymous: boolean;\n /**\n * The token's custom claims, for the same reason `isAnonymous` is here: a\n * socket has no request to look anything up on, and every refetch it\n * triggers has to evaluate the same policies against the same principal.\n * A tenancy policy reads a claim.\n */\n claims?: Record<string, unknown>;\n /**\n * The administrator this session acts for, when it authenticated with\n * `impersonate`: every other field is then the impersonated user's.\n */\n impersonator?: string;\n /**\n * The API key this session authenticated with: its frames count in the\n * key's own rate-limit bucket, at the key's own `rate_limit`, as its HTTP\n * requests do.\n */\n apiKey?: { id: string; rate_limit: number | null };\n}\n\n/**\n * The uid the service key authenticates as, here and through the built-in\n * auth adapter — which is how the socket tells that credential apart from a\n * person's session.\n */\nconst SERVICE_KEY_UID = \"service\";\n\ninterface ClientSession {\n ws: WebSocket;\n user?: WsUserIdentity;\n authenticated: boolean;\n /**\n * The credential `user` was resolved from, kept so the identity can be\n * re-read: every frame asks again, and so does the sweep for a socket\n * that only listens. See `recheckIdentity`.\n */\n credential?: string;\n /**\n * The uid `AUTHENTICATE` asked to act as, kept beside the credential so\n * every re-check decides the impersonation again — a demoted\n * administrator, or a disabled target, ends the session.\n */\n impersonate?: string;\n /** When `user` was last confirmed against the credential, in ms. */\n checkedAt: number;\n /** Ends the session the instant the credential expires. */\n expiryTimer?: ReturnType<typeof setTimeout>;\n /** Sliding window message counter for rate limiting */\n messageCount: number;\n messageWindowStart: number;\n /** The same window, counted separately for channel frames. */\n channelMessageCount: number;\n channelWindowStart: number;\n /**\n * How many more fetching realtime frames this socket may send before\n * they count in its caller's data bucket, and when that was last topped\n * up. See {@link SOCKET_FETCH_ALLOWANCE}.\n */\n fetchAllowance: number;\n fetchAllowanceAt: number;\n}\n\n\n/** Maximum messages per client per window */\nconst WS_RATE_LIMIT = 2000;\n/** Rate limit window in milliseconds (60 seconds) */\nconst WS_RATE_WINDOW_MS = 60_000;\n\n/**\n * Channel frames get their own budget, because they are a different workload.\n *\n * 2000/minute is 33/second, which is generous for queries and subscriptions and\n * an order of magnitude below what the documented channel idiom asks for: the\n * capacity note in `docs/backend/realtime.md` uses 60 fps cursor movement as\n * its worked example, and the presence idiom re-`track()`s on every move, so\n * one client sustaining that sends ~120 frames/second — 7200 a minute. Sharing\n * one counter meant the cursor stream ate the query budget and then froze for\n * the rest of the window.\n *\n * The number is sized to that documented workload and nothing more; it is not\n * a considered product limit (see `docs/channel-authorization.md`).\n */\nconst WS_CHANNEL_RATE_LIMIT = 7200;\n\n/**\n * Realtime frames that read the database as they arrive: a subscription's\n * first frame is a fetch and a count, run as the subscriber, and a history\n * request reads the channel's retained messages. Each is a data request by\n * another name, and counts in the caller's data bucket like a\n * `FETCH_COLLECTION` — past the socket's own allowance.\n */\nconst FETCHING_REALTIME_TYPES = new Set([\"subscribe_collection\", \"subscribe_one\", \"channel_history\"]);\n\n/**\n * How many fetching realtime frames one socket may send without counting them\n * in its caller's data bucket: at least as many as a socket may hold\n * subscriptions, so a reconnect that re-subscribes everything it held is\n * never refused, and never spends the budget the person's next request needs.\n * The default data bucket is 1,000 requests per 15 minutes, so counting a\n * reconnect against it would have refused most of one, or locked the person\n * out of the data API until the window turned.\n */\nconst SOCKET_FETCH_ALLOWANCE = 1000;\n\n/**\n * How long a spent allowance takes to come back in full, in ms — the data\n * API's default window. A socket churning subscriptions gets its allowance\n * once, and then this much a quarter-hour; past that, every subscribe is a\n * request in its person's bucket, which their HTTP requests and other sockets\n * share.\n */\nconst SOCKET_FETCH_ALLOWANCE_REFILL_MS = 15 * 60_000;\n\n/**\n * The largest frame a socket that has not authenticated may send, in bytes.\n *\n * All such a socket can usefully say is `AUTHENTICATE` with a token — a few\n * KiB with custom claims — and everything else is answered `UNAUTHORIZED`.\n * Without this, each of those answers came after the whole frame was buffered,\n * stringified and parsed, up to the body limit: a handful of connections that\n * never signed in could hold the process's memory. A larger frame closes the\n * socket with 1009 before it is read.\n */\nconst MAX_UNAUTHENTICATED_FRAME_BYTES = 64 * 1024;\n\n/**\n * How often a socket that only listens has its identity re-read, in ms.\n *\n * A socket that sends frames is re-checked before each one, as an HTTP request\n * is; a socket that only receives pushes sends none, so without this a\n * subscription opened by an identity that has since signed out everywhere, been\n * demoted or deleted went on receiving rows as that identity for as long as the\n * connection lasted.\n */\nconst DEFAULT_IDENTITY_RECHECK_MS = 30_000;\n\n/**\n * How many auth-store lookups the sweep runs at once. It shares the pool with\n * every REST request — 20 connections by default — so it takes a few of them,\n * however many sockets are due: 5,000 sockets at ~0.3 ms a lookup is under\n * half a second of sweep, rather than a burst that held the whole pool.\n */\nconst IDENTITY_SWEEP_CONCURRENCY = 4;\n\n/**\n * Channel frames are re-checked at most this often, in ms. They are the\n * high-rate traffic (see `WS_CHANNEL_RATE_LIMIT`): a database read for every\n * cursor position would make realtime presence the busiest query a backend\n * runs, for a window no wider than this.\n */\nconst CHANNEL_RECHECK_MIN_MS = 1_000;\n\n/**\n * The close code for a socket whose identity ended: signed out, revoked,\n * demoted out of existence, deleted or expired. In the 4000–4999 range the\n * protocol leaves to applications. The SDK reconnects and authenticates with\n * whatever session it holds now — a refreshed token, or none.\n */\nexport const SESSION_ENDED_CLOSE_CODE = 4001;\n\n/**\n * The close code for a socket refused because its user already holds\n * `REALTIME_MAX_SOCKETS_PER_USER` sockets on this server. The SDK reconnects\n * with backoff, so a tab refused this way gets in once another one closes.\n */\nexport const TOO_MANY_SOCKETS_CLOSE_CODE = 4002;\n\n/**\n * When a JWT expires, in ms, or undefined for a credential that is not one\n * (an API key, the service key, an adapter's opaque token).\n *\n * Read without verifying, and only ever for a token that was verified a moment\n * ago by the same request: the signature is not in question, only the instant.\n */\nfunction jwtExpiryMs(token: string): number | undefined {\n const parts = token.split(\".\");\n if (parts.length !== 3) return undefined;\n try {\n const claims: unknown = JSON.parse(Buffer.from(parts[1], \"base64url\").toString(\"utf8\"));\n if (typeof claims !== \"object\" || claims === null || !(\"exp\" in claims)) return undefined;\n return typeof claims.exp === \"number\" ? claims.exp * 1000 : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A socket's identity, asked again: still honoured, ended, or not answerable\n * right now — with the refusal that says why.\n */\ntype IdentityCheck = \"live\" | \"ended\" | { unavailable: { code: string; message: string } };\n\n/**\n * The refusal for an auth store that could not answer, as the HTTP doors give\n * it: the 503 the store's own error declares (`DATABASE_UNAVAILABLE`,\n * `ROLE_LOOKUP_FAILED`), else the database unreachable when that is what the\n * error says, else the lookup failing.\n */\nfunction storeUnavailableAnswer(error: unknown): { code: string; message: string } {\n const declared = declaredErrorAnswer(error);\n if (declared && declared.status === 503) return { code: declared.code, message: declared.message };\n if (isDatabaseUnreachable(error)) return { code: \"DATABASE_UNAVAILABLE\", message: DATABASE_UNAVAILABLE_MESSAGE };\n return { code: \"ROLE_LOOKUP_FAILED\", message: \"Could not verify your permissions. Please try again.\" };\n}\n\n/** Everything about an identity that decides what its frames may do. */\nfunction identityFingerprint(user: WsUserIdentity): string {\n return JSON.stringify([user.uid, [...user.roles].sort(), user.isAdmin, user.isAnonymous, user.scopes ?? null, user.claims ?? null, user.apiKey ?? null]);\n}\n\n/** Frames counted against the channel budget rather than the general one. */\nconst CHANNEL_MESSAGE_TYPES = new Set([\n \"join_channel\",\n \"leave_channel\",\n \"broadcast\",\n \"presence_track\",\n \"presence_untrack\",\n \"presence_state\",\n \"channel_history\"\n]);\n\n/**\n * WebSocket message types on the admin plane, and the scope each needs.\n *\n * Exported so the test can READ it. A privileged verb added without a scope\n * check is what this list exists to make impossible: `PUBLIC_TYPES` below is\n * its counterpart, and a test asserts that every `case` this file handles\n * appears in exactly one of the two — so a verb added to neither fails rather\n * than defaulting to reachable.\n *\n * All of these run on the owner connection, outside RLS. `database:read`\n * covers the catalogue — including `FETCH_APPLICATION_ROLES`, which runs\n * `SELECT DISTINCT unnest(roles)` over the users table — and\n * `database:write` covers running SQL and creating or deleting branches.\n */\nexport const ADMIN_PLANE_TYPES: ReadonlyMap<string, AdminScope> = new Map<string, AdminScope>([\n [\"EXECUTE_SQL\", \"database:write\"],\n // Stops a console run of the same socket, which `EXECUTE_SQL` started.\n [\"CANCEL_SQL\", \"database:write\"],\n [\"CREATE_BRANCH\", \"database:write\"],\n [\"DELETE_BRANCH\", \"database:write\"],\n [\"FETCH_DATABASES\", \"database:read\"],\n [\"FETCH_ROLES\", \"database:read\"],\n [\"FETCH_APPLICATION_ROLES\", \"database:read\"],\n [\"FETCH_UNMAPPED_TABLES\", \"database:read\"],\n [\"FETCH_TABLE_METADATA\", \"database:read\"],\n [\"FETCH_CURRENT_DATABASE\", \"database:read\"],\n [\"LIST_BRANCHES\", \"database:read\"]\n]);\n\n/**\n * The data scope each data verb needs, for a session whose credential narrows\n * it. A person's own session holds the whole data plane; RLS decides its rows.\n */\nconst DATA_VERB_SCOPES: ReadonlyMap<string, string> = new Map([\n [\"FETCH_COLLECTION\", \"data:read\"],\n [\"FETCH_ONE\", \"data:read\"],\n [\"COUNT\", \"data:read\"],\n [\"CHECK_UNIQUE_FIELD\", \"data:read\"],\n [\"subscribe_collection\", \"data:read\"],\n [\"subscribe_one\", \"data:read\"],\n [\"SAVE\", \"data:write\"],\n [\"DELETE\", \"data:delete\"]\n]);\n\n/**\n * Message types deliberately reachable by a non-admin session.\n *\n * Data operations, gated per row by RLS and per request by the same write\n * checks the REST layer applies — not by this list. It exists so that \"which\n * bucket is this verb in\" is a question with an answer for every verb, and\n * adding one without answering it is a test failure.\n */\nexport const PUBLIC_TYPES = new Set([\n \"FETCH_COLLECTION\",\n \"FETCH_ONE\",\n \"COUNT\",\n \"SAVE\",\n \"DELETE\",\n \"CHECK_UNIQUE_FIELD\"\n]);\n\n/**\n * Refuse a path whose collection — its first segment, for a nested path — this\n * server does not have, with the answer REST gives (`NOT_FOUND`).\n */\nfunction assertKnownCollection(\n registry: Pick<PostgresCollectionRegistry, \"getCollectionByPath\"> | undefined,\n path: unknown\n): void {\n if (!registry || typeof path !== \"string\") return;\n const root = path.split(\"/\").find(segment => segment !== \"\");\n if (root !== undefined && !registry.getCollectionByPath(root)) {\n throw ApiError.notFound(`Unknown collection '${root}'.`);\n }\n}\n\n/**\n * Recursively extract the deepest error message from an error's cause chain (e.g., Drizzle wrapping a PG error).\n */\nfunction extractErrorMessage(error: unknown): string {\n if (!error) return \"Unknown error\";\n if (error instanceof Error) {\n if (\"cause\" in error && error.cause) {\n return extractErrorMessage(error.cause);\n }\n return error.message;\n }\n if (typeof error === \"object\" && \"message\" in error && typeof (error as { message: unknown }).message === \"string\") {\n return (error as { message: string }).message;\n }\n return String(error);\n}\n\n/**\n * Who a socket reads and writes as, for its request frames and its\n * subscriptions alike.\n *\n * One answer because there were two, and they disagreed about guests. The\n * subscriptions carried the session's `isAnonymous`; the request frames wrote\n * `false` for every signed-in session, so a guest read and wrote as an account\n * through `FETCH_*`, `SAVE` and `DELETE` while its subscriptions did not. And a\n * socket with no session was a guest on the request frames (`true`) but not on\n * its subscriptions or over REST: an unauthenticated caller is not a guest\n * session.\n */\nfunction sessionAuthContext(session: ClientSession | undefined): SubscriptionAuthContext {\n if (!session?.user) {\n return { uid: ANONYMOUS_USER_ID, roles: [\"anon\"], isAnonymous: false };\n }\n return {\n uid: session.user.uid,\n roles: session.user.roles ?? [],\n isAnonymous: session.user.isAnonymous === true,\n // A tenancy policy reads a claim, so a frame without them answers\n // from the tenant of nobody.\n ...(session.user.claims ? { claims: session.user.claims } : {})\n };\n}\n\n/**\n * Everything a session may do: its credential's scopes, or what its roles hold\n * in `model` — the backend's access model.\n */\nfunction sessionScopes(session: ClientSession | undefined, model: AccessModel): string[] {\n if (!session?.user) return [];\n return session.user.scopes ?? scopesForRoles(session.user.roles, model);\n}\n\n/**\n * The collection a data frame addresses, for a narrowed session's scope check.\n * A nested path (`authors/1/posts`) answers undefined: its target is a\n * relation's, not a slug on the wire, so only an unqualified scope covers it.\n */\nfunction frameCollection(type: string, payload: unknown): string | undefined {\n if (typeof payload !== \"object\" || payload === null) return undefined;\n let path: unknown = \"path\" in payload ? payload.path : undefined;\n if (type === \"DELETE\" && \"row\" in payload && typeof payload.row === \"object\" && payload.row !== null && \"path\" in payload.row) {\n path = payload.row.path;\n }\n if (typeof path !== \"string\" || path === \"\" || isNestedPath(path)) return undefined;\n return path;\n}\n\n/**\n * A URL path with its trailing `/` dropped, so `/app` and `/app/` are one\n * path — and `/` stays `/`.\n */\nfunction normalizeSocketPath(path: string): string {\n const withLeadingSlash = path.startsWith(\"/\") ? path : `/${path}`;\n const trimmed = withLeadingSlash.replace(/\\/+$/, \"\");\n return trimmed === \"\" ? \"/\" : trimmed;\n}\n\n/** The path an upgrade request asks for, without its query. */\nfunction upgradePathOf(request: IncomingMessage): string {\n const url = request.url ?? \"/\";\n const queryAt = url.indexOf(\"?\");\n return normalizeSocketPath(queryAt === -1 ? url : url.slice(0, queryAt));\n}\n\n/**\n * Answer an upgrade nobody will take with a plain HTTP 404 and close it, so\n * the client sees a refusal instead of a socket that never opens.\n */\nfunction refuseUpgrade(socket: Duplex): void {\n if (!socket.writable) {\n socket.destroy();\n return;\n }\n // Destroyed once the answer is flushed — destroying straight away could\n // drop it, and the client would read a reset instead of the 404.\n socket.once(\"finish\", () => socket.destroy());\n socket.end(\"HTTP/1.1 404 Not Found\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n\\r\\n\");\n}\n\n/** A frame's size in bytes, however `ws` delivered it. */\nfunction frameBytes(data: RawData): number {\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.length, 0);\n return data.byteLength;\n}\n\n/**\n * Where a connection comes from, as the data API's rate limiter reads an\n * address: the upgrade request's headers (believed only as far as proxies are\n * declared) and the socket's own address.\n */\nfunction connectionOrigin(request: IncomingMessage | undefined): Omit<DataRateLimitCaller, \"uid\"> {\n return {\n header: (name) => {\n const value = request?.headers[name];\n return Array.isArray(value) ? value.join(\", \") : value;\n },\n socketAddress: request?.socket?.remoteAddress\n };\n}\n\n/**\n * A request frame with its `filter` decoded by the codec every other door uses.\n *\n * REST, MCP and the in-process SDK read `where` through `deserializeFilter`,\n * so `{ title: [\"foo\", \"bar\"] }` is an implicit `in` and `[\"!!\", \"x\"]` is\n * refused by name. The socket handed the frame's filter to the driver raw,\n * which read the first element as an operator: one filter, two meanings,\n * depending on the transport. A typed `[op, value]` tuple decodes to itself.\n */\nfunction withDecodedFilter(request: FetchCollectionProps): FetchCollectionProps {\n if (!request?.filter) return request;\n try {\n return { ...request, filter: deserializeFilter(request.filter) };\n } catch (e) {\n if (e instanceof UnknownFilterOperatorError) throw ApiError.badRequest(e.message, e.code, e.details);\n throw e;\n }\n}\n\n/**\n * `options` carry the data API's limits, for the same rows reached through this door:\n * frames larger than its body limit close the socket with 1009 before they are\n * read, and every data frame counts in the caller's data-API bucket — the one\n * their HTTP requests count in — rather than only in a budget per connection,\n * which a caller multiplied by opening more.\n */\nexport function createPostgresWebSocket(\n server: Server,\n realtimeService: SocketRealtimeService,\n driver: PostgresBackendDriver,\n authConfig?: WsAuthConfig,\n authAdapter?: AuthAdapter,\n options?: RealtimeSocketOptions\n) {\n // Session map scoped to this factory invocation — prevents stale sessions\n // leaking across hot reloads or multiple factory calls.\n const clientSessions = new Map<string, ClientSession>();\n\n /** The sockets that opted into heartbeats — see `socket-opt-ins.ts`. */\n const heartbeatSockets = new Set<WebSocket>();\n const heartbeatIntervalMs = options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n /** How many sockets one identity may hold here; unset is no ceiling. See `REALTIME_MAX_SOCKETS_PER_USER`. */\n const maxSocketsPerUser = options?.maxSocketsPerUser;\n\n const isProduction = process.env.NODE_ENV === \"production\";\n /**\n * Debug tracing for this socket.\n *\n * Through `logger.debug`, not `console.debug`. A bare console call sits\n * outside the one level system, so `LOG_LEVEL=warn` — a line the scaffold's\n * own `.env.example` ships — silenced every structured line in the process\n * and left these ones printing; the deletion of the old `console.debug`\n * override is what exposed that. It also skipped the redactor, and the\n * extras these calls carry are rows.\n *\n * The production guard stays on top of the level gate rather than being\n * replaced by it: these lines trace requests and their payloads, and\n * `LOG_LEVEL=debug` on a deployment is not consent to put a user's row on\n * stdout.\n */\n const wsDebug = (message: string, ...extras: unknown[]) => {\n if (isProduction) return;\n if (extras.length === 0) logger.debug(message);\n else logger.debug(message, { details: extras.length === 1 ? extras[0] : extras });\n };\n // `ws` defaults to 100 MiB. The data API refuses a body over its limit\n // before any handler runs; this is the same limit, enforced by `ws` while\n // it reads the frame. `0` is \"none\" to both.\n const maxPayload = options?.maxPayload ?? RUNTIME_DEFAULT_MAX_BODY_SIZE;\n const wss = new WebSocketServer({ noServer: true, maxPayload: maxPayload > 0 ? maxPayload : 0 });\n\n wss.on(\"error\", (err: Error) => {\n logger.error(\"❌ [WebSocket Server] Error\", { error: err });\n });\n\n // The host's `http.Server` is shared, so this socket takes the upgrades\n // on its own path and no others. Given the server, `ws` would claim every\n // upgrade on it — and refuse a path it was not given with a 400 — so a\n // host's own WebSocket on `/chat` had both servers answering one socket.\n //\n // An upgrade to another path is left alone for whichever listener it is\n // for. When this listener is the only one, nothing else will answer it,\n // so it is refused rather than left open until the client gives up. A\n // dispatcher that lifts this listener off and calls it for the upgrades\n // it does not claim itself (the cloud control plane does) is then the\n // only listener, and gets the same refusal.\n const socketPath = normalizeSocketPath(options?.path ?? \"/\");\n const onUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n if (upgradePathOf(request) !== socketPath) {\n if (server.listenerCount(\"upgrade\") <= 1) refuseUpgrade(socket);\n return;\n }\n wss.handleUpgrade(request, socket, head, (client) => wss.emit(\"connection\", client, request));\n };\n if (typeof server.on === \"function\") {\n server.on(\"upgrade\", onUpgrade);\n wss.on(\"close\", () => server.off(\"upgrade\", onUpgrade));\n }\n\n // The same predicate the HTTP data routes use, from the same function —\n // this socket is the other enforcement point for one product decision, and\n // while it computed the answer itself it computed a different one. See\n // `resolveRequireAuth` for what its local copy got wrong and why a `false`\n // here grants access rather than skipping a check.\n const requireAuth = !!authAdapter || resolveRequireAuth(authConfig as never);\n\n // The backend's own keys and roles, as the server passes them; a socket\n // built by hand without them answers for the process default.\n const verifySessionToken = options?.verifyAccessToken ?? extractUserFromToken;\n const accessModel = (): AccessModel => options?.accessModel ?? getAccessModel();\n\n if (requireAuth && !authAdapter && !authConfig?.jwtSecret && !authConfig?.serviceKey) {\n logger.warn(\n \"🔐 [WebSocket Server] Authentication is required but no adapter, jwtSecret or \" +\n \"serviceKey is configured — no client can complete AUTH, so every realtime \" +\n \"message will be refused with UNAUTHORIZED.\"\n );\n }\n\n /**\n * Who a credential is, now: the same verification `AUTHENTICATE` runs, so\n * re-asking it later answers what signing in would answer. A refusal is a\n * value; a store that cannot answer throws.\n */\n async function resolveCaller(token: string): Promise<{ user: WsUserIdentity; credential: ImpersonationCredential } | { refused: string; apiKey?: true }> {\n if (token.startsWith(\"rk_\")) {\n // An API key: the same verification the HTTP middlewares run, so a\n // key means one thing on both — revoked or expired included.\n const resolved = options?.resolveApiKey ? await options.resolveApiKey(token) : undefined;\n if (!resolved || !(\"uid\" in resolved)) {\n return { refused: resolved?.message ?? \"API keys are not enabled on this server\", apiKey: true };\n }\n return {\n credential: \"api-key\",\n user: {\n uid: resolved.uid,\n roles: resolved.roles,\n isAdmin: hasAdminRole(resolved.roles),\n isAnonymous: false,\n scopes: resolved.scopes,\n ...(resolved.apiKey ? { apiKey: { id: resolved.apiKey.id, rate_limit: resolved.apiKey.rate_limit } } : {})\n }\n };\n }\n\n if (authAdapter) {\n // Custom auth, Clerk, the built-in auth — anything that is an\n // adapter. One that throws could not answer (its store is\n // unreachable); that is not a refusal, and the caller decides\n // what a question nobody could answer means.\n const adapterUser = authAdapter.verifyToken\n ? await authAdapter.verifyToken(token)\n : await authAdapter.verifyRequest(new Request(\"http://localhost/_ws_auth\", {\n headers: { Authorization: `Bearer ${token}` }\n }));\n if (!adapterUser) return { refused: \"Invalid or expired token\" };\n return {\n // The built-in adapter verifies the service key as its own\n // identity rather than as a person.\n credential: adapterUser.uid === SERVICE_KEY_UID ? \"service-key\" : \"session\",\n user: {\n uid: adapterUser.uid,\n roles: adapterUser.roles,\n isAdmin: adapterUser.isAdmin,\n // Read, as the JWT path reads it: dropped here, a guest\n // signed in through an adapter subscribed as an account.\n // Absent from an adapter with no such concept, and absent\n // reads as \"not a guest\".\n isAnonymous: adapterUser.isAnonymous === true,\n // Kept, as the JWT branch keeps them: the built-in auth is\n // an adapter, so dropping them here left every\n // claim-tenanted collection with no tenant on the socket.\n ...(adapterUser.claims ? { claims: adapterUser.claims } : {})\n }\n };\n }\n\n if (authConfig?.serviceKey && safeCompare(token, authConfig.serviceKey)) {\n // Service key: a static secret, not a JWT. Checked before\n // verification, mirroring the HTTP middleware — verifying it as a\n // JWT can only ever fail.\n return { credential: \"service-key\", user: { uid: SERVICE_KEY_UID, roles: [\"admin\"], isAdmin: true, isAnonymous: false } };\n }\n\n // Standard JWT path: the signature and the expiry, nothing more —\n // there is no account store on this path to ask. Verified with the\n // backend's own keys when the server handed them over.\n const jwtPayload = await verifySessionToken(token);\n if (!jwtPayload) return { refused: \"Invalid or expired token\" };\n return {\n credential: \"session\",\n user: {\n uid: jwtPayload.uid,\n roles: jwtPayload.roles ?? [],\n isAdmin: hasAdminRole(jwtPayload.roles ?? []),\n isAnonymous: jwtPayload.isAnonymous === true,\n claims: jwtPayload.claims\n }\n };\n }\n\n /**\n * Who this socket runs as: its caller, or — when it asked to `impersonate`\n * — the user it named, by the same decision the data API takes for\n * `x-rebase-impersonate`. A refusal carries the code to answer with.\n *\n * `audit` is passed by `AUTHENTICATE` only: the re-check before every frame\n * decides again without writing a security event each time.\n */\n async function resolveIdentity(\n token: string,\n impersonate?: string,\n audit?: Record<string, unknown>\n ): Promise<{ user: WsUserIdentity } | { refused: string; apiKey?: true; code?: string }> {\n const caller = await resolveCaller(token);\n if (!(\"user\" in caller) || impersonate === undefined) return caller;\n\n const decision = await decideImpersonation({\n requestedUid: impersonate,\n credential: caller.credential,\n caller: caller.user,\n resolveUser: authAdapter?.resolveUser ? authAdapter.resolveUser.bind(authAdapter) : undefined,\n audit\n });\n if (\"refused\" in decision) return { refused: decision.refused.message, code: decision.refused.code };\n const target = decision.granted;\n return {\n user: {\n uid: target.uid,\n roles: target.roles,\n isAdmin: hasAdminRole(target.roles),\n isAnonymous: target.isAnonymous === true,\n ...(target.claims ? { claims: target.claims } : {}),\n impersonator: decision.impersonator\n }\n };\n }\n\n /** Stop honouring the session at the instant its token expires. */\n function scheduleExpiry(clientId: string, token: string): void {\n const session = clientSessions.get(clientId);\n if (!session) return;\n if (session.expiryTimer) clearTimeout(session.expiryTimer);\n session.expiryTimer = undefined;\n const expiresAt = jwtExpiryMs(token);\n if (expiresAt === undefined) return;\n // `setTimeout` overflows past 2^31-1 ms (~24.8 days) and fires at once;\n // a token living longer than that is re-checked by the sweep instead.\n const delay = Math.max(0, expiresAt - Date.now());\n if (delay > 2_147_483_647) return;\n session.expiryTimer = setTimeout(() => {\n const current = clientSessions.get(clientId);\n if (current?.credential === token) {\n endSession(clientId, \"TOKEN_EXPIRED\", \"The token this socket authenticated with has expired.\");\n }\n }, delay);\n session.expiryTimer.unref?.();\n }\n\n /**\n * The identity this socket authenticated as is over: say so, forget it,\n * and close. Closing rather than carrying on anonymously, because every\n * subscription the socket holds was opened as that identity — the SDK\n * reconnects, authenticates with whatever session it has now, and\n * re-subscribes as that.\n */\n function endSession(clientId: string, code: string, message: string): void {\n const session = clientSessions.get(clientId);\n if (!session) return;\n if (session.expiryTimer) clearTimeout(session.expiryTimer);\n session.expiryTimer = undefined;\n session.user = undefined;\n session.credential = undefined;\n session.authenticated = !requireAuth;\n wsDebug(`[WS] ${clientId} session ended: ${code}`);\n try {\n session.ws.send(JSON.stringify({ type: \"AUTH_ERROR\", payload: { error: { code, message } } }));\n } catch {\n // Already closing.\n }\n session.ws.close(SESSION_ENDED_CLOSE_CODE, message.slice(0, 120));\n }\n\n /**\n * Is the identity this socket authenticated as still the one its\n * credential names?\n *\n * Asked of the database, through the same verification as\n * `AUTHENTICATE`, before each frame and by the sweep — never by an event\n * one instance publishes to the others. A sign-out, a demotion or a\n * deletion is written to the database by whichever instance served it;\n * re-reading there is correct on every instance with nothing in between,\n * where an event would reach only the instances a bus connects (and the\n * bus is opt-in).\n *\n * - `\"live\"`: still honoured; roles or claims that changed are applied,\n * and the socket's subscriptions are re-scoped to them.\n * - `\"ended\"`: refused — revoked, signed out, deleted, expired. The\n * socket is closed.\n * - `{ unavailable }`: the store could not answer, and this is the\n * refusal that says why. The frame is refused, the session kept, and\n * the next frame or sweep asks again.\n */\n async function recheckIdentity(\n clientId: string,\n resolve: (token: string, impersonate?: string) => ReturnType<typeof resolveIdentity> = resolveIdentity\n ): Promise<IdentityCheck> {\n const session = clientSessions.get(clientId);\n const credential = session?.credential;\n if (!session?.user || !credential) return \"live\";\n let outcome: Awaited<ReturnType<typeof resolveIdentity>>;\n try {\n outcome = await resolve(credential, session.impersonate);\n } catch (error) {\n return { unavailable: storeUnavailableAnswer(error) };\n }\n // Re-authenticated while this was in flight: that answer is newer.\n if (session.credential !== credential || !session.user) return session.user ? \"live\" : \"ended\";\n session.checkedAt = Date.now();\n if (!(\"user\" in outcome)) {\n // An impersonation that no longer holds — the administrator was\n // demoted, the user disabled — says which; anything else is the\n // credential itself.\n if (outcome.code) endSession(clientId, outcome.code, outcome.refused);\n else endSession(clientId, \"SESSION_ENDED\", \"This session has ended: it was signed out, revoked, or its account changed. Authenticate again.\");\n return \"ended\";\n }\n if (identityFingerprint(outcome.user) !== identityFingerprint(session.user)) {\n session.user = outcome.user;\n await realtimeService.rescopeClient(clientId, sessionAuthContext(session));\n }\n return \"live\";\n }\n\n /** The per-frame check in flight on each socket, and what it asks. */\n const frameChecks = new Map<string, { credential: string; impersonate?: string; outcome: Promise<IdentityCheck> }>();\n\n /**\n * {@link recheckIdentity} for a frame, shared by the frames of one socket\n * that arrive while it is in flight.\n *\n * A client that reconnects after a deploy re-sends all its subscriptions\n * at once, and each frame asked the auth store the same question at the\n * same moment: a thousand clients with ten subscriptions were ten thousand\n * lookups where a thousand answer. A frame that arrives while this\n * socket's check of the same credential and impersonation target is under\n * way waits for that answer instead. The answer is the flight's only:\n * dropped as it settles, so the next frame asks afresh. What this trades\n * is bounded by one lookup: a demotion that lands while the shared lookup\n * runs is seen by the next frame rather than by these.\n */\n function recheckIdentityForFrame(clientId: string): Promise<IdentityCheck> {\n const session = clientSessions.get(clientId);\n const credential = session?.credential;\n if (!session || !credential) return recheckIdentity(clientId);\n const inFlight = frameChecks.get(clientId);\n if (inFlight && inFlight.credential === credential && inFlight.impersonate === session.impersonate) {\n return inFlight.outcome;\n }\n const outcome = recheckIdentity(clientId).finally(() => {\n if (frameChecks.get(clientId)?.outcome === outcome) frameChecks.delete(clientId);\n });\n frameChecks.set(clientId, { credential, impersonate: session.impersonate, outcome });\n return outcome;\n }\n\n /**\n * A socket's whole allowance of fetching realtime frames: the default, or\n * the realtime service's subscription ceiling where a deployment raised\n * it, so a full socket still re-subscribes without being charged.\n */\n function fetchAllowanceCapacity(): number {\n const ceiling: unknown = Reflect.get(realtimeService, \"maxSubscriptionsPerSocket\");\n return typeof ceiling === \"number\" && Number.isFinite(ceiling)\n ? Math.max(SOCKET_FETCH_ALLOWANCE, ceiling)\n : SOCKET_FETCH_ALLOWANCE;\n }\n\n /**\n * Take one fetching realtime frame from the socket's own allowance,\n * topped up for the time since it was last asked. False when none is\n * left: the frame then counts in the caller's data bucket.\n */\n function spendFetchAllowance(clientId: string): boolean {\n const session = clientSessions.get(clientId);\n if (!session) return false;\n const now = Date.now();\n const capacity = fetchAllowanceCapacity();\n const refilled = Math.max(0, now - session.fetchAllowanceAt) * capacity / SOCKET_FETCH_ALLOWANCE_REFILL_MS;\n session.fetchAllowance = Math.min(capacity, session.fetchAllowance + refilled);\n session.fetchAllowanceAt = now;\n if (session.fetchAllowance < 1) return false;\n session.fetchAllowance -= 1;\n return true;\n }\n\n // The sweep: a socket that only listens sends nothing to be re-checked\n // before, so it is re-checked on a timer instead.\n const identityRecheckMs = Math.max(250, options?.identityRecheckIntervalMs ?? DEFAULT_IDENTITY_RECHECK_MS);\n\n /**\n * Re-check every socket that is due, asking the auth store once per\n * question.\n *\n * Each socket used to be asked about on its own, all in the same tick: S\n * listening sockets were S concurrent lookups on the pool REST shares,\n * and a person with five tabs was five identical lookups of one token.\n * Sockets are grouped by what they would ask — their credential and the\n * user they impersonate — and each group asks once, at most\n * {@link IDENTITY_SWEEP_CONCURRENCY} groups at a time.\n *\n * The answer is the group's alone, made when the group's turn comes and\n * dropped when it is done; nothing is kept from one sweep to the next, so\n * a credential revoked after a sweep is asked about afresh, and refused,\n * by the next one. A socket that authenticated again while its group\n * waited is asked about the credential it holds now — see `answerFor`.\n */\n async function sweepIdentities(): Promise<void> {\n const now = Date.now();\n const groups = new Map<string, { credential: string; impersonate?: string; clientIds: string[] }>();\n for (const [id, session] of clientSessions) {\n if (!session.user || !session.credential || now - session.checkedAt < identityRecheckMs / 2) continue;\n const key = JSON.stringify([session.credential, session.impersonate ?? null]);\n const group = groups.get(key);\n if (group) group.clientIds.push(id);\n else groups.set(key, { credential: session.credential, impersonate: session.impersonate, clientIds: [id] });\n }\n const queue = [...groups.values()];\n const work = async (): Promise<void> => {\n for (let group = queue.shift(); group; group = queue.shift()) {\n let answer: ReturnType<typeof resolveIdentity> | undefined;\n // The group's question is answered once. Any other — a socket\n // that has since authenticated with another token, or asked to\n // act as someone else — is asked on its own.\n const answerFor = (token: string, impersonate?: string): ReturnType<typeof resolveIdentity> => {\n if (token !== group!.credential || impersonate !== group!.impersonate) return resolveIdentity(token, impersonate);\n answer ??= resolveIdentity(token, impersonate);\n return answer;\n };\n for (const id of group.clientIds) {\n await recheckIdentity(id, answerFor).catch(() => undefined);\n }\n }\n };\n await Promise.all(Array.from({ length: Math.min(IDENTITY_SWEEP_CONCURRENCY, queue.length) }, work));\n }\n\n // A sweep still working through a large number of sockets when the next\n // is due lets that one pass, rather than asking about them twice at once.\n let sweeping = false;\n const identitySweep = setInterval(() => {\n if (sweeping) return;\n sweeping = true;\n void sweepIdentities().catch(() => undefined).finally(() => {\n sweeping = false;\n });\n }, identityRecheckMs);\n // Unref'd, so it never holds a process open; stopped with whichever goes\n // first, the socket server or the HTTP server it is attached to.\n identitySweep.unref?.();\n const stopSweep = () => clearInterval(identitySweep);\n wss.on(\"close\", stopSweep);\n if (typeof server.on === \"function\") server.on(\"close\", stopSweep);\n\n wss.on(\"connection\", (ws, request?: IncomingMessage) => {\n const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n const origin = connectionOrigin(request);\n wsDebug(`WebSocket client connected: ${clientId}`);\n\n // What the client opted into on its URL, answered with a `hello`\n // before anything else. A client that named nothing — one that\n // predates the opt-ins — is sent neither the `hello` nor anything it\n // would have had to ask for.\n const optIns = requestedOptIns(request?.url);\n if (optIns) {\n ws.send(helloFrame(optIns, heartbeatIntervalMs, clientId));\n realtimeService.setClientOptIns?.(clientId, optIns);\n if (optIns.has(\"heartbeat\")) {\n heartbeatSockets.add(ws);\n ws.once(\"close\", () => heartbeatSockets.delete(ws));\n }\n }\n\n // Initialize client session\n clientSessions.set(clientId, { ws,\nauthenticated: !requireAuth,\ncheckedAt: Date.now(),\nmessageCount: 0,\nmessageWindowStart: Date.now(),\nchannelMessageCount: 0,\nchannelWindowStart: Date.now(),\nfetchAllowance: fetchAllowanceCapacity(),\nfetchAllowanceAt: Date.now() });\n realtimeService.addClient(clientId, ws);\n\n // This socket's console runs in flight, by the `requestId` that\n // started each: what `CANCEL_SQL` can stop. One socket's map, so a\n // frame can only ever stop a run its own socket started.\n const sqlRuns = new Map<string, AbortController>();\n\n ws.on(\"close\", () => {\n wsDebug(`WebSocket client disconnected: ${clientId}`);\n const session = clientSessions.get(clientId);\n if (session?.expiryTimer) clearTimeout(session.expiryTimer);\n clientSessions.delete(clientId);\n });\n\n // A frame over `maxPayload` is refused by `ws` as an `error` on this\n // socket, then closed with 1009. An `error` with no listener is thrown\n // by the emitter — out of the process — so the socket that set the\n // limit listens for it rather than trusting a realtime provider to.\n ws.on(\"error\", (error: Error) => {\n wsDebug(`WebSocket client ${clientId} error: ${error.message}`);\n });\n\n // Route all messages through RealtimeService for unified handling\n ws.on(\"message\", async (message: RawData) => {\n // Every frame is answered, so one from a client that has not read\n // its earlier answers would only add to a backlog it is not draining.\n if (terminateIfBacklogged(ws, clientId)) return;\n let requestId: string | undefined;\n try {\n if (!clientSessions.get(clientId)?.authenticated && frameBytes(message) > MAX_UNAUTHENTICATED_FRAME_BYTES) {\n ws.close(1009, \"Authenticate before sending anything larger than a token.\");\n return;\n }\n const {\n type,\n payload,\n requestId: reqId\n } = JSON.parse(message.toString());\n requestId = reqId; // Capture requestId for use in catch block\n\n wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : \"\");\n\n // Handle authentication first\n // Helper: send a canonical error frame\n // `channel` is carried for a channel frame so the client can\n // deliver the error to the channel it is about. Channel frames\n // are fire-and-forget — no pending request to reject, no\n // subscription id — so without it a `RATE_LIMITED` on a\n // broadcast had nowhere to go but a console warning.\n const sendError = (errType: \"ERROR\" | \"AUTH_ERROR\", code: string, msg: string) => {\n const channel = CHANNEL_MESSAGE_TYPES.has(type)\n ? (payload as { channel?: unknown } | undefined)?.channel\n : undefined;\n ws.send(JSON.stringify({\n type: errType,\n requestId,\n ...(typeof channel === \"string\" && { channel }),\n payload: {\n error: { message: msg, code },\n ...(typeof channel === \"string\" && { channel })\n }\n }));\n };\n\n // Rate limiting: reject if client exceeds message limit.\n // Channel frames are counted against their own budget — see\n // WS_CHANNEL_RATE_LIMIT for why one shared counter starved them.\n //\n // Counted before the session is asked whether it has\n // authenticated, so a socket that never does — sending\n // `AUTHENTICATE` after `AUTHENTICATE`, each a token\n // verification — spends this budget like any other.\n {\n const session = clientSessions.get(clientId);\n if (session) {\n const now = Date.now();\n const isChannelFrame = CHANNEL_MESSAGE_TYPES.has(type);\n if (isChannelFrame) {\n if (now - session.channelWindowStart > WS_RATE_WINDOW_MS) {\n session.channelMessageCount = 0;\n session.channelWindowStart = now;\n }\n session.channelMessageCount++;\n if (session.channelMessageCount > WS_CHANNEL_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many channel messages. Please slow down.\");\n return;\n }\n } else {\n if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {\n session.messageCount = 0;\n session.messageWindowStart = now;\n }\n session.messageCount++;\n if (session.messageCount > WS_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests. Please slow down.\");\n return;\n }\n }\n }\n }\n\n if (type === \"AUTHENTICATE\") {\n const { token, impersonate } = payload || {};\n if (!token) {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"Token is required\");\n return;\n }\n if (impersonate !== undefined && typeof impersonate !== \"string\") {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"impersonate must be the uid of the user to act as\");\n return;\n }\n\n // A lookup for an impersonation that throws is refused\n // with the answer it carries when it has one, and is never\n // authenticated as the caller. A store that cannot answer\n // at all — the database unreachable — is said as that, in\n // an `ERROR`: an `AUTH_ERROR` would tell the SDK the token\n // is bad and send it to refresh a session that is fine.\n // Any other throw is an invalid token, as it always was:\n // there is no session yet to keep.\n let resolved: Awaited<ReturnType<typeof resolveIdentity>>;\n try {\n resolved = await resolveIdentity(token, impersonate, impersonate === undefined ? undefined : { door: \"socket\", clientId });\n } catch (error) {\n const declared = declaredErrorAnswer(error);\n if ((declared && declared.status === 503) || isDatabaseUnreachable(error)) {\n const unavailable = storeUnavailableAnswer(error);\n sendError(\"ERROR\", unavailable.code, unavailable.message);\n return;\n }\n const answer = impersonate === undefined ? undefined : declared;\n if (answer) {\n sendError(\"AUTH_ERROR\", answer.code, answer.message);\n return;\n }\n if (token.startsWith(\"rk_\") || !authAdapter) throw error;\n resolved = { refused: \"Invalid or expired token\" };\n }\n if (\"refused\" in resolved && (resolved.apiKey || resolved.code)) {\n sendError(\"AUTH_ERROR\", resolved.code ?? \"INVALID_TOKEN\", resolved.refused);\n return;\n }\n const verifiedUser = \"user\" in resolved ? resolved.user : null;\n\n if (verifiedUser) {\n const session = clientSessions.get(clientId);\n // The per-identity ceiling, when one is set: a socket\n // re-authenticating as whom it already is does not\n // count against itself. Refused with an `ERROR`, not\n // an `AUTH_ERROR` — the credential is fine, and an\n // auth error sends the SDK to refresh the session —\n // then closed, so the tab does not run on as nobody.\n if (maxSocketsPerUser !== undefined && session) {\n let held = 0;\n for (const other of clientSessions.values()) {\n if (other !== session && other.user?.uid === verifiedUser.uid) held++;\n }\n if (held >= maxSocketsPerUser) {\n logger.warn(`[WebSocket Server] Refused a socket for ${verifiedUser.uid}: ${held} already open, ceiling ${maxSocketsPerUser}`);\n sendError(\n \"ERROR\",\n \"TOO_MANY_SOCKETS\",\n `This user already has ${held} realtime connections open on this server, the most it allows ` +\n \"(REALTIME_MAX_SOCKETS_PER_USER). Close another tab or client and try again.\"\n );\n ws.close(TOO_MANY_SOCKETS_CLOSE_CODE, \"Too many realtime connections for this user\");\n return;\n }\n }\n if (session) {\n session.user = verifiedUser;\n session.authenticated = true;\n // Kept to be asked again: before every frame, by\n // the sweep, and — for a token that expires — at\n // the instant it does.\n session.credential = token;\n session.impersonate = impersonate;\n session.checkedAt = Date.now();\n scheduleExpiry(clientId, token);\n // What this socket already holds open — a session\n // refresh, a demotion, another account — is read\n // as the identity it has now, before it is told\n // the sign-in took.\n await realtimeService.rescopeClient(clientId, sessionAuthContext(session));\n }\n wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);\n ws.send(JSON.stringify({\n type: \"AUTH_SUCCESS\",\n requestId,\n payload: { uid: verifiedUser.uid,\nroles: verifiedUser.roles }\n }));\n wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.uid}`);\n } else {\n wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);\n sendError(\"AUTH_ERROR\", \"INVALID_TOKEN\", \"Invalid or expired token\");\n }\n return;\n }\n\n // The identity this socket authenticated as is asked again\n // before the frame runs, as an HTTP request asks on every\n // request: a sign-out everywhere, a revoked session, a\n // demotion, a deleted account or an expired token stops being\n // honoured here, not when the socket happens to reconnect.\n {\n const session = clientSessions.get(clientId);\n if (session?.user && session.credential\n && Date.now() - session.checkedAt >= (CHANNEL_MESSAGE_TYPES.has(type) ? CHANNEL_RECHECK_MIN_MS : 0)) {\n const outcome = await recheckIdentityForFrame(clientId);\n if (outcome === \"ended\") return;\n if (outcome !== \"live\") {\n sendError(\"ERROR\", outcome.unavailable.code, outcome.unavailable.message);\n return;\n }\n }\n }\n\n // Check authentication for protected operations\n if (requireAuth) {\n const session = clientSessions.get(clientId);\n if (!session?.authenticated) {\n sendError(\"ERROR\", \"UNAUTHORIZED\", \"Authentication required\");\n return;\n }\n }\n\n // Admin-plane verbs need their scope; a narrowed session needs\n // the data scope for the collection a data frame addresses, and\n // cannot use channels, which no scope covers.\n const adminScope = ADMIN_PLANE_TYPES.get(type);\n if (adminScope) {\n const session = clientSessions.get(clientId);\n if (!scopeGrants(sessionScopes(session, accessModel()), adminScope)) {\n sendError(\"ERROR\", \"SCOPE_MISSING\", `This operation needs the \"${adminScope}\" scope`);\n return;\n }\n }\n const narrowedScopes = clientSessions.get(clientId)?.user?.scopes;\n if (narrowedScopes) {\n const dataScope = DATA_VERB_SCOPES.get(type);\n if (dataScope) {\n const collection = frameCollection(type, payload);\n if (!scopeGrants(narrowedScopes, dataScope, collection)) {\n const wanted = collection ? `${dataScope}:${collection}` : dataScope;\n sendError(\"ERROR\", \"SCOPE_MISSING\", `This API key does not hold the \"${wanted}\" scope`);\n return;\n }\n } else if (CHANNEL_MESSAGE_TYPES.has(type)) {\n sendError(\"ERROR\", \"SCOPE_MISSING\", \"An API key cannot use channels\");\n return;\n }\n }\n\n // A data frame is a data-API request by another door, and\n // counts in the bucket that request would: this person's — or\n // the API key's, at its own rate limit, or with no account this\n // address's — shared with their HTTP requests and their other\n // sockets. The counter above is per connection, so on its own\n // a caller bought budget by opening sockets.\n //\n // A subscribe and a history read are reads too, and count the\n // same way once the socket's own allowance is spent — see\n // SOCKET_FETCH_ALLOWANCE for why a socket has one.\n if (options?.dataRateLimit && (PUBLIC_TYPES.has(type)\n || (FETCHING_REALTIME_TYPES.has(type) && !spendFetchAllowance(clientId)))) {\n const decision = await options.dataRateLimit({\n ...origin,\n uid: clientSessions.get(clientId)?.user?.uid,\n apiKey: clientSessions.get(clientId)?.user?.apiKey\n });\n if (decision && !decision.allowed) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests, please try again later.\");\n return;\n }\n }\n\n /**\n * Apply the REST layer's write checks to a socket payload.\n *\n * Silent when the path names no registered collection: the\n * driver decides what a path means, and refusing here would\n * turn \"unknown collection\" into a validation error.\n */\n const assertWriteRequest = (path: string | undefined, values: unknown, status?: SaveProps[\"status\"]): void => {\n if (!path || !values || typeof values !== \"object\") return;\n const collection = driver.registry?.getCollectionByPath(path);\n if (!collection) return;\n // The session's roles, and `[\"anon\"]` for a socket that\n // never authenticated — the same list `getScopedDelegate`\n // below scopes such a socket's driver with. Never\n // `undefined`: that means the trusted server plane, which\n // satisfies every role list, and a socket is not it.\n const session = clientSessions.get(clientId);\n assertWriteRequestValid(values as Record<string, unknown>, collection, {\n viewer: { roles: session?.user?.roles ?? [\"anon\"] },\n // `required` holds on a create, as on `POST`.\n status\n });\n // Field operations (`{ views: { $inc: 1 } }`) reach the\n // driver through this door exactly as they reach it through\n // `PATCH`, and are compiled to SQL in the same place. What\n // the REST route adds is the type check — `$push` on a\n // number is a 400 naming the field, not a Postgres error\n // from inside the transaction — so the socket runs it too,\n // or the same payload is refused at one boundary and\n // accepted at the other.\n assertFieldOpsValid(values as Record<string, unknown>, collection);\n };\n\n /**\n * Apply the REST layer's read check to a socket request: no\n * filter, sort, projection or uniqueness probe over a field\n * this caller may not read. The strip keeps the value off the\n * wire; without this a `COUNT` still answers whether a hidden\n * value matches a pattern, one character at a time.\n *\n * Same viewer as `assertWriteRequest`, and silent in the same\n * case: a path that names no registered collection is the\n * driver's to answer.\n */\n const assertReadRequest = (\n path: string | undefined,\n request: Parameters<typeof assertReadRequestReadable>[0]\n ): void => {\n if (!path) return;\n let collection;\n try {\n collection = driver.registry?.getCollectionByPath(path);\n } catch {\n // A malformed nested path: the driver refuses it with\n // its own message.\n return;\n }\n if (!collection) return;\n const session = clientSessions.get(clientId);\n assertReadRequestReadable(request, collection, {\n roles: session?.user?.roles ?? [\"anon\"]\n });\n };\n\n /**\n * Refuse a write through a nested path that sets a relation\n * this caller may not write — the rule REST's nested routes\n * apply, from the same function. The body is checked by\n * `assertWriteRequest`; this is what the address writes.\n *\n * Silent for a root path, and for a nested one the registry\n * cannot walk: the driver refuses that with its own message.\n */\n const assertNestedWrite = (path: unknown, kind: NestedWriteKind): void => {\n if (typeof path !== \"string\" || !isNestedPath(path) || !driver.registry) return;\n let hop;\n try {\n hop = resolveNestedPath(path, driver.registry);\n } catch {\n return;\n }\n if (!hop) return;\n const session = clientSessions.get(clientId);\n assertNestedWriteAllowed(hop, kind, path, { roles: session?.user?.roles ?? [\"anon\"] });\n };\n\n /** The auth collection at `path`, or `undefined` for any other. */\n const authCollectionAt = (path: unknown): CollectionConfig | undefined => {\n if (typeof path !== \"string\") return undefined;\n let collection;\n try {\n collection = driver.registry?.getCollectionByPath(path);\n } catch {\n return undefined;\n }\n const auth = collection?.auth;\n const isAuthCollection = auth === true || (!!auth && typeof auth === \"object\" && auth.enabled === true);\n return isAuthCollection ? collection : undefined;\n };\n /**\n * The auth collection's rows are the users, so a write to one\n * is user administration — the auth adapter's to check, as it\n * is on the REST routes and `/admin/users`. `undefined` for any\n * other collection, or an adapter with nothing to say.\n */\n const userAdminFor = (path: unknown): AuthAdapter | undefined =>\n authAdapter && authCollectionAt(path) ? authAdapter : undefined;\n\n /**\n * An upsert's options, under the rules `POST /api/data`\n * applies to `?on_conflict=`: refused on a nested path,\n * whose insert branch stamps the parent's key into the\n * DO UPDATE and so moves the row it matched under this\n * parent; and a target that is a declared unique key of\n * the registry's collection, or a 400 naming the ones\n * there are rather than Postgres's 42P10.\n */\n const upsertOptions = (request: SaveProps): Pick<SaveProps, \"upsert\" | \"onConflict\"> => {\n if (request.upsert !== true) return {};\n if (typeof request.path === \"string\" && isNestedPath(request.path)) {\n throw ApiError.badRequest(\n \"An upsert is not accepted on a nested path: a row it matched would be moved \" +\n \"under this parent. Send the upsert to the collection directly.\",\n \"INVALID_CONFLICT_TARGET\"\n );\n }\n const collection = driver.registry?.getCollectionByPath(request.path);\n const onConflict = collection\n ? resolveConflictTarget(request.onConflict, collection, { where: \"`onConflict`\" })\n : undefined;\n return { upsert: true, ...(onConflict && { onConflict }) };\n };\n\n // Helper to get correctly scoped delegate for the current request\n const getScopedDelegate = async (): Promise<DataDriver> => {\n // Check if the driver supports RLS-scoped delegates\n if (typeof driver.withAuth === \"function\") {\n try {\n const who = sessionAuthContext(clientSessions.get(clientId));\n const userForAuth: User = {\n uid: who.uid,\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"websocket\",\n isAnonymous: who.isAnonymous === true,\n roles: who.roles,\n ...(who.claims ? { claims: who.claims } : {})\n };\n return await driver.withAuth(userForAuth);\n } catch (e) {\n logger.error(\"Failed to create RLS scoped delegate for WS request\", { error: e });\n throw new Error(\"Internal authentication error\");\n }\n }\n return driver;\n };\n\n // A data frame naming a collection this server does not have\n // is the 404 REST gives for it. Left to the driver it was a\n // plain error — `INTERNAL_ERROR`, logged as an incident, its\n // message replaced in production.\n if (PUBLIC_TYPES.has(type)) {\n assertKnownCollection(driver.registry, type === \"DELETE\" ? payload?.row?.path : payload?.path);\n }\n\n switch (type) {\n case \"FETCH_COLLECTION\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_COLLECTION request\");\n const request: FetchCollectionProps = withDecodedFilter(payload);\n assertReadRequest(request.path, request);\n const delegate = await getScopedDelegate();\n // Bound the client-supplied limit with the SAME guarantee\n // the REST ingress and `subscribe_collection` apply\n // (`resolveClientListLimit`). Without it an absent limit\n // reached the driver as `undefined`, which emits no LIMIT\n // clause — one socket frame streamed the whole table, on\n // the one transport that skipped the ceiling every other\n // read path enforces.\n const rows = await delegate.fetchCollection({\n ...request,\n limit: resolveClientListLimit(request.limit, {\n vectorSearch: !!request.vectorSearch\n })\n });\n wsDebug(\"📋 [WebSocket Server] FETCH_COLLECTION result - rows count:\", rows.length);\n const response = {\n type: \"FETCH_COLLECTION_SUCCESS\",\n payload: { rows },\n requestId\n };\n wsDebug(\"📋 [WebSocket Server] Sending FETCH_COLLECTION_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ONE\": {\n wsDebug(\"📄 [WebSocket Server] Processing FETCH_ENTITY request\");\n const request: FetchOneProps = payload;\n const delegate = await getScopedDelegate();\n const row = await delegate.fetchOne(request);\n wsDebug(\"📄 [WebSocket Server] FETCH_ENTITY result:\", row);\n const response = {\n type: \"FETCH_ONE_SUCCESS\",\n payload: { row: row ?? null },\n requestId\n };\n wsDebug(\"📄 [WebSocket Server] Sending FETCH_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"SAVE\": {\n wsDebug(\"💾 [WebSocket Server] Processing SAVE_ENTITY request\");\n const request: SaveProps = payload;\n wsDebug(\"💾 [WebSocket Server] Saving row with request:\", inspect(request, { depth: null,\ncolors: true }));\n // The same two checks the REST write routes run, on the\n // same input, at the same point. This socket is the\n // other request boundary — the comment on `requireAuth`\n // above says so — and it used to hand the client's\n // payload straight to the driver, so a value the HTTP\n // API answers 400 for was written when it arrived here.\n //\n // The collection comes from the registry by path, never\n // from `request.collection`: that field is client-\n // supplied, and reading the rules out of it would let\n // the caller choose which rules to be checked against.\n //\n // A create on the auth collection is a user creation,\n // as on `POST /users`: the adapter's step, not a plain\n // insert, and the body checked against what that step\n // consumes (`password` is not a column). An upsert\n // there is refused, as over HTTP: it may be a create or\n // an update, and each has its own rules.\n const upsertedUsers = request.upsert === true ? authCollectionAt(request.path) : undefined;\n if (upsertedUsers) assertNoUserUpsert(authAdapter, upsertedUsers);\n const usersCollection = request.status !== \"existing\" && request.upsert !== true && authAdapter?.prepareUserCreation\n ? authCollectionAt(request.path)\n : undefined;\n const caller = { roles: clientSessions.get(clientId)?.user?.roles ?? [\"anon\"] };\n if (usersCollection) {\n const body = (request.values ?? {}) as Record<string, unknown>;\n assertUserCreationBodyValid(authAdapter, usersCollection, body, caller);\n assertNoFieldOpsOnCreate(body, \"A create\", usersCollection);\n } else {\n assertWriteRequest(request.path, request.values as Record<string, unknown>, request.status);\n }\n // A create is one when the driver takes it for one:\n // no id, or a status that asks for a new row.\n assertNestedWrite(request.path, request.id === undefined || request.status !== \"existing\" ? \"create\" : \"update\");\n const delegate = await getScopedDelegate();\n // The address, the values and the status, and nothing\n // else the frame says — the DELETE case below is\n // narrowed for the same reason. A `collection` here was\n // merged under the registry's in the driver, so a key\n // the registry leaves unset was the caller's to set\n // (`history: true` on a collection declaring none).\n const userAdmin = request.status === \"existing\" && request.id !== undefined\n ? userAdminFor(request.path)\n : undefined;\n const [values] = userAdmin?.prepareUserUpdates\n ? await userAdmin.prepareUserUpdates([{ uid: String(request.id), values: request.values as Record<string, unknown> }])\n : [request.values];\n const write = (toWrite: SaveProps[\"values\"]) => delegate.save({\n path: request.path,\n ...(request.id !== undefined && { id: request.id }),\n values: toWrite,\n status: request.status,\n ...upsertOptions(request)\n });\n // The user creation answers with what `POST /users`\n // does: the row, and whether an invitation went out or\n // the temporary password that did not.\n const created = usersCollection\n ? await createUserThroughAuthCollection(authAdapter, usersCollection, (values ?? {}) as Record<string, unknown>, caller, write)\n : undefined;\n const row = created ? { ...created.row, ...created.delivery } : await write(values);\n wsDebug(\"💾 [WebSocket Server] SAVE_ENTITY result:\", inspect(row, { depth: null,\ncolors: true }));\n const response = {\n type: \"SAVE_SUCCESS\",\n payload: { row },\n requestId\n };\n wsDebug(\"💾 [WebSocket Server] Sending SAVE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_ENTITY request\");\n const request: DeleteProps = payload;\n wsDebug(\"🗑️ [WebSocket Server] Deleting row:\", request.row);\n // Through a many-to-many path this drops the link — a\n // write of the parent's relation.\n assertNestedWrite(request.row?.path, \"unlink\");\n const delegate = await getScopedDelegate();\n // The address and `hard`, and nothing else the frame\n // says. The driver reads the row itself, and resolves\n // the collection from the registry by path — a\n // `collection` passed along was merged under the\n // registry's, so a key the registry does not declare\n // survived: `softDelete: { field: \"title\" }` turned\n // this DELETE into an UPDATE of `title` that no\n // `beforeSave` and no write validator saw.\n const userAdmin = userAdminFor(request.row?.path);\n const uids = [String(request.row.id)];\n await userAdmin?.prepareUserDeletions?.(uids);\n await delegate.delete({\n row: { id: request.row.id, path: request.row.path },\n hard: request.hard\n });\n await userAdmin?.finalizeUserDeletions?.(uids);\n wsDebug(\"🗑️ [WebSocket Server] DELETE_ENTITY completed successfully\");\n const response = {\n type: \"DELETE_SUCCESS\",\n payload: { success: true },\n requestId\n };\n wsDebug(\"🗑️ [WebSocket Server] Sending DELETE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CHECK_UNIQUE_FIELD\": {\n wsDebug(\"🔍 [WebSocket Server] Processing CHECK_UNIQUE_FIELD request\");\n const {\n path,\n name,\n value,\n id,\n collection\n } = payload;\n // \"Does anybody hold this value\" is a read of the field,\n // answered for every row in the table.\n if (typeof name === \"string\") assertReadRequest(path, { filter: { [name]: [\"==\", value] } });\n const delegate = await getScopedDelegate();\n const isUnique = await delegate.checkUniqueField(path, name, value, id, collection);\n wsDebug(\"🔍 [WebSocket Server] CHECK_UNIQUE_FIELD result:\", isUnique);\n const response = {\n type: \"CHECK_UNIQUE_FIELD_SUCCESS\",\n payload: { isUnique },\n requestId\n };\n wsDebug(\"🔍 [WebSocket Server] Sending CHECK_UNIQUE_FIELD_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n\n case \"COUNT\": {\n // Deliberately NOT routed through `resolveClientListLimit`:\n // this answers with a scalar, and the driver drops `limit`\n // on the way to `SELECT count(*)`. Clamping here could only\n // ever make `total` describe fewer rows than the collection\n // holds — the page size is the caller's business, the total\n // is not.\n const request: FetchCollectionProps = withDecodedFilter(payload);\n assertReadRequest(request.path, request);\n const delegate = await getScopedDelegate();\n const count = await delegate.count!(request);\n const response = {\n type: \"COUNT_SUCCESS\",\n payload: { count },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"EXECUTE_SQL\": {\n const { sql, options, mode } = payload;\n // A console run can be stopped by `CANCEL_SQL` while\n // it is in flight — one with a requestId to name it.\n const cancel = mode === \"script\" && typeof requestId === \"string\" && !sqlRuns.has(requestId)\n ? new AbortController()\n : undefined;\n if (cancel && requestId) sqlRuns.set(requestId, cancel);\n try {\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n if (!isSQLAdmin(admin)) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"SQL execution is not available for this driver.\");\n break;\n }\n // The console's own runs: the result says, per\n // column, which table column the database read it\n // from — what an edit of a cell is written back\n // by. The rows still go in `result`, where a\n // client that reads nothing else finds them.\n const script = mode === \"script\" && typeof admin.runSqlScript === \"function\"\n ? await admin.runSqlScript(String(sql), {\n database: typeof options?.database === \"string\" ? options.database : undefined,\n role: typeof options?.role === \"string\" ? options.role : undefined,\n ...(cancel ? { signal: cancel.signal } : {})\n })\n : undefined;\n // A session of its own, reset before it goes back\n // to the pool: what a person types may `SET ROLE`,\n // and that must not reach the next request on\n // that connection. And a result past the byte\n // budget refused, never sent in part. Both set\n // here, after the client's options, so a frame\n // cannot turn them off.\n const result = script\n ? script.rows\n : await admin.executeSql(sql, { ...options, isolateSession: true, maxResultBytes: SQL_RESULT_BYTE_BUDGET });\n // Which role the rows were read as, said beside them:\n // the role picked, unless the server runs every\n // statement as the connection owner.\n const ranAs = typeof options?.role === \"string\" && options.role\n ? await sqlRoleRanAs(admin, options.role, typeof options?.database === \"string\" ? options.database : undefined)\n : undefined;\n if (process.env.NODE_ENV !== \"production\") {\n wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : \"non-array\"} rows.`);\n }\n const auditSession = clientSessions.get(clientId);\n // Through `logger`, not `console.log`: this line is\n // emitted in production, and a bare console call\n // has no severity, no timestamp, no JSON envelope\n // and no LOG_LEVEL gate, so it lands in Cloud\n // Logging as unstructured text the queries written\n // for every other line cannot match.\n //\n // The bound values are counted, never written — the\n // statement is the audit signal, the parameters are\n // whatever row the operator was touching. (stdout is\n // not an audit sink either; a real trail belongs in\n // a table with an actor and a retention policy.)\n //\n // And for the same reason the values written inline\n // as literals are masked: `ALTER ROLE app PASSWORD\n // '…'` put the password in the production logs.\n // Masked before the cut, so the cut cannot land\n // inside a literal and keep half of it.\n logger.info(\"[SQL Audit] WebSocket SQL execution\", {\n sql: redactSqlLiterals(String(sql)).substring(0, 500),\n database: options?.database,\n role: options?.role,\n // The role the statement asked for is above;\n // this is the one it ran as. They used to be\n // assumed identical, so an execution that fell\n // back to the owner was audited under the role\n // it had failed to assume. That fallback is now\n // an error everywhere except the documented\n // `DISABLE_DB_ROLE_SWITCHING` opt-out — which is\n // precisely the case this line still has to\n // report honestly.\n effectiveRole: effectiveSqlRole(options?.role),\n paramCount: Array.isArray(options?.params) ? options.params.length : 0,\n resultRows: Array.isArray(result) ? result.length : \"unknown\",\n uid: auditSession?.user?.uid ?? \"unknown\",\n roles: auditSession?.user?.roles ?? [],\n isAdmin: auditSession?.user?.isAdmin ?? false,\n ...(auditSession?.user?.impersonator ? { impersonatedBy: auditSession.user.impersonator } : {}),\n requestId\n });\n const response = {\n type: \"EXECUTE_SQL_SUCCESS\",\n // The rows once, as `result`; the rest of the\n // script's description beside them.\n payload: {\n ...(script ? { ...scriptDescription(script), result } : { result }),\n ...(ranAs ? { effectiveRole: ranAs } : {})\n },\n requestId\n };\n ws.send(JSON.stringify(response));\n } catch (sqlError: unknown) {\n // A refusal, not a failure of the SQL: its own code,\n // and a message that names the limit and the way\n // round it.\n if (sqlError instanceof SqlResultTooLargeError) {\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: sqlError.message, code: \"RESULT_TOO_LARGE\" } }\n }));\n break;\n }\n // This is a query execution error (e.g., syntax error, permission denied).\n // We return it cleanly to the client without logging a server stack trace.\n const errMsg = extractErrorMessage(sqlError);\n sendError(\"ERROR\", \"SQL_ERROR\", errMsg);\n } finally {\n if (cancel && requestId && sqlRuns.get(requestId) === cancel) sqlRuns.delete(requestId);\n }\n }\n break;\n\n case \"CANCEL_SQL\": {\n // The run named is this socket's, or nothing: the map\n // is per socket. `cancelled` says whether one was\n // still in flight; the run itself answers on its own\n // requestId, with the database's \"canceling statement\".\n const target = (payload as { requestId?: unknown } | undefined)?.requestId;\n const run = typeof target === \"string\" ? sqlRuns.get(target) : undefined;\n run?.abort();\n ws.send(JSON.stringify({\n type: \"CANCEL_SQL_SUCCESS\",\n requestId,\n payload: { cancelled: run !== undefined }\n }));\n }\n break;\n\n case \"FETCH_DATABASES\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_DATABASES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let databases: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) {\n databases = await admin.fetchAvailableDatabases();\n }\n wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);\n const response = {\n type: \"FETCH_DATABASES_SUCCESS\",\n payload: { databases },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {\n roles = await admin.fetchAvailableRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);\n const response = {\n type: \"FETCH_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_APPLICATION_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_APPLICATION_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchApplicationRoles) {\n roles = await admin.fetchApplicationRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} application roles.`);\n const response = {\n type: \"FETCH_APPLICATION_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_CURRENT_DATABASE\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let database: string | undefined = undefined;\n if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) {\n database = await admin.fetchCurrentDatabase();\n }\n const response = {\n type: \"FETCH_CURRENT_DATABASE_SUCCESS\",\n payload: { database },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_UNMAPPED_TABLES\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let tables: string[] = [];\n if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) {\n tables = await admin.fetchUnmappedTables(payload?.mappedPaths);\n }\n wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);\n const response = {\n type: \"FETCH_UNMAPPED_TABLES_SUCCESS\",\n payload: { tables },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_TABLE_METADATA\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request\");\n const { tableName } = payload;\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let metadata: TableMetadata | undefined;\n if (isSchemaAdmin(admin) && admin.fetchTableMetadata) {\n metadata = await admin.fetchTableMetadata(tableName) as TableMetadata;\n }\n wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);\n const response = {\n type: \"FETCH_TABLE_METADATA_SUCCESS\",\n payload: { metadata },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CREATE_BRANCH\": {\n wsDebug(\"🌿 [WebSocket Server] Processing CREATE_BRANCH request\");\n const { name, options } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.createBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available. Configure adminConnectionString.\");\n break;\n }\n const branch: BranchInfo = await delegate.admin.createBranch(name, options);\n wsDebug(`🌿 [WebSocket Server] Branch created: ${branch.name}`);\n const response = {\n type: \"CREATE_BRANCH_SUCCESS\",\n payload: { branch },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE_BRANCH\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_BRANCH request\");\n const { name: branchName } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.deleteBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available.\");\n break;\n }\n await delegate.admin.deleteBranch(branchName);\n wsDebug(`🗑️ [WebSocket Server] Branch deleted: ${branchName}`);\n const response = {\n type: \"DELETE_BRANCH_SUCCESS\",\n payload: { success: true },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"LIST_BRANCHES\": {\n wsDebug(\"🌿 [WebSocket Server] Processing LIST_BRANCHES request\");\n const delegate = await getScopedDelegate();\n let branches: BranchInfo[] = [];\n if (delegate.admin?.listBranches) {\n branches = await delegate.admin.listBranches();\n }\n wsDebug(`🌿 [WebSocket Server] Listed ${branches.length} branches.`);\n const response = {\n type: \"LIST_BRANCHES_SUCCESS\",\n payload: { branches },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n // Route subscription messages, broadcast channels, and presence to RealtimeService\n case \"subscribe_collection\":\n case \"subscribe_one\":\n case \"unsubscribe\":\n case \"join_channel\":\n case \"leave_channel\":\n case \"broadcast\":\n case \"presence_track\":\n case \"presence_untrack\":\n case \"presence_state\":\n case \"channel_history\": {\n wsDebug(\"🔄 [WebSocket Server] Routing realtime message to RealtimeService:\", type);\n // Attach auth context from the WS session so RLS-aware\n // refetches read as the same principal the request\n // frames do.\n const authContext = sessionAuthContext(clientSessions.get(clientId));\n // Let RealtimeService handle these messages\n await realtimeService.handleClientMessage(clientId, {\n type,\n payload,\n subscriptionId: payload?.subscriptionId\n }, authContext);\n break;\n }\n\n default:\n // Answered, so the client's request settles with a\n // reason rather than timing out on silence.\n logger.warn(\"[WebSocket Server] Unknown message type\", { detail: type });\n throw ApiError.badRequest(\n `Unknown message type '${String(type)}'.`,\n \"UNKNOWN_MESSAGE_TYPE\"\n );\n }\n } catch (error: unknown) {\n // A refused `limit` is the caller's mistake, not a server fault.\n // Left to the generic branch below it answers INTERNAL_ERROR\n // with the message suppressed in production — so the one thing\n // that would tell the caller what to send instead is exactly\n // what gets dropped. Answered here the way\n // `subscribe_collection` already answers it: INVALID_LIMIT,\n // message intact. The text names the ceiling and nothing else.\n if (error instanceof ListLimitError) {\n logger.warn(`[WebSocket Server] Refused a list read: ${error.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: error.message,\ncode: \"INVALID_LIMIT\" } }\n }));\n return;\n }\n // \"This server cannot branch\" is a refusal too, and the same\n // reasoning applies twice over: the generic branch would drop\n // the text in production, and the text is the only thing that\n // says what to do instead. Studio renders it where the success\n // toast used to be.\n if (error instanceof BranchingUnsupportedError\n || (error as Error)?.name === \"BranchingUnsupportedError\") {\n const refusal = error as BranchingUnsupportedError;\n logger.warn(`[WebSocket Server] Refused a branch operation: ${refusal.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: refusal.message,\ncode: \"BRANCHING_UNSUPPORTED\" } }\n }));\n return;\n }\n // A refused write is the caller's mistake, and its message is\n // the only thing that says what to send instead — the same\n // reasoning as `ListLimitError` above. Left to the generic\n // branch it becomes INTERNAL_ERROR with the text dropped in\n // production, so the socket would refuse the write and decline\n // to say why.\n //\n // \"Refused\" means whatever REST answers with the error's own\n // status, by the same predicate: an `ApiError`, and the\n // `RebaseApiError` every collection-callback veto is. Listing\n // only the first here is how a `beforeDelete` veto reached the\n // admin panel as \"An unexpected error occurred\".\n // The database unreachable is said as that, the answer the\n // HTTP doors give, rather than as a server fault.\n const answer = declaredErrorAnswer(error)\n ?? (isDatabaseUnreachable(error) ? declaredErrorAnswer(ApiError.databaseUnavailable()) : undefined);\n if (answer) {\n const line = `[WebSocket Server] Refused: ${answer.code} ${answer.message}`;\n if (answer.expected) logger.debug(line);\n else logger.warn(line);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: {\n message: answer.message,\n code: answer.code,\n ...(answer.details !== undefined && { details: answer.details })\n } }\n }));\n return;\n }\n logger.error(\"💥 [WebSocket Server] Error handling message\", { error: error });\n if (error instanceof Error) {\n logger.error(\"Stack trace\", { detail: error.stack });\n }\n // Unwrap the cause chain: a Drizzle failure reports itself as\n // \"Failed query: <sql> params:\", which tells the user nothing and\n // echoes the statement back at them. The reason is in the cause.\n const errorMessage = process.env.NODE_ENV === \"production\"\n ? \"An unexpected error occurred\"\n : extractErrorMessage(error);\n const errorResponse = {\n type: \"ERROR\",\n requestId,\n payload: {\n error: {\n message: errorMessage,\n code: \"INTERNAL_ERROR\"\n }\n }\n };\n ws.send(JSON.stringify(errorResponse));\n }\n });\n });\n\n // A peer that vanished without a close — lost signal, sleep, a forgotten\n // NAT mapping — is let go of once it stops answering pings. Stopped with\n // whichever goes first, like the identity sweep.\n const stopReaping = reapSilentSockets(wss);\n wss.on(\"close\", stopReaping);\n if (typeof server.on === \"function\") server.on(\"close\", stopReaping);\n\n // And what its sockets hold unread counts in the budget for all of the\n // process's sockets, not only in each one's own ceiling.\n const stopBudgeting = budgetSendBuffers(wss);\n wss.on(\"close\", stopBudgeting);\n if (typeof server.on === \"function\") server.on(\"close\", stopBudgeting);\n\n // The heartbeat a browser needs to notice a half-open socket, for the\n // sockets that asked for it. One timer for all of them.\n const stopHeartbeats = startHeartbeats(() => heartbeatSockets, heartbeatIntervalMs);\n wss.on(\"close\", stopHeartbeats);\n if (typeof server.on === \"function\") server.on(\"close\", stopHeartbeats);\n}\n"],"mappings":"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"}
|