@rebasepro/server-postgres 0.16.0 → 0.16.1-canary.g0d7af95

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