@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e

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 (136) hide show
  1. package/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
  2. package/dist/common/src/util/entities.d.ts +22 -0
  3. package/dist/common/src/util/relations.d.ts +14 -4
  4. package/dist/common/src/util/resolutions.d.ts +1 -1
  5. package/dist/index.es.js +1254 -591
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +1254 -591
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
  10. package/dist/server-postgresql/src/auth/services.d.ts +7 -3
  11. package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
  12. package/dist/server-postgresql/src/connection.d.ts +34 -1
  13. package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
  14. package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
  15. package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
  16. package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
  17. package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
  18. package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
  19. package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
  20. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
  21. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
  22. package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
  23. package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
  24. package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
  25. package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
  26. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
  27. package/dist/types/src/controllers/auth.d.ts +2 -0
  28. package/dist/types/src/controllers/client.d.ts +119 -7
  29. package/dist/types/src/controllers/collection_registry.d.ts +4 -3
  30. package/dist/types/src/controllers/customization_controller.d.ts +7 -1
  31. package/dist/types/src/controllers/data.d.ts +34 -7
  32. package/dist/types/src/controllers/data_driver.d.ts +20 -28
  33. package/dist/types/src/controllers/database_admin.d.ts +2 -2
  34. package/dist/types/src/controllers/email.d.ts +34 -0
  35. package/dist/types/src/controllers/index.d.ts +1 -0
  36. package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
  37. package/dist/types/src/controllers/navigation.d.ts +5 -5
  38. package/dist/types/src/controllers/registry.d.ts +6 -3
  39. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
  40. package/dist/types/src/controllers/storage.d.ts +24 -26
  41. package/dist/types/src/rebase_context.d.ts +8 -4
  42. package/dist/types/src/types/backend.d.ts +4 -1
  43. package/dist/types/src/types/builders.d.ts +5 -4
  44. package/dist/types/src/types/chips.d.ts +1 -1
  45. package/dist/types/src/types/collections.d.ts +169 -125
  46. package/dist/types/src/types/cron.d.ts +102 -0
  47. package/dist/types/src/types/data_source.d.ts +1 -1
  48. package/dist/types/src/types/entity_actions.d.ts +8 -8
  49. package/dist/types/src/types/entity_callbacks.d.ts +15 -15
  50. package/dist/types/src/types/entity_link_builder.d.ts +1 -1
  51. package/dist/types/src/types/entity_overrides.d.ts +2 -1
  52. package/dist/types/src/types/entity_views.d.ts +8 -8
  53. package/dist/types/src/types/export_import.d.ts +3 -3
  54. package/dist/types/src/types/index.d.ts +1 -0
  55. package/dist/types/src/types/plugins.d.ts +72 -18
  56. package/dist/types/src/types/properties.d.ts +118 -33
  57. package/dist/types/src/types/relations.d.ts +1 -1
  58. package/dist/types/src/types/slots.d.ts +30 -6
  59. package/dist/types/src/types/translations.d.ts +44 -0
  60. package/dist/types/src/types/user_management_delegate.d.ts +1 -0
  61. package/drizzle-test/0000_woozy_junta.sql +6 -0
  62. package/drizzle-test/0001_youthful_arachne.sql +1 -0
  63. package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
  64. package/drizzle-test/0003_mean_king_cobra.sql +2 -0
  65. package/drizzle-test/meta/0000_snapshot.json +47 -0
  66. package/drizzle-test/meta/0001_snapshot.json +48 -0
  67. package/drizzle-test/meta/0002_snapshot.json +38 -0
  68. package/drizzle-test/meta/0003_snapshot.json +48 -0
  69. package/drizzle-test/meta/_journal.json +34 -0
  70. package/drizzle-test-out/0000_tan_trauma.sql +6 -0
  71. package/drizzle-test-out/0001_rapid_drax.sql +1 -0
  72. package/drizzle-test-out/meta/0000_snapshot.json +44 -0
  73. package/drizzle-test-out/meta/0001_snapshot.json +54 -0
  74. package/drizzle-test-out/meta/_journal.json +20 -0
  75. package/drizzle.test.config.ts +10 -0
  76. package/package.json +88 -89
  77. package/scratch.ts +41 -0
  78. package/src/PostgresBackendDriver.ts +63 -79
  79. package/src/PostgresBootstrapper.ts +7 -8
  80. package/src/auth/ensure-tables.ts +158 -86
  81. package/src/auth/services.ts +109 -50
  82. package/src/cli.ts +259 -16
  83. package/src/collections/PostgresCollectionRegistry.ts +6 -6
  84. package/src/connection.ts +70 -48
  85. package/src/data-transformer.ts +155 -116
  86. package/src/databasePoolManager.ts +6 -5
  87. package/src/history/HistoryService.ts +3 -12
  88. package/src/interfaces.ts +3 -3
  89. package/src/schema/auth-schema.ts +26 -3
  90. package/src/schema/doctor-cli.ts +47 -0
  91. package/src/schema/doctor.ts +595 -0
  92. package/src/schema/generate-drizzle-schema-logic.ts +204 -57
  93. package/src/schema/generate-drizzle-schema.ts +6 -6
  94. package/src/schema/test-schema.ts +11 -0
  95. package/src/services/BranchService.ts +5 -5
  96. package/src/services/EntityFetchService.ts +317 -188
  97. package/src/services/EntityPersistService.ts +15 -17
  98. package/src/services/RelationService.ts +299 -37
  99. package/src/services/entity-helpers.ts +39 -13
  100. package/src/services/entityService.ts +11 -9
  101. package/src/services/realtimeService.ts +58 -29
  102. package/src/utils/drizzle-conditions.ts +25 -24
  103. package/src/websocket.ts +52 -21
  104. package/test/auth-services.test.ts +131 -39
  105. package/test/batch-many-to-many-regression.test.ts +573 -0
  106. package/test/branchService.test.ts +22 -12
  107. package/test/data-transformer-hardening.test.ts +417 -0
  108. package/test/data-transformer.test.ts +175 -0
  109. package/test/doctor.test.ts +182 -0
  110. package/test/entityService.errors.test.ts +31 -16
  111. package/test/entityService.relations.test.ts +155 -59
  112. package/test/entityService.subcollection-search.test.ts +107 -57
  113. package/test/entityService.test.ts +105 -47
  114. package/test/generate-drizzle-schema.test.ts +262 -69
  115. package/test/historyService.test.ts +31 -16
  116. package/test/n-plus-one-regression.test.ts +314 -0
  117. package/test/postgresDataDriver.test.ts +260 -168
  118. package/test/realtimeService.test.ts +70 -39
  119. package/test/relation-pipeline-gaps.test.ts +637 -0
  120. package/test/relations.test.ts +492 -39
  121. package/test-drizzle-bug.ts +18 -0
  122. package/test-drizzle-out/0000_cultured_freak.sql +7 -0
  123. package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
  124. package/test-drizzle-out/meta/0000_snapshot.json +55 -0
  125. package/test-drizzle-out/meta/0001_snapshot.json +63 -0
  126. package/test-drizzle-out/meta/_journal.json +20 -0
  127. package/test-drizzle-prompt.sh +2 -0
  128. package/test-policy-prompt.sh +3 -0
  129. package/test-programmatic.ts +30 -0
  130. package/test-programmatic2.ts +59 -0
  131. package/test-schema-no-policies.ts +12 -0
  132. package/test_drizzle_mock.js +2 -2
  133. package/test_find_changed.mjs +3 -1
  134. package/test_hash.js +14 -0
  135. package/tsconfig.json +1 -1
  136. package/vite.config.ts +5 -5
