@rebasepro/server-postgres 0.12.0 → 0.12.1-canary.g009ed95

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 (91) hide show
  1. package/dist/PostgresBackendDriver.d.ts +1 -1
  2. package/dist/PostgresBootstrapper.d.ts +25 -1
  3. package/dist/auth/services.d.ts +21 -0
  4. package/dist/backup/backup-service.d.ts +10 -1
  5. package/dist/backup/pg-tools.d.ts +47 -0
  6. package/dist/backup-service-CD8o_1Sl.js +8999 -0
  7. package/dist/backup-service-CD8o_1Sl.js.map +1 -0
  8. package/dist/cli-helpers.d.ts +39 -0
  9. package/dist/connection-BuZ97wsr.js +250 -0
  10. package/dist/connection-BuZ97wsr.js.map +1 -0
  11. package/dist/connection.d.ts +42 -0
  12. package/dist/ensure-collection-policies-BrUVgjz3.js +57 -0
  13. package/dist/ensure-collection-policies-BrUVgjz3.js.map +1 -0
  14. package/dist/ensure-collection-tables-Da2oGkX2.js +650 -0
  15. package/dist/ensure-collection-tables-Da2oGkX2.js.map +1 -0
  16. package/dist/index.es.js +816 -9679
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/policy-CeA1JcxP.js +105 -0
  19. package/dist/policy-CeA1JcxP.js.map +1 -0
  20. package/dist/schema/auth-schema.d.ts +83 -144
  21. package/dist/schema/ensure-collection-policies.d.ts +60 -0
  22. package/dist/schema/ensure-collection-tables.d.ts +44 -2
  23. package/dist/schema/generate-postgres-ddl-logic.d.ts +135 -1
  24. package/dist/schema/introspect-db-constraints.d.ts +57 -0
  25. package/dist/schema/introspect-db-logic.d.ts +94 -5
  26. package/dist/schema/introspect-db-queries.d.ts +119 -0
  27. package/dist/schema/introspect-db-structure.d.ts +263 -0
  28. package/dist/schema/introspect-db-types.d.ts +11 -0
  29. package/dist/services/FetchService.d.ts +4 -1
  30. package/dist/services/RelationService.d.ts +24 -1
  31. package/dist/services/channel-bus/index.d.ts +1 -7
  32. package/dist/services/collection-helpers.d.ts +24 -1
  33. package/dist/services/dataService.d.ts +3 -1
  34. package/dist/services/row-pipeline.d.ts +1 -1
  35. package/dist/{src-BbFOPJ1S.js → src-CzbghKwf.js} +271 -173
  36. package/dist/src-CzbghKwf.js.map +1 -0
  37. package/dist/{src-Zqwaw3P5.js → src-DoU9yPqq.js} +3 -159
  38. package/dist/src-DoU9yPqq.js.map +1 -0
  39. package/dist/utils/connection-string.d.ts +29 -0
  40. package/dist/utils/drizzle-conditions.d.ts +5 -4
  41. package/dist/utils/pg-error-utils.d.ts +19 -0
  42. package/dist/websocket-B2LsrINK.js +530 -0
  43. package/dist/websocket-B2LsrINK.js.map +1 -0
  44. package/package.json +14 -14
  45. package/src/PostgresAdapter.ts +21 -2
  46. package/src/PostgresBackendDriver.ts +4 -0
  47. package/src/PostgresBootstrapper.ts +192 -33
  48. package/src/auth/ensure-tables.ts +164 -9
  49. package/src/auth/services.ts +24 -2
  50. package/src/backup/backup-cli.ts +41 -2
  51. package/src/backup/backup-service.ts +38 -5
  52. package/src/backup/pg-tools.ts +96 -3
  53. package/src/cli-helpers.ts +70 -0
  54. package/src/cli.ts +44 -26
  55. package/src/collections/validate-relations.ts +15 -0
  56. package/src/connection.ts +73 -0
  57. package/src/data-transformer.ts +9 -3
  58. package/src/databasePoolManager.ts +5 -2
  59. package/src/schema/auth-schema.ts +30 -19
  60. package/src/schema/ensure-collection-policies.ts +105 -0
  61. package/src/schema/ensure-collection-tables.test.ts +105 -9
  62. package/src/schema/ensure-collection-tables.ts +220 -32
  63. package/src/schema/generate-drizzle-schema-logic.ts +23 -6
  64. package/src/schema/generate-postgres-ddl-logic.ts +382 -19
  65. package/src/schema/introspect-db-constraints.ts +385 -0
  66. package/src/schema/introspect-db-inference.ts +18 -8
  67. package/src/schema/introspect-db-logic.ts +385 -71
  68. package/src/schema/introspect-db-queries.ts +326 -0
  69. package/src/schema/introspect-db-structure.ts +670 -0
  70. package/src/schema/introspect-db-types.ts +56 -0
  71. package/src/schema/introspect-db.ts +37 -80
  72. package/src/schema/introspect-runtime.test.ts +56 -8
  73. package/src/schema/introspect-runtime.ts +31 -9
  74. package/src/security/policy-drift.test.ts +11 -3
  75. package/src/services/FetchService.ts +76 -14
  76. package/src/services/PersistService.ts +20 -6
  77. package/src/services/RelationService.ts +249 -48
  78. package/src/services/channel-bus/index.ts +0 -9
  79. package/src/services/collection-helpers.ts +40 -1
  80. package/src/services/dataService.ts +3 -1
  81. package/src/services/realtimeService.ts +3 -3
  82. package/src/services/row-pipeline.ts +1 -1
  83. package/src/utils/connection-string.ts +58 -0
  84. package/src/utils/drizzle-conditions.ts +31 -6
  85. package/src/utils/pg-error-utils.ts +46 -0
  86. package/src/websocket.ts +18 -9
  87. package/dist/chunk-DSJWtz9O.js +0 -40
  88. package/dist/ensure-collection-tables-CNTcZGvn.js +0 -304
  89. package/dist/ensure-collection-tables-CNTcZGvn.js.map +0 -1
  90. package/dist/src-BbFOPJ1S.js.map +0 -1
  91. package/dist/src-Zqwaw3P5.js.map +0 -1
