@rebasepro/server-mongo 0.14.0 → 0.14.1
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/auth/services.d.ts.map +1 -1
- package/dist/db/MongoConditionBuilder.d.ts +28 -4
- package/dist/db/MongoConditionBuilder.d.ts.map +1 -1
- package/dist/db/MongoDataService.d.ts +3 -3
- package/dist/db/MongoDataService.d.ts.map +1 -1
- package/dist/index.es.js +132 -48
- package/dist/index.es.js.map +1 -1
- package/dist/services/MongoDriver.d.ts +1 -1
- package/dist/services/MongoDriver.d.ts.map +1 -1
- package/dist/services/MongoRealtimeService.d.ts +36 -0
- package/dist/services/MongoRealtimeService.d.ts.map +1 -1
- package/dist/{websocket-Bs54-gR-.js → websocket-Do3I42Uc.js} +22 -2
- package/dist/websocket-Do3I42Uc.js.map +1 -0
- package/dist/websocket.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/auth/services.ts +60 -9
- package/src/db/MongoConditionBuilder.ts +46 -7
- package/src/db/MongoDataService.ts +3 -3
- package/src/services/MongoDriver.ts +14 -23
- package/src/services/MongoRealtimeService.ts +116 -28
- package/src/websocket.ts +25 -1
- package/dist/websocket-Bs54-gR-.js.map +0 -1
|
@@ -59,7 +59,7 @@ export declare class MongoDriver implements DataDriver {
|
|
|
59
59
|
* what every re-fetch reads — the wrapper used to stamp the field on the
|
|
60
60
|
* `Subscription` object instead, and nothing has ever read that one.
|
|
61
61
|
*/
|
|
62
|
-
listenCollection<M extends Record<string, any>>({
|
|
62
|
+
listenCollection<M extends Record<string, any>>({ onUpdate, onError, collection, vectorSearch, ...query }: ListenCollectionProps<M>, authContext?: {
|
|
63
63
|
uid: string;
|
|
64
64
|
roles: string[];
|
|
65
65
|
}): () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MongoDriver.d.ts","sourceRoot":"","sources":["../../src/services/MongoDriver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EACH,UAAU,EACV,WAAW,EAEX,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,SAAS,EAET,2BAA2B,EAC3B,IAAI,EACJ,YAAY,EAEZ,aAAa,EAEhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAS5D;;;;;GAKG;AACH,qBAAa,WAAY,YAAW,UAAU;IAYtC,OAAO,CAAC,EAAE;aAGM,QAAQ,CAAC,EAAE,2BAA2B;IAd1D,GAAG,SAAa;IAChB,WAAW,UAAQ;IAEnB,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,eAAe,CAAuB;IACvC,cAAc,EAAE,mBAAmB,CAAC;IACpC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;gBAGjB,EAAE,EAAE,EAAE,EACd,eAAe,CAAC,EAAE,oBAAoB,EACtC,cAAc,CAAC,EAAE,mBAAmB,EACpB,QAAQ,CAAC,EAAE,2BAA2B,YAAA,EACtD,IAAI,CAAC,EAAE,IAAI;IAUf;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;;OAGG;IACH,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,EAC3C,IAAI,EAAE,MAAM;;;;;;;;;;;IA2BhB;;OAEG;IACG,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/C,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAuDrC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"MongoDriver.d.ts","sourceRoot":"","sources":["../../src/services/MongoDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,22 @@ interface Subscription {
|
|
|
26
26
|
};
|
|
27
27
|
changeStream?: ChangeStream;
|
|
28
28
|
callback?: (data: any) => void;
|
|
29
|
+
/**
|
|
30
|
+
* How many deliveries have been started for this subscription, and the
|
|
31
|
+
* highest that has already reached the callback.
|
|
32
|
+
*
|
|
33
|
+
* Every delivery here is a re-fetch, and three independent things start one
|
|
34
|
+
* for the same subscription: the initial fetch, the change stream, and
|
|
35
|
+
* `notifyUpdate` after a save. They overlap, and a fetch that started
|
|
36
|
+
* earlier can finish later — at which point the callback replaces the
|
|
37
|
+
* client's whole list with the state before the change. Nothing corrects it
|
|
38
|
+
* until something else happens to that collection.
|
|
39
|
+
*
|
|
40
|
+
* A counter taken before the await and checked after it is what makes the
|
|
41
|
+
* last *started* delivery the last *delivered* one.
|
|
42
|
+
*/
|
|
43
|
+
started: number;
|
|
44
|
+
delivered: number;
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
31
47
|
* MongoDB Realtime Service
|
|
@@ -44,6 +60,26 @@ export declare class MongoRealtimeService implements RealtimeProvider {
|
|
|
44
60
|
* Get the collection name from a path
|
|
45
61
|
*/
|
|
46
62
|
private getCollectionName;
|
|
63
|
+
/**
|
|
64
|
+
* Claim a delivery slot for a subscription, before doing the work.
|
|
65
|
+
*
|
|
66
|
+
* Returns the check to run immediately before calling the callback. It
|
|
67
|
+
* refuses in three cases, all of which used to deliver:
|
|
68
|
+
*
|
|
69
|
+
* - **Out of order.** A newer fetch has already delivered, so this one is
|
|
70
|
+
* stale — the client would go back to the state before the change.
|
|
71
|
+
* - **Unsubscribed.** The subscription was cancelled while the fetch was in
|
|
72
|
+
* flight, and its callback belongs to a client that stopped listening.
|
|
73
|
+
* - **Re-subscribed.** `subscribeToCollection` unsubscribes first, so the
|
|
74
|
+
* same id can name a *different* subscription by the time a fetch lands —
|
|
75
|
+
* with a different filter, and a different caller's rows.
|
|
76
|
+
*
|
|
77
|
+
* Synchronous deliveries claim a slot too. A `delete` notification with no
|
|
78
|
+
* fetch behind it is the newest thing known about the row, so it must also
|
|
79
|
+
* be the thing that closes the door on an older fetch still in flight —
|
|
80
|
+
* otherwise the deleted row reappears a moment after it vanished.
|
|
81
|
+
*/
|
|
82
|
+
private beginDelivery;
|
|
47
83
|
/**
|
|
48
84
|
* Subscribe to collection changes
|
|
49
85
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MongoRealtimeService.d.ts","sourceRoot":"","sources":["../../src/services/MongoRealtimeService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,EAAE,EAAE,YAAY,EAA4C,MAAM,SAAS,CAAC;AACrF,OAAO,EAIH,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EAK3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,iFAAiF;AACjF,MAAM,WAAW,uBAAuB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"MongoRealtimeService.d.ts","sourceRoot":"","sources":["../../src/services/MongoRealtimeService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isDocumentAdmin, isSchemaAdmin } from "@rebasepro/types";
|
|
2
|
-
import { extractUserFromToken, logger, resolveRequireAuth } from "@rebasepro/server";
|
|
2
|
+
import { ApiError, assertWriteRequestValid, extractUserFromToken, logger, resolveRequireAuth } from "@rebasepro/server";
|
|
3
3
|
import { WebSocketServer } from "ws";
|
|
4
4
|
//#region src/websocket.ts
|
|
5
5
|
function isDriverWithAuth(driver) {
|
|
@@ -137,6 +137,13 @@ function createMongoWebSocket(server, realtimeService, driver, authConfig, admin
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
/** @see the Postgres socket — same rule, same reason. */
|
|
141
|
+
const assertWriteRequest = (path, values) => {
|
|
142
|
+
if (!path || !values || typeof values !== "object") return;
|
|
143
|
+
const collection = driver.registry?.getCollectionByPath(path);
|
|
144
|
+
if (!collection) return;
|
|
145
|
+
assertWriteRequestValid(values, collection);
|
|
146
|
+
};
|
|
140
147
|
const getScopedDelegate = async () => {
|
|
141
148
|
const session = clientSessions.get(clientId);
|
|
142
149
|
if (session?.user && isDriverWithAuth(driver)) try {
|
|
@@ -179,6 +186,7 @@ function createMongoWebSocket(server, realtimeService, driver, authConfig, admin
|
|
|
179
186
|
}
|
|
180
187
|
case "SAVE": {
|
|
181
188
|
const request = payload;
|
|
189
|
+
assertWriteRequest(request.path, request.values);
|
|
182
190
|
const row = await (await getScopedDelegate()).save(request);
|
|
183
191
|
ws.send(JSON.stringify({
|
|
184
192
|
type: "SAVE_SUCCESS",
|
|
@@ -284,6 +292,18 @@ function createMongoWebSocket(server, realtimeService, driver, authConfig, admin
|
|
|
284
292
|
default: logger.error("❌ [WebSocket Server] Unknown message type", { detail: type });
|
|
285
293
|
}
|
|
286
294
|
} catch (error) {
|
|
295
|
+
if (error instanceof ApiError || error?.name === "ApiError") {
|
|
296
|
+
const apiError = error;
|
|
297
|
+
ws.send(JSON.stringify({
|
|
298
|
+
type: "ERROR",
|
|
299
|
+
requestId,
|
|
300
|
+
payload: { error: {
|
|
301
|
+
message: apiError.message,
|
|
302
|
+
code: apiError.code
|
|
303
|
+
} }
|
|
304
|
+
}));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
287
307
|
const errorMessage = process.env.NODE_ENV === "production" ? "An unexpected error occurred" : error instanceof Error ? error.message : "An unexpected error occurred";
|
|
288
308
|
ws.send(JSON.stringify({
|
|
289
309
|
type: "ERROR",
|
|
@@ -300,4 +320,4 @@ function createMongoWebSocket(server, realtimeService, driver, authConfig, admin
|
|
|
300
320
|
//#endregion
|
|
301
321
|
export { createMongoWebSocket };
|
|
302
322
|
|
|
303
|
-
//# sourceMappingURL=websocket-
|
|
323
|
+
//# sourceMappingURL=websocket-Do3I42Uc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-Do3I42Uc.js","names":[],"sources":["../src/websocket.ts"],"sourcesContent":["import { RealtimeProvider, DataDriver, FetchCollectionProps, FetchOneProps, SaveProps, DeleteProps, TableMetadata, DatabaseAdmin, isSchemaAdmin, isDocumentAdmin, User, AuthAdapter } from \"@rebasepro/types\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { Server } from \"http\";\nimport { inspect } from \"util\";\nimport { extractUserFromToken, resolveRequireAuth, assertWriteRequestValid, ApiError } 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\n/**\n * Normalized user identity for WebSocket sessions — the same shape the Postgres\n * socket keeps, because an `AuthAdapter` user is not an access-token payload.\n */\ninterface WsUserIdentity {\n uid: string;\n email?: string;\n displayName?: string;\n photoURL?: string;\n roles: string[];\n isAdmin: boolean;\n}\n\ninterface ClientSession {\n ws: WebSocket;\n user?: WsUserIdentity;\n authenticated: boolean;\n messageCount: number;\n messageWindowStart: number;\n}\n\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) return false;\n // The adapter's own answer first; a role *named* `admin` is only the\n // fallback for the built-in JWT path.\n if (session.user.isAdmin) return true;\n return (session.user.roles ?? []).some((r) => r === \"admin\");\n}\n\nexport function createMongoWebSocket(\n server: Server,\n realtimeService: MongoRealtimeService,\n driver: MongoDriver,\n authConfig?: RebaseAuthConfig,\n admin?: DatabaseAdmin,\n authAdapter?: AuthAdapter\n) {\n // Scoped to this factory invocation rather than the module, so sessions do\n // not leak across hot reloads or a second server on the same process — the\n // Postgres socket keeps it here for the same reason.\n const clientSessions = new Map<string, ClientSession>();\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 // The same predicate the HTTP data routes use, from the same function. See\n // `resolveRequireAuth` for what this socket's local copy got wrong — most\n // importantly that a `false` here does not skip a check, it marks every\n // session `authenticated` at connect time.\n const requireAuth = !!authAdapter || resolveRequireAuth(authConfig);\n\n if (requireAuth && !authAdapter && !authConfig?.jwtSecret) {\n logger.warn(\n \"🔐 [WebSocket Server] Authentication is required but no adapter or jwtSecret is \" +\n \"configured — no client can complete AUTH, so every realtime message will be refused.\"\n );\n }\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 // The adapter verifies when one is configured, exactly as the\n // HTTP routes do; the built-in JWT path is the fallback.\n let verifiedUser: WsUserIdentity | null = null;\n\n if (authAdapter) {\n try {\n const adapterUser = authAdapter.verifyToken\n ? await authAdapter.verifyToken(token)\n : await authAdapter.verifyRequest(new Request(\"http://localhost/_ws_auth\", {\n headers: { Authorization: `Bearer ${token}` }\n }));\n if (adapterUser) {\n verifiedUser = {\n uid: adapterUser.uid,\n email: adapterUser.email,\n roles: adapterUser.roles ?? [],\n isAdmin: !!adapterUser.isAdmin\n };\n }\n } catch {\n // Adapter threw — treat as invalid token\n }\n } else {\n const jwtPayload = extractUserFromToken(token);\n if (jwtPayload) {\n verifiedUser = {\n uid: jwtPayload.uid,\n email: jwtPayload.email,\n displayName: jwtPayload.displayName,\n photoURL: jwtPayload.photoURL,\n roles: jwtPayload.roles ?? [],\n isAdmin: (jwtPayload.roles ?? []).some((r: string) => r === \"admin\")\n };\n }\n }\n\n if (verifiedUser) {\n const session = clientSessions.get(clientId);\n if (session) {\n session.user = verifiedUser;\n session.authenticated = true;\n }\n ws.send(JSON.stringify({ type: \"AUTH_SUCCESS\",\nrequestId,\npayload: { uid: verifiedUser.uid,\nroles: verifiedUser.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 /** @see the Postgres socket — same rule, same reason. */\n const assertWriteRequest = (path: string | undefined, values: unknown): void => {\n if (!path || !values || typeof values !== \"object\") return;\n const collection = driver.registry?.getCollectionByPath(path);\n if (!collection) return;\n assertWriteRequestValid(values as Record<string, unknown>, collection);\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.uid,\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 // The REST layer's write checks, at this boundary too —\n // the socket is a second way in, and it used to be the\n // unchecked one. Collection from the registry by path,\n // never from the client's `request.collection`.\n assertWriteRequest(request.path, request.values as Record<string, unknown>);\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 ? { uid: session.user.uid,\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 // A refused write keeps its message: it is the only thing that\n // tells the caller what to send instead, and the generic branch\n // below drops it in production.\n if (error instanceof ApiError || (error as Error)?.name === \"ApiError\") {\n const apiError = error as ApiError;\n ws.send(JSON.stringify({ type: \"ERROR\",\nrequestId,\npayload: { error: { message: apiError.message,\ncode: apiError.code } } }));\n return;\n }\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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"}
|
package/dist/websocket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4G,aAAa,EAAwC,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE9M,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAG9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAuDrD,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,oBAAoB,EACrC,MAAM,EAAE,WAAW,EACnB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,KAAK,CAAC,EAAE,aAAa,EACrB,WAAW,CAAC,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4G,aAAa,EAAwC,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE9M,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAG9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAuDrD,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,oBAAoB,EACrC,MAAM,EAAE,WAAW,EACnB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,KAAK,CAAC,EAAE,aAAa,EACrB,WAAW,CAAC,EAAE,WAAW,QA8T5B"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-mongo",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.1",
|
|
5
5
|
"description": "MongoDB backend for Rebase",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"mongodb": "^7.5.0",
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@rebasepro/common": "0.14.
|
|
44
|
-
"@rebasepro/
|
|
45
|
-
"@rebasepro/
|
|
43
|
+
"@rebasepro/common": "0.14.1",
|
|
44
|
+
"@rebasepro/types": "0.14.1",
|
|
45
|
+
"@rebasepro/utils": "0.14.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/jest": "^30.0.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"vite": "^8.1.5"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@rebasepro/server": "0.14.
|
|
58
|
+
"@rebasepro/server": "0.14.1"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@rebasepro/server": {
|
package/src/auth/services.ts
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
ListUsersOptions,
|
|
21
21
|
PaginatedUsersResult,
|
|
22
22
|
MfaFactor,
|
|
23
|
-
MfaChallengeInfo
|
|
23
|
+
MfaChallengeInfo,
|
|
24
|
+
ApiError
|
|
24
25
|
} from "@rebasepro/server";
|
|
25
26
|
|
|
26
27
|
export type Role = RoleData;
|
|
@@ -77,7 +78,19 @@ export class MongoUserService implements UserRepository {
|
|
|
77
78
|
createdAt: now,
|
|
78
79
|
updatedAt: now
|
|
79
80
|
};
|
|
80
|
-
|
|
81
|
+
try {
|
|
82
|
+
await this.collection.insertOne(doc);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// 11000 is Mongo's duplicate key, and the unique index on `email`
|
|
85
|
+
// in `ensure-collections.ts` is what raises it. Same answer as
|
|
86
|
+
// Postgres gives for its 23505, and the same answer the route
|
|
87
|
+
// gives when its pre-check sees the row — see
|
|
88
|
+
// `UserRepository.createUser`.
|
|
89
|
+
if ((error as { code?: number })?.code === 11000) {
|
|
90
|
+
throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
81
94
|
return toUser(doc);
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -772,9 +785,27 @@ export class MongoAuthRepository implements AuthRepository {
|
|
|
772
785
|
await this.tokenRepository.markMagicLinkTokenUsed(tokenHash);
|
|
773
786
|
}
|
|
774
787
|
|
|
775
|
-
// MFA
|
|
788
|
+
// ── MFA: not implemented on this engine ──────────────────────────────
|
|
789
|
+
//
|
|
790
|
+
// The routes are mounted for every backend, so `POST /auth/mfa/enroll`
|
|
791
|
+
// is live here and reaches these. Throwing a bare `Error` made every one
|
|
792
|
+
// of them a 500 — and a 500's message is sanitized on the way out, so the
|
|
793
|
+
// caller was told "Internal Server Error" while the reason stayed in the
|
|
794
|
+
// server log. Someone turning on two-factor auth got a server fault
|
|
795
|
+
// instead of an answer, every time, on this engine.
|
|
796
|
+
//
|
|
797
|
+
// 501 with the reason, which is what `init.ts` already does for an admin
|
|
798
|
+
// surface it mounts and cannot serve: "they answer 501 instead, and stay
|
|
799
|
+
// mounted to say why". The reads below stay as they are — no factor can
|
|
800
|
+
// exist here, so `[]`, `null` and `false` are true rather than merely
|
|
801
|
+
// convenient, and `assertMfaSatisfied` reading `false` correctly leaves
|
|
802
|
+
// the login gate inert.
|
|
776
803
|
async createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
|
|
777
|
-
throw new
|
|
804
|
+
throw new ApiError(
|
|
805
|
+
501,
|
|
806
|
+
"MFA_NOT_SUPPORTED",
|
|
807
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so a factor cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
808
|
+
);
|
|
778
809
|
}
|
|
779
810
|
async getMfaFactors(uid: string): Promise<MfaFactor[]> {
|
|
780
811
|
return [];
|
|
@@ -783,22 +814,42 @@ export class MongoAuthRepository implements AuthRepository {
|
|
|
783
814
|
return null;
|
|
784
815
|
}
|
|
785
816
|
async verifyMfaFactor(factorId: string): Promise<void> {
|
|
786
|
-
throw new
|
|
817
|
+
throw new ApiError(
|
|
818
|
+
501,
|
|
819
|
+
"MFA_NOT_SUPPORTED",
|
|
820
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so a factor's verification cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
821
|
+
);
|
|
787
822
|
}
|
|
788
823
|
async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
|
|
789
|
-
throw new
|
|
824
|
+
throw new ApiError(
|
|
825
|
+
501,
|
|
826
|
+
"MFA_NOT_SUPPORTED",
|
|
827
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so a factor cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
828
|
+
);
|
|
790
829
|
}
|
|
791
830
|
async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {
|
|
792
|
-
throw new
|
|
831
|
+
throw new ApiError(
|
|
832
|
+
501,
|
|
833
|
+
"MFA_NOT_SUPPORTED",
|
|
834
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so a challenge cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
835
|
+
);
|
|
793
836
|
}
|
|
794
837
|
async getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null> {
|
|
795
838
|
return null;
|
|
796
839
|
}
|
|
797
840
|
async verifyMfaChallenge(challengeId: string): Promise<void> {
|
|
798
|
-
throw new
|
|
841
|
+
throw new ApiError(
|
|
842
|
+
501,
|
|
843
|
+
"MFA_NOT_SUPPORTED",
|
|
844
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so a challenge's verification cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
845
|
+
);
|
|
799
846
|
}
|
|
800
847
|
async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
|
|
801
|
-
throw new
|
|
848
|
+
throw new ApiError(
|
|
849
|
+
501,
|
|
850
|
+
"MFA_NOT_SUPPORTED",
|
|
851
|
+
"Multi-factor authentication is not implemented for the MongoDB backend, so recovery codes cannot be stored. Use a Postgres data source for accounts that need a second factor."
|
|
852
|
+
);
|
|
802
853
|
}
|
|
803
854
|
async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
|
|
804
855
|
return false;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Translates Rebase filter conditions to MongoDB query operators.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { CollectionConfig, FilterCondition, FilterValues, LogicalCondition, WhereFilterOp } from "@rebasepro/types";
|
|
8
|
-
import { toFilterTuples } from "@rebasepro/common";
|
|
7
|
+
import { CollectionConfig, FilterCondition, FilterValues, LogicalCondition, OrderByTuple, WhereFilterOp } from "@rebasepro/types";
|
|
8
|
+
import { normalizeDriverOrderBy, toFilterTuples } from "@rebasepro/common";
|
|
9
9
|
import { Filter, Document } from "mongodb";
|
|
10
10
|
import { ApiError, logger } from "@rebasepro/server";
|
|
11
11
|
|
|
@@ -292,18 +292,57 @@ export class MongoConditionBuilder {
|
|
|
292
292
|
return this.combineConditionsWithAnd(conditions) ?? {};
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
/**
|
|
296
|
+
* The primary key's name in a stored document.
|
|
297
|
+
*
|
|
298
|
+
* Rows leave this driver with `_id` renamed to `id` — see
|
|
299
|
+
* `MongoDataService.documentToRow` — so `id` is the only name a caller ever
|
|
300
|
+
* sees, and the one the SDK's own examples use for a tie-breaker. A sort
|
|
301
|
+
* document naming `id` names a field no document has: Mongo does not
|
|
302
|
+
* complain, it just returns them in natural order, so `.orderBy("id")` read
|
|
303
|
+
* as "no sort at all" with a 200 to go with it.
|
|
304
|
+
*/
|
|
305
|
+
private static readonly ID_FIELD = "_id";
|
|
306
|
+
|
|
295
307
|
/**
|
|
296
308
|
* Build MongoDB sort options from Rebase options
|
|
297
309
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
310
|
+
* A Mongo sort document is already an ordered map of field to direction, so
|
|
311
|
+
* a multi-key sort is expressed directly: the keys are applied in insertion
|
|
312
|
+
* order, each breaking ties on the one before it.
|
|
313
|
+
*
|
|
314
|
+
* The id closes every sort, descending, for the same reason the Postgres
|
|
315
|
+
* driver appends `id DESC`: it is what makes the ordering *total*. Without
|
|
316
|
+
* it, two rows sharing a sort value are returned in whatever order the
|
|
317
|
+
* engine pleases, and that order is free to differ between two executions of
|
|
318
|
+
* the same query — so paging by `offset` over a non-unique sort column
|
|
319
|
+
* repeats some rows and skips others. A single-column sort has always had
|
|
320
|
+
* this exposure here; a multi-key sort merely made it easier to reach, since
|
|
321
|
+
* the whole point of the later keys is that the earlier ones tie.
|
|
322
|
+
*
|
|
323
|
+
* @param orderBy - Field to order by, or the `[field, direction]` list
|
|
324
|
+
* @param order - Sort direction, for the single-field spelling
|
|
300
325
|
* @returns MongoDB sort object
|
|
301
326
|
*/
|
|
302
327
|
static buildSort(
|
|
303
|
-
orderBy?: string,
|
|
328
|
+
orderBy?: string | OrderByTuple[],
|
|
304
329
|
order?: "asc" | "desc"
|
|
305
330
|
): Record<string, 1 | -1> | undefined {
|
|
306
|
-
|
|
307
|
-
|
|
331
|
+
const keys = normalizeDriverOrderBy(orderBy, order);
|
|
332
|
+
if (!keys) return undefined;
|
|
333
|
+
const sort: Record<string, 1 | -1> = {};
|
|
334
|
+
// A repeated field is the first occurrence's: that is the key Mongo
|
|
335
|
+
// would sort by, and letting a later duplicate overwrite it would order
|
|
336
|
+
// by a direction the caller listed as less significant.
|
|
337
|
+
for (const [field, direction] of keys) {
|
|
338
|
+
const key = field === "id" ? MongoConditionBuilder.ID_FIELD : field;
|
|
339
|
+
if (!(key in sort)) sort[key] = direction === "desc" ? -1 : 1;
|
|
340
|
+
}
|
|
341
|
+
// Already named by the caller — at whatever direction and rank they
|
|
342
|
+
// chose — the sort is total and there is nothing left to break.
|
|
343
|
+
if (!(MongoConditionBuilder.ID_FIELD in sort)) {
|
|
344
|
+
sort[MongoConditionBuilder.ID_FIELD] = -1;
|
|
345
|
+
}
|
|
346
|
+
return sort;
|
|
308
347
|
}
|
|
309
348
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Db, ObjectId, Collection, Document, FindOptions, Filter } from "mongodb";
|
|
9
|
-
import { FilterValues, DataRepository, CollectionConfig, EntityReference, LogicalCondition } from "@rebasepro/types";
|
|
9
|
+
import { FilterValues, DataRepository, CollectionConfig, EntityReference, LogicalCondition, OrderByTuple } from "@rebasepro/types";
|
|
10
10
|
import { MongoConditionBuilder } from "./MongoConditionBuilder";
|
|
11
11
|
import { ApiError } from "@rebasepro/server";
|
|
12
12
|
|
|
@@ -195,7 +195,7 @@ export class MongoDataService implements DataRepository {
|
|
|
195
195
|
filter?: FilterValues<Extract<keyof M, string>>;
|
|
196
196
|
/** An `or(...)`/`and(...)` group, AND-ed with `filter`. */
|
|
197
197
|
logical?: LogicalCondition;
|
|
198
|
-
orderBy?: string;
|
|
198
|
+
orderBy?: string | OrderByTuple[];
|
|
199
199
|
order?: "desc" | "asc";
|
|
200
200
|
limit?: number;
|
|
201
201
|
offset?: number;
|
|
@@ -253,7 +253,7 @@ export class MongoDataService implements DataRepository {
|
|
|
253
253
|
searchString: string,
|
|
254
254
|
options: {
|
|
255
255
|
filter?: FilterValues<Extract<keyof M, string>>;
|
|
256
|
-
orderBy?: string;
|
|
256
|
+
orderBy?: string | OrderByTuple[];
|
|
257
257
|
order?: "desc" | "asc";
|
|
258
258
|
limit?: number;
|
|
259
259
|
databaseId?: string;
|
|
@@ -176,18 +176,13 @@ propertyCallbacks: undefined };
|
|
|
176
176
|
* what every re-fetch reads — the wrapper used to stamp the field on the
|
|
177
177
|
* `Subscription` object instead, and nothing has ever read that one.
|
|
178
178
|
*/
|
|
179
|
-
listenCollection<M extends Record<string, any>>(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
searchString,
|
|
187
|
-
order,
|
|
188
|
-
onUpdate,
|
|
189
|
-
onError
|
|
190
|
-
}: ListenCollectionProps<M>, authContext?: { uid: string; roles: string[] }): () => void {
|
|
179
|
+
listenCollection<M extends Record<string, any>>(
|
|
180
|
+
// `collection` is re-resolved from the registry on every re-fetch, and
|
|
181
|
+
// `vectorSearch` is not a thing a subscription can do — the Postgres
|
|
182
|
+
// service refuses it outright rather than run it once and never again.
|
|
183
|
+
{ onUpdate, onError, collection, vectorSearch, ...query }: ListenCollectionProps<M>,
|
|
184
|
+
authContext?: { uid: string; roles: string[] }
|
|
185
|
+
): () => void {
|
|
191
186
|
const subscriptionId = this.generateSubscriptionId();
|
|
192
187
|
|
|
193
188
|
const callback = (rows: Record<string, unknown>[]) => {
|
|
@@ -201,19 +196,15 @@ propertyCallbacks: undefined };
|
|
|
201
196
|
}
|
|
202
197
|
};
|
|
203
198
|
|
|
199
|
+
// Forwarded whole rather than re-listed, for the reason `fetchCollection`
|
|
200
|
+
// gives above: the hand-written list named seven of the eleven fields
|
|
201
|
+
// `ListenCollectionProps` declares, so `logical` and `offset` were
|
|
202
|
+
// accepted at every type-checked boundary and then dropped — an
|
|
203
|
+
// `or(...)` subscription was pushed every row, and a subscription to
|
|
204
|
+
// page two was pushed page one.
|
|
204
205
|
this.realtimeService.subscribeToCollection(
|
|
205
206
|
subscriptionId,
|
|
206
|
-
{
|
|
207
|
-
clientId: "driver",
|
|
208
|
-
path,
|
|
209
|
-
filter,
|
|
210
|
-
orderBy,
|
|
211
|
-
order,
|
|
212
|
-
limit,
|
|
213
|
-
startAfter,
|
|
214
|
-
searchString,
|
|
215
|
-
authContext
|
|
216
|
-
},
|
|
207
|
+
{ clientId: "driver", ...query, authContext },
|
|
217
208
|
callback
|
|
218
209
|
);
|
|
219
210
|
|