@rebasepro/server-postgres 0.25.0-canary.ge06257e → 0.25.1
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-DNg4txyf.js → BranchService-ClklxyAQ.js} +283 -20
- package/dist/BranchService-ClklxyAQ.js.map +1 -0
- package/dist/backup/backup-cli.d.ts +70 -1
- package/dist/backup/backup-service.d.ts +79 -0
- package/dist/backup/pg-binary.d.ts +2 -2
- package/dist/backup/pg-tools.d.ts +60 -1
- package/dist/backup-cli-gijeXe0u.js +1961 -0
- package/dist/backup-cli-gijeXe0u.js.map +1 -0
- package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-DYH95Qeg.js} +3 -3
- package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-DYH95Qeg.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-prune.d.ts +18 -0
- package/dist/{cli-console-C_ISGwJo.js → cli-console-CQrQ0lAY.js} +19 -13
- package/dist/cli-console-CQrQ0lAY.js.map +1 -0
- package/dist/cli-console.d.ts +4 -3
- package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DiA0XFIS.js} +110 -14
- package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DiA0XFIS.js.map} +1 -1
- package/dist/cli-errors.d.ts +26 -1
- 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 +120 -184
- package/dist/cli.js.map +1 -1
- package/dist/collections/validate-relations.d.ts +8 -2
- 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/{rls-bootstrap-sql-Bd-hJk08.js → db-subcommands-BPoHCxJR.js} +455 -3
- package/dist/db-subcommands-BPoHCxJR.js.map +1 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-BE0I6sYU.js} +8 -8
- package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-BE0I6sYU.js.map} +1 -1
- package/dist/{doctor-CJyuhV1n.js → doctor-BT--1kq3.js} +107 -11
- package/dist/doctor-BT--1kq3.js.map +1 -0
- package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-BnGOURpG.js} +60 -11
- package/dist/ensure-collection-policies-BnGOURpG.js.map +1 -0
- package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-Cet4650m.js} +9 -9
- package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-Cet4650m.js.map} +1 -1
- package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-l_OgFQjN.js} +5 -5
- package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-l_OgFQjN.js.map} +1 -1
- package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-nheku1pB.js} +3 -3
- package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-nheku1pB.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-D50Pxw1G.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-D50Pxw1G.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DgK5BlBn.js} +22 -14
- package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.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/index.d.ts +1 -0
- package/dist/index.es.js +70 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/migrate-policy-stamps-3eqfhes6.js +89 -0
- package/dist/migrate-policy-stamps-3eqfhes6.js.map +1 -0
- package/dist/{open-rebase-D8rytTP0.js → open-rebase-EmqVmS14.js} +78 -1027
- package/dist/open-rebase-EmqVmS14.js.map +1 -0
- package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-CFEnm2m9.js} +11 -4
- package/dist/pg-error-utils-CFEnm2m9.js.map +1 -0
- package/dist/{plan-schema-CsNgK0qi.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-COd7i6wm.js} +4 -4
- package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-COd7i6wm.js.map} +1 -1
- package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-CLgYYwPE.js} +4 -4
- package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-CLgYYwPE.js.map} +1 -1
- package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-D2gpQsdv.js} +23 -7
- package/dist/push-policy-plan-D2gpQsdv.js.map +1 -0
- package/dist/{render-ddl-CrfEe__B.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-DDyNoOSy.js} +2 -2
- package/dist/{rls-enforcement-D7P4-ZYX.js.map → rls-enforcement-DDyNoOSy.js.map} +1 -1
- package/dist/schema/atlas-argv.d.ts +3 -1
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +28 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +20 -8
- 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 +6 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/push-policy-plan.d.ts +10 -2
- package/dist/services/BranchService.d.ts +85 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/{source-DB6xJv3p.js → source-fZb92pN8.js} +46 -3
- package/dist/source-fZb92pN8.js.map +1 -0
- 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-DMeXOoyv.js → websocket-BKZwsjmf.js} +18 -5
- package/dist/websocket-BKZwsjmf.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-DNg4txyf.js.map +0 -1
- package/dist/backup-cli-HCEkPcQj.js +0 -556
- package/dist/backup-cli-HCEkPcQj.js.map +0 -1
- package/dist/backup-service-D-UpT7DH.js +0 -857
- package/dist/backup-service-D-UpT7DH.js.map +0 -1
- package/dist/cli-console-C_ISGwJo.js.map +0 -1
- package/dist/cli-flags-DUozBFyK.js +0 -205
- package/dist/cli-flags-DUozBFyK.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-CJyuhV1n.js.map +0 -1
- package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
- package/dist/open-rebase-D8rytTP0.js.map +0 -1
- package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
- package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/source-DB6xJv3p.js.map +0 -1
- package/dist/websocket-DMeXOoyv.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-BKZwsjmf.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\n/** The `id` of the built-in auth adapter (`createBuiltinAuthAdapter` in `@rebasepro/server`). */\nconst BUILTIN_AUTH_ADAPTER_ID = \"rebase-builtin\";\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; code?: string }> {\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 //\n // The built-in adapter is asked through `verifyRequest`: the same\n // judgement as its `verifyToken`, which answers a refusal with\n // null, but thrown as the 401 that says why — `SESSION_REVOKED`,\n // `ACCOUNT_DISABLED`. So a refused session is refused here in the\n // words the HTTP routes use for the same token, and the SDK reads\n // it as they do. Its 503 is still a throw: the store could not\n // answer.\n const builtIn = authAdapter.id === BUILTIN_AUTH_ADAPTER_ID;\n let adapterUser: Awaited<ReturnType<AuthAdapter[\"verifyRequest\"]>>;\n try {\n adapterUser = authAdapter.verifyToken && !builtIn\n ? await authAdapter.verifyToken(token)\n : await authAdapter.verifyRequest(new Request(\"http://localhost/_ws_auth\", {\n headers: { Authorization: `Bearer ${token}` }\n }));\n } catch (error) {\n const declared = builtIn ? declaredErrorAnswer(error) : undefined;\n if (declared?.status === 401) return { refused: declared.message, code: declared.code };\n throw error;\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-postgres",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"execa": "^9.6.1",
|
|
51
51
|
"pg": "^8.22.0",
|
|
52
52
|
"ws": "^8.21.1",
|
|
53
|
-
"@rebasepro/
|
|
54
|
-
"@rebasepro/server": "0.25.
|
|
55
|
-
"@rebasepro/
|
|
56
|
-
"@rebasepro/types": "0.25.
|
|
57
|
-
"@rebasepro/
|
|
53
|
+
"@rebasepro/codegen": "0.25.1",
|
|
54
|
+
"@rebasepro/server": "0.25.1",
|
|
55
|
+
"@rebasepro/common": "0.25.1",
|
|
56
|
+
"@rebasepro/types": "0.25.1",
|
|
57
|
+
"@rebasepro/utils": "0.25.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@electric-sql/pglite": "0.5.6",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"vite": "^8.1.5",
|
|
72
72
|
"vitest": "^4.1.11",
|
|
73
|
-
"@rebasepro/client": "0.25.
|
|
73
|
+
"@rebasepro/client": "0.25.1"
|
|
74
74
|
},
|
|
75
75
|
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
76
76
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BranchService-DNg4txyf.js","names":[],"sources":["../src/services/BranchService.ts"],"sourcesContent":["/**\n * BranchService\n *\n * Manages database branching by creating/deleting PostgreSQL databases\n * using `CREATE DATABASE ... TEMPLATE`. Branch metadata is stored in the\n * `rebase.branches` table in the default (main) database, following the\n * same `rebase` schema convention used by entity_history, auth, etc.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { BranchInfo } from \"@rebasepro/types\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { BranchRow } from \"../branch-prune\";\nimport { DatabasePoolManager } from \"../databasePoolManager\";\nimport { extractPgError, extractCauseMessage } from \"../utils/pg-error-utils\";\n\n/** Internal prefix applied to branch database names to avoid collisions. */\nconst BRANCH_DB_PREFIX = \"rb_\";\n\n/** `duplicate_database` — the target database name is already taken. */\nconst PG_DUPLICATE_DATABASE = \"42P04\";\n\n/** `object_in_use` — the database still has connections attached. */\nconst PG_OBJECT_IN_USE = \"55006\";\n\n/**\n * Describe a failed branch DDL statement in terms a user can act on.\n *\n * Drizzle reports failures as `Failed query: <sql> params:` and hides the real\n * PostgreSQL error in the `cause` chain, so matching on `err.message` never sees\n * the actual problem. Match on the PG error code instead — it survives wrapping\n * and, unlike the message text, is not locale-dependent.\n */\nfunction describeBranchDdlError(err: unknown, fallbackContext: string): Error {\n const pgError = extractPgError(err);\n\n if (pgError?.code === PG_DUPLICATE_DATABASE) {\n return new Error(`Database \"${fallbackContext}\" already exists on the server. Choose a different branch name.`);\n }\n if (pgError?.code === PG_OBJECT_IN_USE) {\n return new Error(\n `Cannot complete the operation: the database \"${fallbackContext}\" has active connections. ` +\n \"Close other clients or connections and try again.\"\n );\n }\n\n // Unknown failure: surface the real PG message rather than the Drizzle\n // wrapper, which would otherwise show the raw SQL and no reason at all.\n const detail = pgError?.message ?? extractCauseMessage(err);\n if (detail) return new Error(detail);\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Fully-qualified metadata table in the rebase schema. */\nconst BRANCHES_TABLE = \"rebase.branches\";\n\n/**\n * The connected server cannot branch, so nothing was done.\n *\n * A refusal, not a fault: typed so the transports can forward it intact.\n * `websocket.ts` answers it with its own code and message rather than letting\n * it fall into the generic handler, which suppresses the text in production —\n * and the text is the only part that says what to do instead.\n */\nexport class BranchingUnsupportedError extends Error {\n /** Wire code, forwarded verbatim to the client. */\n readonly code = \"BRANCHING_UNSUPPORTED\";\n\n constructor(message: string) {\n super(message);\n this.name = \"BranchingUnsupportedError\";\n }\n}\n\n/**\n * What to say when the server serves one database.\n *\n * Deliberately the same argument the CLI's `refuseBranchOnManagedDatabase`\n * makes, because it is the same refusal — it just used to be available only to\n * whoever came in through argv. One paragraph, because this one has to fit in a\n * toast as well as a terminal.\n */\nconst BRANCHING_UNSUPPORTED_MESSAGE =\n \"Branching does not work on the managed development database. It copies a database with \"\n + \"CREATE DATABASE ... TEMPLATE, and the managed database is PGlite, which serves exactly one — \"\n + \"the copy would be the original, and every write you meant to sandbox would land in it. \"\n + \"Branching needs a real Postgres: run `rebase dev --docker`, or uncomment DATABASE_URL in .env \"\n + \"to point at your own.\";\n\n/**\n * Validate that a user-provided identifier only contains safe characters.\n * Throws if the value contains characters outside [a-zA-Z0-9_-].\n */\nfunction validateIdentifier(value: string, label: string): void {\n if (!/^[a-zA-Z0-9_-]+$/.test(value)) {\n throw new Error(`Invalid ${label}: only letters, digits, underscores, and hyphens are allowed.`);\n }\n}\n\n/**\n * Postgres truncates identifiers at NAMEDATALEN-1 = 63 bytes, and does it\n * silently. The prefix comes out of the same budget.\n */\nconst MAX_BRANCH_NAME_LENGTH = 63 - BRANCH_DB_PREFIX.length;\n\n/**\n * Check a user-provided branch name, and otherwise leave it exactly as given.\n *\n * This used to strip everything outside [a-zA-Z0-9_], so `my-feature` was\n * quietly created as `myfeature`: the name you typed was not the name `list`\n * gave back. Nothing ever needed that. Every identifier this service builds is\n * double-quoted (see `CREATE DATABASE` below), which is what makes a hyphen\n * safe, and `validateIdentifier` has always accepted hyphens for the `--from`\n * source database — the two disagreed about the same character class.\n *\n * Refusing a name we cannot represent is better than representing a different\n * one, which is also why the length is checked here rather than left to\n * Postgres, whose answer to an over-long identifier is a silent rename.\n */\nfunction assertValidBranchName(name: string): void {\n validateIdentifier(name, \"branch name\");\n if (name.length > MAX_BRANCH_NAME_LENGTH) {\n throw new Error(\n `Branch name \"${name}\" is too long: ${name.length} characters, maximum ${MAX_BRANCH_NAME_LENGTH}. ` +\n \"Postgres truncates identifiers past 63 bytes, which would give the branch a name you did not choose.\"\n );\n }\n}\n\n/**\n * Convert a user-facing branch name to the actual PostgreSQL database name.\n */\nfunction toBranchDbName(name: string): string {\n assertValidBranchName(name);\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/**\n * Where a server's branches are kept: the main database — what a branch is\n * copied from unless told otherwise, and what may never be dropped — and the\n * connection whose `rebase.branches` records them.\n */\nexport interface BranchHome {\n /** The main database's name. */\n database(): Promise<string>;\n /**\n * A connection on the database the registry is in. Asked for at each use:\n * a pool to the main database is closed before it is copied.\n */\n registry(): Promise<DrizzleClient>;\n}\n\nexport class BranchService {\n private readonly home: BranchHome;\n\n /**\n * @param db A connection on the server the branches are made on. Branch\n * DDL runs here.\n * @param home Where branches are kept. Without one, the database the\n * pool manager's connection string names, and `db`'s registry.\n */\n constructor(\n private db: DrizzleClient,\n private poolManager: DatabasePoolManager,\n home?: BranchHome\n ) {\n this.home = home ?? {\n database: async () => poolManager.defaultDatabaseName,\n registry: async () => db\n };\n }\n\n /**\n * Refuse a branch mutation the connected server cannot honour.\n *\n * The check lives here rather than in the CLI's argv parsing because the\n * CLI is not the only caller: Studio reaches `createBranch` over the\n * websocket, and before this it got \"Branch created successfully\" and a\n * detail pane promising isolation for a database that is the parent.\n *\n * Reads only, and only mutations are guarded: `listBranches` on a managed\n * database is an honest empty list, and refusing it would turn the Branches\n * pane into a failure with a \"Try again\" that can never succeed.\n */\n private async assertBranchingSupported(): Promise<void> {\n if (await this.poolManager.servesOneDatabase()) {\n throw new BranchingUnsupportedError(BRANCHING_UNSUPPORTED_MESSAGE);\n }\n }\n\n /**\n * Whether the `rebase.branches` registry exists. A database that has never\n * had a branch made on it may not have it, and reading it there is an\n * empty answer, not an error — nor a reason to create it. The CLI's `list`\n * and `info` ask this rather than running the DDL that creates it.\n */\n async hasBranchRegistry(): Promise<boolean> {\n const result = await (await this.home.registry()).execute(\n sql`SELECT to_regclass('rebase.branches') IS NOT NULL AS present`\n );\n const [row] = result.rows;\n return row !== undefined && Reflect.get(row, \"present\") === true;\n }\n\n /**\n * Ensure the `rebase.branches` metadata table exists in the default database.\n * Idempotent — safe to call on every startup.\n */\n async ensureBranchMetadataTable(): Promise<void> {\n // Create the rebase schema (idempotent — may already exist from auth/history init)\n const registry = await this.home.registry();\n await registry.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);\n\n await registry.execute(sql.raw(`\n CREATE TABLE IF NOT EXISTS ${BRANCHES_TABLE} (\n name TEXT PRIMARY KEY,\n db_name TEXT NOT NULL UNIQUE,\n parent_db TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'\n );\n `));\n\n // Not a collection, so no RLS — and it names every branch database on\n // this server. The driver's schema-wide grant reaches it, so revoke.\n await registry.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"branches\")));\n }\n\n /**\n * Create a new branch database by templating the source database.\n *\n * Uses `CREATE DATABASE ... TEMPLATE` for an instant, full-fidelity copy\n * of both schema and data.\n *\n * @param name User-facing branch name (e.g., \"feature_auth\")\n * @param options.source Source database to clone; defaults to the main database.\n */\n async createBranch(\n name: string,\n options?: { source?: string; force?: boolean }\n ): Promise<BranchInfo> {\n await this.assertBranchingSupported();\n\n if (options?.source) {\n validateIdentifier(options.source, \"source database name\");\n }\n\n const dbName = toBranchDbName(name);\n const sourceDb = options?.source || await this.home.database();\n\n // Check if branch already exists\n const existing = await (await this.home.registry()).execute(\n sql`SELECT name FROM rebase.branches WHERE name = ${name} OR db_name = ${dbName}`\n );\n if ((existing.rows as unknown[]).length > 0) {\n throw new Error(`Branch \"${name}\" already exists.`);\n }\n\n // Disconnect any idle pools to the source DB so TEMPLATE works.\n // CREATE DATABASE ... TEMPLATE requires no other connections to the template.\n //\n // This only reaches pools inside *this* process. The CLI runs as its own\n // process, so a `rebase dev` holding the database open is untouched by\n // it — which is why the failure below has to name what is connected, and\n // why `--force` exists.\n await this.poolManager.disconnectDatabase(sourceDb);\n\n if (options?.force) {\n const closed = await this.terminateConnections(sourceDb);\n if (closed > 0) {\n // Postgres reports the backend as gone before its slot is\n // actually free, and CREATE DATABASE then fails on a connection\n // that no longer exists. One short settle beats a confusing retry.\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n\n // Create the database using the source as a template.\n // Note: Identifiers must be double-quoted, not parameterized.\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n const safeSourceDb = sourceDb.replace(/\"/g, '\"\"');\n try {\n await this.db.execute(\n sql.raw(`CREATE DATABASE \"${safeDbName}\" TEMPLATE \"${safeSourceDb}\"`)\n );\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n // The template — not the new database — is the one still in use.\n throw new Error(\n `Cannot create branch: the source database \"${sourceDb}\" has active connections.\\n`\n + await this.describeBlockingConnections(sourceDb)\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Record metadata in the registry — asked for again, as copying the\n // main database closed any pool on it.\n const now = new Date();\n await (await this.home.registry()).execute(\n sql`INSERT INTO rebase.branches (name, db_name, parent_db, created_at)\n VALUES (${name}, ${dbName}, ${sourceDb}, ${now.toISOString()})`\n );\n\n return {\n name,\n database: dbName,\n parentDatabase: sourceDb,\n createdAt: now\n };\n }\n\n /**\n * Delete a branch database and remove its metadata.\n * Cannot delete the main/default database.\n */\n async deleteBranch(name: string, options?: { force?: boolean }): Promise<void> {\n await this.assertBranchingSupported();\n\n assertValidBranchName(name);\n\n // Safety, first pass: a request that would target the default database\n // is refused before any metadata is read, so the answer costs nothing\n // and cannot depend on what a row happens to say.\n const mainDatabase = await this.home.database();\n if (toBranchDbName(name) === mainDatabase) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Verify the branch exists, and take the database name from the row\n // rather than deriving it again. A branch created before names stopped\n // being stripped is recorded as `rb_myfeature` while `my-feature` now\n // derives `rb_my-feature`; re-deriving would drop a database this row\n // never named — or, if that name happened to exist, somebody else's.\n // The stored value is the only one that is true by construction.\n const existing = await (await this.home.registry()).execute(\n sql`SELECT db_name FROM rebase.branches WHERE name = ${name}`\n );\n const existingRows = existing.rows as Record<string, unknown>[];\n if (existingRows.length === 0) {\n throw new Error(`Branch \"${name}\" not found.`);\n }\n const dbName = existingRows[0].db_name as string;\n\n // Safety, second pass: the first checked a name we derived, this checks\n // the one we are about to drop. They differ for any row written under\n // the old scheme, and it is this one that governs.\n if (dbName === mainDatabase) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Disconnect any pools to this branch before dropping. Same limit as\n // `createBranch`: in-process pools only.\n await this.poolManager.disconnectDatabase(dbName);\n\n if (options?.force) {\n const closed = await this.terminateConnections(dbName);\n if (closed > 0) await new Promise((resolve) => setTimeout(resolve, 250));\n }\n\n // Drop the database\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n try {\n await this.db.execute(sql.raw(`DROP DATABASE \"${safeDbName}\"`));\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n throw new Error(\n `Cannot delete branch \"${name}\": the database has active connections.\\n`\n + await this.describeBlockingConnections(dbName)\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Remove metadata\n await (await this.home.registry()).execute(\n sql`DELETE FROM rebase.branches WHERE name = ${name}`\n );\n }\n\n /**\n * Who is holding the database open, in words a developer can act on.\n *\n * `CREATE DATABASE ... TEMPLATE` and `DROP DATABASE` both require that no\n * other session is connected, and the one thing the old message could not\n * say is the only thing worth knowing: *which* session. \"Close other\n * clients or connections and try again\" is advice you cannot follow when\n * you do not know there is a `rebase dev` in another terminal — and that is\n * the common case, because wanting a branch and running the app are the\n * same moment.\n *\n * `application_name` is the useful column: node-postgres sends none by\n * default, so a Rebase process shows as `(unnamed)` while DBeaver, pgAdmin\n * and psql all name themselves. Counting by it separates \"my own dev\n * server\" from \"the GUI I forgot about\" without any guessing.\n *\n * Best-effort by construction: this runs while reporting a failure, and a\n * diagnostic that throws would replace a real error with its own.\n */\n private async describeBlockingConnections(dbName: string): Promise<string> {\n try {\n const result = await this.db.execute(sql`\n SELECT coalesce(nullif(application_name, ''), '(unnamed)') AS app,\n count(*)::int AS n\n FROM pg_stat_activity\n WHERE datname = ${dbName}\n AND pid <> pg_backend_pid()\n GROUP BY 1\n ORDER BY 2 DESC, 1\n `);\n const rows = result.rows as Record<string, unknown>[];\n if (rows.length === 0) {\n // The blocker went away between the failure and this query, or\n // it is a session Postgres does not attribute to this database.\n return \" Retry — whatever was connected has since disconnected.\";\n }\n\n const listed = rows\n .map((row) => ` ${row.n} × ${String(row.app)}`)\n .join(\"\\n\");\n\n return ` Connected right now:\\n${listed}\\n`\n + \" A running `rebase dev` is the usual one — stop it, or re-run with --force to\\n\"\n + \" disconnect them for you.\";\n } catch {\n return \" Close other clients and connections, or re-run with --force.\";\n }\n }\n\n /**\n * Disconnect every other session on a database, and say how many.\n *\n * `pg_terminate_backend` rather than `pg_cancel_backend`: cancelling a\n * query leaves the session connected, and a connected session is exactly\n * what blocks the operation.\n *\n * The current backend is excluded — terminating the connection running this\n * statement would abort the command that asked for it.\n */\n private async terminateConnections(dbName: string): Promise<number> {\n const result = await this.db.execute(sql`\n SELECT pg_terminate_backend(pid)\n FROM pg_stat_activity\n WHERE datname = ${dbName}\n AND pid <> pg_backend_pid()\n `);\n\n return (result.rows as unknown[]).length;\n }\n\n /**\n * Everything prune needs to decide, in two queries.\n *\n * Reads the rows and the server's database list rather than joining them,\n * so the *disagreements* between the two are visible — a row whose database\n * is gone and a database with no row are the two things prune exists to\n * find, and a join hides both.\n */\n async pruneCandidates(): Promise<{ rows: BranchRow[]; databases: string[] }> {\n const rowResult = await (await this.home.registry()).execute(sql.raw(`\n SELECT name, db_name, created_at FROM ${BRANCHES_TABLE} ORDER BY created_at\n `));\n const rows = (rowResult.rows as Record<string, unknown>[]).map((row) => ({\n name: row.name as string,\n dbName: row.db_name as string,\n createdAt: new Date(row.created_at as string)\n }));\n\n const dbResult = await this.db.execute(sql`\n SELECT datname FROM pg_database WHERE NOT datistemplate\n `);\n const databases = (dbResult.rows as Record<string, unknown>[]).map((row) => String(row.datname));\n\n return { rows, databases };\n }\n\n /** Remove a metadata row whose database is already gone. */\n async forgetBranchRow(name: string): Promise<void> {\n await (await this.home.registry()).execute(sql`DELETE FROM rebase.branches WHERE name = ${name}`);\n }\n\n /**\n * Drop a database by name, with no metadata row required.\n *\n * `deleteBranch` deliberately takes its database name from the row, because\n * the row is the only value that is true by construction. Prune has the\n * opposite job — the cases it handles are exactly the ones where row and\n * database disagree — so it needs to name a database directly. Guarded the\n * same way regardless: the main database is never droppable.\n */\n async dropDatabase(dbName: string): Promise<void> {\n validateIdentifier(dbName, \"database name\");\n if (dbName === await this.home.database()) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n await this.poolManager.disconnectDatabase(dbName);\n try {\n await this.db.execute(sql.raw(`DROP DATABASE \"${dbName.replace(/\"/g, '\"\"')}\"`));\n } catch (err) {\n throw describeBranchDdlError(err, dbName);\n }\n }\n\n /**\n * List all branches recorded in the metadata table.\n * Optionally fetches database sizes from pg_database.\n */\n async listBranches(): Promise<BranchInfo[]> {\n const result = await (await this.home.registry()).execute(sql.raw(`\n SELECT \n b.name,\n b.db_name,\n b.parent_db,\n b.created_at,\n pg_database_size(b.db_name) as size_bytes\n FROM ${BRANCHES_TABLE} b\n JOIN pg_database d ON d.datname = b.db_name\n ORDER BY b.created_at DESC\n `));\n\n return (result.rows as Record<string, unknown>[]).map((row) => ({\n name: row.name as string,\n database: row.db_name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : undefined\n }));\n }\n\n /**\n * Get info about a specific branch.\n */\n async getBranchInfo(name: string): Promise<BranchInfo | undefined> {\n assertValidBranchName(name);\n\n const result = await (await this.home.registry()).execute(sql`\n SELECT\n b.name,\n b.db_name,\n b.parent_db,\n b.created_at\n FROM rebase.branches b\n WHERE b.name = ${name}\n `);\n\n const rows = result.rows as Record<string, unknown>[];\n if (rows.length === 0) return undefined;\n\n const row = rows[0];\n\n // Attempt to get size — may fail if the DB was externally dropped.\n // Same reason as `deleteBranch`: the size of a re-derived name is the\n // size of some other database, or of nothing.\n let sizeBytes: number | undefined;\n try {\n const dbName = row.db_name as string;\n const sizeResult = await this.db.execute(\n sql`SELECT pg_database_size(${dbName}) as size_bytes`\n );\n const sizeRows = sizeResult.rows as Record<string, unknown>[];\n if (sizeRows.length > 0 && sizeRows[0].size_bytes != null) {\n sizeBytes = Number(sizeRows[0].size_bytes);\n }\n } catch {\n // Database might not exist anymore\n }\n\n return {\n name: row.name as string,\n database: row.db_name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkBA,IAAM,mBAAmB;;AAGzB,IAAM,wBAAwB;;AAG9B,IAAM,mBAAmB;;;;;;;;;AAUzB,SAAS,uBAAuB,KAAc,iBAAgC;CAC1E,MAAM,UAAU,eAAe,GAAG;CAElC,IAAI,SAAS,SAAS,uBAClB,uBAAO,IAAI,MAAM,aAAa,gBAAgB,gEAAgE;CAElH,IAAI,SAAS,SAAS,kBAClB,uBAAO,IAAI,MACP,gDAAgD,gBAAgB,4EAEpE;CAKJ,MAAM,SAAS,SAAS,WAAW,oBAAoB,GAAG;CAC1D,IAAI,QAAQ,OAAO,IAAI,MAAM,MAAM;CACnC,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC7D;;AAGA,IAAM,iBAAiB;;;;;;;;;AAUvB,IAAa,4BAAb,cAA+C,MAAM;;CAEjD,OAAgB;CAEhB,YAAY,SAAiB;EACzB,MAAM,OAAO;EACb,KAAK,OAAO;CAChB;AACJ;;;;;;;;;AAUA,IAAM,gCACF;;;;;AAUJ,SAAS,mBAAmB,OAAe,OAAqB;CAC5D,IAAI,CAAC,mBAAmB,KAAK,KAAK,GAC9B,MAAM,IAAI,MAAM,WAAW,MAAM,8DAA8D;AAEvG;;;;;AAMA,IAAM,yBAAyB;;;;;;;;;;;;;;;AAgB/B,SAAS,sBAAsB,MAAoB;CAC/C,mBAAmB,MAAM,aAAa;CACtC,IAAI,KAAK,SAAS,wBACd,MAAM,IAAI,MACN,gBAAgB,KAAK,iBAAiB,KAAK,OAAO,uBAAuB,uBAAuB,uGAEpG;AAER;;;;AAKA,SAAS,eAAe,MAAsB;CAC1C,sBAAsB,IAAI;CAC1B,OAAO,GAAG,mBAAmB;AACjC;AAiBA,IAAa,gBAAb,MAA2B;CAUX;CACA;CAVZ;;;;;;;CAQA,YACI,IACA,aACA,MACF;EAHU,KAAA,KAAA;EACA,KAAA,cAAA;EAGR,KAAK,OAAO,QAAQ;GAChB,UAAU,YAAY,YAAY;GAClC,UAAU,YAAY;EAC1B;CACJ;;;;;;;;;;;;;CAcA,MAAc,2BAA0C;EACpD,IAAI,MAAM,KAAK,YAAY,kBAAkB,GACzC,MAAM,IAAI,0BAA0B,6BAA6B;CAEzE;;;;;;;CAQA,MAAM,oBAAsC;EAIxC,MAAM,CAAC,QAAO,OAHQ,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAC9C,GAAG,8DACP,EAAA,CACqB;EACrB,OAAO,QAAQ,KAAA,KAAa,QAAQ,IAAI,KAAK,SAAS,MAAM;CAChE;;;;;CAMA,MAAM,4BAA2C;EAE7C,MAAM,WAAW,MAAM,KAAK,KAAK,SAAS;EAC1C,MAAM,SAAS,QAAQ,GAAG,oCAAoC;EAE9D,MAAM,SAAS,QAAQ,IAAI,IAAI;yCACE,eAAe;;;;;;;SAO/C,CAAC;EAIF,MAAM,SAAS,QAAQ,IAAI,IAAI,uBAAuB,UAAU,UAAU,CAAC,CAAC;CAChF;;;;;;;;;;CAWA,MAAM,aACF,MACA,SACmB;EACnB,MAAM,KAAK,yBAAyB;EAEpC,IAAI,SAAS,QACT,mBAAmB,QAAQ,QAAQ,sBAAsB;EAG7D,MAAM,SAAS,eAAe,IAAI;EAClC,MAAM,WAAW,SAAS,UAAU,MAAM,KAAK,KAAK,SAAS;EAM7D,KAAK,OAHmB,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAChD,GAAG,iDAAiD,KAAK,gBAAgB,QAC7E,EAAA,CACc,KAAmB,SAAS,GACtC,MAAM,IAAI,MAAM,WAAW,KAAK,kBAAkB;EAUtD,MAAM,KAAK,YAAY,mBAAmB,QAAQ;EAElD,IAAI,SAAS;OAEL,MADiB,KAAK,qBAAqB,QAAQ,IAC1C,GAIT,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;EAAA;EAM/D,MAAM,aAAa,OAAO,QAAQ,MAAM,MAAI;EAC5C,MAAM,eAAe,SAAS,QAAQ,MAAM,MAAI;EAChD,IAAI;GACA,MAAM,KAAK,GAAG,QACV,IAAI,IAAI,oBAAoB,WAAW,cAAc,aAAa,EAAE,CACxE;EACJ,SAAS,KAAK;GAEV,IADgB,eAAe,GAC3B,CAAA,EAAS,SAAS,kBAElB,MAAM,IAAI,MACN,8CAA8C,SAAS,+BACrD,MAAM,KAAK,4BAA4B,QAAQ,CACrD;GAEJ,MAAM,uBAAuB,KAAK,MAAM;EAC5C;EAIA,MAAM,sBAAM,IAAI,KAAK;EACrB,OAAO,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAC/B,GAAG;0BACW,KAAK,IAAI,OAAO,IAAI,SAAS,IAAI,IAAI,YAAY,EAAE,EACrE;EAEA,OAAO;GACH;GACA,UAAU;GACV,gBAAgB;GAChB,WAAW;EACf;CACJ;;;;;CAMA,MAAM,aAAa,MAAc,SAA8C;EAC3E,MAAM,KAAK,yBAAyB;EAEpC,sBAAsB,IAAI;EAK1B,MAAM,eAAe,MAAM,KAAK,KAAK,SAAS;EAC9C,IAAI,eAAe,IAAI,MAAM,cACzB,MAAM,IAAI,MAAM,kCAAkC;EAYtD,MAAM,gBAAe,OAHG,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAChD,GAAG,oDAAoD,MAC3D,EAAA,CAC8B;EAC9B,IAAI,aAAa,WAAW,GACxB,MAAM,IAAI,MAAM,WAAW,KAAK,aAAa;EAEjD,MAAM,SAAS,aAAa,EAAE,CAAC;EAK/B,IAAI,WAAW,cACX,MAAM,IAAI,MAAM,kCAAkC;EAKtD,MAAM,KAAK,YAAY,mBAAmB,MAAM;EAEhD,IAAI,SAAS;OAEL,MADiB,KAAK,qBAAqB,MAAM,IACxC,GAAG,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;EAAA;EAI3E,MAAM,aAAa,OAAO,QAAQ,MAAM,MAAI;EAC5C,IAAI;GACA,MAAM,KAAK,GAAG,QAAQ,IAAI,IAAI,kBAAkB,WAAW,EAAE,CAAC;EAClE,SAAS,KAAK;GAEV,IADgB,eAAe,GAC3B,CAAA,EAAS,SAAS,kBAClB,MAAM,IAAI,MACN,yBAAyB,KAAK,6CAC5B,MAAM,KAAK,4BAA4B,MAAM,CACnD;GAEJ,MAAM,uBAAuB,KAAK,MAAM;EAC5C;EAGA,OAAO,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAC/B,GAAG,4CAA4C,MACnD;CACJ;;;;;;;;;;;;;;;;;;;;CAqBA,MAAc,4BAA4B,QAAiC;EACvE,IAAI;GAUA,MAAM,QAAO,MATQ,KAAK,GAAG,QAAQ,GAAG;;;;mCAIjB,OAAO;;;;aAI7B,EAAA,CACmB;GACpB,IAAI,KAAK,WAAW,GAGhB,OAAO;GAOX,OAAO,2BAJQ,KACV,KAAK,QAAQ,OAAO,IAAI,EAAE,KAAK,OAAO,IAAI,GAAG,GAAG,CAAC,CACjD,KAAK,IAEwB,EAAO;;EAG7C,QAAQ;GACJ,OAAO;EACX;CACJ;;;;;;;;;;;CAYA,MAAc,qBAAqB,QAAiC;EAQhE,QAAQ,MAPa,KAAK,GAAG,QAAQ,GAAG;;;+BAGjB,OAAO;;SAE7B,EAAA,CAEc,KAAmB;CACtC;;;;;;;;;CAUA,MAAM,kBAAuE;EAezE,OAAO;GAAE,OAXK,OAHW,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAAQ,IAAI,IAAI;oDACzB,eAAe;SAC1D,CAAC,EAAA,CACsB,KAAmC,KAAK,SAAS;IACrE,MAAM,IAAI;IACV,QAAQ,IAAI;IACZ,WAAW,IAAI,KAAK,IAAI,UAAoB;GAChD,EAOS;GAAM,YAFI,MAHI,KAAK,GAAG,QAAQ,GAAG;;SAEzC,EAAA,CAC2B,KAAmC,KAAK,QAAQ,OAAO,IAAI,OAAO,CAE/E;EAAU;CAC7B;;CAGA,MAAM,gBAAgB,MAA6B;EAC/C,OAAO,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAAQ,GAAG,4CAA4C,MAAM;CACpG;;;;;;;;;;CAWA,MAAM,aAAa,QAA+B;EAC9C,mBAAmB,QAAQ,eAAe;EAC1C,IAAI,WAAW,MAAM,KAAK,KAAK,SAAS,GACpC,MAAM,IAAI,MAAM,kCAAkC;EAGtD,MAAM,KAAK,YAAY,mBAAmB,MAAM;EAChD,IAAI;GACA,MAAM,KAAK,GAAG,QAAQ,IAAI,IAAI,kBAAkB,OAAO,QAAQ,MAAM,MAAI,EAAE,EAAE,CAAC;EAClF,SAAS,KAAK;GACV,MAAM,uBAAuB,KAAK,MAAM;EAC5C;CACJ;;;;;CAMA,MAAM,eAAsC;EAaxC,QAAQ,OAZc,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAAQ,IAAI,IAAI;;;;;;;mBAOvD,eAAe;;;SAGzB,CAAC,EAAA,CAEa,KAAmC,KAAK,SAAS;GAC5D,MAAM,IAAI;GACV,UAAU,IAAI;GACd,gBAAgB,IAAI;GACpB,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,WAAW,IAAI,cAAc,OAAO,OAAO,IAAI,UAAU,IAAI,KAAA;EACjE,EAAE;CACN;;;;CAKA,MAAM,cAAc,MAA+C;EAC/D,sBAAsB,IAAI;EAY1B,MAAM,QAAO,OAVS,MAAM,KAAK,KAAK,SAAS,EAAA,CAAG,QAAQ,GAAG;;;;;;;6BAOxC,KAAK;SACzB,EAAA,CAEmB;EACpB,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;EAE9B,MAAM,MAAM,KAAK;EAKjB,IAAI;EACJ,IAAI;GACA,MAAM,SAAS,IAAI;GAInB,MAAM,YAAW,MAHQ,KAAK,GAAG,QAC7B,GAAG,2BAA2B,OAAO,gBACzC,EAAA,CAC4B;GAC5B,IAAI,SAAS,SAAS,KAAK,SAAS,EAAE,CAAC,cAAc,MACjD,YAAY,OAAO,SAAS,EAAE,CAAC,UAAU;EAEjD,QAAQ,CAER;EAEA,OAAO;GACH,MAAM,IAAI;GACV,UAAU,IAAI;GACd,gBAAgB,IAAI;GACpB,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C;EACJ;CACJ;AACJ"}
|