@rebasepro/server-mongo 0.0.1-canary.4829d6e
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/LICENSE +22 -0
- package/README.md +86 -0
- package/dist/MongoBootstrapper.d.ts +18 -0
- package/dist/MongoBootstrapper.d.ts.map +1 -0
- package/dist/auth/ensure-collections.d.ts +3 -0
- package/dist/auth/ensure-collections.d.ts.map +1 -0
- package/dist/auth/services.d.ts +156 -0
- package/dist/auth/services.d.ts.map +1 -0
- package/dist/connection.d.ts +35 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/db/MongoConditionBuilder.d.ts +64 -0
- package/dist/db/MongoConditionBuilder.d.ts.map +1 -0
- package/dist/db/MongoDataService.d.ts +101 -0
- package/dist/db/MongoDataService.d.ts.map +1 -0
- package/dist/ensure-collections-Bkx_O5CQ.js +94 -0
- package/dist/ensure-collections-Bkx_O5CQ.js.map +1 -0
- package/dist/ensure-history-collection-yajOt2dv.js +21 -0
- package/dist/ensure-history-collection-yajOt2dv.js.map +1 -0
- package/dist/factory.d.ts +151 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/history/ensure-history-collection.d.ts +3 -0
- package/dist/history/ensure-history-collection.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +2508 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2925 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/services/MongoDriver.d.ts +125 -0
- package/dist/services/MongoDriver.d.ts.map +1 -0
- package/dist/services/MongoHistoryService.d.ts +37 -0
- package/dist/services/MongoHistoryService.d.ts.map +1 -0
- package/dist/services/MongoRealtimeService.d.ts +103 -0
- package/dist/services/MongoRealtimeService.d.ts.map +1 -0
- package/dist/websocket-DQlwCHFq.js +281 -0
- package/dist/websocket-DQlwCHFq.js.map +1 -0
- package/dist/websocket.d.ts +7 -0
- package/dist/websocket.d.ts.map +1 -0
- package/package.json +81 -0
- package/src/MongoBootstrapper.ts +196 -0
- package/src/auth/ensure-collections.ts +105 -0
- package/src/auth/services.ts +732 -0
- package/src/connection.ts +60 -0
- package/src/db/MongoConditionBuilder.ts +224 -0
- package/src/db/MongoDataService.ts +368 -0
- package/src/factory.ts +305 -0
- package/src/history/ensure-history-collection.ts +22 -0
- package/src/index.ts +25 -0
- package/src/services/MongoDriver.ts +1120 -0
- package/src/services/MongoHistoryService.ts +181 -0
- package/src/services/MongoRealtimeService.ts +446 -0
- package/src/websocket.ts +297 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoDriver.d.ts","sourceRoot":"","sources":["../../src/services/MongoDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Db, ObjectId } from "mongodb";
|
|
2
|
+
/**
|
|
3
|
+
* Shallow comparison to find top-level keys that changed between two objects.
|
|
4
|
+
*/
|
|
5
|
+
export declare function findChangedFields(oldValues: Record<string, unknown>, newValues: Record<string, unknown>): string[] | null;
|
|
6
|
+
export interface HistoryEntry {
|
|
7
|
+
_id?: ObjectId;
|
|
8
|
+
id: string;
|
|
9
|
+
table_name: string;
|
|
10
|
+
entity_id: string;
|
|
11
|
+
action: "create" | "update" | "delete";
|
|
12
|
+
changed_fields: string[] | null;
|
|
13
|
+
values: Record<string, unknown> | null;
|
|
14
|
+
previous_values: Record<string, unknown> | null;
|
|
15
|
+
updated_by: string | null;
|
|
16
|
+
updated_at: Date;
|
|
17
|
+
}
|
|
18
|
+
export interface RecordHistoryParams {
|
|
19
|
+
tableName: string;
|
|
20
|
+
id: string;
|
|
21
|
+
action: "create" | "update" | "delete";
|
|
22
|
+
values?: Record<string, unknown> | null;
|
|
23
|
+
previousValues?: Record<string, unknown> | null;
|
|
24
|
+
updatedBy?: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface HistoryRetentionConfig {
|
|
27
|
+
maxEntries: number;
|
|
28
|
+
ttlDays: number;
|
|
29
|
+
}
|
|
30
|
+
export declare class MongoHistoryService {
|
|
31
|
+
private db;
|
|
32
|
+
retention: HistoryRetentionConfig;
|
|
33
|
+
constructor(db: Db, retention?: Partial<HistoryRetentionConfig>);
|
|
34
|
+
recordHistory(params: RecordHistoryParams): Promise<void>;
|
|
35
|
+
private pruneHistory;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=MongoHistoryService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoHistoryService.d.ts","sourceRoot":"","sources":["../../src/services/MongoHistoryService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA0BvC;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,EAAE,GAAG,IAAI,CA8BjB;AAED,MAAM,WAAW,YAAY;IACzB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB;AAOD,qBAAa,mBAAmB;IAIxB,OAAO,CAAC,EAAE;IAHP,SAAS,EAAE,sBAAsB,CAAC;gBAG7B,EAAE,EAAE,EAAE,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC;IAMzC,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;YA0CjD,YAAY;CA+B7B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB Realtime Service
|
|
3
|
+
*
|
|
4
|
+
* Implements RealtimeProvider interface using MongoDB Change Streams.
|
|
5
|
+
* Provides real-time subscriptions to collection and row changes.
|
|
6
|
+
*/
|
|
7
|
+
import { Db, ChangeStream } from "mongodb";
|
|
8
|
+
import { RealtimeProvider, CollectionSubscriptionConfig, SingleSubscriptionConfig } from "@rebasepro/types";
|
|
9
|
+
import { WebSocket } from "ws";
|
|
10
|
+
import { MongoDriver } from "./MongoDriver";
|
|
11
|
+
interface Subscription {
|
|
12
|
+
type: "collection" | "single";
|
|
13
|
+
config: CollectionSubscriptionConfig | SingleSubscriptionConfig;
|
|
14
|
+
changeStream?: ChangeStream;
|
|
15
|
+
callback?: (data: any) => void;
|
|
16
|
+
authContext?: {
|
|
17
|
+
userId: string;
|
|
18
|
+
roles: string[];
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* MongoDB Realtime Service
|
|
23
|
+
*
|
|
24
|
+
* Implements real-time subscriptions using MongoDB Change Streams.
|
|
25
|
+
* Requires MongoDB replica set for change streams to work.
|
|
26
|
+
*/
|
|
27
|
+
export declare class MongoRealtimeService implements RealtimeProvider {
|
|
28
|
+
private db;
|
|
29
|
+
private subscriptions;
|
|
30
|
+
private clients;
|
|
31
|
+
private dataService;
|
|
32
|
+
private driver?;
|
|
33
|
+
constructor(db: Db);
|
|
34
|
+
setDataDriver(driver: MongoDriver): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get the collection name from a path
|
|
37
|
+
*/
|
|
38
|
+
private getCollectionName;
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to collection changes
|
|
41
|
+
*/
|
|
42
|
+
subscribeToCollection(subscriptionId: string, config: CollectionSubscriptionConfig & {
|
|
43
|
+
authContext?: {
|
|
44
|
+
userId: string;
|
|
45
|
+
roles: string[];
|
|
46
|
+
};
|
|
47
|
+
}, callback?: (rows: Record<string, unknown>[]) => void): void;
|
|
48
|
+
/**
|
|
49
|
+
* Fetch collection and notify callback
|
|
50
|
+
*/
|
|
51
|
+
private fetchAndNotifyCollection;
|
|
52
|
+
/**
|
|
53
|
+
* Subscribe to single row changes
|
|
54
|
+
*/
|
|
55
|
+
subscribeToOne(subscriptionId: string, config: SingleSubscriptionConfig & {
|
|
56
|
+
authContext?: {
|
|
57
|
+
userId: string;
|
|
58
|
+
roles: string[];
|
|
59
|
+
};
|
|
60
|
+
}, callback?: (row: Record<string, unknown> | null) => void): void;
|
|
61
|
+
/**
|
|
62
|
+
* Fetch row and notify callback
|
|
63
|
+
*/
|
|
64
|
+
private fetchAndNotifyOne;
|
|
65
|
+
/**
|
|
66
|
+
* Unsubscribe from a subscription
|
|
67
|
+
*/
|
|
68
|
+
unsubscribe(subscriptionId: string): void;
|
|
69
|
+
/**
|
|
70
|
+
* Notify all relevant subscribers of an row update
|
|
71
|
+
* This is called after save/delete operations to push updates
|
|
72
|
+
*/
|
|
73
|
+
notifyUpdate(path: string, id: string, row: Record<string, unknown> | null, _databaseId?: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Get all active subscriptions (for debugging)
|
|
76
|
+
*/
|
|
77
|
+
getSubscriptions(): Map<string, Subscription>;
|
|
78
|
+
/**
|
|
79
|
+
* Close all subscriptions
|
|
80
|
+
*/
|
|
81
|
+
closeAll(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Register a WebSocket client for real-time communication
|
|
84
|
+
*/
|
|
85
|
+
addClient(clientId: string, ws: WebSocket): void;
|
|
86
|
+
/**
|
|
87
|
+
* Remove a WebSocket client and clean up its subscriptions
|
|
88
|
+
*/
|
|
89
|
+
private removeClient;
|
|
90
|
+
/**
|
|
91
|
+
* Handle an incoming WebSocket message for subscription management
|
|
92
|
+
*/
|
|
93
|
+
handleClientMessage(clientId: string, message: {
|
|
94
|
+
type: string;
|
|
95
|
+
payload?: any;
|
|
96
|
+
subscriptionId?: string;
|
|
97
|
+
}, _authContext?: {
|
|
98
|
+
userId: string;
|
|
99
|
+
roles: unknown[];
|
|
100
|
+
}): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
export {};
|
|
103
|
+
//# sourceMappingURL=MongoRealtimeService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoRealtimeService.d.ts","sourceRoot":"","sources":["../../src/services/MongoRealtimeService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { isDocumentAdmin, isSchemaAdmin } from "@rebasepro/types";
|
|
2
|
+
import { extractUserFromToken, logger } from "@rebasepro/server";
|
|
3
|
+
import { WebSocketServer } from "ws";
|
|
4
|
+
//#region src/websocket.ts
|
|
5
|
+
function isDriverWithAuth(driver) {
|
|
6
|
+
return "withAuth" in driver && typeof driver.withAuth === "function";
|
|
7
|
+
}
|
|
8
|
+
var clientSessions = /* @__PURE__ */ new Map();
|
|
9
|
+
var WS_RATE_LIMIT = 2e3;
|
|
10
|
+
var WS_RATE_WINDOW_MS = 6e4;
|
|
11
|
+
var ADMIN_ONLY_TYPES = new Set([
|
|
12
|
+
"EXECUTE_SQL",
|
|
13
|
+
"FETCH_DATABASES",
|
|
14
|
+
"FETCH_ROLES",
|
|
15
|
+
"FETCH_UNMAPPED_TABLES",
|
|
16
|
+
"FETCH_TABLE_METADATA",
|
|
17
|
+
"FETCH_CURRENT_DATABASE",
|
|
18
|
+
"CREATE_BRANCH",
|
|
19
|
+
"DELETE_BRANCH",
|
|
20
|
+
"LIST_BRANCHES"
|
|
21
|
+
]);
|
|
22
|
+
function isAdminSession(session) {
|
|
23
|
+
if (!session?.user?.roles) return false;
|
|
24
|
+
return session.user.roles.includes("admin");
|
|
25
|
+
}
|
|
26
|
+
function createMongoWebSocket(server, realtimeService, driver, authConfig, admin) {
|
|
27
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
28
|
+
const wsDebug = (...args) => {
|
|
29
|
+
if (!isProduction) console.debug(...args);
|
|
30
|
+
};
|
|
31
|
+
const wss = new WebSocketServer({ server });
|
|
32
|
+
wss.on("error", (err) => {
|
|
33
|
+
if (err.code === "EADDRINUSE") return;
|
|
34
|
+
logger.error("❌ [WebSocket Server] Error", { error: err });
|
|
35
|
+
});
|
|
36
|
+
const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
|
|
37
|
+
wss.on("connection", (ws) => {
|
|
38
|
+
const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
39
|
+
wsDebug(`WebSocket client connected: ${clientId}`);
|
|
40
|
+
clientSessions.set(clientId, {
|
|
41
|
+
ws,
|
|
42
|
+
authenticated: !requireAuth,
|
|
43
|
+
messageCount: 0,
|
|
44
|
+
messageWindowStart: Date.now()
|
|
45
|
+
});
|
|
46
|
+
realtimeService.addClient(clientId, ws);
|
|
47
|
+
ws.on("close", () => {
|
|
48
|
+
wsDebug(`WebSocket client disconnected: ${clientId}`);
|
|
49
|
+
clientSessions.delete(clientId);
|
|
50
|
+
});
|
|
51
|
+
ws.on("message", async (message) => {
|
|
52
|
+
let requestId;
|
|
53
|
+
try {
|
|
54
|
+
const { type, payload, requestId: reqId } = JSON.parse(message.toString());
|
|
55
|
+
requestId = reqId;
|
|
56
|
+
wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : "");
|
|
57
|
+
const sendError = (errType, code, msg) => {
|
|
58
|
+
ws.send(JSON.stringify({
|
|
59
|
+
type: errType,
|
|
60
|
+
requestId,
|
|
61
|
+
payload: { error: {
|
|
62
|
+
message: msg,
|
|
63
|
+
code
|
|
64
|
+
} }
|
|
65
|
+
}));
|
|
66
|
+
};
|
|
67
|
+
if (type === "AUTHENTICATE") {
|
|
68
|
+
const { token } = payload || {};
|
|
69
|
+
if (!token) {
|
|
70
|
+
sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const user = extractUserFromToken(token);
|
|
74
|
+
if (user) {
|
|
75
|
+
const session = clientSessions.get(clientId);
|
|
76
|
+
if (session) {
|
|
77
|
+
session.user = user;
|
|
78
|
+
session.authenticated = true;
|
|
79
|
+
}
|
|
80
|
+
ws.send(JSON.stringify({
|
|
81
|
+
type: "AUTH_SUCCESS",
|
|
82
|
+
requestId,
|
|
83
|
+
payload: {
|
|
84
|
+
userId: user.userId,
|
|
85
|
+
roles: user.roles
|
|
86
|
+
}
|
|
87
|
+
}));
|
|
88
|
+
} else sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (requireAuth) {
|
|
92
|
+
if (!clientSessions.get(clientId)?.authenticated) {
|
|
93
|
+
sendError("ERROR", "UNAUTHORIZED", "Authentication required");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
const session = clientSessions.get(clientId);
|
|
99
|
+
if (session) {
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {
|
|
102
|
+
session.messageCount = 0;
|
|
103
|
+
session.messageWindowStart = now;
|
|
104
|
+
}
|
|
105
|
+
session.messageCount++;
|
|
106
|
+
if (session.messageCount > WS_RATE_LIMIT) {
|
|
107
|
+
sendError("ERROR", "RATE_LIMITED", "Too many requests. Please slow down.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (ADMIN_ONLY_TYPES.has(type)) {
|
|
113
|
+
if (!isAdminSession(clientSessions.get(clientId))) {
|
|
114
|
+
sendError("ERROR", "FORBIDDEN", "Admin access required for this operation");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const getScopedDelegate = async () => {
|
|
119
|
+
const session = clientSessions.get(clientId);
|
|
120
|
+
if (session?.user && isDriverWithAuth(driver)) try {
|
|
121
|
+
const userForAuth = {
|
|
122
|
+
uid: session.user.userId,
|
|
123
|
+
email: session.user.email ?? "",
|
|
124
|
+
displayName: session.user.displayName ?? "",
|
|
125
|
+
photoURL: session.user.photoURL ?? "",
|
|
126
|
+
providerId: "jwt",
|
|
127
|
+
isAnonymous: false,
|
|
128
|
+
roles: session.user.roles ?? []
|
|
129
|
+
};
|
|
130
|
+
return await driver.withAuth(userForAuth);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
logger.error("Failed to create authenticated delegate for WS request", { error: e });
|
|
133
|
+
return driver;
|
|
134
|
+
}
|
|
135
|
+
return driver;
|
|
136
|
+
};
|
|
137
|
+
switch (type) {
|
|
138
|
+
case "FETCH_COLLECTION": {
|
|
139
|
+
const request = payload;
|
|
140
|
+
const rows = await (await getScopedDelegate()).fetchCollection(request);
|
|
141
|
+
ws.send(JSON.stringify({
|
|
142
|
+
type: "FETCH_COLLECTION_SUCCESS",
|
|
143
|
+
payload: { rows },
|
|
144
|
+
requestId
|
|
145
|
+
}));
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "FETCH_ONE": {
|
|
149
|
+
const request = payload;
|
|
150
|
+
const row = await (await getScopedDelegate()).fetchOne(request);
|
|
151
|
+
ws.send(JSON.stringify({
|
|
152
|
+
type: "FETCH_ONE_SUCCESS",
|
|
153
|
+
payload: { row },
|
|
154
|
+
requestId
|
|
155
|
+
}));
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "SAVE": {
|
|
159
|
+
const request = payload;
|
|
160
|
+
const row = await (await getScopedDelegate()).save(request);
|
|
161
|
+
ws.send(JSON.stringify({
|
|
162
|
+
type: "SAVE_SUCCESS",
|
|
163
|
+
payload: { row },
|
|
164
|
+
requestId
|
|
165
|
+
}));
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "DELETE": {
|
|
169
|
+
const request = payload;
|
|
170
|
+
await (await getScopedDelegate()).delete(request);
|
|
171
|
+
ws.send(JSON.stringify({
|
|
172
|
+
type: "DELETE_SUCCESS",
|
|
173
|
+
payload: { success: true },
|
|
174
|
+
requestId
|
|
175
|
+
}));
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case "CHECK_UNIQUE_FIELD": {
|
|
179
|
+
const { path, name, value, id, collection } = payload;
|
|
180
|
+
const isUnique = await (await getScopedDelegate()).checkUniqueField(path, name, value, id, collection);
|
|
181
|
+
ws.send(JSON.stringify({
|
|
182
|
+
type: "CHECK_UNIQUE_FIELD_SUCCESS",
|
|
183
|
+
payload: { isUnique },
|
|
184
|
+
requestId
|
|
185
|
+
}));
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "COUNT": {
|
|
189
|
+
const request = payload;
|
|
190
|
+
const count = await (await getScopedDelegate()).count(request);
|
|
191
|
+
ws.send(JSON.stringify({
|
|
192
|
+
type: "COUNT_SUCCESS",
|
|
193
|
+
payload: { count },
|
|
194
|
+
requestId
|
|
195
|
+
}));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "EXECUTE_SQL": {
|
|
199
|
+
const { sql, options } = payload;
|
|
200
|
+
if (admin && isDocumentAdmin(admin) && admin.executeAggregate) {
|
|
201
|
+
const result = await admin.executeAggregate(sql);
|
|
202
|
+
ws.send(JSON.stringify({
|
|
203
|
+
type: "EXECUTE_SQL_SUCCESS",
|
|
204
|
+
payload: { result },
|
|
205
|
+
requestId
|
|
206
|
+
}));
|
|
207
|
+
} else ws.send(JSON.stringify({
|
|
208
|
+
type: "ERROR",
|
|
209
|
+
requestId,
|
|
210
|
+
payload: { error: {
|
|
211
|
+
message: "SQL execution not supported for this driver",
|
|
212
|
+
code: "NOT_SUPPORTED"
|
|
213
|
+
} }
|
|
214
|
+
}));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "FETCH_UNMAPPED_TABLES":
|
|
218
|
+
if (admin && isSchemaAdmin(admin)) {
|
|
219
|
+
const tables = await admin.fetchUnmappedTables?.(payload?.mappedPaths) || [];
|
|
220
|
+
ws.send(JSON.stringify({
|
|
221
|
+
type: "FETCH_UNMAPPED_TABLES_SUCCESS",
|
|
222
|
+
payload: { tables },
|
|
223
|
+
requestId
|
|
224
|
+
}));
|
|
225
|
+
} else ws.send(JSON.stringify({
|
|
226
|
+
type: "FETCH_UNMAPPED_TABLES_SUCCESS",
|
|
227
|
+
payload: { tables: [] },
|
|
228
|
+
requestId
|
|
229
|
+
}));
|
|
230
|
+
break;
|
|
231
|
+
case "FETCH_TABLE_METADATA": {
|
|
232
|
+
const { tableName } = payload;
|
|
233
|
+
if (admin && isSchemaAdmin(admin)) {
|
|
234
|
+
const metadata = await admin.fetchTableMetadata?.(tableName);
|
|
235
|
+
ws.send(JSON.stringify({
|
|
236
|
+
type: "FETCH_TABLE_METADATA_SUCCESS",
|
|
237
|
+
payload: { metadata },
|
|
238
|
+
requestId
|
|
239
|
+
}));
|
|
240
|
+
} else ws.send(JSON.stringify({
|
|
241
|
+
type: "FETCH_TABLE_METADATA_SUCCESS",
|
|
242
|
+
payload: { metadata: null },
|
|
243
|
+
requestId
|
|
244
|
+
}));
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case "subscribe_collection":
|
|
248
|
+
case "subscribe_one":
|
|
249
|
+
case "unsubscribe": {
|
|
250
|
+
const session = clientSessions.get(clientId);
|
|
251
|
+
const authContext = session?.user ? {
|
|
252
|
+
userId: session.user.userId,
|
|
253
|
+
roles: session.user.roles ?? []
|
|
254
|
+
} : void 0;
|
|
255
|
+
await realtimeService.handleClientMessage(clientId, {
|
|
256
|
+
type,
|
|
257
|
+
payload,
|
|
258
|
+
subscriptionId: payload?.subscriptionId
|
|
259
|
+
}, authContext);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
default: logger.error("❌ [WebSocket Server] Unknown message type", { detail: type });
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
const errorMessage = process.env.NODE_ENV === "production" ? "An unexpected error occurred" : error instanceof Error ? error.message : "An unexpected error occurred";
|
|
266
|
+
ws.send(JSON.stringify({
|
|
267
|
+
type: "ERROR",
|
|
268
|
+
requestId,
|
|
269
|
+
payload: { error: {
|
|
270
|
+
message: errorMessage,
|
|
271
|
+
code: "INTERNAL_ERROR"
|
|
272
|
+
} }
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
export { createMongoWebSocket };
|
|
280
|
+
|
|
281
|
+
//# sourceMappingURL=websocket-DQlwCHFq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-DQlwCHFq.js","names":[],"sources":["../src/websocket.ts"],"sourcesContent":["import { RealtimeProvider, DataDriver, FetchCollectionProps, FetchOneProps, SaveProps, DeleteProps, TableMetadata, DatabaseAdmin, isSchemaAdmin, isDocumentAdmin, User } from \"@rebasepro/types\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { Server } from \"http\";\nimport { inspect } from \"util\";\nimport type { AccessTokenPayload } from \"@rebasepro/server\";\nimport { extractUserFromToken } from \"@rebasepro/server\";\nimport type { RebaseAuthConfig } from \"@rebasepro/server\";\nimport { MongoRealtimeService } from \"./services/MongoRealtimeService\";\nimport { MongoDriver } from \"./services/MongoDriver\";\nimport { logger } from \"@rebasepro/server\";\n\ninterface DriverWithAuth extends DataDriver {\n withAuth(user: Record<string, unknown>): Promise<DataDriver>;\n}\n\nfunction isDriverWithAuth(driver: DataDriver): driver is DriverWithAuth {\n return \"withAuth\" in driver && typeof (driver as Record<string, unknown>).withAuth === \"function\";\n}\n\ninterface ClientSession {\n ws: WebSocket;\n user?: AccessTokenPayload;\n authenticated: boolean;\n messageCount: number;\n messageWindowStart: number;\n}\n\nconst clientSessions = new Map<string, ClientSession>();\nconst WS_RATE_LIMIT = 2000;\nconst WS_RATE_WINDOW_MS = 60_000;\n\nconst ADMIN_ONLY_TYPES = new Set([\n \"EXECUTE_SQL\",\n \"FETCH_DATABASES\",\n \"FETCH_ROLES\",\n \"FETCH_UNMAPPED_TABLES\",\n \"FETCH_TABLE_METADATA\",\n \"FETCH_CURRENT_DATABASE\",\n \"CREATE_BRANCH\",\n \"DELETE_BRANCH\",\n \"LIST_BRANCHES\"\n]);\n\nfunction isAdminSession(session: ClientSession | undefined): boolean {\n if (!session?.user?.roles) return false;\n return session.user.roles.includes(\"admin\");\n}\n\nexport function createMongoWebSocket(\n server: Server,\n realtimeService: MongoRealtimeService,\n driver: MongoDriver,\n authConfig?: RebaseAuthConfig,\n admin?: DatabaseAdmin\n) {\n const isProduction = process.env.NODE_ENV === \"production\";\n const wsDebug = (...args: unknown[]) => { if (!isProduction) console.debug(...args); };\n const wss = new WebSocketServer({ server });\n\n wss.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") return;\n logger.error(\"❌ [WebSocket Server] Error\", { error: err });\n });\n\n const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;\n\n wss.on(\"connection\", (ws) => {\n const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n wsDebug(`WebSocket client connected: ${clientId}`);\n\n clientSessions.set(clientId, { ws,\nauthenticated: !requireAuth,\nmessageCount: 0,\nmessageWindowStart: Date.now() });\n realtimeService.addClient(clientId, ws);\n\n ws.on(\"close\", () => {\n wsDebug(`WebSocket client disconnected: ${clientId}`);\n clientSessions.delete(clientId);\n });\n\n ws.on(\"message\", async (message) => {\n let requestId: string | undefined;\n try {\n const { type, payload, requestId: reqId } = JSON.parse(message.toString());\n requestId = reqId;\n\n wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : \"\");\n\n const sendError = (errType: \"ERROR\" | \"AUTH_ERROR\", code: string, msg: string) => {\n ws.send(JSON.stringify({ type: errType,\nrequestId,\npayload: { error: { message: msg,\ncode } } }));\n };\n\n if (type === \"AUTHENTICATE\") {\n const { token } = payload || {};\n if (!token) {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"Token is required\");\n return;\n }\n\n const user = extractUserFromToken(token);\n if (user) {\n const session = clientSessions.get(clientId);\n if (session) {\n session.user = user;\n session.authenticated = true;\n }\n ws.send(JSON.stringify({ type: \"AUTH_SUCCESS\",\nrequestId,\npayload: { userId: user.userId,\nroles: user.roles } }));\n } else {\n sendError(\"AUTH_ERROR\", \"INVALID_TOKEN\", \"Invalid or expired token\");\n }\n return;\n }\n\n if (requireAuth) {\n const session = clientSessions.get(clientId);\n if (!session?.authenticated) {\n sendError(\"ERROR\", \"UNAUTHORIZED\", \"Authentication required\");\n return;\n }\n }\n\n {\n const session = clientSessions.get(clientId);\n if (session) {\n const now = Date.now();\n if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {\n session.messageCount = 0;\n session.messageWindowStart = now;\n }\n session.messageCount++;\n if (session.messageCount > WS_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests. Please slow down.\");\n return;\n }\n }\n }\n\n if (ADMIN_ONLY_TYPES.has(type)) {\n const session = clientSessions.get(clientId);\n if (!isAdminSession(session)) {\n sendError(\"ERROR\", \"FORBIDDEN\", \"Admin access required for this operation\");\n return;\n }\n }\n\n const getScopedDelegate = async (): Promise<DataDriver> => {\n const session = clientSessions.get(clientId);\n if (session?.user && isDriverWithAuth(driver)) {\n try {\n const userForAuth: User = {\n uid: session.user.userId,\n email: session.user.email ?? \"\",\n displayName: session.user.displayName ?? \"\",\n photoURL: session.user.photoURL ?? \"\",\n providerId: \"jwt\",\n isAnonymous: false,\n roles: session.user.roles ?? []\n };\n return await driver.withAuth(userForAuth);\n } catch (e) {\n logger.error(\"Failed to create authenticated delegate for WS request\", { error: e });\n return driver;\n }\n }\n return driver;\n };\n\n switch (type) {\n case \"FETCH_COLLECTION\": {\n const request: FetchCollectionProps = payload;\n const delegate = await getScopedDelegate();\n const rows = await delegate.fetchCollection(request);\n ws.send(JSON.stringify({ type: \"FETCH_COLLECTION_SUCCESS\",\npayload: { rows },\nrequestId }));\n break;\n }\n case \"FETCH_ONE\": {\n const request: FetchOneProps = payload;\n const delegate = await getScopedDelegate();\n const row = await delegate.fetchOne(request);\n ws.send(JSON.stringify({ type: \"FETCH_ONE_SUCCESS\",\npayload: { row },\nrequestId }));\n break;\n }\n case \"SAVE\": {\n const request: SaveProps = payload;\n const delegate = await getScopedDelegate();\n const row = await delegate.save(request);\n ws.send(JSON.stringify({ type: \"SAVE_SUCCESS\",\npayload: { row },\nrequestId }));\n break;\n }\n case \"DELETE\": {\n const request: DeleteProps = payload;\n const delegate = await getScopedDelegate();\n await delegate.delete(request);\n ws.send(JSON.stringify({ type: \"DELETE_SUCCESS\",\npayload: { success: true },\nrequestId }));\n break;\n }\n case \"CHECK_UNIQUE_FIELD\": {\n const { path, name, value, id, collection } = payload;\n const delegate = await getScopedDelegate();\n const isUnique = await delegate.checkUniqueField(path, name, value, id, collection);\n ws.send(JSON.stringify({ type: \"CHECK_UNIQUE_FIELD_SUCCESS\",\npayload: { isUnique },\nrequestId }));\n break;\n }\n case \"COUNT\": {\n const request: FetchCollectionProps = payload;\n const delegate = await getScopedDelegate();\n const count = await delegate.count!(request);\n ws.send(JSON.stringify({ type: \"COUNT_SUCCESS\",\npayload: { count },\nrequestId }));\n break;\n }\n case \"EXECUTE_SQL\": {\n const { sql, options } = payload;\n if (admin && isDocumentAdmin(admin) && admin.executeAggregate) {\n const result = await admin.executeAggregate(sql as Record<string, unknown>[]);\n ws.send(JSON.stringify({ type: \"EXECUTE_SQL_SUCCESS\",\npayload: { result },\nrequestId }));\n } else {\n ws.send(JSON.stringify({ type: \"ERROR\",\nrequestId,\npayload: { error: { message: \"SQL execution not supported for this driver\",\ncode: \"NOT_SUPPORTED\" } } }));\n }\n break;\n }\n case \"FETCH_UNMAPPED_TABLES\": {\n if (admin && isSchemaAdmin(admin)) {\n const tables = await admin.fetchUnmappedTables?.(payload?.mappedPaths) || [];\n ws.send(JSON.stringify({ type: \"FETCH_UNMAPPED_TABLES_SUCCESS\",\npayload: { tables },\nrequestId }));\n } else {\n ws.send(JSON.stringify({ type: \"FETCH_UNMAPPED_TABLES_SUCCESS\",\npayload: { tables: [] },\nrequestId }));\n }\n break;\n }\n case \"FETCH_TABLE_METADATA\": {\n const { tableName } = payload;\n if (admin && isSchemaAdmin(admin)) {\n const metadata = await admin.fetchTableMetadata?.(tableName);\n ws.send(JSON.stringify({ type: \"FETCH_TABLE_METADATA_SUCCESS\",\npayload: { metadata },\nrequestId }));\n } else {\n ws.send(JSON.stringify({ type: \"FETCH_TABLE_METADATA_SUCCESS\",\npayload: { metadata: null },\nrequestId }));\n }\n break;\n }\n case \"subscribe_collection\":\n case \"subscribe_one\":\n case \"unsubscribe\": {\n const session = clientSessions.get(clientId);\n const authContext = session?.user ? { userId: session.user.userId,\nroles: session.user.roles ?? [] } : undefined;\n await realtimeService.handleClientMessage(clientId, {\n type,\n payload,\n subscriptionId: payload?.subscriptionId\n }, authContext);\n break;\n }\n default:\n logger.error(\"❌ [WebSocket Server] Unknown message type\", { detail: type });\n }\n } catch (error: unknown) {\n const errorMessage = process.env.NODE_ENV === \"production\" ? \"An unexpected error occurred\" : (error instanceof Error ? error.message : \"An unexpected error occurred\");\n ws.send(JSON.stringify({ type: \"ERROR\",\nrequestId,\npayload: { error: { message: errorMessage,\ncode: \"INTERNAL_ERROR\" } } }));\n }\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DatabaseAdmin } from "@rebasepro/types";
|
|
2
|
+
import { Server } from "http";
|
|
3
|
+
import type { RebaseAuthConfig } from "@rebasepro/server";
|
|
4
|
+
import { MongoRealtimeService } from "./services/MongoRealtimeService";
|
|
5
|
+
import { MongoDriver } from "./services/MongoDriver";
|
|
6
|
+
export declare function createMongoWebSocket(server: Server, realtimeService: MongoRealtimeService, driver: MongoDriver, authConfig?: RebaseAuthConfig, admin?: DatabaseAdmin): void;
|
|
7
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4G,aAAa,EAAwC,MAAM,kBAAkB,CAAC;AAEjM,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAI9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAwCrD,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,oBAAoB,EACrC,MAAM,EAAE,WAAW,EACnB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,KAAK,CAAC,EAAE,aAAa,QAmPxB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rebasepro/server-mongo",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1-canary.4829d6e",
|
|
5
|
+
"description": "MongoDB backend for Rebase",
|
|
6
|
+
"funding": {
|
|
7
|
+
"url": "https://github.com/sponsors/rebaseco"
|
|
8
|
+
},
|
|
9
|
+
"author": "Rebase",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
14
|
+
"directory": "packages/server-mongo"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.umd.js",
|
|
17
|
+
"module": "./dist/index.es.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"source": "src/index.ts",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=14"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"rebase",
|
|
25
|
+
"cms",
|
|
26
|
+
"admin",
|
|
27
|
+
"admin panel",
|
|
28
|
+
"mongodb",
|
|
29
|
+
"backend",
|
|
30
|
+
"database"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"development": "./dist/index.es.js",
|
|
36
|
+
"import": "./dist/index.es.js",
|
|
37
|
+
"require": "./dist/index.umd.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"mongodb": "^7.3.0",
|
|
43
|
+
"ws": "^8.21.0",
|
|
44
|
+
"@rebasepro/common": "0.0.1-canary.4829d6e",
|
|
45
|
+
"@rebasepro/types": "0.0.1-canary.4829d6e",
|
|
46
|
+
"@rebasepro/utils": "0.0.1-canary.4829d6e"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/jest": "^30.0.0",
|
|
50
|
+
"@types/node": "^25.9.3",
|
|
51
|
+
"@types/ws": "^8.18.1",
|
|
52
|
+
"jest": "^30.4.2",
|
|
53
|
+
"mongodb-memory-server": "^11.2.0",
|
|
54
|
+
"ts-jest": "29.4.11",
|
|
55
|
+
"typescript": "^6.0.3",
|
|
56
|
+
"vite": "^8.0.16"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@rebasepro/server": "0.0.1-canary.4829d6e"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@rebasepro/server": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"dist",
|
|
68
|
+
"src"
|
|
69
|
+
],
|
|
70
|
+
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
71
|
+
"publishConfig": {
|
|
72
|
+
"access": "public"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"watch": "vite build --watch",
|
|
76
|
+
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
|
|
77
|
+
"test:lint": "eslint \"src/**\" --quiet",
|
|
78
|
+
"test": "jest --passWithNoTests --forceExit",
|
|
79
|
+
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
80
|
+
}
|
|
81
|
+
}
|