@@ -6,7 +6,7 @@ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionReg
6
6
  import { ConditionBuilderStatic } from "../interfaces";
7
7
 
8
8
  /** Drizzle dynamic query builder — accepts innerJoin + where chaining */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+
10
10
  export interface DrizzleDynamicQuery {
11
11
  innerJoin(table: PgTable<any>, condition: SQL): this;
12
12
  where(condition: SQL | undefined): this;
@@ -15,10 +15,10 @@ export interface DrizzleDynamicQuery {
15
15
 
16
16
  /**
17
17
  * Unified condition builder for Drizzle/PostgreSQL queries.
18
- *
18
+ *
19
19
  * This class uses static methods and satisfies the ConditionBuilderStatic<SQL> type.
20
20
  * It translates Rebase filter conditions to Drizzle SQL conditions.
21
- *
21
+ *
22
22
  * @example
23
23
  * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;
24
24
  */
@@ -27,7 +27,7 @@ export class DrizzleConditionBuilder {
27
27
  /**
28
28
  * Build filter conditions from FilterValues
29
29
  */
30
- static buildFilterConditions<M extends Record<string, any>>(
30
+ static buildFilterConditions<M extends Record<string, unknown>>(
31
31
  filter: FilterValues<Extract<keyof M, string>>,
32
32
  table: PgTable<any>,
33
33
  collectionPath: string
@@ -104,7 +104,7 @@ export class DrizzleConditionBuilder {
104
104
  joinConditions: { table: PgTable<any>; condition: SQL }[];
105
105
  whereConditions: SQL[];
106
106
  } {
107
- console.debug(`🔍 [buildRelationConditions] Building conditions for relation:`, {
107
+ console.debug("🔍 [buildRelationConditions] Building conditions for relation:", {
108
108
  relationName: relation.relationName,
109
109
  cardinality: relation.cardinality,
110
110
  direction: relation.direction,
@@ -118,7 +118,7 @@ export class DrizzleConditionBuilder {
118
118
  const whereConditions: SQL[] = [];
119
119
 
120
120
  if (relation.joinPath && relation.joinPath.length > 0) {
121
- console.debug(`🔍 [buildRelationConditions] Using joinPath logic`);
121
+ console.debug("🔍 [buildRelationConditions] Using joinPath logic");
122
122
  // Handle join path relations
123
123
  const {
124
124
  joins,
@@ -135,7 +135,7 @@ export class DrizzleConditionBuilder {
135
135
  whereConditions.push(finalCondition);
136
136
 
137
137
  } else if (relation.through && relation.cardinality === "many" && relation.direction === "owning") {
138
- console.debug(`🔍 [buildRelationConditions] Using owning many-to-many with explicit through`);
138
+ console.debug("🔍 [buildRelationConditions] Using owning many-to-many with explicit through");
139
139
  // Handle many-to-many relations with junction table
140
140
  const junctionResult = this.buildJunctionTableConditions(
141
141
  relation.through,
@@ -147,7 +147,7 @@ export class DrizzleConditionBuilder {
147
147
  whereConditions.push(junctionResult.condition);
148
148
 
149
149
  } else if (relation.through && relation.cardinality === "many" && relation.direction === "inverse") {
150
- console.debug(`🔍 [buildRelationConditions] Using inverse many-to-many with explicit through`);
150
+ console.debug("🔍 [buildRelationConditions] Using inverse many-to-many with explicit through");
151
151
  // Handle inverse many-to-many relations with junction table
152
152
  const junctionResult = this.buildInverseJunctionTableConditions(
153
153
  relation.through,
@@ -159,12 +159,12 @@ export class DrizzleConditionBuilder {
159
159
  whereConditions.push(junctionResult.condition);
160
160
 
161
161
  } else if (relation.cardinality === "many" && relation.direction === "inverse" && !relation.through) {
162
- console.debug(`🔍 [buildRelationConditions] Handling inverse many relationship without explicit through`);
162
+ console.debug("🔍 [buildRelationConditions] Handling inverse many relationship without explicit through");
163
163
 
164
164
  // First, try to find a junction table (for many-to-many relationships)
165
165
  const junctionInfo = this.findCorrespondingJunctionTable(relation, registry);
166
166
  if (junctionInfo) {
167
- console.debug(`🔍 [buildRelationConditions] Found junction info for inverse many-to-many, building junction conditions`);
167
+ console.debug("🔍 [buildRelationConditions] Found junction info for inverse many-to-many, building junction conditions");
168
168
  const junctionResult = this.buildInverseJunctionTableConditions(
169
169
  junctionInfo,
170
170
  targetIdColumn,
@@ -174,7 +174,7 @@ export class DrizzleConditionBuilder {
174
174
  joinConditions.push(junctionResult.join);
175
175
  whereConditions.push(junctionResult.condition);
176
176
  } else if (relation.foreignKeyOnTarget) {
177
- console.debug(`🔍 [buildRelationConditions] No junction table found, treating as inverse one-to-many with foreign key on target`);
177
+ console.debug("🔍 [buildRelationConditions] No junction table found, treating as inverse one-to-many with foreign key on target");
178
178
  // This is a true inverse one-to-many relationship
179
179
  const simpleCondition = this.buildSimpleRelationCondition(
180
180
  relation,
@@ -184,11 +184,11 @@ export class DrizzleConditionBuilder {
184
184
  );
185
185
  whereConditions.push(simpleCondition);
186
186
  } else {
187
- console.error(`🔍 [buildRelationConditions] Failed to find junction table info and no foreign key specified`);
187
+ console.error("🔍 [buildRelationConditions] Failed to find junction table info and no foreign key specified");
188
188
  throw new Error(`Cannot resolve inverse many relation '${relation.relationName}'. Either specify 'through' property, ensure corresponding owning relation exists with junction table configuration, or specify 'foreignKeyOnTarget' for one-to-many relationships.`);
189
189
  }
190
190
  } else {
191
- console.debug(`🔍 [buildRelationConditions] Using simple relation logic - THIS IS WHERE THE ERROR MIGHT OCCUR`);
191
+ console.debug("🔍 [buildRelationConditions] Using simple relation logic - THIS IS WHERE THE ERROR MIGHT OCCUR");
192
192
  // Handle simple relations
193
193
  const simpleCondition = this.buildSimpleRelationCondition(
194
194
  relation,
@@ -199,7 +199,7 @@ export class DrizzleConditionBuilder {
199
199
  whereConditions.push(simpleCondition);
200
200
  }
201
201
 
202
- console.debug(`🔍 [buildRelationConditions] Final result:`, {
202
+ console.debug("🔍 [buildRelationConditions] Final result:", {
203
203
  joinConditionsCount: joinConditions.length,
204
204
  whereConditionsCount: whereConditions.length
205
205
  });
@@ -635,15 +635,16 @@ export class DrizzleConditionBuilder {
635
635
  */
636
636
  static buildSearchConditions(
637
637
  searchString: string,
638
- properties: Record<string, any>,
638
+ properties: Record<string, unknown>,
639
639
  table: PgTable<any>
640
640
  ): SQL[] {
641
641
  const searchConditions: SQL[] = [];
642
642
 
643
643
  for (const [key, prop] of Object.entries(properties)) {
644
+ const p = prop as Record<string, unknown>;
644
645
  // Only include string properties that don't have enum defined
645
646
  // PostgreSQL enum and uuid columns don't support ILIKE, so we skip them
646
- if (prop.type === "string" && !prop.enum && prop.isId !== "uuid") {
647
+ if (p.type === "string" && !p.enum && p.isId !== "uuid") {
647
648
  const fieldColumn = table[key as keyof typeof table] as AnyPgColumn;
648
649
  if (fieldColumn) {
649
650
  searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
@@ -938,7 +939,7 @@ export class DrizzleConditionBuilder {
938
939
  console.debug(`🔍 [findCorrespondingJunctionTable] Looking for junction table for inverse relation '${relation.relationName}' with inverseRelationName '${relation.inverseRelationName}'`);
939
940
 
940
941
  if (!relation.inverseRelationName) {
941
- console.debug(`🔍 [findCorrespondingJunctionTable] No inverseRelationName specified`);
942
+ console.debug("🔍 [findCorrespondingJunctionTable] No inverseRelationName specified");
942
943
  return null;
943
944
  }
944
945
 
@@ -947,8 +948,8 @@ export class DrizzleConditionBuilder {
947
948
  console.debug(`🔍 [findCorrespondingJunctionTable] Target collection: ${targetCollection.slug}`);
948
949
 
949
950
  // Find the corresponding owning relation on the target collection
950
- const targetCollectionRelations = resolveCollectionRelations(targetCollection as import("@rebasepro/types").PostgresCollection<any, any>);
951
- console.debug(`🔍 [findCorrespondingJunctionTable] Target collection relations:`, Object.keys(targetCollectionRelations));
951
+ const targetCollectionRelations = resolveCollectionRelations(targetCollection);
952
+ console.debug("🔍 [findCorrespondingJunctionTable] Target collection relations:", Object.keys(targetCollectionRelations));
952
953
 
953
954
  // Look for the owning many-to-many relation that matches our inverseRelationName
954
955
  const correspondingRelation = targetCollectionRelations[relation.inverseRelationName];
@@ -958,7 +959,7 @@ export class DrizzleConditionBuilder {
958
959
  return null;
959
960
  }
960
961
 
961
- console.debug(`🔍 [findCorrespondingJunctionTable] Found relation:`, {
962
+ console.debug("🔍 [findCorrespondingJunctionTable] Found relation:", {
962
963
  relationName: correspondingRelation.relationName,
963
964
  cardinality: correspondingRelation.cardinality,
964
965
  direction: correspondingRelation.direction,
@@ -969,21 +970,21 @@ export class DrizzleConditionBuilder {
969
970
  if (correspondingRelation.cardinality !== "many" ||
970
971
  correspondingRelation.direction !== "owning" ||
971
972
  !correspondingRelation.through) {
972
- console.debug(`🔍 [findCorrespondingJunctionTable] Relation is not an owning many-to-many with junction table`);
973
+ console.debug("🔍 [findCorrespondingJunctionTable] Relation is not an owning many-to-many with junction table");
973
974
  return null;
974
975
  }
975
976
 
976
- console.debug(`🔍 [findCorrespondingJunctionTable] Found matching owning relation with junction table!`);
977
+ console.debug("🔍 [findCorrespondingJunctionTable] Found matching owning relation with junction table!");
977
978
 
978
979
  // For inverse relation, we need to swap source and target columns
979
980
  const through = correspondingRelation.through;
980
981
  const result = {
981
982
  table: through.table,
982
983
  sourceColumn: through.targetColumn, // Swapped for inverse relation
983
- targetColumn: through.sourceColumn // Swapped for inverse relation
984
+ targetColumn: through.sourceColumn // Swapped for inverse relation
984
985
  };
985
986
 
986
- console.debug(`🔍 [findCorrespondingJunctionTable] Returning junction info:`, result);
987
+ console.debug("🔍 [findCorrespondingJunctionTable] Returning junction info:", result);
987
988
  return result;
988
989
  } catch (error) {
989
990
  console.error(`🔍 [findCorrespondingJunctionTable] Error finding corresponding junction table for relation '${relation.relationName}':`, error);
package/src/websocket.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RealtimeService } from "./services/realtimeService";
2
2
  import { PostgresBackendDriver } from "./PostgresBackendDriver";
3
- import { DataDriver, DeleteEntityProps, FetchCollectionProps, FetchEntityProps, SaveEntityProps, TableMetadata, BranchInfo } from "@rebasepro/types";
3
+ import { DataDriver, DeleteEntityProps, FetchCollectionProps, FetchEntityProps, SaveEntityProps, TableMetadata, BranchInfo, isSQLAdmin, isSchemaAdmin } from "@rebasepro/types";
4
4
  import { WebSocketServer, WebSocket } from "ws";
5
5
  import { Server } from "http";
6
6
  import { inspect } from "util";
@@ -21,7 +21,7 @@ interface ClientSession {
21
21
  const clientSessions = new Map<string, ClientSession>();
22
22
 
23
23
  /** Maximum messages per client per window */
24
- const WS_RATE_LIMIT = 200;
24
+ const WS_RATE_LIMIT = 2000;
25
25
  /** Rate limit window in milliseconds (60 seconds) */
26
26
  const WS_RATE_WINDOW_MS = 60_000;
27
27
 
@@ -61,6 +61,19 @@ export function createPostgresWebSocket(
61
61
  /** Debug logger that is suppressed in production to prevent PII/data leaks */
62
62
  const wsDebug = (...args: unknown[]) => { if (!isProduction) console.debug(...args); };
63
63
  const wss = new WebSocketServer({ server });
64
+
65
+ // Handle errors on the WSS so that EADDRINUSE from the underlying HTTP
66
+ // server doesn't surface as an unhandled 'error' event and crash the
67
+ // process. The dev-mode `listenWithPortRetry` utility handles retry
68
+ // logic on the HTTP server side — we just need the WSS not to throw.
69
+ wss.on("error", (err: NodeJS.ErrnoException) => {
70
+ if (err.code === "EADDRINUSE") {
71
+ // Silently absorbed — listenWithPortRetry will retry the next port
72
+ return;
73
+ }
74
+ console.error("❌ [WebSocket Server] Error:", err);
75
+ });
76
+
64
77
  const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
65
78
 
66
79
  wss.on("connection", (ws) => {
@@ -68,7 +81,10 @@ export function createPostgresWebSocket(
68
81
  wsDebug(`WebSocket client connected: ${clientId}`);
69
82
 
70
83
  // Initialize client session
71
- clientSessions.set(clientId, { ws, authenticated: !requireAuth, messageCount: 0, messageWindowStart: Date.now() });
84
+ clientSessions.set(clientId, { ws,
85
+ authenticated: !requireAuth,
86
+ messageCount: 0,
87
+ messageWindowStart: Date.now() });
72
88
  realtimeService.addClient(clientId, ws);
73
89
 
74
90
  ws.on("close", () => {
@@ -95,7 +111,8 @@ export function createPostgresWebSocket(
95
111
  ws.send(JSON.stringify({
96
112
  type: errType,
97
113
  requestId,
98
- payload: { error: { message: msg, code } }
114
+ payload: { error: { message: msg,
115
+ code } }
99
116
  }));
100
117
  };
101
118
 
@@ -117,7 +134,8 @@ export function createPostgresWebSocket(
117
134
  ws.send(JSON.stringify({
118
135
  type: "AUTH_SUCCESS",
119
136
  requestId,
120
- payload: { userId: user.userId, roles: user.roles }
137
+ payload: { userId: user.userId,
138
+ roles: user.roles }
121
139
  }));
122
140
  wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${user.userId}`);
123
141
  } else {
@@ -163,7 +181,7 @@ export function createPostgresWebSocket(
163
181
  }
164
182
 
165
183
  // Helper to get correctly scoped delegate for the current request
166
- const getScopedDelegate = async () => {
184
+ const getScopedDelegate = async (): Promise<DataDriver> => {
167
185
  const session = clientSessions.get(clientId);
168
186
  if (session?.user && "withAuth" in driver && typeof (driver as unknown as Record<string, unknown>).withAuth === "function") {
169
187
  try {
@@ -217,10 +235,12 @@ export function createPostgresWebSocket(
217
235
  case "SAVE_ENTITY": {
218
236
  wsDebug("💾 [WebSocket Server] Processing SAVE_ENTITY request");
219
237
  const request: SaveEntityProps = payload;
220
- wsDebug("💾 [WebSocket Server] Saving entity with request:", inspect(request, { depth: null, colors: true }));
238
+ wsDebug("💾 [WebSocket Server] Saving entity with request:", inspect(request, { depth: null,
239
+ colors: true }));
221
240
  const delegate = await getScopedDelegate();
222
241
  const entity = await delegate.saveEntity(request);
223
- wsDebug("💾 [WebSocket Server] SAVE_ENTITY result:", inspect(entity, { depth: null, colors: true }));
242
+ wsDebug("💾 [WebSocket Server] SAVE_ENTITY result:", inspect(entity, { depth: null,
243
+ colors: true }));
224
244
  const response = {
225
245
  type: "SAVE_ENTITY_SUCCESS",
226
246
  payload: { entity },
@@ -287,9 +307,14 @@ export function createPostgresWebSocket(
287
307
  case "EXECUTE_SQL": {
288
308
  const { sql, options } = payload;
289
309
  const delegate = await getScopedDelegate();
290
- const result = await (delegate as unknown as { executeSql: (sql: string, options?: { database?: string, role?: string }) => Promise<Record<string, unknown>[]> }).executeSql(sql, options);
310
+ const admin = delegate.admin;
311
+ if (!isSQLAdmin(admin)) {
312
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
313
+ break;
314
+ }
315
+ const result = await admin.executeSql(sql, options);
291
316
  if (process.env.NODE_ENV !== "production") {
292
- wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : 'non-array'} rows.`);
317
+ wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
293
318
  }
294
319
  const response = {
295
320
  type: "EXECUTE_SQL_SUCCESS",
@@ -303,9 +328,10 @@ export function createPostgresWebSocket(
303
328
  case "FETCH_DATABASES": {
304
329
  wsDebug("📚 [WebSocket Server] Processing FETCH_DATABASES request");
305
330
  const delegate = await getScopedDelegate();
331
+ const admin = delegate.admin;
306
332
  let databases: string[] = [];
307
- if (delegate.fetchAvailableDatabases) {
308
- databases = await delegate.fetchAvailableDatabases();
333
+ if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) {
334
+ databases = await admin.fetchAvailableDatabases();
309
335
  }
310
336
  wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);
311
337
  const response = {
@@ -320,9 +346,10 @@ export function createPostgresWebSocket(
320
346
  case "FETCH_ROLES": {
321
347
  wsDebug("👤 [WebSocket Server] Processing FETCH_ROLES request");
322
348
  const delegate = await getScopedDelegate();
349
+ const admin = delegate.admin;
323
350
  let roles: string[] = [];
324
- if (delegate.fetchAvailableRoles) {
325
- roles = await delegate.fetchAvailableRoles();
351
+ if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {
352
+ roles = await admin.fetchAvailableRoles();
326
353
  }
327
354
  wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);
328
355
  const response = {
@@ -337,9 +364,10 @@ export function createPostgresWebSocket(
337
364
  case "FETCH_CURRENT_DATABASE": {
338
365
  wsDebug("📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request");
339
366
  const delegate = await getScopedDelegate();
367
+ const admin = delegate.admin;
340
368
  let database: string | undefined = undefined;
341
- if (delegate.fetchCurrentDatabase) {
342
- database = await delegate.fetchCurrentDatabase();
369
+ if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) {
370
+ database = await admin.fetchCurrentDatabase();
343
371
  }
344
372
  const response = {
345
373
  type: "FETCH_CURRENT_DATABASE_SUCCESS",
@@ -353,9 +381,10 @@ export function createPostgresWebSocket(
353
381
  case "FETCH_UNMAPPED_TABLES": {
354
382
  wsDebug("📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request");
355
383
  const delegate = await getScopedDelegate();
384
+ const admin = delegate.admin;
356
385
  let tables: string[] = [];
357
- if (delegate.fetchUnmappedTables) {
358
- tables = await delegate.fetchUnmappedTables(payload?.mappedPaths);
386
+ if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) {
387
+ tables = await admin.fetchUnmappedTables(payload?.mappedPaths);
359
388
  }
360
389
  wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);
361
390
  const response = {
@@ -371,9 +400,10 @@ export function createPostgresWebSocket(
371
400
  wsDebug("📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request");
372
401
  const { tableName } = payload;
373
402
  const delegate = await getScopedDelegate();
403
+ const admin = delegate.admin;
374
404
  let metadata: TableMetadata | undefined;
375
- if (delegate.fetchTableMetadata) {
376
- metadata = await delegate.fetchTableMetadata(tableName);
405
+ if (isSchemaAdmin(admin) && admin.fetchTableMetadata) {
406
+ metadata = await admin.fetchTableMetadata(tableName) as TableMetadata;
377
407
  }
378
408
  wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);
379
409
  const response = {
@@ -448,7 +478,8 @@ export function createPostgresWebSocket(
448
478
  // Attach auth context from the WS session so RLS-aware refetches work
449
479
  const session = clientSessions.get(clientId);
450
480
  const authContext = session?.user
451
- ? { userId: session.user.userId, roles: session.user.roles ?? [] }
481
+ ? { userId: session.user.userId,
482
+ roles: session.user.roles ?? [] }
452
483
  : undefined;
453
484
  // Let RealtimeService handle these messages
454
485
  await realtimeService.handleClientMessage(clientId, {