@rebasepro/server-postgres 0.16.1-canary.ge71347e → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/PostgresBackendDriver.d.ts +59 -5
  2. package/dist/{backup-service-BL5x6Fj5.js → backup-service-BtgHxfFm.js} +2 -1
  3. package/dist/{backup-service-BL5x6Fj5.js.map → backup-service-BtgHxfFm.js.map} +1 -1
  4. package/dist/cli-helpers.d.ts +41 -0
  5. package/dist/{ensure-collection-tables-C_Gr59le.js → collection-index-DxJBvVTH.js} +427 -1914
  6. package/dist/collection-index-DxJBvVTH.js.map +1 -0
  7. package/dist/{ensure-collection-policies-CMYAvFpM.js → ensure-collection-policies-DFpOl8SM.js} +3 -3
  8. package/dist/{ensure-collection-policies-CMYAvFpM.js.map → ensure-collection-policies-DFpOl8SM.js.map} +1 -1
  9. package/dist/ensure-collection-tables-DMjOkeRy.js +1952 -0
  10. package/dist/ensure-collection-tables-DMjOkeRy.js.map +1 -0
  11. package/dist/index.es.js +13 -7488
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/{rls-enforcement-HLy7w5hL.js → rls-enforcement-CInuYj1-.js} +3 -3
  14. package/dist/rls-enforcement-CInuYj1-.js.map +1 -0
  15. package/dist/schema/collection-index.d.ts +182 -0
  16. package/dist/schema/introspect-db-inference.d.ts +1 -1
  17. package/dist/schema/introspect-db-logic.d.ts +4 -4
  18. package/dist/schema/introspect-db-project.d.ts +2 -2
  19. package/dist/src-DiDgtX8P.js.map +1 -1
  20. package/dist/websocket-HcyLl1ZM.js +8188 -0
  21. package/dist/websocket-HcyLl1ZM.js.map +1 -0
  22. package/package.json +6 -6
  23. package/src/PostgresBackendDriver.ts +149 -57
  24. package/src/cli-helpers.ts +114 -0
  25. package/src/cli.ts +22 -0
  26. package/src/schema/collection-index.ts +427 -0
  27. package/src/schema/ensure-collection-tables.ts +21 -0
  28. package/src/schema/generate-postgres-ddl-logic.ts +17 -5
  29. package/src/schema/introspect-db-inference.ts +1 -1
  30. package/src/schema/introspect-db-logic.ts +4 -4
  31. package/src/schema/introspect-db-project.ts +2 -2
  32. package/src/schema/introspect-db.ts +2 -2
  33. package/src/services/realtimeService.ts +17 -4
  34. package/src/websocket.ts +12 -2
  35. package/dist/data_driver-ULAyJEi9.js +0 -193
  36. package/dist/data_driver-ULAyJEi9.js.map +0 -1
  37. package/dist/ensure-collection-tables-C_Gr59le.js.map +0 -1
  38. package/dist/rls-enforcement-HLy7w5hL.js.map +0 -1
  39. package/dist/websocket-D0YNv8hp.js +0 -651
  40. package/dist/websocket-D0YNv8hp.js.map +0 -1