@@ -0,0 +1,530 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ import process from "process";
3
+ __createRequire(import.meta.url);
4
+ import { c as __exportAll } from "./connection-BuZ97wsr.js";
5
+ import "./src-DoU9yPqq.js";
6
+ import { t as ANONYMOUS_USER_ID } from "./policy-CeA1JcxP.js";
7
+ import { 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({ createPostgresWebSocket: () => createPostgresWebSocket });
28
+ /** Maximum messages per client per window */
29
+ var WS_RATE_LIMIT = 2e3;
30
+ /** Rate limit window in milliseconds (60 seconds) */
31
+ var WS_RATE_WINDOW_MS = 6e4;
32
+ /** Admin-only WebSocket message types */
33
+ var ADMIN_ONLY_TYPES = /* @__PURE__ */ new Set([
34
+ "EXECUTE_SQL",
35
+ "FETCH_DATABASES",
36
+ "FETCH_ROLES",
37
+ "FETCH_UNMAPPED_TABLES",
38
+ "FETCH_TABLE_METADATA",
39
+ "FETCH_CURRENT_DATABASE",
40
+ "CREATE_BRANCH",
41
+ "DELETE_BRANCH",
42
+ "LIST_BRANCHES"
43
+ ]);
44
+ /**
45
+ * Recursively extract the deepest error message from an error's cause chain (e.g., Drizzle wrapping a PG error).
46
+ */
47
+ function extractErrorMessage(error) {
48
+ if (!error) return "Unknown error";
49
+ if (error instanceof Error) {
50
+ if ("cause" in error && error.cause) return extractErrorMessage(error.cause);
51
+ return error.message;
52
+ }
53
+ if (typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
54
+ return String(error);
55
+ }
56
+ /**
57
+ * Check if the current session belongs to an admin user.
58
+ */
59
+ function isAdminSession(session) {
60
+ if (!session?.user) return false;
61
+ if (session.user.isAdmin) return true;
62
+ if (!session.user.roles) return false;
63
+ return session.user.roles.some((r) => r === "admin");
64
+ }
65
+ function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
66
+ const clientSessions = /* @__PURE__ */ new Map();
67
+ const isProduction = process.env.NODE_ENV === "production";
68
+ /** Debug logger that is suppressed in production to prevent PII/data leaks */
69
+ const wsDebug = (...args) => {
70
+ if (!isProduction) console.debug(...args);
71
+ };
72
+ const wss = new WebSocketServer({ server });
73
+ wss.on("error", (err) => {
74
+ if (err.code === "EADDRINUSE") return;
75
+ logger.error("❌ [WebSocket Server] Error", { error: err });
76
+ });
77
+ const requireAuth = !!authAdapter || resolveRequireAuth(authConfig);
78
+ 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.");
79
+ wss.on("connection", (ws) => {
80
+ const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
81
+ wsDebug(`WebSocket client connected: ${clientId}`);
82
+ clientSessions.set(clientId, {
83
+ ws,
84
+ authenticated: !requireAuth,
85
+ messageCount: 0,
86
+ messageWindowStart: Date.now()
87
+ });
88
+ realtimeService.addClient(clientId, ws);
89
+ ws.on("close", () => {
90
+ wsDebug(`WebSocket client disconnected: ${clientId}`);
91
+ clientSessions.delete(clientId);
92
+ });
93
+ ws.on("message", async (message) => {
94
+ let requestId;
95
+ try {
96
+ const { type, payload, requestId: reqId } = JSON.parse(message.toString());
97
+ requestId = reqId;
98
+ wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : "");
99
+ const sendError = (errType, code, msg) => {
100
+ ws.send(JSON.stringify({
101
+ type: errType,
102
+ requestId,
103
+ payload: { error: {
104
+ message: msg,
105
+ code
106
+ } }
107
+ }));
108
+ };
109
+ if (type === "AUTHENTICATE") {
110
+ const { token } = payload || {};
111
+ if (!token) {
112
+ sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
113
+ return;
114
+ }
115
+ let verifiedUser = null;
116
+ if (authAdapter) try {
117
+ const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", { headers: { Authorization: `Bearer ${token}` } }));
118
+ if (adapterUser) verifiedUser = {
119
+ uid: adapterUser.uid,
120
+ roles: adapterUser.roles,
121
+ isAdmin: adapterUser.isAdmin
122
+ };
123
+ } catch {}
124
+ else if (authConfig?.serviceKey && safeCompare(token, authConfig.serviceKey)) verifiedUser = {
125
+ uid: "service",
126
+ roles: ["admin"],
127
+ isAdmin: true
128
+ };
129
+ else {
130
+ const jwtPayload = extractUserFromToken(token);
131
+ if (jwtPayload) verifiedUser = {
132
+ uid: jwtPayload.uid,
133
+ roles: jwtPayload.roles ?? [],
134
+ isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
135
+ };
136
+ }
137
+ if (verifiedUser) {
138
+ const session = clientSessions.get(clientId);
139
+ if (session) {
140
+ session.user = verifiedUser;
141
+ session.authenticated = true;
142
+ }
143
+ wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
144
+ ws.send(JSON.stringify({
145
+ type: "AUTH_SUCCESS",
146
+ requestId,
147
+ payload: {
148
+ uid: verifiedUser.uid,
149
+ roles: verifiedUser.roles
150
+ }
151
+ }));
152
+ wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.uid}`);
153
+ } else {
154
+ wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
155
+ sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
156
+ }
157
+ return;
158
+ }
159
+ if (requireAuth) {
160
+ if (!clientSessions.get(clientId)?.authenticated) {
161
+ sendError("ERROR", "UNAUTHORIZED", "Authentication required");
162
+ return;
163
+ }
164
+ }
165
+ {
166
+ const session = clientSessions.get(clientId);
167
+ if (session) {
168
+ const now = Date.now();
169
+ if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {
170
+ session.messageCount = 0;
171
+ session.messageWindowStart = now;
172
+ }
173
+ session.messageCount++;
174
+ if (session.messageCount > WS_RATE_LIMIT) {
175
+ sendError("ERROR", "RATE_LIMITED", "Too many requests. Please slow down.");
176
+ return;
177
+ }
178
+ }
179
+ }
180
+ if (ADMIN_ONLY_TYPES.has(type)) {
181
+ if (!isAdminSession(clientSessions.get(clientId))) {
182
+ sendError("ERROR", "FORBIDDEN", "Admin access required for this operation");
183
+ return;
184
+ }
185
+ }
186
+ const getScopedDelegate = async () => {
187
+ const session = clientSessions.get(clientId);
188
+ if (typeof driver.withAuth === "function") try {
189
+ const userForAuth = session?.user ? {
190
+ uid: session.user.uid,
191
+ displayName: null,
192
+ email: null,
193
+ photoURL: null,
194
+ providerId: "websocket",
195
+ isAnonymous: false,
196
+ roles: session.user.roles ?? []
197
+ } : {
198
+ uid: ANONYMOUS_USER_ID,
199
+ displayName: null,
200
+ email: null,
201
+ photoURL: null,
202
+ providerId: "websocket",
203
+ isAnonymous: true,
204
+ roles: ["anon"]
205
+ };
206
+ return await driver.withAuth(userForAuth);
207
+ } catch (e) {
208
+ logger.error("Failed to create RLS scoped delegate for WS request", { error: e });
209
+ throw new Error("Internal authentication error");
210
+ }
211
+ return driver;
212
+ };
213
+ switch (type) {
214
+ case "FETCH_COLLECTION":
215
+ {
216
+ wsDebug("📋 [WebSocket Server] Processing FETCH_COLLECTION request");
217
+ const request = payload;
218
+ const rows = await (await getScopedDelegate()).fetchCollection(request);
219
+ wsDebug("📋 [WebSocket Server] FETCH_COLLECTION result - rows count:", rows.length);
220
+ const response = {
221
+ type: "FETCH_COLLECTION_SUCCESS",
222
+ payload: { rows },
223
+ requestId
224
+ };
225
+ wsDebug("📋 [WebSocket Server] Sending FETCH_COLLECTION_SUCCESS response");
226
+ ws.send(JSON.stringify(response));
227
+ }
228
+ break;
229
+ case "FETCH_ONE":
230
+ {
231
+ wsDebug("📄 [WebSocket Server] Processing FETCH_ENTITY request");
232
+ const request = payload;
233
+ const row = await (await getScopedDelegate()).fetchOne(request);
234
+ wsDebug("📄 [WebSocket Server] FETCH_ENTITY result:", row);
235
+ const response = {
236
+ type: "FETCH_ONE_SUCCESS",
237
+ payload: { row: row ?? null },
238
+ requestId
239
+ };
240
+ wsDebug("📄 [WebSocket Server] Sending FETCH_ENTITY_SUCCESS response");
241
+ ws.send(JSON.stringify(response));
242
+ }
243
+ break;
244
+ case "SAVE":
245
+ {
246
+ wsDebug("💾 [WebSocket Server] Processing SAVE_ENTITY request");
247
+ const request = payload;
248
+ wsDebug("💾 [WebSocket Server] Saving row with request:", inspect(request, {
249
+ depth: null,
250
+ colors: true
251
+ }));
252
+ const row = await (await getScopedDelegate()).save(request);
253
+ wsDebug("💾 [WebSocket Server] SAVE_ENTITY result:", inspect(row, {
254
+ depth: null,
255
+ colors: true
256
+ }));
257
+ const response = {
258
+ type: "SAVE_SUCCESS",
259
+ payload: { row },
260
+ requestId
261
+ };
262
+ wsDebug("💾 [WebSocket Server] Sending SAVE_ENTITY_SUCCESS response");
263
+ ws.send(JSON.stringify(response));
264
+ }
265
+ break;
266
+ case "DELETE":
267
+ {
268
+ wsDebug("🗑️ [WebSocket Server] Processing DELETE_ENTITY request");
269
+ const request = payload;
270
+ wsDebug("🗑️ [WebSocket Server] Deleting row:", request.row);
271
+ await (await getScopedDelegate()).delete(request);
272
+ wsDebug("🗑️ [WebSocket Server] DELETE_ENTITY completed successfully");
273
+ const response = {
274
+ type: "DELETE_SUCCESS",
275
+ payload: { success: true },
276
+ requestId
277
+ };
278
+ wsDebug("🗑️ [WebSocket Server] Sending DELETE_ENTITY_SUCCESS response");
279
+ ws.send(JSON.stringify(response));
280
+ }
281
+ break;
282
+ case "CHECK_UNIQUE_FIELD":
283
+ {
284
+ wsDebug("🔍 [WebSocket Server] Processing CHECK_UNIQUE_FIELD request");
285
+ const { path, name, value, id, collection } = payload;
286
+ const isUnique = await (await getScopedDelegate()).checkUniqueField(path, name, value, id, collection);
287
+ wsDebug("🔍 [WebSocket Server] CHECK_UNIQUE_FIELD result:", isUnique);
288
+ const response = {
289
+ type: "CHECK_UNIQUE_FIELD_SUCCESS",
290
+ payload: { isUnique },
291
+ requestId
292
+ };
293
+ wsDebug("🔍 [WebSocket Server] Sending CHECK_UNIQUE_FIELD_SUCCESS response");
294
+ ws.send(JSON.stringify(response));
295
+ }
296
+ break;
297
+ case "COUNT":
298
+ {
299
+ const request = payload;
300
+ const response = {
301
+ type: "COUNT_SUCCESS",
302
+ payload: { count: await (await getScopedDelegate()).count(request) },
303
+ requestId
304
+ };
305
+ ws.send(JSON.stringify(response));
306
+ }
307
+ break;
308
+ case "EXECUTE_SQL":
309
+ {
310
+ const { sql, options } = payload;
311
+ try {
312
+ const admin = (await getScopedDelegate()).admin;
313
+ if (!isSQLAdmin(admin)) {
314
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
315
+ break;
316
+ }
317
+ const result = await admin.executeSql(sql, options);
318
+ if (process.env.NODE_ENV !== "production") wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
319
+ const auditSession = clientSessions.get(clientId);
320
+ console.log("[SQL Audit] WebSocket SQL execution", JSON.stringify({
321
+ sql: typeof sql === "string" ? sql.substring(0, 500) : sql,
322
+ options,
323
+ resultRows: Array.isArray(result) ? result.length : "unknown",
324
+ uid: auditSession?.user?.uid ?? "unknown",
325
+ roles: auditSession?.user?.roles ?? [],
326
+ isAdmin: auditSession?.user?.isAdmin ?? false
327
+ }));
328
+ const response = {
329
+ type: "EXECUTE_SQL_SUCCESS",
330
+ payload: { result },
331
+ requestId
332
+ };
333
+ ws.send(JSON.stringify(response));
334
+ } catch (sqlError) {
335
+ sendError("ERROR", "SQL_ERROR", extractErrorMessage(sqlError));
336
+ }
337
+ }
338
+ break;
339
+ case "FETCH_DATABASES":
340
+ {
341
+ wsDebug("📚 [WebSocket Server] Processing FETCH_DATABASES request");
342
+ const admin = (await getScopedDelegate()).admin;
343
+ let databases = [];
344
+ if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) databases = await admin.fetchAvailableDatabases();
345
+ wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);
346
+ const response = {
347
+ type: "FETCH_DATABASES_SUCCESS",
348
+ payload: { databases },
349
+ requestId
350
+ };
351
+ ws.send(JSON.stringify(response));
352
+ }
353
+ break;
354
+ case "FETCH_ROLES":
355
+ {
356
+ wsDebug("👤 [WebSocket Server] Processing FETCH_ROLES request");
357
+ const admin = (await getScopedDelegate()).admin;
358
+ let roles = [];
359
+ if (isSQLAdmin(admin) && admin.fetchAvailableRoles) roles = await admin.fetchAvailableRoles();
360
+ wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);
361
+ const response = {
362
+ type: "FETCH_ROLES_SUCCESS",
363
+ payload: { roles },
364
+ requestId
365
+ };
366
+ ws.send(JSON.stringify(response));
367
+ }
368
+ break;
369
+ case "FETCH_APPLICATION_ROLES":
370
+ {
371
+ wsDebug("👤 [WebSocket Server] Processing FETCH_APPLICATION_ROLES request");
372
+ const admin = (await getScopedDelegate()).admin;
373
+ let roles = [];
374
+ if (isSQLAdmin(admin) && admin.fetchApplicationRoles) roles = await admin.fetchApplicationRoles();
375
+ wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} application roles.`);
376
+ const response = {
377
+ type: "FETCH_APPLICATION_ROLES_SUCCESS",
378
+ payload: { roles },
379
+ requestId
380
+ };
381
+ ws.send(JSON.stringify(response));
382
+ }
383
+ break;
384
+ case "FETCH_CURRENT_DATABASE":
385
+ {
386
+ wsDebug("📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request");
387
+ const admin = (await getScopedDelegate()).admin;
388
+ let database = void 0;
389
+ if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) database = await admin.fetchCurrentDatabase();
390
+ const response = {
391
+ type: "FETCH_CURRENT_DATABASE_SUCCESS",
392
+ payload: { database },
393
+ requestId
394
+ };
395
+ ws.send(JSON.stringify(response));
396
+ }
397
+ break;
398
+ case "FETCH_UNMAPPED_TABLES":
399
+ {
400
+ wsDebug("📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request");
401
+ const admin = (await getScopedDelegate()).admin;
402
+ let tables = [];
403
+ if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) tables = await admin.fetchUnmappedTables(payload?.mappedPaths);
404
+ wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);
405
+ const response = {
406
+ type: "FETCH_UNMAPPED_TABLES_SUCCESS",
407
+ payload: { tables },
408
+ requestId
409
+ };
410
+ ws.send(JSON.stringify(response));
411
+ }
412
+ break;
413
+ case "FETCH_TABLE_METADATA":
414
+ {
415
+ wsDebug("📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request");
416
+ const { tableName } = payload;
417
+ const admin = (await getScopedDelegate()).admin;
418
+ let metadata;
419
+ if (isSchemaAdmin(admin) && admin.fetchTableMetadata) metadata = await admin.fetchTableMetadata(tableName);
420
+ wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);
421
+ const response = {
422
+ type: "FETCH_TABLE_METADATA_SUCCESS",
423
+ payload: { metadata },
424
+ requestId
425
+ };
426
+ ws.send(JSON.stringify(response));
427
+ }
428
+ break;
429
+ case "CREATE_BRANCH":
430
+ {
431
+ wsDebug("🌿 [WebSocket Server] Processing CREATE_BRANCH request");
432
+ const { name, options } = payload;
433
+ const delegate = await getScopedDelegate();
434
+ if (!delegate.admin?.createBranch) {
435
+ sendError("ERROR", "NOT_SUPPORTED", "Database branching is not available. Configure adminConnectionString.");
436
+ break;
437
+ }
438
+ const branch = await delegate.admin.createBranch(name, options);
439
+ wsDebug(`🌿 [WebSocket Server] Branch created: ${branch.name}`);
440
+ const response = {
441
+ type: "CREATE_BRANCH_SUCCESS",
442
+ payload: { branch },
443
+ requestId
444
+ };
445
+ ws.send(JSON.stringify(response));
446
+ }
447
+ break;
448
+ case "DELETE_BRANCH":
449
+ {
450
+ wsDebug("🗑️ [WebSocket Server] Processing DELETE_BRANCH request");
451
+ const { name: branchName } = payload;
452
+ const delegate = await getScopedDelegate();
453
+ if (!delegate.admin?.deleteBranch) {
454
+ sendError("ERROR", "NOT_SUPPORTED", "Database branching is not available.");
455
+ break;
456
+ }
457
+ await delegate.admin.deleteBranch(branchName);
458
+ wsDebug(`🗑️ [WebSocket Server] Branch deleted: ${branchName}`);
459
+ const response = {
460
+ type: "DELETE_BRANCH_SUCCESS",
461
+ payload: { success: true },
462
+ requestId
463
+ };
464
+ ws.send(JSON.stringify(response));
465
+ }
466
+ break;
467
+ case "LIST_BRANCHES":
468
+ {
469
+ wsDebug("🌿 [WebSocket Server] Processing LIST_BRANCHES request");
470
+ const delegate = await getScopedDelegate();
471
+ let branches = [];
472
+ if (delegate.admin?.listBranches) branches = await delegate.admin.listBranches();
473
+ wsDebug(`🌿 [WebSocket Server] Listed ${branches.length} branches.`);
474
+ const response = {
475
+ type: "LIST_BRANCHES_SUCCESS",
476
+ payload: { branches },
477
+ requestId
478
+ };
479
+ ws.send(JSON.stringify(response));
480
+ }
481
+ break;
482
+ case "subscribe_collection":
483
+ case "subscribe_one":
484
+ case "unsubscribe":
485
+ case "join_channel":
486
+ case "leave_channel":
487
+ case "broadcast":
488
+ case "presence_track":
489
+ case "presence_untrack":
490
+ case "presence_state":
491
+ case "channel_history": {
492
+ wsDebug("🔄 [WebSocket Server] Routing realtime message to RealtimeService:", type);
493
+ const session = clientSessions.get(clientId);
494
+ const authContext = session?.user ? {
495
+ uid: session.user.uid,
496
+ roles: session.user.roles ?? []
497
+ } : {
498
+ uid: ANONYMOUS_USER_ID,
499
+ roles: ["anon"]
500
+ };
501
+ await realtimeService.handleClientMessage(clientId, {
502
+ type,
503
+ payload,
504
+ subscriptionId: payload?.subscriptionId
505
+ }, authContext);
506
+ break;
507
+ }
508
+ default: logger.error("❌ [WebSocket Server] Unknown message type", { detail: type });
509
+ }
510
+ } catch (error) {
511
+ logger.error("💥 [WebSocket Server] Error handling message", { error });
512
+ if (error instanceof Error) logger.error("Stack trace", { detail: error.stack });
513
+ const errorMessage = process.env.NODE_ENV === "production" ? "An unexpected error occurred" : extractErrorMessage(error);
514
+ const errorResponse = {
515
+ type: "ERROR",
516
+ requestId,
517
+ payload: { error: {
518
+ message: errorMessage,
519
+ code: "INTERNAL_ERROR"
520
+ } }
521
+ };
522
+ ws.send(JSON.stringify(errorResponse));
523
+ }
524
+ });
525
+ });
526
+ }
527
+ //#endregion
528
+ export { websocket_exports as n, createPostgresWebSocket as t };
529
+
530
+ //# sourceMappingURL=websocket-B2LsrINK.js.map