@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.
- package/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index.es.js +1254 -591
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1254 -591
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
- package/dist/server-postgresql/src/auth/services.d.ts +7 -3
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
- package/dist/server-postgresql/src/connection.d.ts +34 -1
- package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
- package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
- package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
- package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
- package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
- package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
- package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/drizzle-test/0000_woozy_junta.sql +6 -0
- package/drizzle-test/0001_youthful_arachne.sql +1 -0
- package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
- package/drizzle-test/0003_mean_king_cobra.sql +2 -0
- package/drizzle-test/meta/0000_snapshot.json +47 -0
- package/drizzle-test/meta/0001_snapshot.json +48 -0
- package/drizzle-test/meta/0002_snapshot.json +38 -0
- package/drizzle-test/meta/0003_snapshot.json +48 -0
- package/drizzle-test/meta/_journal.json +34 -0
- package/drizzle-test-out/0000_tan_trauma.sql +6 -0
- package/drizzle-test-out/0001_rapid_drax.sql +1 -0
- package/drizzle-test-out/meta/0000_snapshot.json +44 -0
- package/drizzle-test-out/meta/0001_snapshot.json +54 -0
- package/drizzle-test-out/meta/_journal.json +20 -0
- package/drizzle.test.config.ts +10 -0
- package/package.json +88 -89
- package/scratch.ts +41 -0
- package/src/PostgresBackendDriver.ts +63 -79
- package/src/PostgresBootstrapper.ts +7 -8
- package/src/auth/ensure-tables.ts +158 -86
- package/src/auth/services.ts +109 -50
- package/src/cli.ts +259 -16
- package/src/collections/PostgresCollectionRegistry.ts +6 -6
- package/src/connection.ts +70 -48
- package/src/data-transformer.ts +155 -116
- package/src/databasePoolManager.ts +6 -5
- package/src/history/HistoryService.ts +3 -12
- package/src/interfaces.ts +3 -3
- package/src/schema/auth-schema.ts +26 -3
- package/src/schema/doctor-cli.ts +47 -0
- package/src/schema/doctor.ts +595 -0
- package/src/schema/generate-drizzle-schema-logic.ts +204 -57
- package/src/schema/generate-drizzle-schema.ts +6 -6
- package/src/schema/test-schema.ts +11 -0
- package/src/services/BranchService.ts +5 -5
- package/src/services/EntityFetchService.ts +317 -188
- package/src/services/EntityPersistService.ts +15 -17
- package/src/services/RelationService.ts +299 -37
- package/src/services/entity-helpers.ts +39 -13
- package/src/services/entityService.ts +11 -9
- package/src/services/realtimeService.ts +58 -29
- package/src/utils/drizzle-conditions.ts +25 -24
- package/src/websocket.ts +52 -21
- package/test/auth-services.test.ts +131 -39
- package/test/batch-many-to-many-regression.test.ts +573 -0
- package/test/branchService.test.ts +22 -12
- package/test/data-transformer-hardening.test.ts +417 -0
- package/test/data-transformer.test.ts +175 -0
- package/test/doctor.test.ts +182 -0
- package/test/entityService.errors.test.ts +31 -16
- package/test/entityService.relations.test.ts +155 -59
- package/test/entityService.subcollection-search.test.ts +107 -57
- package/test/entityService.test.ts +105 -47
- package/test/generate-drizzle-schema.test.ts +262 -69
- package/test/historyService.test.ts +31 -16
- package/test/n-plus-one-regression.test.ts +314 -0
- package/test/postgresDataDriver.test.ts +260 -168
- package/test/realtimeService.test.ts +70 -39
- package/test/relation-pipeline-gaps.test.ts +637 -0
- package/test/relations.test.ts +492 -39
- package/test-drizzle-bug.ts +18 -0
- package/test-drizzle-out/0000_cultured_freak.sql +7 -0
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
- package/test-drizzle-out/meta/0000_snapshot.json +55 -0
- package/test-drizzle-out/meta/0001_snapshot.json +63 -0
- package/test-drizzle-out/meta/_journal.json +20 -0
- package/test-drizzle-prompt.sh +2 -0
- package/test-policy-prompt.sh +3 -0
- package/test-programmatic.ts +30 -0
- package/test-programmatic2.ts +59 -0
- package/test-schema-no-policies.ts +12 -0
- package/test_drizzle_mock.js +2 -2
- package/test_find_changed.mjs +3 -1
- package/test_hash.js +14 -0
- package/tsconfig.json +1 -1
- 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
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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 (
|
|
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(
|
|
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
|
|
951
|
-
console.debug(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
984
|
+
targetColumn: through.sourceColumn // Swapped for inverse relation
|
|
984
985
|
};
|
|
985
986
|
|
|
986
|
-
console.debug(
|
|
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 =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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 :
|
|
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 (
|
|
308
|
-
databases = await
|
|
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 (
|
|
325
|
-
roles = await
|
|
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 (
|
|
342
|
-
database = await
|
|
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 (
|
|
358
|
-
tables = await
|
|
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 (
|
|
376
|
-
metadata = await
|
|
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,
|
|
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, {
|