@@ -1,651 +0,0 @@
1
- import { createRequire as __createRequire } from "module";
2
- import process from "process";
3
- __createRequire(import.meta.url);
4
- import { u as __exportAll } from "./connection-GOKU3Hu5.js";
5
- import { n as resolveClientListLimit, r as ANONYMOUS_USER_ID, t as ListLimitError } from "./data_driver-ULAyJEi9.js";
6
- import "./src-DiDgtX8P.js";
7
- import { ApiError, assertWriteRequestValid, extractUserFromToken, logger, resolveRequireAuth, safeCompare } from "@rebasepro/server";
8
- import { WebSocketServer } from "ws";
9
- import { inspect } from "util";
10
- //#region ../types/src/types/backend.ts
11
- /**
12
- * Type guard: does this admin support SQL operations?
13
- * @group Admin
14
- */
15
- function isSQLAdmin(admin) {
16
- return !!admin && typeof admin.executeSql === "function";
17
- }
18
- /**
19
- * Type guard: does this admin support schema management?
20
- * @group Admin
21
- */
22
- function isSchemaAdmin(admin) {
23
- return !!admin && (typeof admin.fetchUnmappedTables === "function" || typeof admin.fetchTableMetadata === "function");
24
- }
25
- //#endregion
26
- //#region src/websocket.ts
27
- var websocket_exports = /* @__PURE__ */ __exportAll({
28
- ADMIN_ONLY_TYPES: () => ADMIN_ONLY_TYPES,
29
- PUBLIC_TYPES: () => PUBLIC_TYPES,
30
- createPostgresWebSocket: () => createPostgresWebSocket
31
- });
32
- /** Maximum messages per client per window */
33
- var WS_RATE_LIMIT = 2e3;
34
- /** Rate limit window in milliseconds (60 seconds) */
35
- var WS_RATE_WINDOW_MS = 6e4;
36
- /**
37
- * Channel frames get their own budget, because they are a different workload.
38
- *
39
- * 2000/minute is 33/second, which is generous for queries and subscriptions and
40
- * an order of magnitude below what the documented channel idiom asks for: the
41
- * capacity note in `docs/backend/realtime.md` uses 60 fps cursor movement as
42
- * its worked example, and the presence idiom re-`track()`s on every move, so
43
- * one client sustaining that sends ~120 frames/second — 7200 a minute. Sharing
44
- * one counter meant the cursor stream ate the query budget and then froze for
45
- * the rest of the window.
46
- *
47
- * The number is sized to that documented workload and nothing more; it is not
48
- * a considered product limit (see `docs/channel-authorization.md`).
49
- */
50
- var WS_CHANNEL_RATE_LIMIT = 7200;
51
- /** Frames counted against the channel budget rather than the general one. */
52
- var CHANNEL_MESSAGE_TYPES = /* @__PURE__ */ new Set([
53
- "join_channel",
54
- "leave_channel",
55
- "broadcast",
56
- "presence_track",
57
- "presence_untrack",
58
- "presence_state",
59
- "channel_history"
60
- ]);
61
- /**
62
- * WebSocket message types that require an admin session.
63
- *
64
- * Exported so the test can READ it. It used to be private, and the test that
65
- * exists to make "a privileged verb added without a role check" impossible held
66
- * a hand-typed copy of the same nine strings — so it agreed with itself, and
67
- * `FETCH_APPLICATION_ROLES` was added to neither. That verb runs
68
- * `SELECT DISTINCT unnest(roles)` over the users table through `executeSql`,
69
- * which is the owner connection and not subject to RLS, so any authenticated
70
- * non-admin could enumerate every role in the project — and any anonymous
71
- * socket could, on a deployment with `requireAuth: false`.
72
- *
73
- * The list is no longer the whole guarantee. `PUBLIC_TYPES` below is its
74
- * counterpart, and a test asserts that every `case` this file handles appears
75
- * in exactly one of the two — so a verb added to neither fails rather than
76
- * defaulting to reachable.
77
- */
78
- var ADMIN_ONLY_TYPES = /* @__PURE__ */ new Set([
79
- "EXECUTE_SQL",
80
- "FETCH_DATABASES",
81
- "FETCH_ROLES",
82
- "FETCH_APPLICATION_ROLES",
83
- "FETCH_UNMAPPED_TABLES",
84
- "FETCH_TABLE_METADATA",
85
- "FETCH_CURRENT_DATABASE",
86
- "CREATE_BRANCH",
87
- "DELETE_BRANCH",
88
- "LIST_BRANCHES"
89
- ]);
90
- /**
91
- * Message types deliberately reachable by a non-admin session.
92
- *
93
- * Data operations, gated per row by RLS and per request by the same write
94
- * checks the REST layer applies — not by this list. It exists so that "which
95
- * bucket is this verb in" is a question with an answer for every verb, and
96
- * adding one without answering it is a test failure.
97
- */
98
- var PUBLIC_TYPES = /* @__PURE__ */ new Set([
99
- "FETCH_COLLECTION",
100
- "FETCH_ONE",
101
- "COUNT",
102
- "SAVE",
103
- "DELETE",
104
- "CHECK_UNIQUE_FIELD"
105
- ]);
106
- /**
107
- * Recursively extract the deepest error message from an error's cause chain (e.g., Drizzle wrapping a PG error).
108
- */
109
- function extractErrorMessage(error) {
110
- if (!error) return "Unknown error";
111
- if (error instanceof Error) {
112
- if ("cause" in error && error.cause) return extractErrorMessage(error.cause);
113
- return error.message;
114
- }
115
- if (typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
116
- return String(error);
117
- }
118
- /**
119
- * Check if the current session belongs to an admin user.
120
- */
121
- function isAdminSession(session) {
122
- if (!session?.user) return false;
123
- if (session.user.isAdmin) return true;
124
- if (!session.user.roles) return false;
125
- return session.user.roles.some((r) => r === "admin");
126
- }
127
- function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
128
- const clientSessions = /* @__PURE__ */ new Map();
129
- const isProduction = process.env.NODE_ENV === "production";
130
- /** Debug logger that is suppressed in production to prevent PII/data leaks */
131
- const wsDebug = (...args) => {
132
- if (!isProduction) console.debug(...args);
133
- };
134
- const wss = new WebSocketServer({ server });
135
- wss.on("error", (err) => {
136
- if (err.code === "EADDRINUSE") return;
137
- logger.error("❌ [WebSocket Server] Error", { error: err });
138
- });
139
- const requireAuth = !!authAdapter || resolveRequireAuth(authConfig);
140
- if (requireAuth && !authAdapter && !authConfig?.jwtSecret && !authConfig?.serviceKey) logger.warn("🔐 [WebSocket Server] Authentication is required but no adapter, jwtSecret or serviceKey is configured — no client can complete AUTH, so every realtime message will be refused with UNAUTHORIZED.");
141
- wss.on("connection", (ws) => {
142
- const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
143
- wsDebug(`WebSocket client connected: ${clientId}`);
144
- clientSessions.set(clientId, {
145
- ws,
146
- authenticated: !requireAuth,
147
- messageCount: 0,
148
- messageWindowStart: Date.now(),
149
- channelMessageCount: 0,
150
- channelWindowStart: Date.now()
151
- });
152
- realtimeService.addClient(clientId, ws);
153
- ws.on("close", () => {
154
- wsDebug(`WebSocket client disconnected: ${clientId}`);
155
- clientSessions.delete(clientId);
156
- });
157
- ws.on("message", async (message) => {
158
- let requestId;
159
- try {
160
- const { type, payload, requestId: reqId } = JSON.parse(message.toString());
161
- requestId = reqId;
162
- wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : "");
163
- const sendError = (errType, code, msg) => {
164
- ws.send(JSON.stringify({
165
- type: errType,
166
- requestId,
167
- payload: { error: {
168
- message: msg,
169
- code
170
- } }
171
- }));
172
- };
173
- if (type === "AUTHENTICATE") {
174
- const { token } = payload || {};
175
- if (!token) {
176
- sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
177
- return;
178
- }
179
- let verifiedUser = null;
180
- if (authAdapter) try {
181
- const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", { headers: { Authorization: `Bearer ${token}` } }));
182
- if (adapterUser) verifiedUser = {
183
- uid: adapterUser.uid,
184
- roles: adapterUser.roles,
185
- isAdmin: adapterUser.isAdmin
186
- };
187
- } catch {}
188
- else if (authConfig?.serviceKey && safeCompare(token, authConfig.serviceKey)) verifiedUser = {
189
- uid: "service",
190
- roles: ["admin"],
191
- isAdmin: true
192
- };
193
- else {
194
- const jwtPayload = extractUserFromToken(token);
195
- if (jwtPayload) verifiedUser = {
196
- uid: jwtPayload.uid,
197
- roles: jwtPayload.roles ?? [],
198
- isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
199
- };
200
- }
201
- if (verifiedUser) {
202
- const session = clientSessions.get(clientId);
203
- if (session) {
204
- session.user = verifiedUser;
205
- session.authenticated = true;
206
- }
207
- wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
208
- ws.send(JSON.stringify({
209
- type: "AUTH_SUCCESS",
210
- requestId,
211
- payload: {
212
- uid: verifiedUser.uid,
213
- roles: verifiedUser.roles
214
- }
215
- }));
216
- wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.uid}`);
217
- } else {
218
- wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
219
- sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
220
- }
221
- return;
222
- }
223
- if (requireAuth) {
224
- if (!clientSessions.get(clientId)?.authenticated) {
225
- sendError("ERROR", "UNAUTHORIZED", "Authentication required");
226
- return;
227
- }
228
- }
229
- {
230
- const session = clientSessions.get(clientId);
231
- if (session) {
232
- const now = Date.now();
233
- if (CHANNEL_MESSAGE_TYPES.has(type)) {
234
- if (now - session.channelWindowStart > WS_RATE_WINDOW_MS) {
235
- session.channelMessageCount = 0;
236
- session.channelWindowStart = now;
237
- }
238
- session.channelMessageCount++;
239
- if (session.channelMessageCount > WS_CHANNEL_RATE_LIMIT) {
240
- sendError("ERROR", "RATE_LIMITED", "Too many channel messages. Please slow down.");
241
- return;
242
- }
243
- } else {
244
- if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {
245
- session.messageCount = 0;
246
- session.messageWindowStart = now;
247
- }
248
- session.messageCount++;
249
- if (session.messageCount > WS_RATE_LIMIT) {
250
- sendError("ERROR", "RATE_LIMITED", "Too many requests. Please slow down.");
251
- return;
252
- }
253
- }
254
- }
255
- }
256
- if (ADMIN_ONLY_TYPES.has(type)) {
257
- if (!isAdminSession(clientSessions.get(clientId))) {
258
- sendError("ERROR", "FORBIDDEN", "Admin access required for this operation");
259
- return;
260
- }
261
- }
262
- /**
263
- * Apply the REST layer's write checks to a socket payload.
264
- *
265
- * Silent when the path names no registered collection: the
266
- * driver decides what a path means, and refusing here would
267
- * turn "unknown collection" into a validation error.
268
- */
269
- const assertWriteRequest = (path, values) => {
270
- if (!path || !values || typeof values !== "object") return;
271
- const collection = driver.registry?.getCollectionByPath(path);
272
- if (!collection) return;
273
- assertWriteRequestValid(values, collection);
274
- };
275
- const getScopedDelegate = async () => {
276
- const session = clientSessions.get(clientId);
277
- if (typeof driver.withAuth === "function") try {
278
- const userForAuth = session?.user ? {
279
- uid: session.user.uid,
280
- displayName: null,
281
- email: null,
282
- photoURL: null,
283
- providerId: "websocket",
284
- isAnonymous: false,
285
- roles: session.user.roles ?? []
286
- } : {
287
- uid: ANONYMOUS_USER_ID,
288
- displayName: null,
289
- email: null,
290
- photoURL: null,
291
- providerId: "websocket",
292
- isAnonymous: true,
293
- roles: ["anon"]
294
- };
295
- return await driver.withAuth(userForAuth);
296
- } catch (e) {
297
- logger.error("Failed to create RLS scoped delegate for WS request", { error: e });
298
- throw new Error("Internal authentication error");
299
- }
300
- return driver;
301
- };
302
- switch (type) {
303
- case "FETCH_COLLECTION":
304
- {
305
- wsDebug("📋 [WebSocket Server] Processing FETCH_COLLECTION request");
306
- const request = payload;
307
- const rows = await (await getScopedDelegate()).fetchCollection({
308
- ...request,
309
- limit: resolveClientListLimit(request.limit, { vectorSearch: !!request.vectorSearch })
310
- });
311
- wsDebug("📋 [WebSocket Server] FETCH_COLLECTION result - rows count:", rows.length);
312
- const response = {
313
- type: "FETCH_COLLECTION_SUCCESS",
314
- payload: { rows },
315
- requestId
316
- };
317
- wsDebug("📋 [WebSocket Server] Sending FETCH_COLLECTION_SUCCESS response");
318
- ws.send(JSON.stringify(response));
319
- }
320
- break;
321
- case "FETCH_ONE":
322
- {
323
- wsDebug("📄 [WebSocket Server] Processing FETCH_ENTITY request");
324
- const request = payload;
325
- const row = await (await getScopedDelegate()).fetchOne(request);
326
- wsDebug("📄 [WebSocket Server] FETCH_ENTITY result:", row);
327
- const response = {
328
- type: "FETCH_ONE_SUCCESS",
329
- payload: { row: row ?? null },
330
- requestId
331
- };
332
- wsDebug("📄 [WebSocket Server] Sending FETCH_ENTITY_SUCCESS response");
333
- ws.send(JSON.stringify(response));
334
- }
335
- break;
336
- case "SAVE":
337
- {
338
- wsDebug("💾 [WebSocket Server] Processing SAVE_ENTITY request");
339
- const request = payload;
340
- wsDebug("💾 [WebSocket Server] Saving row with request:", inspect(request, {
341
- depth: null,
342
- colors: true
343
- }));
344
- assertWriteRequest(request.path, request.values);
345
- const row = await (await getScopedDelegate()).save(request);
346
- wsDebug("💾 [WebSocket Server] SAVE_ENTITY result:", inspect(row, {
347
- depth: null,
348
- colors: true
349
- }));
350
- const response = {
351
- type: "SAVE_SUCCESS",
352
- payload: { row },
353
- requestId
354
- };
355
- wsDebug("💾 [WebSocket Server] Sending SAVE_ENTITY_SUCCESS response");
356
- ws.send(JSON.stringify(response));
357
- }
358
- break;
359
- case "DELETE":
360
- {
361
- wsDebug("🗑️ [WebSocket Server] Processing DELETE_ENTITY request");
362
- const request = payload;
363
- wsDebug("🗑️ [WebSocket Server] Deleting row:", request.row);
364
- await (await getScopedDelegate()).delete(request);
365
- wsDebug("🗑️ [WebSocket Server] DELETE_ENTITY completed successfully");
366
- const response = {
367
- type: "DELETE_SUCCESS",
368
- payload: { success: true },
369
- requestId
370
- };
371
- wsDebug("🗑️ [WebSocket Server] Sending DELETE_ENTITY_SUCCESS response");
372
- ws.send(JSON.stringify(response));
373
- }
374
- break;
375
- case "CHECK_UNIQUE_FIELD":
376
- {
377
- wsDebug("🔍 [WebSocket Server] Processing CHECK_UNIQUE_FIELD request");
378
- const { path, name, value, id, collection } = payload;
379
- const isUnique = await (await getScopedDelegate()).checkUniqueField(path, name, value, id, collection);
380
- wsDebug("🔍 [WebSocket Server] CHECK_UNIQUE_FIELD result:", isUnique);
381
- const response = {
382
- type: "CHECK_UNIQUE_FIELD_SUCCESS",
383
- payload: { isUnique },
384
- requestId
385
- };
386
- wsDebug("🔍 [WebSocket Server] Sending CHECK_UNIQUE_FIELD_SUCCESS response");
387
- ws.send(JSON.stringify(response));
388
- }
389
- break;
390
- case "COUNT":
391
- {
392
- const request = payload;
393
- const response = {
394
- type: "COUNT_SUCCESS",
395
- payload: { count: await (await getScopedDelegate()).count(request) },
396
- requestId
397
- };
398
- ws.send(JSON.stringify(response));
399
- }
400
- break;
401
- case "EXECUTE_SQL":
402
- {
403
- const { sql, options } = payload;
404
- try {
405
- const admin = (await getScopedDelegate()).admin;
406
- if (!isSQLAdmin(admin)) {
407
- sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
408
- break;
409
- }
410
- const result = await admin.executeSql(sql, options);
411
- if (process.env.NODE_ENV !== "production") wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
412
- const auditSession = clientSessions.get(clientId);
413
- logger.info("[SQL Audit] WebSocket SQL execution", {
414
- sql: typeof sql === "string" ? sql.substring(0, 500) : String(sql),
415
- database: options?.database,
416
- role: options?.role,
417
- paramCount: Array.isArray(options?.params) ? options.params.length : 0,
418
- resultRows: Array.isArray(result) ? result.length : "unknown",
419
- uid: auditSession?.user?.uid ?? "unknown",
420
- roles: auditSession?.user?.roles ?? [],
421
- isAdmin: auditSession?.user?.isAdmin ?? false,
422
- requestId
423
- });
424
- const response = {
425
- type: "EXECUTE_SQL_SUCCESS",
426
- payload: { result },
427
- requestId
428
- };
429
- ws.send(JSON.stringify(response));
430
- } catch (sqlError) {
431
- sendError("ERROR", "SQL_ERROR", extractErrorMessage(sqlError));
432
- }
433
- }
434
- break;
435
- case "FETCH_DATABASES":
436
- {
437
- wsDebug("📚 [WebSocket Server] Processing FETCH_DATABASES request");
438
- const admin = (await getScopedDelegate()).admin;
439
- let databases = [];
440
- if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) databases = await admin.fetchAvailableDatabases();
441
- wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);
442
- const response = {
443
- type: "FETCH_DATABASES_SUCCESS",
444
- payload: { databases },
445
- requestId
446
- };
447
- ws.send(JSON.stringify(response));
448
- }
449
- break;
450
- case "FETCH_ROLES":
451
- {
452
- wsDebug("👤 [WebSocket Server] Processing FETCH_ROLES request");
453
- const admin = (await getScopedDelegate()).admin;
454
- let roles = [];
455
- if (isSQLAdmin(admin) && admin.fetchAvailableRoles) roles = await admin.fetchAvailableRoles();
456
- wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);
457
- const response = {
458
- type: "FETCH_ROLES_SUCCESS",
459
- payload: { roles },
460
- requestId
461
- };
462
- ws.send(JSON.stringify(response));
463
- }
464
- break;
465
- case "FETCH_APPLICATION_ROLES":
466
- {
467
- wsDebug("👤 [WebSocket Server] Processing FETCH_APPLICATION_ROLES request");
468
- const admin = (await getScopedDelegate()).admin;
469
- let roles = [];
470
- if (isSQLAdmin(admin) && admin.fetchApplicationRoles) roles = await admin.fetchApplicationRoles();
471
- wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} application roles.`);
472
- const response = {
473
- type: "FETCH_APPLICATION_ROLES_SUCCESS",
474
- payload: { roles },
475
- requestId
476
- };
477
- ws.send(JSON.stringify(response));
478
- }
479
- break;
480
- case "FETCH_CURRENT_DATABASE":
481
- {
482
- wsDebug("📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request");
483
- const admin = (await getScopedDelegate()).admin;
484
- let database = void 0;
485
- if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) database = await admin.fetchCurrentDatabase();
486
- const response = {
487
- type: "FETCH_CURRENT_DATABASE_SUCCESS",
488
- payload: { database },
489
- requestId
490
- };
491
- ws.send(JSON.stringify(response));
492
- }
493
- break;
494
- case "FETCH_UNMAPPED_TABLES":
495
- {
496
- wsDebug("📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request");
497
- const admin = (await getScopedDelegate()).admin;
498
- let tables = [];
499
- if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) tables = await admin.fetchUnmappedTables(payload?.mappedPaths);
500
- wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);
501
- const response = {
502
- type: "FETCH_UNMAPPED_TABLES_SUCCESS",
503
- payload: { tables },
504
- requestId
505
- };
506
- ws.send(JSON.stringify(response));
507
- }
508
- break;
509
- case "FETCH_TABLE_METADATA":
510
- {
511
- wsDebug("📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request");
512
- const { tableName } = payload;
513
- const admin = (await getScopedDelegate()).admin;
514
- let metadata;
515
- if (isSchemaAdmin(admin) && admin.fetchTableMetadata) metadata = await admin.fetchTableMetadata(tableName);
516
- wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);
517
- const response = {
518
- type: "FETCH_TABLE_METADATA_SUCCESS",
519
- payload: { metadata },
520
- requestId
521
- };
522
- ws.send(JSON.stringify(response));
523
- }
524
- break;
525
- case "CREATE_BRANCH":
526
- {
527
- wsDebug("🌿 [WebSocket Server] Processing CREATE_BRANCH request");
528
- const { name, options } = payload;
529
- const delegate = await getScopedDelegate();
530
- if (!delegate.admin?.createBranch) {
531
- sendError("ERROR", "NOT_SUPPORTED", "Database branching is not available. Configure adminConnectionString.");
532
- break;
533
- }
534
- const branch = await delegate.admin.createBranch(name, options);
535
- wsDebug(`🌿 [WebSocket Server] Branch created: ${branch.name}`);
536
- const response = {
537
- type: "CREATE_BRANCH_SUCCESS",
538
- payload: { branch },
539
- requestId
540
- };
541
- ws.send(JSON.stringify(response));
542
- }
543
- break;
544
- case "DELETE_BRANCH":
545
- {
546
- wsDebug("🗑️ [WebSocket Server] Processing DELETE_BRANCH request");
547
- const { name: branchName } = payload;
548
- const delegate = await getScopedDelegate();
549
- if (!delegate.admin?.deleteBranch) {
550
- sendError("ERROR", "NOT_SUPPORTED", "Database branching is not available.");
551
- break;
552
- }
553
- await delegate.admin.deleteBranch(branchName);
554
- wsDebug(`🗑️ [WebSocket Server] Branch deleted: ${branchName}`);
555
- const response = {
556
- type: "DELETE_BRANCH_SUCCESS",
557
- payload: { success: true },
558
- requestId
559
- };
560
- ws.send(JSON.stringify(response));
561
- }
562
- break;
563
- case "LIST_BRANCHES":
564
- {
565
- wsDebug("🌿 [WebSocket Server] Processing LIST_BRANCHES request");
566
- const delegate = await getScopedDelegate();
567
- let branches = [];
568
- if (delegate.admin?.listBranches) branches = await delegate.admin.listBranches();
569
- wsDebug(`🌿 [WebSocket Server] Listed ${branches.length} branches.`);
570
- const response = {
571
- type: "LIST_BRANCHES_SUCCESS",
572
- payload: { branches },
573
- requestId
574
- };
575
- ws.send(JSON.stringify(response));
576
- }
577
- break;
578
- case "subscribe_collection":
579
- case "subscribe_one":
580
- case "unsubscribe":
581
- case "join_channel":
582
- case "leave_channel":
583
- case "broadcast":
584
- case "presence_track":
585
- case "presence_untrack":
586
- case "presence_state":
587
- case "channel_history": {
588
- wsDebug("🔄 [WebSocket Server] Routing realtime message to RealtimeService:", type);
589
- const session = clientSessions.get(clientId);
590
- const authContext = session?.user ? {
591
- uid: session.user.uid,
592
- roles: session.user.roles ?? []
593
- } : {
594
- uid: ANONYMOUS_USER_ID,
595
- roles: ["anon"]
596
- };
597
- await realtimeService.handleClientMessage(clientId, {
598
- type,
599
- payload,
600
- subscriptionId: payload?.subscriptionId
601
- }, authContext);
602
- break;
603
- }
604
- default: logger.error("❌ [WebSocket Server] Unknown message type", { detail: type });
605
- }
606
- } catch (error) {
607
- if (error instanceof ListLimitError) {
608
- logger.warn(`[WebSocket Server] Refused a list read: ${error.message}`);
609
- ws.send(JSON.stringify({
610
- type: "ERROR",
611
- requestId,
612
- payload: { error: {
613
- message: error.message,
614
- code: "INVALID_LIMIT"
615
- } }
616
- }));
617
- return;
618
- }
619
- if (error instanceof ApiError || error?.name === "ApiError") {
620
- const apiError = error;
621
- logger.warn(`[WebSocket Server] Refused a write: ${apiError.message}`);
622
- ws.send(JSON.stringify({
623
- type: "ERROR",
624
- requestId,
625
- payload: { error: {
626
- message: apiError.message,
627
- code: apiError.code
628
- } }
629
- }));
630
- return;
631
- }
632
- logger.error("💥 [WebSocket Server] Error handling message", { error });
633
- if (error instanceof Error) logger.error("Stack trace", { detail: error.stack });
634
- const errorMessage = process.env.NODE_ENV === "production" ? "An unexpected error occurred" : extractErrorMessage(error);
635
- const errorResponse = {
636
- type: "ERROR",
637
- requestId,
638
- payload: { error: {
639
- message: errorMessage,
640
- code: "INTERNAL_ERROR"
641
- } }
642
- };
643
- ws.send(JSON.stringify(errorResponse));
644
- }
645
- });
646
- });
647
- }
648
- //#endregion
649
- export { websocket_exports as i, PUBLIC_TYPES as n, createPostgresWebSocket as r, ADMIN_ONLY_TYPES as t };
650
-
651
- //# sourceMappingURL=websocket-D0YNv8hp.js.map