@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.
Files changed (52) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +86 -0
  3. package/dist/MongoBootstrapper.d.ts +18 -0
  4. package/dist/MongoBootstrapper.d.ts.map +1 -0
  5. package/dist/auth/ensure-collections.d.ts +3 -0
  6. package/dist/auth/ensure-collections.d.ts.map +1 -0
  7. package/dist/auth/services.d.ts +156 -0
  8. package/dist/auth/services.d.ts.map +1 -0
  9. package/dist/connection.d.ts +35 -0
  10. package/dist/connection.d.ts.map +1 -0
  11. package/dist/db/MongoConditionBuilder.d.ts +64 -0
  12. package/dist/db/MongoConditionBuilder.d.ts.map +1 -0
  13. package/dist/db/MongoDataService.d.ts +101 -0
  14. package/dist/db/MongoDataService.d.ts.map +1 -0
  15. package/dist/ensure-collections-Bkx_O5CQ.js +94 -0
  16. package/dist/ensure-collections-Bkx_O5CQ.js.map +1 -0
  17. package/dist/ensure-history-collection-yajOt2dv.js +21 -0
  18. package/dist/ensure-history-collection-yajOt2dv.js.map +1 -0
  19. package/dist/factory.d.ts +151 -0
  20. package/dist/factory.d.ts.map +1 -0
  21. package/dist/history/ensure-history-collection.d.ts +3 -0
  22. package/dist/history/ensure-history-collection.d.ts.map +1 -0
  23. package/dist/index.d.ts +18 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.es.js +2508 -0
  26. package/dist/index.es.js.map +1 -0
  27. package/dist/index.umd.js +2925 -0
  28. package/dist/index.umd.js.map +1 -0
  29. package/dist/services/MongoDriver.d.ts +125 -0
  30. package/dist/services/MongoDriver.d.ts.map +1 -0
  31. package/dist/services/MongoHistoryService.d.ts +37 -0
  32. package/dist/services/MongoHistoryService.d.ts.map +1 -0
  33. package/dist/services/MongoRealtimeService.d.ts +103 -0
  34. package/dist/services/MongoRealtimeService.d.ts.map +1 -0
  35. package/dist/websocket-DQlwCHFq.js +281 -0
  36. package/dist/websocket-DQlwCHFq.js.map +1 -0
  37. package/dist/websocket.d.ts +7 -0
  38. package/dist/websocket.d.ts.map +1 -0
  39. package/package.json +81 -0
  40. package/src/MongoBootstrapper.ts +196 -0
  41. package/src/auth/ensure-collections.ts +105 -0
  42. package/src/auth/services.ts +732 -0
  43. package/src/connection.ts +60 -0
  44. package/src/db/MongoConditionBuilder.ts +224 -0
  45. package/src/db/MongoDataService.ts +368 -0
  46. package/src/factory.ts +305 -0
  47. package/src/history/ensure-history-collection.ts +22 -0
  48. package/src/index.ts +25 -0
  49. package/src/services/MongoDriver.ts +1120 -0
  50. package/src/services/MongoHistoryService.ts +181 -0
  51. package/src/services/MongoRealtimeService.ts +446 -0
  52. package/src/websocket.ts +297 -0
@@ -0,0 +1,2925 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("mongodb"), require("@rebasepro/types"), require("@rebasepro/server"), require("@rebasepro/common"), require("@rebasepro/utils"), require("ws")) : typeof define === "function" && define.amd ? define([
3
+ "exports",
4
+ "mongodb",
5
+ "@rebasepro/types",
6
+ "@rebasepro/server",
7
+ "@rebasepro/common",
8
+ "@rebasepro/utils",
9
+ "ws"
10
+ ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase MongoDB"] = {}, global.mongodb, global._rebasepro_types, global._rebasepro_server, global._rebasepro_common, global._rebasepro_utils, global.ws));
11
+ })(this, function(exports, mongodb, _rebasepro_types, _rebasepro_server, _rebasepro_common, _rebasepro_utils, ws) {
12
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
13
+ //#region \0rolldown/runtime.js
14
+ var __defProp = Object.defineProperty;
15
+ var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
+ var __exportAll = (all, no_symbols) => {
17
+ let target = {};
18
+ for (var name in all) __defProp(target, name, {
19
+ get: all[name],
20
+ enumerable: true
21
+ });
22
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
23
+ return target;
24
+ };
25
+ //#endregion
26
+ //#region src/connection.ts
27
+ /**
28
+ * MongoDB Connection
29
+ *
30
+ * Wraps MongoDB connection to implement the DatabaseConnection interface.
31
+ */
32
+ /**
33
+ * MongoDB database connection wrapper that implements DatabaseConnection interface.
34
+ */
35
+ var MongoDBConnection = class {
36
+ db;
37
+ client;
38
+ type = "mongodb";
39
+ constructor(db, client) {
40
+ this.db = db;
41
+ this.client = client;
42
+ }
43
+ get isConnected() {
44
+ try {
45
+ return this.client.topology?.isConnected?.() ?? false;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+ async close() {
51
+ await this.client.close();
52
+ }
53
+ };
54
+ /**
55
+ * Create a MongoDB database connection from a connection string.
56
+ *
57
+ * @param connectionString - MongoDB connection string (e.g., mongodb://localhost:27017)
58
+ * @param databaseName - Name of the database to use
59
+ * @returns Promise resolving to MongoDBConnection
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const connection = await createMongoDBConnection(
64
+ * "mongodb://localhost:27017",
65
+ * "my_database"
66
+ * );
67
+ * ```
68
+ */
69
+ async function createMongoDBConnection(connectionString, databaseName) {
70
+ const client = new mongodb.MongoClient(connectionString);
71
+ await client.connect();
72
+ return new MongoDBConnection(client.db(databaseName), client);
73
+ }
74
+ //#endregion
75
+ //#region src/db/MongoConditionBuilder.ts
76
+ /**
77
+ * Mapping from Rebase filter operators to MongoDB query operators
78
+ */
79
+ var REBASE_TO_MONGO_OP = {
80
+ "<": "$lt",
81
+ "<=": "$lte",
82
+ "==": "$eq",
83
+ "!=": "$ne",
84
+ ">=": "$gte",
85
+ ">": "$gt",
86
+ "array-contains": "$elemMatch",
87
+ "array-contains-any": "$in",
88
+ "in": "$in",
89
+ "not-in": "$nin"
90
+ };
91
+ function escapeRegExp$1(str) {
92
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
93
+ }
94
+ /**
95
+ * Translate a SQL LIKE/ILIKE pattern into an anchored regular expression.
96
+ * `%` matches any sequence of characters, `_` matches a single character;
97
+ * every other character is matched literally.
98
+ */
99
+ function likePatternToRegExp(pattern, caseInsensitive) {
100
+ let body = "";
101
+ for (const ch of String(pattern)) if (ch === "%") body += ".*";
102
+ else if (ch === "_") body += ".";
103
+ else body += escapeRegExp$1(ch);
104
+ return new RegExp(`^${body}$`, caseInsensitive ? "i" : "");
105
+ }
106
+ /**
107
+ * MongoDB Condition Builder
108
+ *
109
+ * Provides static methods to translate Rebase filter conditions
110
+ * to MongoDB query filters.
111
+ */
112
+ var MongoConditionBuilder = class {
113
+ /**
114
+ * Build MongoDB filter conditions from Rebase FilterValues
115
+ *
116
+ * @param filter - Rebase filter values
117
+ * @returns Array of MongoDB filter objects
118
+ */
119
+ static buildFilterConditions(filter) {
120
+ if (!filter) return [];
121
+ const conditions = [];
122
+ for (const [field, filterParam] of Object.entries(filter)) {
123
+ if (!filterParam) continue;
124
+ const [op, value] = filterParam;
125
+ if (op === "is-null") {
126
+ conditions.push({ [field]: { $eq: null } });
127
+ continue;
128
+ }
129
+ if (op === "is-not-null") {
130
+ conditions.push({ [field]: { $ne: null } });
131
+ continue;
132
+ }
133
+ if (op === "like" || op === "ilike" || op === "not-like" || op === "not-ilike") {
134
+ const regex = likePatternToRegExp(value, op === "ilike" || op === "not-ilike");
135
+ const negated = op === "not-like" || op === "not-ilike";
136
+ conditions.push({ [field]: negated ? { $not: regex } : { $regex: regex } });
137
+ continue;
138
+ }
139
+ const mongoOp = REBASE_TO_MONGO_OP[op];
140
+ if (!mongoOp) {
141
+ _rebasepro_server.logger.warn(`Unsupported filter operator: ${op}`);
142
+ continue;
143
+ }
144
+ if (op === "array-contains") conditions.push({ [field]: { $elemMatch: { $eq: value } } });
145
+ else conditions.push({ [field]: { [mongoOp]: value } });
146
+ }
147
+ return conditions;
148
+ }
149
+ /**
150
+ * Build search conditions for text search
151
+ *
152
+ * @param searchString - Text to search for
153
+ * @param properties - Properties to search in
154
+ * @returns Array of MongoDB filter objects for text search
155
+ */
156
+ static buildSearchConditions(searchString, properties) {
157
+ if (!searchString) return [];
158
+ const orConditions = [];
159
+ const escapedSearch = escapeRegExp$1(searchString);
160
+ const searchRegex = new RegExp(escapedSearch, "i");
161
+ for (const [key, prop] of Object.entries(properties)) if (prop?.dataType === "string" || typeof prop === "string") orConditions.push({ [key]: { $regex: searchRegex } });
162
+ if (orConditions.length === 0) return [{ $text: { $search: searchString } }];
163
+ return orConditions;
164
+ }
165
+ /**
166
+ * Combine multiple conditions with AND operator
167
+ *
168
+ * @param conditions - Array of filter conditions
169
+ * @returns Combined filter or undefined if empty
170
+ */
171
+ static combineConditionsWithAnd(conditions) {
172
+ if (conditions.length === 0) return void 0;
173
+ if (conditions.length === 1) return conditions[0];
174
+ return { $and: conditions };
175
+ }
176
+ /**
177
+ * Combine multiple conditions with OR operator
178
+ *
179
+ * @param conditions - Array of filter conditions
180
+ * @returns Combined filter or undefined if empty
181
+ */
182
+ static combineConditionsWithOr(conditions) {
183
+ if (conditions.length === 0) return void 0;
184
+ if (conditions.length === 1) return conditions[0];
185
+ return { $or: conditions };
186
+ }
187
+ /**
188
+ * Build a complete MongoDB query from Rebase options
189
+ *
190
+ * @param options - Rebase fetch options
191
+ * @returns MongoDB filter object
192
+ */
193
+ static buildQuery(options) {
194
+ const conditions = [];
195
+ if (options.filter) {
196
+ const filterConditions = this.buildFilterConditions(options.filter);
197
+ conditions.push(...filterConditions);
198
+ }
199
+ if (options.searchString && options.properties) {
200
+ const searchConditions = this.buildSearchConditions(options.searchString, options.properties);
201
+ if (searchConditions.length > 0) {
202
+ const searchFilter = this.combineConditionsWithOr(searchConditions);
203
+ if (searchFilter) conditions.push(searchFilter);
204
+ }
205
+ }
206
+ return this.combineConditionsWithAnd(conditions) ?? {};
207
+ }
208
+ /**
209
+ * Build MongoDB sort options from Rebase options
210
+ *
211
+ * @param orderBy - Field to order by
212
+ * @param order - Sort direction
213
+ * @returns MongoDB sort object
214
+ */
215
+ static buildSort(orderBy, order) {
216
+ if (!orderBy) return void 0;
217
+ return { [orderBy]: order === "desc" ? -1 : 1 };
218
+ }
219
+ };
220
+ //#endregion
221
+ //#region src/db/MongoDataService.ts
222
+ /**
223
+ * MongoDB Row Service
224
+ *
225
+ * Implements DataRepository interface for MongoDB.
226
+ * Provides all CRUD operations for rows.
227
+ */
228
+ /**
229
+ * MongoDB Row Service
230
+ *
231
+ * Implements the DataRepository interface for MongoDB.
232
+ * Provides all CRUD operations for rows stored in MongoDB collections.
233
+ */
234
+ var MongoDataService = class {
235
+ db;
236
+ constructor(db) {
237
+ this.db = db;
238
+ }
239
+ /**
240
+ * Get a MongoDB collection by its path
241
+ */
242
+ getCollection(collectionPath) {
243
+ const collectionName = collectionPath.replace(/\//g, "_");
244
+ return this.db.collection(collectionName);
245
+ }
246
+ /**
247
+ * Convert a string ID to ObjectId if it's a valid ObjectId string
248
+ */
249
+ toObjectId(id) {
250
+ if (typeof id === "string" && mongodb.ObjectId.isValid(id) && id.length === 24) return new mongodb.ObjectId(id);
251
+ return id;
252
+ }
253
+ /**
254
+ * Convert a MongoDB document to a flat row (`{ id, ...fields }`)
255
+ */
256
+ documentToRow(doc) {
257
+ const { _id, ...values } = doc;
258
+ return {
259
+ ...this.convertFromMongoValues(values),
260
+ id: _id.toString()
261
+ };
262
+ }
263
+ /**
264
+ * Convert values from MongoDB format to Rebase format
265
+ */
266
+ convertFromMongoValues(values) {
267
+ const result = {};
268
+ for (const [key, value] of Object.entries(values)) result[key] = this.convertFromMongoValue(value);
269
+ return result;
270
+ }
271
+ /**
272
+ * Convert a single value from MongoDB format
273
+ */
274
+ convertFromMongoValue(value) {
275
+ if (value === null || value === void 0) return value;
276
+ if (value instanceof mongodb.ObjectId) return value.toString();
277
+ if (value instanceof Date) return value;
278
+ if (Array.isArray(value)) return value.map((v) => this.convertFromMongoValue(v));
279
+ if (typeof value === "object") {
280
+ const keys = Object.keys(value);
281
+ const isTagged = value.__type === "reference" && "id" in value && "path" in value;
282
+ const isLegacy = keys.length === 2 && keys.includes("id") && keys.includes("path");
283
+ if (isTagged || isLegacy) return new _rebasepro_types.EntityReference({
284
+ id: value.id instanceof mongodb.ObjectId ? value.id.toString() : String(value.id),
285
+ path: value.path,
286
+ driver: value.driver,
287
+ databaseId: value.databaseId
288
+ });
289
+ }
290
+ if (typeof value === "object") return this.convertFromMongoValues(value);
291
+ return value;
292
+ }
293
+ /**
294
+ * Convert values to MongoDB format for storage
295
+ */
296
+ convertToMongoValues(values) {
297
+ const result = {};
298
+ for (const [key, value] of Object.entries(values)) result[key] = this.convertToMongoValue(value);
299
+ return result;
300
+ }
301
+ /**
302
+ * Convert a single value to MongoDB format
303
+ */
304
+ convertToMongoValue(value) {
305
+ if (value === null || value === void 0) return value;
306
+ if (typeof value === "object" && value.isEntityReference?.()) {
307
+ const ref = {
308
+ __type: "reference",
309
+ id: mongodb.ObjectId.isValid(value.id) ? new mongodb.ObjectId(value.id) : value.id,
310
+ path: value.path
311
+ };
312
+ if (value.driver !== void 0) ref.driver = value.driver;
313
+ if (value.databaseId !== void 0) ref.databaseId = value.databaseId;
314
+ return ref;
315
+ }
316
+ if (value instanceof Date) return value;
317
+ if (Array.isArray(value)) return value.map((v) => this.convertToMongoValue(v));
318
+ if (typeof value === "object") return this.convertToMongoValues(value);
319
+ return value;
320
+ }
321
+ /**
322
+ * Fetch a single row by ID
323
+ */
324
+ async fetchOne(collectionPath, id, _databaseId) {
325
+ const collection = this.getCollection(collectionPath);
326
+ const objectId = this.toObjectId(id);
327
+ const doc = await collection.findOne({ _id: objectId });
328
+ if (!doc) return void 0;
329
+ return this.documentToRow(doc);
330
+ }
331
+ /**
332
+ * Fetch a collection of rows with optional filtering, ordering, and pagination
333
+ */
334
+ async fetchCollection(collectionPath, options = {}) {
335
+ const collection = this.getCollection(collectionPath);
336
+ const query = options.rawQuery ?? MongoConditionBuilder.buildQuery({
337
+ filter: options.filter,
338
+ searchString: options.searchString,
339
+ properties: options.collection?.properties ?? {}
340
+ });
341
+ const findOptions = {};
342
+ const sort = MongoConditionBuilder.buildSort(options.orderBy, options.order);
343
+ if (sort) findOptions.sort = sort;
344
+ if (options.limit) findOptions.limit = options.limit;
345
+ if (options.startAfter !== void 0) findOptions.skip = Number(options.startAfter);
346
+ return (await collection.find(query, findOptions).toArray()).map((doc) => this.documentToRow(doc));
347
+ }
348
+ /**
349
+ * Search rows by text
350
+ */
351
+ async searchRows(collectionPath, searchString, options = {}) {
352
+ return this.fetchCollection(collectionPath, {
353
+ ...options,
354
+ searchString
355
+ });
356
+ }
357
+ /**
358
+ * Count rows in a collection
359
+ */
360
+ async count(collectionPath, options = {}) {
361
+ const collection = this.getCollection(collectionPath);
362
+ const query = options.rawQuery ?? (options.filter ? MongoConditionBuilder.buildQuery({ filter: options.filter }) : {});
363
+ return collection.countDocuments(query);
364
+ }
365
+ /**
366
+ * Save an row (create or update)
367
+ */
368
+ async save(collectionPath, values, id, _databaseId) {
369
+ const collection = this.getCollection(collectionPath);
370
+ const mongoValues = this.convertToMongoValues(values);
371
+ if (id) {
372
+ const objectId = this.toObjectId(id);
373
+ await collection.updateOne({ _id: objectId }, { $set: mongoValues }, { upsert: true });
374
+ return {
375
+ ...values,
376
+ id: id.toString()
377
+ };
378
+ } else {
379
+ const newId = new mongodb.ObjectId();
380
+ await collection.insertOne({
381
+ _id: newId,
382
+ ...mongoValues
383
+ });
384
+ return {
385
+ ...values,
386
+ id: newId.toString()
387
+ };
388
+ }
389
+ }
390
+ /**
391
+ * Delete an row by ID
392
+ */
393
+ async delete(collectionPath, id, _databaseId) {
394
+ const collection = this.getCollection(collectionPath);
395
+ const objectId = this.toObjectId(id);
396
+ if ((await collection.deleteOne({ _id: objectId })).deletedCount === 0) _rebasepro_server.logger.warn(`Row ${id} not found in collection ${collectionPath}`);
397
+ }
398
+ /**
399
+ * Check if a field value is unique in a collection
400
+ */
401
+ async checkUniqueField(collectionPath, fieldName, value, excludeEntityId, _databaseId) {
402
+ const collection = this.getCollection(collectionPath);
403
+ const query = { [fieldName]: value };
404
+ if (excludeEntityId) query._id = { $ne: this.toObjectId(excludeEntityId) };
405
+ return await collection.countDocuments(query) === 0;
406
+ }
407
+ /**
408
+ * Generate a new row ID
409
+ */
410
+ generateId() {
411
+ return new mongodb.ObjectId().toString();
412
+ }
413
+ };
414
+ //#endregion
415
+ //#region src/services/MongoRealtimeService.ts
416
+ /**
417
+ * MongoDB Realtime Service
418
+ *
419
+ * Implements RealtimeProvider interface using MongoDB Change Streams.
420
+ * Provides real-time subscriptions to collection and row changes.
421
+ */
422
+ /**
423
+ * MongoDB Realtime Service
424
+ *
425
+ * Implements real-time subscriptions using MongoDB Change Streams.
426
+ * Requires MongoDB replica set for change streams to work.
427
+ */
428
+ var MongoRealtimeService = class {
429
+ db;
430
+ subscriptions = /* @__PURE__ */ new Map();
431
+ clients = /* @__PURE__ */ new Map();
432
+ dataService;
433
+ driver;
434
+ constructor(db) {
435
+ this.db = db;
436
+ this.dataService = new MongoDataService(db);
437
+ }
438
+ setDataDriver(driver) {
439
+ this.driver = driver;
440
+ }
441
+ /**
442
+ * Get the collection name from a path
443
+ */
444
+ getCollectionName(path) {
445
+ return path.replace(/\//g, "_");
446
+ }
447
+ /**
448
+ * Subscribe to collection changes
449
+ */
450
+ subscribeToCollection(subscriptionId, config, callback) {
451
+ this.unsubscribe(subscriptionId);
452
+ const collectionName = this.getCollectionName(config.path);
453
+ const collection = this.db.collection(collectionName);
454
+ const pipeline = [];
455
+ pipeline.push({ $match: { operationType: { $in: [
456
+ "insert",
457
+ "update",
458
+ "replace",
459
+ "delete"
460
+ ] } } });
461
+ try {
462
+ const changeStream = collection.watch(pipeline, { fullDocument: "updateLookup" });
463
+ const subscription = {
464
+ type: "collection",
465
+ config,
466
+ changeStream,
467
+ callback,
468
+ authContext: config.authContext
469
+ };
470
+ this.subscriptions.set(subscriptionId, subscription);
471
+ this.fetchAndNotifyCollection(subscriptionId, config, callback);
472
+ changeStream.on("change", async (change) => {
473
+ await this.fetchAndNotifyCollection(subscriptionId, config, callback);
474
+ });
475
+ changeStream.on("error", (error) => {
476
+ _rebasepro_server.logger.error(`Change stream error for subscription ${subscriptionId}`, { error });
477
+ });
478
+ } catch (error) {
479
+ _rebasepro_server.logger.warn("Change streams not available, falling back to polling", { error });
480
+ const subscription = {
481
+ type: "collection",
482
+ config,
483
+ callback,
484
+ authContext: config.authContext
485
+ };
486
+ this.subscriptions.set(subscriptionId, subscription);
487
+ this.fetchAndNotifyCollection(subscriptionId, config, callback);
488
+ }
489
+ }
490
+ /**
491
+ * Fetch collection and notify callback
492
+ */
493
+ async fetchAndNotifyCollection(subscriptionId, config, callback) {
494
+ try {
495
+ let rows;
496
+ const registryCollection = this.driver?.registry?.getCollectionByPath(config.path);
497
+ if (config.authContext && this.driver) {
498
+ const mockUser = {
499
+ uid: config.authContext.userId,
500
+ roles: config.authContext.roles
501
+ };
502
+ rows = await (await this.driver.withAuth(mockUser)).fetchCollection({
503
+ path: config.path,
504
+ collection: registryCollection,
505
+ filter: config.filter,
506
+ orderBy: config.orderBy,
507
+ order: config.order,
508
+ limit: config.limit,
509
+ startAfter: config.startAfter,
510
+ searchString: config.searchString
511
+ });
512
+ } else rows = await this.dataService.fetchCollection(config.path, {
513
+ filter: config.filter,
514
+ orderBy: config.orderBy,
515
+ order: config.order,
516
+ limit: config.limit,
517
+ startAfter: config.startAfter,
518
+ searchString: config.searchString,
519
+ collection: registryCollection
520
+ });
521
+ if (callback) callback(rows);
522
+ } catch (error) {
523
+ _rebasepro_server.logger.error(`Error fetching collection for subscription ${subscriptionId}`, { error });
524
+ }
525
+ }
526
+ /**
527
+ * Subscribe to single row changes
528
+ */
529
+ subscribeToOne(subscriptionId, config, callback) {
530
+ this.unsubscribe(subscriptionId);
531
+ const collectionName = this.getCollectionName(config.path);
532
+ const collection = this.db.collection(collectionName);
533
+ const pipeline = [{ $match: {
534
+ "documentKey._id": typeof config.id === "string" && mongodb.ObjectId.isValid(config.id) ? new mongodb.ObjectId(config.id) : config.id,
535
+ operationType: { $in: [
536
+ "insert",
537
+ "update",
538
+ "replace",
539
+ "delete"
540
+ ] }
541
+ } }];
542
+ try {
543
+ const changeStream = collection.watch(pipeline, { fullDocument: "updateLookup" });
544
+ const subscription = {
545
+ type: "single",
546
+ config,
547
+ changeStream,
548
+ callback,
549
+ authContext: config.authContext
550
+ };
551
+ this.subscriptions.set(subscriptionId, subscription);
552
+ this.fetchAndNotifyOne(subscriptionId, config, callback);
553
+ changeStream.on("change", async (change) => {
554
+ if (change.operationType === "delete") {
555
+ if (callback) callback(null);
556
+ } else await this.fetchAndNotifyOne(subscriptionId, config, callback);
557
+ });
558
+ changeStream.on("error", (error) => {
559
+ _rebasepro_server.logger.error(`Change stream error for subscription ${subscriptionId}`, { error });
560
+ });
561
+ } catch (error) {
562
+ _rebasepro_server.logger.warn("Change streams not available, falling back to polling", { error });
563
+ const subscription = {
564
+ type: "single",
565
+ config,
566
+ callback,
567
+ authContext: config.authContext
568
+ };
569
+ this.subscriptions.set(subscriptionId, subscription);
570
+ this.fetchAndNotifyOne(subscriptionId, config, callback);
571
+ }
572
+ }
573
+ /**
574
+ * Fetch row and notify callback
575
+ */
576
+ async fetchAndNotifyOne(subscriptionId, config, callback) {
577
+ try {
578
+ let row;
579
+ const registryCollection = this.driver?.registry?.getCollectionByPath(config.path);
580
+ if (config.authContext && this.driver) {
581
+ const mockUser = {
582
+ uid: config.authContext.userId,
583
+ roles: config.authContext.roles
584
+ };
585
+ row = await (await this.driver.withAuth(mockUser)).fetchOne({
586
+ path: config.path,
587
+ id: config.id,
588
+ collection: registryCollection
589
+ });
590
+ } else row = await this.dataService.fetchOne(config.path, config.id);
591
+ if (callback) callback(row || null);
592
+ } catch (error) {
593
+ _rebasepro_server.logger.error(`Error fetching row for subscription ${subscriptionId}`, { error });
594
+ }
595
+ }
596
+ /**
597
+ * Unsubscribe from a subscription
598
+ */
599
+ unsubscribe(subscriptionId) {
600
+ const subscription = this.subscriptions.get(subscriptionId);
601
+ if (subscription) {
602
+ if (subscription.changeStream) subscription.changeStream.close().catch((err) => _rebasepro_server.logger.error("Operation failed", { error: err }));
603
+ this.subscriptions.delete(subscriptionId);
604
+ }
605
+ }
606
+ /**
607
+ * Notify all relevant subscribers of an row update
608
+ * This is called after save/delete operations to push updates
609
+ */
610
+ async notifyUpdate(path, id, row, _databaseId) {
611
+ for (const [subscriptionId, subscription] of this.subscriptions) if (subscription.type === "single") {
612
+ const config = subscription.config;
613
+ if (config.path === path && config.id.toString() === id) {
614
+ if (subscription.callback) subscription.callback(row);
615
+ }
616
+ } else if (subscription.type === "collection") {
617
+ const config = subscription.config;
618
+ if (config.path === path) await this.fetchAndNotifyCollection(subscriptionId, config, subscription.callback);
619
+ }
620
+ }
621
+ /**
622
+ * Get all active subscriptions (for debugging)
623
+ */
624
+ getSubscriptions() {
625
+ return this.subscriptions;
626
+ }
627
+ /**
628
+ * Close all subscriptions
629
+ */
630
+ async closeAll() {
631
+ for (const [subscriptionId] of this.subscriptions) this.unsubscribe(subscriptionId);
632
+ }
633
+ /**
634
+ * Register a WebSocket client for real-time communication
635
+ */
636
+ addClient(clientId, ws) {
637
+ this.clients.set(clientId, ws);
638
+ ws.on("close", () => {
639
+ this.removeClient(clientId);
640
+ });
641
+ ws.on("error", (error) => {
642
+ _rebasepro_server.logger.error("WebSocket error for client", {
643
+ detail: clientId,
644
+ error
645
+ });
646
+ this.removeClient(clientId);
647
+ });
648
+ }
649
+ /**
650
+ * Remove a WebSocket client and clean up its subscriptions
651
+ */
652
+ removeClient(clientId) {
653
+ this.clients.delete(clientId);
654
+ }
655
+ /**
656
+ * Handle an incoming WebSocket message for subscription management
657
+ */
658
+ async handleClientMessage(clientId, message, _authContext) {
659
+ const ws = this.clients.get(clientId);
660
+ if (!ws) return;
661
+ const authContext = _authContext ? {
662
+ userId: _authContext.userId,
663
+ roles: (_authContext.roles ?? []).map(String)
664
+ } : void 0;
665
+ switch (message.type) {
666
+ case "subscribe_collection": {
667
+ const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
668
+ if (!subscriptionId) return;
669
+ this.subscribeToCollection(subscriptionId, {
670
+ clientId,
671
+ path: message.payload?.path,
672
+ filter: message.payload?.filter,
673
+ orderBy: message.payload?.orderBy,
674
+ order: message.payload?.order,
675
+ limit: message.payload?.limit,
676
+ startAfter: message.payload?.startAfter,
677
+ searchString: message.payload?.searchString,
678
+ authContext
679
+ }, (rows) => {
680
+ ws.send(JSON.stringify({
681
+ type: "collection_update",
682
+ subscriptionId,
683
+ rows
684
+ }));
685
+ });
686
+ break;
687
+ }
688
+ case "subscribe_one": {
689
+ const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
690
+ if (!subscriptionId) return;
691
+ this.subscribeToOne(subscriptionId, {
692
+ clientId,
693
+ path: message.payload?.path,
694
+ id: message.payload?.id,
695
+ authContext
696
+ }, (row) => {
697
+ ws.send(JSON.stringify({
698
+ type: "single_update",
699
+ subscriptionId,
700
+ row
701
+ }));
702
+ });
703
+ break;
704
+ }
705
+ case "unsubscribe": {
706
+ const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
707
+ if (subscriptionId) this.unsubscribe(subscriptionId);
708
+ break;
709
+ }
710
+ }
711
+ }
712
+ };
713
+ //#endregion
714
+ //#region src/services/MongoHistoryService.ts
715
+ var MongoHistoryService_exports = /* @__PURE__ */ __exportAll({
716
+ MongoHistoryService: () => MongoHistoryService,
717
+ findChangedFields: () => findChangedFields
718
+ });
719
+ /**
720
+ * Deep equality without JSON.stringify.
721
+ * Handles primitives, arrays, Dates, and plain objects recursively.
722
+ */
723
+ function deepEqual(a, b) {
724
+ if (a === b) return true;
725
+ if (a == null || b == null) return false;
726
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
727
+ if (Array.isArray(a) && Array.isArray(b)) {
728
+ if (a.length !== b.length) return false;
729
+ return a.every((v, i) => deepEqual(v, b[i]));
730
+ }
731
+ if (typeof a === "object" && typeof b === "object") {
732
+ const aObj = a;
733
+ const bObj = b;
734
+ const aKeys = Object.keys(aObj);
735
+ const bKeys = Object.keys(bObj);
736
+ if (aKeys.length !== bKeys.length) return false;
737
+ return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
738
+ }
739
+ return false;
740
+ }
741
+ /**
742
+ * Shallow comparison to find top-level keys that changed between two objects.
743
+ */
744
+ function findChangedFields(oldValues, newValues) {
745
+ const changed = [];
746
+ const allKeys = new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
747
+ for (const key of allKeys) {
748
+ const oldVal = oldValues[key];
749
+ const newVal = newValues[key];
750
+ if (key.startsWith("__")) continue;
751
+ if (oldVal !== newVal) if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
752
+ if (!deepEqual(oldVal, newVal)) changed.push(key);
753
+ } else changed.push(key);
754
+ }
755
+ return changed.length > 0 ? changed : null;
756
+ }
757
+ var DEFAULT_RETENTION, MongoHistoryService;
758
+ var init_MongoHistoryService = __esmMin((() => {
759
+ DEFAULT_RETENTION = {
760
+ maxEntries: 200,
761
+ ttlDays: 90
762
+ };
763
+ MongoHistoryService = class {
764
+ db;
765
+ retention;
766
+ constructor(db, retention) {
767
+ this.db = db;
768
+ this.retention = {
769
+ ...DEFAULT_RETENTION,
770
+ ...retention
771
+ };
772
+ }
773
+ async recordHistory(params) {
774
+ const { tableName, id, action, values, previousValues, updatedBy } = params;
775
+ const changedFields = previousValues && values ? findChangedFields(previousValues, values) : null;
776
+ if (action === "update" && (!changedFields || changedFields.length === 0)) return;
777
+ try {
778
+ const entry = {
779
+ id: new mongodb.ObjectId().toString(),
780
+ table_name: tableName,
781
+ entity_id: String(id),
782
+ action,
783
+ changed_fields: changedFields,
784
+ values: values || null,
785
+ previous_values: previousValues || null,
786
+ updated_by: updatedBy || null,
787
+ updated_at: /* @__PURE__ */ new Date()
788
+ };
789
+ await this.db.collection("__rebase_history").insertOne(entry);
790
+ this.pruneHistory(String(id), tableName).catch((e) => {
791
+ _rebasepro_server.logger.error(`[HistoryService] Failed to prune history for ${tableName}/${id}`, { error: e });
792
+ });
793
+ } catch (error) {
794
+ _rebasepro_server.logger.error(`[HistoryService] Failed to record history for ${tableName}/${id}`, { error });
795
+ }
796
+ }
797
+ async pruneHistory(id, tableName) {
798
+ const collection = this.db.collection("__rebase_history");
799
+ const count = await collection.countDocuments({
800
+ entity_id: id,
801
+ table_name: tableName
802
+ });
803
+ if (count > this.retention.maxEntries) {
804
+ const toDelete = count - this.retention.maxEntries;
805
+ const oldestEntries = await collection.find({
806
+ entity_id: id,
807
+ table_name: tableName
808
+ }).sort({ updated_at: 1 }).limit(toDelete).toArray();
809
+ if (oldestEntries.length > 0) {
810
+ const idsToDelete = oldestEntries.map((entry) => entry._id);
811
+ await collection.deleteMany({ _id: { $in: idsToDelete } });
812
+ }
813
+ }
814
+ const cutoffDate = /* @__PURE__ */ new Date();
815
+ cutoffDate.setDate(cutoffDate.getDate() - this.retention.ttlDays);
816
+ await collection.deleteMany({
817
+ entity_id: id,
818
+ table_name: tableName,
819
+ updated_at: { $lt: cutoffDate }
820
+ });
821
+ }
822
+ };
823
+ }));
824
+ //#endregion
825
+ //#region src/services/MongoDriver.ts
826
+ init_MongoHistoryService();
827
+ /**
828
+ * MongoDB DataDriver Delegate
829
+ *
830
+ * Implements the DataDriver interface for Rebase.
831
+ * Provides all data operations needed by the Rebase frontend.
832
+ */
833
+ var MongoDriver = class {
834
+ db;
835
+ registry;
836
+ key = "mongodb";
837
+ initialised = true;
838
+ dataService;
839
+ realtimeService;
840
+ historyService;
841
+ user;
842
+ data;
843
+ client;
844
+ constructor(db, realtimeService, historyService, registry, user) {
845
+ this.db = db;
846
+ this.registry = registry;
847
+ this.dataService = new MongoDataService(db);
848
+ this.realtimeService = realtimeService ?? new MongoRealtimeService(db);
849
+ this.historyService = historyService ?? new MongoHistoryService(db);
850
+ this.user = user;
851
+ this.data = (0, _rebasepro_common.buildSdkData)(this);
852
+ this.realtimeService.setDataDriver(this);
853
+ }
854
+ /**
855
+ * Get the current timestamp
856
+ */
857
+ currentTime() {
858
+ return /* @__PURE__ */ new Date();
859
+ }
860
+ /**
861
+ * Resolve a collection's callbacks and property callbacks from the registry.
862
+ * Used by AuthenticatedMongoDriver to apply callbacks after RLS filtering.
863
+ */
864
+ resolveCollectionCallbacks(collection, path) {
865
+ if (!collection && !path) return {
866
+ collection: void 0,
867
+ callbacks: void 0,
868
+ globalCallbacks: void 0,
869
+ propertyCallbacks: void 0
870
+ };
871
+ const registryCollection = this.registry?.getCollectionByPath(path);
872
+ const resolvedCollection = registryCollection ? {
873
+ ...collection,
874
+ ...registryCollection
875
+ } : collection;
876
+ const callbacks = resolvedCollection?.callbacks;
877
+ const globalCallbacks = this.registry?.getGlobalCallbacks();
878
+ const properties = resolvedCollection?.properties;
879
+ let propertyCallbacks;
880
+ if (properties) propertyCallbacks = (0, _rebasepro_common.buildPropertyCallbacks)(properties);
881
+ return {
882
+ collection: resolvedCollection,
883
+ callbacks,
884
+ globalCallbacks,
885
+ propertyCallbacks
886
+ };
887
+ }
888
+ /**
889
+ * Fetch a collection of rows
890
+ */
891
+ async fetchCollection({ path, collection, filter, limit, startAfter, orderBy, searchString, order }) {
892
+ const rows = await this.dataService.fetchCollection(path, {
893
+ filter,
894
+ limit,
895
+ startAfter,
896
+ orderBy,
897
+ order,
898
+ searchString,
899
+ collection
900
+ });
901
+ const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = this.resolveCollectionCallbacks(collection, path);
902
+ if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {
903
+ const contextForCallback = {
904
+ user: this.user,
905
+ driver: this,
906
+ data: this.data,
907
+ client: this.client,
908
+ storageSource: this.client?.storage
909
+ };
910
+ return Promise.all(rows.map(async (row) => {
911
+ let fetched = row;
912
+ if (globalCallbacks?.afterRead) fetched = await globalCallbacks.afterRead({
913
+ collection: resolvedCollection,
914
+ path,
915
+ row: fetched,
916
+ context: contextForCallback
917
+ }) ?? fetched;
918
+ if (callbacks?.afterRead) fetched = await callbacks.afterRead({
919
+ collection: resolvedCollection,
920
+ path,
921
+ row: fetched,
922
+ context: contextForCallback
923
+ }) ?? fetched;
924
+ if (propertyCallbacks?.afterRead) fetched = await propertyCallbacks.afterRead({
925
+ collection: resolvedCollection,
926
+ path,
927
+ row: fetched,
928
+ context: contextForCallback
929
+ }) ?? fetched;
930
+ return fetched;
931
+ }));
932
+ }
933
+ return rows;
934
+ }
935
+ /**
936
+ * Listen to collection changes
937
+ */
938
+ listenCollection({ path, collection, filter, limit, startAfter, orderBy, searchString, order, onUpdate, onError }) {
939
+ const subscriptionId = this.generateSubscriptionId();
940
+ const callback = (rows) => {
941
+ try {
942
+ onUpdate(rows);
943
+ } catch (error) {
944
+ _rebasepro_server.logger.error("Error in collection update callback", { error });
945
+ if (onError) onError(error instanceof Error ? error : new Error(String(error)));
946
+ }
947
+ };
948
+ this.realtimeService.subscribeToCollection(subscriptionId, {
949
+ clientId: "driver",
950
+ path,
951
+ filter,
952
+ orderBy,
953
+ order,
954
+ limit,
955
+ startAfter,
956
+ searchString
957
+ }, callback);
958
+ return () => {
959
+ this.realtimeService.unsubscribe(subscriptionId);
960
+ };
961
+ }
962
+ /**
963
+ * Fetch a single row
964
+ */
965
+ async fetchOne({ path, id, databaseId, collection }) {
966
+ let row = await this.dataService.fetchOne(path, id, databaseId);
967
+ const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = this.resolveCollectionCallbacks(collection, path);
968
+ if (row && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {
969
+ const contextForCallback = {
970
+ user: this.user,
971
+ driver: this,
972
+ data: this.data,
973
+ client: this.client,
974
+ storageSource: this.client?.storage
975
+ };
976
+ let processedRow = row;
977
+ if (globalCallbacks?.afterRead) processedRow = await globalCallbacks.afterRead({
978
+ collection: resolvedCollection,
979
+ path,
980
+ row: processedRow,
981
+ context: contextForCallback
982
+ }) ?? processedRow;
983
+ if (callbacks?.afterRead) processedRow = await callbacks.afterRead({
984
+ collection: resolvedCollection,
985
+ path,
986
+ row: processedRow,
987
+ context: contextForCallback
988
+ }) ?? processedRow;
989
+ if (propertyCallbacks?.afterRead) processedRow = await propertyCallbacks.afterRead({
990
+ collection: resolvedCollection,
991
+ path,
992
+ row: processedRow,
993
+ context: contextForCallback
994
+ }) ?? processedRow;
995
+ row = processedRow;
996
+ }
997
+ return row;
998
+ }
999
+ /**
1000
+ * Listen to row changes
1001
+ */
1002
+ listenOne({ path, id, collection, onUpdate, onError }) {
1003
+ const subscriptionId = this.generateSubscriptionId();
1004
+ const callback = (row) => {
1005
+ try {
1006
+ onUpdate(row);
1007
+ } catch (error) {
1008
+ _rebasepro_server.logger.error("Error in row update callback", { error });
1009
+ if (onError) onError(error instanceof Error ? error : new Error(String(error)));
1010
+ }
1011
+ };
1012
+ this.realtimeService.subscribeToOne(subscriptionId, {
1013
+ clientId: "driver",
1014
+ path,
1015
+ id
1016
+ }, callback);
1017
+ return () => {
1018
+ this.realtimeService.unsubscribe(subscriptionId);
1019
+ };
1020
+ }
1021
+ /**
1022
+ * Save an row (create or update)
1023
+ */
1024
+ async save({ path, id, values, collection, status }) {
1025
+ const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = this.resolveCollectionCallbacks(collection, path);
1026
+ let updatedValues = values;
1027
+ const contextForCallback = {
1028
+ user: this.user,
1029
+ driver: this,
1030
+ data: this.data,
1031
+ client: this.client,
1032
+ storageSource: this.client?.storage
1033
+ };
1034
+ let previousValuesForHistory;
1035
+ if (status === "existing" && id) {
1036
+ const existing = await this.dataService.fetchOne(path, id, resolvedCollection?.databaseId);
1037
+ if (existing) {
1038
+ const { id: _existingId, ...existingValues } = existing;
1039
+ previousValuesForHistory = existingValues;
1040
+ }
1041
+ }
1042
+ if (globalCallbacks?.beforeSave || callbacks?.beforeSave || propertyCallbacks?.beforeSave) {
1043
+ if (globalCallbacks?.beforeSave) {
1044
+ const result = await globalCallbacks.beforeSave({
1045
+ collection: resolvedCollection,
1046
+ path,
1047
+ id,
1048
+ values: updatedValues,
1049
+ previousValues: previousValuesForHistory,
1050
+ status,
1051
+ context: contextForCallback
1052
+ });
1053
+ if (result) updatedValues = (0, _rebasepro_utils.mergeDeep)(updatedValues, result);
1054
+ }
1055
+ if (callbacks?.beforeSave) {
1056
+ const result = await callbacks.beforeSave({
1057
+ collection: resolvedCollection,
1058
+ path,
1059
+ id,
1060
+ values: updatedValues,
1061
+ previousValues: previousValuesForHistory,
1062
+ status,
1063
+ context: contextForCallback
1064
+ });
1065
+ if (result) updatedValues = (0, _rebasepro_utils.mergeDeep)(updatedValues, result);
1066
+ }
1067
+ if (propertyCallbacks?.beforeSave) {
1068
+ const result = await propertyCallbacks.beforeSave({
1069
+ collection: resolvedCollection,
1070
+ path,
1071
+ id,
1072
+ values: updatedValues,
1073
+ previousValues: previousValuesForHistory,
1074
+ status,
1075
+ context: contextForCallback
1076
+ });
1077
+ if (result) updatedValues = (0, _rebasepro_utils.mergeDeep)(updatedValues, result);
1078
+ }
1079
+ }
1080
+ if (resolvedCollection?.properties) updatedValues = (0, _rebasepro_common.updateDateAutoValues)({
1081
+ inputValues: updatedValues,
1082
+ properties: resolvedCollection.properties,
1083
+ status: status ?? "new",
1084
+ timestampNowValue: /* @__PURE__ */ new Date()
1085
+ });
1086
+ try {
1087
+ let savedRow = await this.dataService.save(path, updatedValues, id, resolvedCollection?.databaseId);
1088
+ if (savedRow && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {
1089
+ if (globalCallbacks?.afterRead) savedRow = await globalCallbacks.afterRead({
1090
+ collection: resolvedCollection,
1091
+ path,
1092
+ row: savedRow,
1093
+ context: contextForCallback
1094
+ }) ?? savedRow;
1095
+ if (callbacks?.afterRead) savedRow = await callbacks.afterRead({
1096
+ collection: resolvedCollection,
1097
+ path,
1098
+ row: savedRow,
1099
+ context: contextForCallback
1100
+ }) ?? savedRow;
1101
+ if (propertyCallbacks?.afterRead) savedRow = await propertyCallbacks.afterRead({
1102
+ collection: resolvedCollection,
1103
+ path,
1104
+ row: savedRow,
1105
+ context: contextForCallback
1106
+ }) ?? savedRow;
1107
+ }
1108
+ const savedId = savedRow.id;
1109
+ const { id: _savedId, ...savedValues } = savedRow;
1110
+ if (globalCallbacks?.afterSave || callbacks?.afterSave || propertyCallbacks?.afterSave) {
1111
+ if (globalCallbacks?.afterSave) await globalCallbacks.afterSave({
1112
+ collection: resolvedCollection,
1113
+ path,
1114
+ id: savedId,
1115
+ values: savedValues,
1116
+ previousValues: previousValuesForHistory,
1117
+ status,
1118
+ context: contextForCallback
1119
+ });
1120
+ if (callbacks?.afterSave) await callbacks.afterSave({
1121
+ collection: resolvedCollection,
1122
+ path,
1123
+ id: savedId,
1124
+ values: savedValues,
1125
+ previousValues: previousValuesForHistory,
1126
+ status,
1127
+ context: contextForCallback
1128
+ });
1129
+ if (propertyCallbacks?.afterSave) await propertyCallbacks.afterSave({
1130
+ collection: resolvedCollection,
1131
+ path,
1132
+ id: savedId,
1133
+ values: savedValues,
1134
+ previousValues: previousValuesForHistory,
1135
+ status,
1136
+ context: contextForCallback
1137
+ });
1138
+ }
1139
+ if (this.historyService && resolvedCollection?.history) this.historyService.recordHistory({
1140
+ tableName: path,
1141
+ id: savedId.toString(),
1142
+ action: status === "new" ? "create" : "update",
1143
+ values: savedValues,
1144
+ previousValues: previousValuesForHistory,
1145
+ updatedBy: this.user?.uid
1146
+ }).catch((err) => {
1147
+ _rebasepro_server.logger.error(`Failed to record history for ${path}/${savedId}`, { error: err });
1148
+ });
1149
+ await this.realtimeService.notifyUpdate(path, savedId.toString(), savedRow);
1150
+ return savedRow;
1151
+ } catch (error) {
1152
+ if (callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {
1153
+ if (callbacks?.afterSaveError) await callbacks.afterSaveError({
1154
+ collection: resolvedCollection,
1155
+ path,
1156
+ id: id || "unknown",
1157
+ values: updatedValues,
1158
+ previousValues: void 0,
1159
+ status,
1160
+ context: contextForCallback
1161
+ });
1162
+ if (propertyCallbacks?.afterSaveError) await propertyCallbacks.afterSaveError({
1163
+ collection: resolvedCollection,
1164
+ path,
1165
+ id: id || "unknown",
1166
+ values: updatedValues,
1167
+ previousValues: void 0,
1168
+ status,
1169
+ context: contextForCallback
1170
+ });
1171
+ }
1172
+ throw error;
1173
+ }
1174
+ }
1175
+ /**
1176
+ * Delete an row
1177
+ */
1178
+ async delete({ row, collection }) {
1179
+ const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = this.resolveCollectionCallbacks(collection, row.path);
1180
+ const callbackRow = {
1181
+ id: row.id,
1182
+ ...row.values ?? {}
1183
+ };
1184
+ const contextForCallback = {
1185
+ user: this.user,
1186
+ driver: this,
1187
+ data: this.data,
1188
+ client: this.client,
1189
+ storageSource: this.client?.storage
1190
+ };
1191
+ if (globalCallbacks?.beforeDelete || callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
1192
+ let preventDefault = false;
1193
+ if (globalCallbacks?.beforeDelete) {
1194
+ if (await globalCallbacks.beforeDelete({
1195
+ collection: resolvedCollection,
1196
+ path: row.path,
1197
+ id: row.id,
1198
+ row: callbackRow,
1199
+ context: contextForCallback
1200
+ }) === false) preventDefault = true;
1201
+ }
1202
+ if (callbacks?.beforeDelete) {
1203
+ if (await callbacks.beforeDelete({
1204
+ collection: resolvedCollection,
1205
+ path: row.path,
1206
+ id: row.id,
1207
+ row: callbackRow,
1208
+ context: contextForCallback
1209
+ }) === false) preventDefault = true;
1210
+ }
1211
+ if (propertyCallbacks?.beforeDelete) {
1212
+ if (await propertyCallbacks.beforeDelete({
1213
+ collection: resolvedCollection,
1214
+ path: row.path,
1215
+ id: row.id,
1216
+ row: callbackRow,
1217
+ context: contextForCallback
1218
+ }) === false) preventDefault = true;
1219
+ }
1220
+ if (preventDefault) return;
1221
+ }
1222
+ await this.dataService.delete(row.path, row.id);
1223
+ if (globalCallbacks?.afterDelete || callbacks?.afterDelete || propertyCallbacks?.afterDelete) {
1224
+ if (globalCallbacks?.afterDelete) await globalCallbacks.afterDelete({
1225
+ collection: resolvedCollection,
1226
+ path: row.path,
1227
+ id: row.id,
1228
+ row: callbackRow,
1229
+ context: contextForCallback
1230
+ });
1231
+ if (callbacks?.afterDelete) await callbacks.afterDelete({
1232
+ collection: resolvedCollection,
1233
+ path: row.path,
1234
+ id: row.id,
1235
+ row: callbackRow,
1236
+ context: contextForCallback
1237
+ });
1238
+ if (propertyCallbacks?.afterDelete) await propertyCallbacks.afterDelete({
1239
+ collection: resolvedCollection,
1240
+ path: row.path,
1241
+ id: row.id,
1242
+ row: callbackRow,
1243
+ context: contextForCallback
1244
+ });
1245
+ }
1246
+ if (this.historyService && resolvedCollection?.history) this.historyService.recordHistory({
1247
+ action: "delete",
1248
+ id: String(row.id),
1249
+ tableName: row.path,
1250
+ previousValues: row.values,
1251
+ updatedBy: this.user?.uid
1252
+ }).catch((err) => {
1253
+ _rebasepro_server.logger.error(`Failed to record history for ${row.path}/${row.id}`, { error: err });
1254
+ });
1255
+ await this.realtimeService.notifyUpdate(row.path, String(row.id), null);
1256
+ }
1257
+ /**
1258
+ * Check if a field value is unique
1259
+ */
1260
+ async checkUniqueField(path, name, value, id, collection) {
1261
+ return this.dataService.checkUniqueField(path, name, value, id);
1262
+ }
1263
+ /**
1264
+ * Generate a new row ID
1265
+ */
1266
+ generateId(path, collection) {
1267
+ return this.dataService.generateId();
1268
+ }
1269
+ /**
1270
+ * Count rows in a collection
1271
+ */
1272
+ async count({ path, collection, filter }) {
1273
+ return this.dataService.count(path, { filter });
1274
+ }
1275
+ /**
1276
+ * Generate a unique subscription ID
1277
+ */
1278
+ generateSubscriptionId() {
1279
+ return `mongo_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1280
+ }
1281
+ /**
1282
+ * Check if the delegate is ready
1283
+ */
1284
+ isReady() {
1285
+ return this.initialised;
1286
+ }
1287
+ /**
1288
+ * Get the underlying row service for direct access
1289
+ */
1290
+ getDataService() {
1291
+ return this.dataService;
1292
+ }
1293
+ /**
1294
+ * Get the underlying realtime service for direct access
1295
+ */
1296
+ getRealtimeService() {
1297
+ return this.realtimeService;
1298
+ }
1299
+ /**
1300
+ * Scope the MongoDriver with an authenticated user context
1301
+ */
1302
+ async withAuth(user) {
1303
+ return new AuthenticatedMongoDriver(this, user);
1304
+ }
1305
+ };
1306
+ var AuthenticatedMongoDriver = class {
1307
+ delegate;
1308
+ key = "mongodb";
1309
+ initialised = true;
1310
+ user;
1311
+ data;
1312
+ constructor(delegate, user) {
1313
+ this.delegate = delegate;
1314
+ this.user = user;
1315
+ this.data = (0, _rebasepro_common.buildSdkData)(this);
1316
+ }
1317
+ currentTime() {
1318
+ return this.delegate.currentTime();
1319
+ }
1320
+ async fetchCollection(props) {
1321
+ const { collection: resolvedCollection } = this.delegate.resolveCollectionCallbacks(props.collection, props.path);
1322
+ const rlsFilter = buildMongoFilterFromSecurityRules(resolvedCollection, this.user, "select");
1323
+ if (rlsFilter === null) return [];
1324
+ const userQuery = MongoConditionBuilder.buildQuery({
1325
+ filter: props.filter,
1326
+ searchString: props.searchString,
1327
+ properties: resolvedCollection?.properties
1328
+ });
1329
+ const combinedQuery = Object.keys(rlsFilter).length > 0 ? { $and: [userQuery, rlsFilter] } : userQuery;
1330
+ const rows = await this.delegate.getDataService().fetchCollection(props.path, {
1331
+ ...props,
1332
+ rawQuery: combinedQuery,
1333
+ collection: resolvedCollection
1334
+ });
1335
+ const { callbacks, globalCallbacks, propertyCallbacks } = this.delegate.resolveCollectionCallbacks(props.collection, props.path);
1336
+ if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {
1337
+ const contextForCallback = {
1338
+ user: this.user,
1339
+ driver: this,
1340
+ data: this.data,
1341
+ client: this.delegate.client,
1342
+ storageSource: this.delegate.client?.storage
1343
+ };
1344
+ return Promise.all(rows.map(async (row) => {
1345
+ let fetched = row;
1346
+ if (globalCallbacks?.afterRead) fetched = await globalCallbacks.afterRead({
1347
+ collection: resolvedCollection,
1348
+ path: props.path,
1349
+ row: fetched,
1350
+ context: contextForCallback
1351
+ }) ?? fetched;
1352
+ if (callbacks?.afterRead) fetched = await callbacks.afterRead({
1353
+ collection: resolvedCollection,
1354
+ path: props.path,
1355
+ row: fetched,
1356
+ context: contextForCallback
1357
+ }) ?? fetched;
1358
+ if (propertyCallbacks?.afterRead) fetched = await propertyCallbacks.afterRead({
1359
+ collection: resolvedCollection,
1360
+ path: props.path,
1361
+ row: fetched,
1362
+ context: contextForCallback
1363
+ }) ?? fetched;
1364
+ return fetched;
1365
+ }));
1366
+ }
1367
+ return rows;
1368
+ }
1369
+ listenCollection(props) {
1370
+ const unsubscribe = this.delegate.listenCollection(props);
1371
+ const authContext = {
1372
+ userId: this.user.uid,
1373
+ roles: this.user.roles ?? []
1374
+ };
1375
+ const subscriptions = this.delegate.getRealtimeService().getSubscriptions();
1376
+ const lastSub = Array.from(subscriptions.entries()).pop()?.[1];
1377
+ if (lastSub && lastSub.config.clientId === "driver") lastSub.authContext = authContext;
1378
+ return unsubscribe;
1379
+ }
1380
+ async fetchOne(props) {
1381
+ const { collection: resolvedCollection } = this.delegate.resolveCollectionCallbacks(props.collection, props.path);
1382
+ const row = await this.delegate.fetchOne(props);
1383
+ if (row) {
1384
+ if (!(0, _rebasepro_common.checkOperation)(resolvedCollection, { user: this.user }, rowToEntityForCheck(row, props.path), "select", { onUnknown: "deny" })) return;
1385
+ }
1386
+ return row;
1387
+ }
1388
+ listenOne(props) {
1389
+ const unsubscribe = this.delegate.listenOne(props);
1390
+ const authContext = {
1391
+ userId: this.user.uid,
1392
+ roles: this.user.roles ?? []
1393
+ };
1394
+ const subscriptions = this.delegate.getRealtimeService().getSubscriptions();
1395
+ const lastSub = Array.from(subscriptions.entries()).pop()?.[1];
1396
+ if (lastSub && lastSub.config.clientId === "driver") lastSub.authContext = authContext;
1397
+ return unsubscribe;
1398
+ }
1399
+ async save(props) {
1400
+ const { collection: resolvedCollection } = this.delegate.resolveCollectionCallbacks(props.collection, props.path);
1401
+ if (props.status === "existing" && props.id) {
1402
+ const existing = await this.delegate.fetchOne({
1403
+ path: props.path,
1404
+ id: props.id,
1405
+ collection: resolvedCollection
1406
+ });
1407
+ if (!existing || !(0, _rebasepro_common.checkOperation)(resolvedCollection, { user: this.user }, rowToEntityForCheck(existing, props.path), "update", { onUnknown: "deny" })) throw _rebasepro_server.ApiError.forbidden("Forbidden");
1408
+ } else {
1409
+ const tempEntity = {
1410
+ id: props.id || "new",
1411
+ path: props.path,
1412
+ values: props.values
1413
+ };
1414
+ if (!(0, _rebasepro_common.checkOperation)(resolvedCollection, { user: this.user }, tempEntity, "insert", { onUnknown: "deny" })) throw _rebasepro_server.ApiError.forbidden("Forbidden");
1415
+ }
1416
+ const saved = await this.delegate.save({
1417
+ ...props,
1418
+ collection: resolvedCollection
1419
+ });
1420
+ if (!(0, _rebasepro_common.checkOperation)(resolvedCollection, { user: this.user }, rowToEntityForCheck(saved, props.path), props.status === "existing" ? "update" : "insert", { onUnknown: "deny" })) throw _rebasepro_server.ApiError.forbidden("Forbidden");
1421
+ return saved;
1422
+ }
1423
+ async delete(props) {
1424
+ const { collection: resolvedCollection } = this.delegate.resolveCollectionCallbacks(props.collection, props.row.path);
1425
+ const existing = await this.delegate.fetchOne({
1426
+ path: props.row.path,
1427
+ id: props.row.id,
1428
+ collection: resolvedCollection
1429
+ });
1430
+ if (!existing || !(0, _rebasepro_common.checkOperation)(resolvedCollection, { user: this.user }, rowToEntityForCheck(existing, props.row.path), "delete", { onUnknown: "deny" })) throw _rebasepro_server.ApiError.forbidden("Forbidden");
1431
+ return this.delegate.delete(props);
1432
+ }
1433
+ async checkUniqueField(path, name, value, id, collection) {
1434
+ return this.delegate.checkUniqueField(path, name, value, id, collection);
1435
+ }
1436
+ generateId(path, collection) {
1437
+ return this.delegate.generateId(path, collection);
1438
+ }
1439
+ async count(props) {
1440
+ const { collection: resolvedCollection } = this.delegate.resolveCollectionCallbacks(props.collection, props.path);
1441
+ const rlsFilter = buildMongoFilterFromSecurityRules(resolvedCollection, this.user, "select");
1442
+ if (rlsFilter === null) return 0;
1443
+ const userQuery = MongoConditionBuilder.buildQuery({
1444
+ filter: props.filter,
1445
+ searchString: props.searchString,
1446
+ properties: resolvedCollection?.properties
1447
+ });
1448
+ const combinedQuery = Object.keys(rlsFilter).length > 0 ? { $and: [userQuery, rlsFilter] } : userQuery;
1449
+ return this.delegate.getDataService().count(props.path, {
1450
+ ...props,
1451
+ rawQuery: combinedQuery
1452
+ });
1453
+ }
1454
+ isReady() {
1455
+ return this.delegate.isReady();
1456
+ }
1457
+ };
1458
+ /**
1459
+ * Wrap a flat row into the Entity shape expected by `checkOperation`,
1460
+ * which evaluates security rules against `row.values`.
1461
+ */
1462
+ function rowToEntityForCheck(row, path) {
1463
+ return {
1464
+ id: row.id,
1465
+ path,
1466
+ values: row
1467
+ };
1468
+ }
1469
+ function getMongoFilterForSQL(sqlString, user) {
1470
+ let cleanedSQL = sqlString.trim();
1471
+ while (cleanedSQL.startsWith("(") && cleanedSQL.endsWith(")")) {
1472
+ let openCount = 0;
1473
+ let isEnclosing = true;
1474
+ for (let i = 0; i < cleanedSQL.length - 1; i++) {
1475
+ if (cleanedSQL[i] === "(") openCount++;
1476
+ else if (cleanedSQL[i] === ")") openCount--;
1477
+ if (openCount === 0) {
1478
+ isEnclosing = false;
1479
+ break;
1480
+ }
1481
+ }
1482
+ if (isEnclosing) cleanedSQL = cleanedSQL.substring(1, cleanedSQL.length - 1).trim();
1483
+ else break;
1484
+ }
1485
+ const splitByTopLevel = (str, delimiter) => {
1486
+ const parts = [];
1487
+ let current = "";
1488
+ let openCount = 0;
1489
+ let i = 0;
1490
+ while (i < str.length) {
1491
+ if (str[i] === "(") openCount++;
1492
+ else if (str[i] === ")") openCount--;
1493
+ if (openCount === 0 && str.substring(i).toUpperCase().startsWith(delimiter)) {
1494
+ parts.push(current);
1495
+ current = "";
1496
+ i += delimiter.length;
1497
+ } else {
1498
+ current += str[i];
1499
+ i++;
1500
+ }
1501
+ }
1502
+ parts.push(current);
1503
+ return parts;
1504
+ };
1505
+ const orParts = splitByTopLevel(cleanedSQL, " OR ");
1506
+ if (orParts.length > 1) {
1507
+ const subFilters = orParts.map((part) => getMongoFilterForSQL(part, user)).filter((f) => f !== null);
1508
+ if (subFilters.length === 0) return null;
1509
+ if (subFilters.length === 1) return subFilters[0];
1510
+ return { $or: subFilters };
1511
+ }
1512
+ const andParts = splitByTopLevel(cleanedSQL, " AND ");
1513
+ if (andParts.length > 1) {
1514
+ const subFilters = andParts.map((part) => getMongoFilterForSQL(part, user)).filter((f) => f !== null);
1515
+ if (subFilters.length === 0) return null;
1516
+ if (subFilters.length === 1) return subFilters[0];
1517
+ return { $and: subFilters };
1518
+ }
1519
+ const roleIntersectMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*&&\s*ARRAY\[(.*?)\]/i);
1520
+ if (roleIntersectMatch && roleIntersectMatch[1]) {
1521
+ const requiredRoles = roleIntersectMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
1522
+ const userRoles = user.roles || [];
1523
+ return requiredRoles.some((r) => userRoles.includes(r)) ? {} : { _id: { $exists: false } };
1524
+ }
1525
+ const roleContainMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*@>\s*ARRAY\[(.*?)\]/i);
1526
+ if (roleContainMatch && roleContainMatch[1]) {
1527
+ const requiredRoles = roleContainMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
1528
+ const userRoles = user.roles || [];
1529
+ return requiredRoles.every((r) => userRoles.includes(r)) ? {} : { _id: { $exists: false } };
1530
+ }
1531
+ const pattern1 = /* @__PURE__ */ new RegExp("^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))");
1532
+ const pattern2 = /* @__PURE__ */ new RegExp("^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?");
1533
+ const match1 = cleanedSQL.match(pattern1);
1534
+ if (match1 && match1[1]) return { [match1[1]]: user.uid };
1535
+ const match2 = cleanedSQL.match(pattern2);
1536
+ if (match2 && match2[1]) return { [match2[1]]: user.uid };
1537
+ const simpleEqualityMatch = cleanedSQL.match(/^\{?([\w_]+)\}?\s*(=|!=)\s*'([^']+)'$/i);
1538
+ if (simpleEqualityMatch) {
1539
+ const field = simpleEqualityMatch[1];
1540
+ const operator = simpleEqualityMatch[2];
1541
+ const value = simpleEqualityMatch[3];
1542
+ if (operator === "=") return { [field]: value };
1543
+ if (operator === "!=") return { [field]: { $ne: value } };
1544
+ }
1545
+ return {};
1546
+ }
1547
+ function getMongoFilterForRule(rule, user) {
1548
+ if (rule.access === "public") return {};
1549
+ const filters = [];
1550
+ if (rule.ownerField) filters.push({ [rule.ownerField]: user.uid });
1551
+ if (rule.using) {
1552
+ const f = getMongoFilterForSQL(rule.using, user);
1553
+ if (f) filters.push(f);
1554
+ }
1555
+ if (rule.withCheck) {
1556
+ const f = getMongoFilterForSQL(rule.withCheck, user);
1557
+ if (f) filters.push(f);
1558
+ }
1559
+ if (filters.length === 0) return {};
1560
+ if (filters.length === 1) return filters[0];
1561
+ return { $and: filters };
1562
+ }
1563
+ function buildMongoFilterFromSecurityRules(collection, user, targetOperation) {
1564
+ if (!collection || !collection.securityRules || collection.securityRules.length === 0) return {};
1565
+ const applicableRules = collection.securityRules.filter((r) => r.operation === targetOperation || r.operation === "all" || r.operations?.includes(targetOperation) || r.operations?.includes("all"));
1566
+ if (applicableRules.length === 0) return null;
1567
+ const userRoles = [...user.roles ?? [], "public"];
1568
+ const roleApplicableRules = applicableRules.filter((rule) => {
1569
+ if (!rule.roles || rule.roles.length === 0) return true;
1570
+ return rule.roles.some((r) => userRoles.includes(r));
1571
+ });
1572
+ if (roleApplicableRules.length === 0) return null;
1573
+ const permissiveFilters = [];
1574
+ const restrictiveFilters = [];
1575
+ for (const rule of roleApplicableRules) {
1576
+ const mode = rule.mode || "permissive";
1577
+ const filter = getMongoFilterForRule(rule, user);
1578
+ if (filter === null) {
1579
+ if (mode === "restrictive") return null;
1580
+ continue;
1581
+ }
1582
+ if (mode === "restrictive") restrictiveFilters.push(filter);
1583
+ else permissiveFilters.push(filter);
1584
+ }
1585
+ const finalAnds = [];
1586
+ if (permissiveFilters.length > 0) {
1587
+ if (!permissiveFilters.some((f) => Object.keys(f).length === 0)) if (permissiveFilters.length === 1) finalAnds.push(permissiveFilters[0]);
1588
+ else finalAnds.push({ $or: permissiveFilters });
1589
+ } else return null;
1590
+ if (restrictiveFilters.length > 0) {
1591
+ for (const rf of restrictiveFilters) if (Object.keys(rf).length > 0) finalAnds.push(rf);
1592
+ }
1593
+ if (finalAnds.length === 0) return {};
1594
+ if (finalAnds.length === 1) return finalAnds[0];
1595
+ return { $and: finalAnds };
1596
+ }
1597
+ //#endregion
1598
+ //#region src/factory.ts
1599
+ init_MongoHistoryService();
1600
+ /**
1601
+ * Simple in-memory collection registry for MongoDB.
1602
+ */
1603
+ var MongoCollectionRegistry = class {
1604
+ collections = /* @__PURE__ */ new Map();
1605
+ _globalCallbacks;
1606
+ /**
1607
+ * Register a collection
1608
+ */
1609
+ register(collection) {
1610
+ this.collections.set(collection.name, collection);
1611
+ }
1612
+ /**
1613
+ * Get a collection by its path
1614
+ */
1615
+ getCollectionByPath(path) {
1616
+ return this.collections.get(path);
1617
+ }
1618
+ /**
1619
+ * Get all registered collections
1620
+ */
1621
+ getCollections() {
1622
+ return Array.from(this.collections.values());
1623
+ }
1624
+ /**
1625
+ * Get the currently registered global callbacks, if any.
1626
+ */
1627
+ getGlobalCallbacks() {
1628
+ return this._globalCallbacks;
1629
+ }
1630
+ /**
1631
+ * Set global lifecycle callbacks that apply to every collection.
1632
+ */
1633
+ setGlobalCallbacks(callbacks) {
1634
+ this._globalCallbacks = callbacks;
1635
+ }
1636
+ };
1637
+ /**
1638
+ * Create a complete MongoDB backend instance.
1639
+ *
1640
+ * This factory function creates all the necessary services for a MongoDB backend:
1641
+ * - MongoDBConnection (database connection wrapper)
1642
+ * - MongoDataService (implements DataRepository)
1643
+ * - MongoRealtimeService (implements RealtimeProvider)
1644
+ * - MongoCollectionRegistry (implements CollectionRegistryInterface)
1645
+ * - MongoDriver (for Rebase integration)
1646
+ *
1647
+ * @example
1648
+ * ```typescript
1649
+ * import { createMongoBackend } from "@rebasepro/server-mongo";
1650
+ *
1651
+ * const client = new MongoClient("mongodb://localhost:27017");
1652
+ * await client.connect();
1653
+ * const db = client.db("my_database");
1654
+ *
1655
+ * const backend = createMongoBackend({
1656
+ * type: "mongodb",
1657
+ * connection: db,
1658
+ * client: client,
1659
+ * collections: myCollections
1660
+ * });
1661
+ *
1662
+ * // Use the backend
1663
+ * const rows = await backend.entityRepository.fetchCollection("users", {});
1664
+ * ```
1665
+ */
1666
+ function createMongoBackend(config) {
1667
+ const { connection: db, client, collections } = config;
1668
+ const collectionRegistry = new MongoCollectionRegistry();
1669
+ if (collections) collections.forEach((collection) => collectionRegistry.register(collection));
1670
+ const dataService = new MongoDataService(db);
1671
+ const realtimeService = new MongoRealtimeService(db);
1672
+ const driver = new MongoDriver(db, realtimeService, new MongoHistoryService(db, config.historyRetention), collectionRegistry);
1673
+ return {
1674
+ connection: new MongoDBConnection(db, client),
1675
+ entityRepository: dataService,
1676
+ realtimeProvider: realtimeService,
1677
+ collectionRegistry,
1678
+ admin: {
1679
+ async executeAggregate(pipeline) {
1680
+ const firstStage = pipeline[0];
1681
+ const collName = typeof firstStage.$from === "string" ? firstStage.$from : "__admin__";
1682
+ return await db.collection(collName).aggregate(pipeline).toArray();
1683
+ },
1684
+ async fetchCollectionStats(collectionName) {
1685
+ const stats = await db.command({ collStats: collectionName });
1686
+ return {
1687
+ count: stats.count,
1688
+ sizeBytes: stats.size
1689
+ };
1690
+ },
1691
+ async fetchUnmappedTables(mappedPaths) {
1692
+ const names = (await db.listCollections().toArray()).map((c) => c.name).filter((n) => !n.startsWith("system."));
1693
+ if (!mappedPaths || mappedPaths.length === 0) return names;
1694
+ const mappedSet = new Set(mappedPaths.map((p) => p.toLowerCase()));
1695
+ return names.filter((n) => !mappedSet.has(n.toLowerCase()));
1696
+ },
1697
+ async fetchTableMetadata(collectionName) {
1698
+ const sample = await db.collection(collectionName).findOne();
1699
+ if (!sample) return {
1700
+ columns: [],
1701
+ foreignKeys: [],
1702
+ junctions: [],
1703
+ policies: []
1704
+ };
1705
+ return {
1706
+ columns: Object.entries(sample).map(([key, value]) => ({
1707
+ column_name: key,
1708
+ data_type: typeof value,
1709
+ udt_name: typeof value,
1710
+ is_nullable: "YES",
1711
+ column_default: null,
1712
+ character_maximum_length: null
1713
+ })),
1714
+ foreignKeys: [],
1715
+ junctions: [],
1716
+ policies: []
1717
+ };
1718
+ }
1719
+ },
1720
+ async initialize() {},
1721
+ async healthCheck() {
1722
+ const start = Date.now();
1723
+ try {
1724
+ await db.command({ ping: 1 });
1725
+ return {
1726
+ healthy: true,
1727
+ latencyMs: Date.now() - start
1728
+ };
1729
+ } catch {
1730
+ return {
1731
+ healthy: false,
1732
+ latencyMs: Date.now() - start
1733
+ };
1734
+ }
1735
+ },
1736
+ async destroy() {
1737
+ await client.close();
1738
+ },
1739
+ db,
1740
+ client,
1741
+ driver,
1742
+ dataService,
1743
+ realtimeService
1744
+ };
1745
+ }
1746
+ /**
1747
+ * Create a MongoDB DataDriver.
1748
+ *
1749
+ * This is a convenience function when you only need the DataDriver
1750
+ * without the full backend instance.
1751
+ *
1752
+ * @example
1753
+ * ```typescript
1754
+ * import { createMongoDelegate } from "@rebasepro/server-mongo";
1755
+ *
1756
+ * const delegate = createMongoDelegate(db);
1757
+ * ```
1758
+ */
1759
+ function createMongoDelegate(db, realtimeService, historyService, registry) {
1760
+ return new MongoDriver(db, realtimeService ?? new MongoRealtimeService(db), historyService ?? new MongoHistoryService(db), registry);
1761
+ }
1762
+ /**
1763
+ * Create a RealtimeService for MongoDB.
1764
+ *
1765
+ * @example
1766
+ * ```typescript
1767
+ * import { createMongoRealtimeService } from "@rebasepro/server-mongo";
1768
+ *
1769
+ * const realtimeService = createMongoRealtimeService(db);
1770
+ * ```
1771
+ */
1772
+ function createMongoRealtimeService(db) {
1773
+ return new MongoRealtimeService(db);
1774
+ }
1775
+ /**
1776
+ * Create a MongoDB row repository.
1777
+ *
1778
+ * @example
1779
+ * ```typescript
1780
+ * import { createMongoEntityRepository } from "@rebasepro/server-mongo";
1781
+ *
1782
+ * const repository = createMongoEntityRepository(db);
1783
+ * const users = await repository.fetchCollection("users", {});
1784
+ * ```
1785
+ */
1786
+ function createMongoEntityRepository(db) {
1787
+ return new MongoDataService(db);
1788
+ }
1789
+ /**
1790
+ * Check if a backend config is for MongoDB.
1791
+ */
1792
+ function isMongoBackendConfig(config) {
1793
+ return config.type === "mongodb" && typeof config.connection !== "undefined" && typeof config.client !== "undefined";
1794
+ }
1795
+ /**
1796
+ * Check if a driver config is for MongoDB.
1797
+ */
1798
+ function isMongoDriverConfig(obj) {
1799
+ return typeof obj === "object" && obj !== null && "type" in obj && obj.type === "mongodb" && "connection" in obj && "client" in obj;
1800
+ }
1801
+ //#endregion
1802
+ //#region src/auth/services.ts
1803
+ function escapeRegExp(str) {
1804
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1805
+ }
1806
+ function toUser(doc) {
1807
+ return {
1808
+ id: doc._id || doc.id,
1809
+ email: doc.email,
1810
+ passwordHash: doc.passwordHash ?? null,
1811
+ displayName: doc.displayName ?? null,
1812
+ photoUrl: doc.photoUrl ?? null,
1813
+ emailVerified: doc.emailVerified ?? false,
1814
+ emailVerificationToken: doc.emailVerificationToken ?? null,
1815
+ emailVerificationSentAt: doc.emailVerificationSentAt ? new Date(doc.emailVerificationSentAt) : null,
1816
+ createdAt: new Date(doc.createdAt),
1817
+ updatedAt: new Date(doc.updatedAt)
1818
+ };
1819
+ }
1820
+ var MongoUserService = class {
1821
+ db;
1822
+ constructor(db) {
1823
+ this.db = db;
1824
+ }
1825
+ get collection() {
1826
+ return this.db.collection("rebase_users");
1827
+ }
1828
+ get identitiesCollection() {
1829
+ return this.db.collection("rebase_user_identities");
1830
+ }
1831
+ get userRolesCollection() {
1832
+ return this.db.collection("rebase_user_roles");
1833
+ }
1834
+ get rolesCollection() {
1835
+ return this.db.collection("rebase_roles");
1836
+ }
1837
+ async createUser(data) {
1838
+ const id = new mongodb.ObjectId().toString();
1839
+ const now = /* @__PURE__ */ new Date();
1840
+ const doc = {
1841
+ _id: id,
1842
+ id,
1843
+ email: data.email.toLowerCase(),
1844
+ passwordHash: data.passwordHash ?? null,
1845
+ displayName: data.displayName ?? null,
1846
+ photoUrl: data.photoUrl ?? null,
1847
+ emailVerified: data.emailVerified ?? false,
1848
+ createdAt: now,
1849
+ updatedAt: now
1850
+ };
1851
+ await this.collection.insertOne(doc);
1852
+ return toUser(doc);
1853
+ }
1854
+ async getUserById(id) {
1855
+ const doc = await this.collection.findOne({ id });
1856
+ return doc ? toUser(doc) : null;
1857
+ }
1858
+ async getUserByEmail(email) {
1859
+ const doc = await this.collection.findOne({ email: email.toLowerCase() });
1860
+ return doc ? toUser(doc) : null;
1861
+ }
1862
+ async getUserByIdentity(provider, providerId) {
1863
+ const identity = await this.identitiesCollection.findOne({
1864
+ provider,
1865
+ providerId
1866
+ });
1867
+ if (!identity) return null;
1868
+ return this.getUserById(identity.userId);
1869
+ }
1870
+ async getUserIdentities(userId) {
1871
+ return (await this.identitiesCollection.find({ userId }).toArray()).map((doc) => ({
1872
+ id: doc.id,
1873
+ userId: doc.userId,
1874
+ provider: doc.provider,
1875
+ providerId: doc.providerId,
1876
+ profileData: doc.profileData ?? null,
1877
+ createdAt: new Date(doc.createdAt),
1878
+ updatedAt: new Date(doc.updatedAt)
1879
+ }));
1880
+ }
1881
+ async linkUserIdentity(userId, provider, providerId, profileData) {
1882
+ const now = /* @__PURE__ */ new Date();
1883
+ await this.identitiesCollection.updateOne({
1884
+ provider,
1885
+ providerId
1886
+ }, {
1887
+ $setOnInsert: {
1888
+ _id: new mongodb.ObjectId().toString(),
1889
+ id: new mongodb.ObjectId().toString(),
1890
+ userId,
1891
+ provider,
1892
+ providerId,
1893
+ createdAt: now
1894
+ },
1895
+ $set: {
1896
+ profileData: profileData ?? null,
1897
+ updatedAt: now
1898
+ }
1899
+ }, { upsert: true });
1900
+ }
1901
+ async updateUser(id, data) {
1902
+ const updateData = {
1903
+ ...data,
1904
+ updatedAt: /* @__PURE__ */ new Date()
1905
+ };
1906
+ if (typeof updateData.email === "string") updateData.email = updateData.email.toLowerCase();
1907
+ await this.collection.updateOne({ id }, { $set: updateData });
1908
+ return this.getUserById(id);
1909
+ }
1910
+ async deleteUser(id) {
1911
+ await this.collection.deleteOne({ id });
1912
+ await this.identitiesCollection.deleteMany({ userId: id });
1913
+ await this.userRolesCollection.deleteMany({ userId: id });
1914
+ }
1915
+ async listUsers() {
1916
+ return (await this.collection.find().toArray()).map(toUser);
1917
+ }
1918
+ async listUsersPaginated(options) {
1919
+ const limit = options?.limit ?? 25;
1920
+ const offset = options?.offset ?? 0;
1921
+ const search = options?.search?.trim() || "";
1922
+ const orderBy = options?.orderBy || "createdAt";
1923
+ const orderDir = options?.orderDir || "desc";
1924
+ const roleId = options?.roleId;
1925
+ const query = {};
1926
+ if (search) {
1927
+ const escapedSearch = escapeRegExp(search);
1928
+ query.$or = [{ email: {
1929
+ $regex: escapedSearch,
1930
+ $options: "i"
1931
+ } }, { displayName: {
1932
+ $regex: escapedSearch,
1933
+ $options: "i"
1934
+ } }];
1935
+ }
1936
+ if (roleId) query.id = { $in: (await this.userRolesCollection.find({ roleId }).toArray()).map((ur) => ur.userId) };
1937
+ const sort = {};
1938
+ sort[orderBy] = orderDir === "asc" ? 1 : -1;
1939
+ const total = await this.collection.countDocuments(query);
1940
+ return {
1941
+ users: (await this.collection.find(query).sort(sort).skip(offset).limit(limit).toArray()).map(toUser),
1942
+ total,
1943
+ limit,
1944
+ offset
1945
+ };
1946
+ }
1947
+ async updatePassword(id, passwordHash) {
1948
+ await this.collection.updateOne({ id }, { $set: {
1949
+ passwordHash,
1950
+ updatedAt: /* @__PURE__ */ new Date()
1951
+ } });
1952
+ }
1953
+ async setEmailVerified(id, verified) {
1954
+ await this.collection.updateOne({ id }, { $set: {
1955
+ emailVerified: verified,
1956
+ emailVerificationToken: null,
1957
+ updatedAt: /* @__PURE__ */ new Date()
1958
+ } });
1959
+ }
1960
+ async setVerificationToken(id, token) {
1961
+ await this.collection.updateOne({ id }, { $set: {
1962
+ emailVerificationToken: token,
1963
+ emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null,
1964
+ updatedAt: /* @__PURE__ */ new Date()
1965
+ } });
1966
+ }
1967
+ async getUserByVerificationToken(token) {
1968
+ const doc = await this.collection.findOne({ emailVerificationToken: token });
1969
+ return doc ? toUser(doc) : null;
1970
+ }
1971
+ async getUserRoles(userId) {
1972
+ const roleIds = (await this.userRolesCollection.find({ userId }).toArray()).map((ur) => ur.roleId);
1973
+ if (roleIds.length === 0) return [];
1974
+ return (await this.rolesCollection.find({ id: { $in: roleIds } }).toArray()).map((r) => ({
1975
+ id: r.id,
1976
+ name: r.name,
1977
+ isAdmin: r.isAdmin ?? false,
1978
+ defaultPermissions: r.defaultPermissions ?? null,
1979
+ collectionPermissions: r.collectionPermissions ?? null
1980
+ }));
1981
+ }
1982
+ async getUserRoleIds(userId) {
1983
+ return (await this.userRolesCollection.find({ userId }).toArray()).map((ur) => ur.roleId);
1984
+ }
1985
+ async setUserRoles(userId, roleIds) {
1986
+ await this.userRolesCollection.deleteMany({ userId });
1987
+ if (roleIds.length > 0) {
1988
+ const docs = roleIds.map((roleId) => ({
1989
+ _id: new mongodb.ObjectId().toString(),
1990
+ userId,
1991
+ roleId
1992
+ }));
1993
+ await this.userRolesCollection.insertMany(docs);
1994
+ }
1995
+ }
1996
+ async assignDefaultRole(userId, roleId) {
1997
+ await this.userRolesCollection.updateOne({
1998
+ userId,
1999
+ roleId
2000
+ }, { $setOnInsert: {
2001
+ _id: new mongodb.ObjectId().toString(),
2002
+ userId,
2003
+ roleId
2004
+ } }, { upsert: true });
2005
+ }
2006
+ async getUserWithRoles(userId) {
2007
+ const user = await this.getUserById(userId);
2008
+ if (!user) return null;
2009
+ return {
2010
+ user,
2011
+ roles: await this.getUserRoles(userId)
2012
+ };
2013
+ }
2014
+ };
2015
+ var MongoRoleService = class {
2016
+ db;
2017
+ constructor(db) {
2018
+ this.db = db;
2019
+ }
2020
+ get collection() {
2021
+ return this.db.collection("rebase_roles");
2022
+ }
2023
+ async getRoleById(id) {
2024
+ const doc = await this.collection.findOne({ id });
2025
+ if (!doc) return null;
2026
+ return {
2027
+ id: doc.id,
2028
+ name: doc.name,
2029
+ isAdmin: doc.isAdmin ?? false,
2030
+ defaultPermissions: doc.defaultPermissions ?? null,
2031
+ collectionPermissions: doc.collectionPermissions ?? null
2032
+ };
2033
+ }
2034
+ async listRoles() {
2035
+ return (await this.collection.find().sort({ name: 1 }).toArray()).map((doc) => ({
2036
+ id: doc.id,
2037
+ name: doc.name,
2038
+ isAdmin: doc.isAdmin ?? false,
2039
+ defaultPermissions: doc.defaultPermissions ?? null,
2040
+ collectionPermissions: doc.collectionPermissions ?? null
2041
+ }));
2042
+ }
2043
+ async createRole(data) {
2044
+ const doc = {
2045
+ _id: data.id,
2046
+ id: data.id,
2047
+ name: data.name,
2048
+ isAdmin: data.isAdmin ?? false,
2049
+ defaultPermissions: data.defaultPermissions ?? null,
2050
+ collectionPermissions: data.collectionPermissions ?? null
2051
+ };
2052
+ await this.collection.insertOne(doc);
2053
+ return { ...doc };
2054
+ }
2055
+ async updateRole(id, data) {
2056
+ await this.collection.updateOne({ id }, { $set: data });
2057
+ return this.getRoleById(id);
2058
+ }
2059
+ async deleteRole(id) {
2060
+ await this.collection.deleteOne({ id });
2061
+ await this.db.collection("rebase_user_roles").deleteMany({ roleId: id });
2062
+ }
2063
+ };
2064
+ var MongoRefreshTokenService = class {
2065
+ db;
2066
+ constructor(db) {
2067
+ this.db = db;
2068
+ }
2069
+ get collection() {
2070
+ return this.db.collection("rebase_refresh_tokens");
2071
+ }
2072
+ async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
2073
+ const safeUserAgent = userAgent || "";
2074
+ const safeIpAddress = ipAddress || "";
2075
+ await this.collection.deleteMany({
2076
+ userId,
2077
+ userAgent: safeUserAgent,
2078
+ ipAddress: safeIpAddress
2079
+ });
2080
+ await this.collection.insertOne({
2081
+ _id: new mongodb.ObjectId().toString(),
2082
+ id: new mongodb.ObjectId().toString(),
2083
+ userId,
2084
+ tokenHash,
2085
+ expiresAt,
2086
+ createdAt: /* @__PURE__ */ new Date(),
2087
+ userAgent: safeUserAgent,
2088
+ ipAddress: safeIpAddress
2089
+ });
2090
+ }
2091
+ async findByHash(tokenHash) {
2092
+ const doc = await this.collection.findOne({ tokenHash });
2093
+ if (!doc) return null;
2094
+ return {
2095
+ id: doc.id,
2096
+ userId: doc.userId,
2097
+ tokenHash: doc.tokenHash,
2098
+ expiresAt: new Date(doc.expiresAt),
2099
+ createdAt: new Date(doc.createdAt),
2100
+ userAgent: doc.userAgent,
2101
+ ipAddress: doc.ipAddress
2102
+ };
2103
+ }
2104
+ async deleteByHash(tokenHash) {
2105
+ await this.collection.deleteOne({ tokenHash });
2106
+ }
2107
+ async deleteAllForUser(userId) {
2108
+ await this.collection.deleteMany({ userId });
2109
+ }
2110
+ async listForUser(userId) {
2111
+ return (await this.collection.find({ userId }).sort({ createdAt: 1 }).toArray()).map((doc) => ({
2112
+ id: doc.id,
2113
+ userId: doc.userId,
2114
+ tokenHash: doc.tokenHash,
2115
+ expiresAt: new Date(doc.expiresAt),
2116
+ createdAt: new Date(doc.createdAt),
2117
+ userAgent: doc.userAgent,
2118
+ ipAddress: doc.ipAddress
2119
+ }));
2120
+ }
2121
+ async deleteById(id, userId) {
2122
+ await this.collection.deleteOne({
2123
+ id,
2124
+ userId
2125
+ });
2126
+ }
2127
+ };
2128
+ var MongoPasswordResetTokenService = class {
2129
+ db;
2130
+ constructor(db) {
2131
+ this.db = db;
2132
+ }
2133
+ get collection() {
2134
+ return this.db.collection("rebase_password_reset_tokens");
2135
+ }
2136
+ async createToken(userId, tokenHash, expiresAt) {
2137
+ await this.collection.deleteMany({
2138
+ userId,
2139
+ usedAt: null
2140
+ });
2141
+ await this.collection.insertOne({
2142
+ _id: new mongodb.ObjectId().toString(),
2143
+ userId,
2144
+ tokenHash,
2145
+ expiresAt,
2146
+ usedAt: null
2147
+ });
2148
+ }
2149
+ async findValidByHash(tokenHash) {
2150
+ const doc = await this.collection.findOne({
2151
+ tokenHash,
2152
+ usedAt: null,
2153
+ expiresAt: { $gt: /* @__PURE__ */ new Date() }
2154
+ });
2155
+ if (!doc) return null;
2156
+ return {
2157
+ userId: doc.userId,
2158
+ expiresAt: new Date(doc.expiresAt)
2159
+ };
2160
+ }
2161
+ async markAsUsed(tokenHash) {
2162
+ await this.collection.updateOne({ tokenHash }, { $set: { usedAt: /* @__PURE__ */ new Date() } });
2163
+ }
2164
+ async deleteAllForUser(userId) {
2165
+ await this.collection.deleteMany({ userId });
2166
+ }
2167
+ async deleteExpired() {
2168
+ await this.collection.deleteMany({ expiresAt: { $lt: /* @__PURE__ */ new Date() } });
2169
+ }
2170
+ };
2171
+ var MongoTokenRepository = class {
2172
+ db;
2173
+ refreshTokenService;
2174
+ passwordResetTokenService;
2175
+ constructor(db) {
2176
+ this.db = db;
2177
+ this.refreshTokenService = new MongoRefreshTokenService(db);
2178
+ this.passwordResetTokenService = new MongoPasswordResetTokenService(db);
2179
+ }
2180
+ async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
2181
+ await this.refreshTokenService.createToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
2182
+ }
2183
+ async findRefreshTokenByHash(tokenHash) {
2184
+ return this.refreshTokenService.findByHash(tokenHash);
2185
+ }
2186
+ async deleteRefreshToken(tokenHash) {
2187
+ await this.refreshTokenService.deleteByHash(tokenHash);
2188
+ }
2189
+ async deleteAllRefreshTokensForUser(userId) {
2190
+ await this.refreshTokenService.deleteAllForUser(userId);
2191
+ }
2192
+ async listRefreshTokensForUser(userId) {
2193
+ return this.refreshTokenService.listForUser(userId);
2194
+ }
2195
+ async deleteRefreshTokenById(id, userId) {
2196
+ await this.refreshTokenService.deleteById(id, userId);
2197
+ }
2198
+ async createPasswordResetToken(userId, tokenHash, expiresAt) {
2199
+ await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
2200
+ }
2201
+ async findValidPasswordResetToken(tokenHash) {
2202
+ return this.passwordResetTokenService.findValidByHash(tokenHash);
2203
+ }
2204
+ async markPasswordResetTokenUsed(tokenHash) {
2205
+ await this.passwordResetTokenService.markAsUsed(tokenHash);
2206
+ }
2207
+ async deleteAllPasswordResetTokensForUser(userId) {
2208
+ await this.passwordResetTokenService.deleteAllForUser(userId);
2209
+ }
2210
+ async deleteExpiredTokens() {
2211
+ await this.passwordResetTokenService.deleteExpired();
2212
+ }
2213
+ async createMagicLinkToken(userId, tokenHash, expiresAt) {
2214
+ const col = this.db.collection("magic_link_tokens");
2215
+ await col.deleteMany({
2216
+ userId,
2217
+ usedAt: null
2218
+ });
2219
+ await col.insertOne({
2220
+ userId,
2221
+ tokenHash,
2222
+ expiresAt,
2223
+ usedAt: null,
2224
+ createdAt: /* @__PURE__ */ new Date()
2225
+ });
2226
+ }
2227
+ async findValidMagicLinkToken(tokenHash) {
2228
+ const doc = await this.db.collection("magic_link_tokens").findOne({
2229
+ tokenHash,
2230
+ usedAt: null,
2231
+ expiresAt: { $gt: /* @__PURE__ */ new Date() }
2232
+ });
2233
+ if (!doc) return null;
2234
+ return {
2235
+ userId: doc.userId,
2236
+ expiresAt: doc.expiresAt
2237
+ };
2238
+ }
2239
+ async markMagicLinkTokenUsed(tokenHash) {
2240
+ await this.db.collection("magic_link_tokens").updateOne({ tokenHash }, { $set: { usedAt: /* @__PURE__ */ new Date() } });
2241
+ }
2242
+ };
2243
+ var MongoAuthRepository = class {
2244
+ db;
2245
+ userService;
2246
+ roleService;
2247
+ tokenRepository;
2248
+ constructor(db) {
2249
+ this.db = db;
2250
+ this.userService = new MongoUserService(db);
2251
+ this.roleService = new MongoRoleService(db);
2252
+ this.tokenRepository = new MongoTokenRepository(db);
2253
+ }
2254
+ async createUser(data) {
2255
+ return this.userService.createUser(data);
2256
+ }
2257
+ async getUserById(id) {
2258
+ return this.userService.getUserById(id);
2259
+ }
2260
+ async getUserByEmail(email) {
2261
+ return this.userService.getUserByEmail(email);
2262
+ }
2263
+ async getUserByIdentity(provider, providerId) {
2264
+ return this.userService.getUserByIdentity(provider, providerId);
2265
+ }
2266
+ async getUserIdentities(userId) {
2267
+ return this.userService.getUserIdentities(userId);
2268
+ }
2269
+ async linkUserIdentity(userId, provider, providerId, profileData) {
2270
+ return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
2271
+ }
2272
+ async updateUser(id, data) {
2273
+ return this.userService.updateUser(id, data);
2274
+ }
2275
+ async deleteUser(id) {
2276
+ await this.userService.deleteUser(id);
2277
+ }
2278
+ async listUsers() {
2279
+ return this.userService.listUsers();
2280
+ }
2281
+ async listUsersPaginated(options) {
2282
+ return this.userService.listUsersPaginated(options);
2283
+ }
2284
+ async updatePassword(id, passwordHash) {
2285
+ await this.userService.updatePassword(id, passwordHash);
2286
+ }
2287
+ async setEmailVerified(id, verified) {
2288
+ await this.userService.setEmailVerified(id, verified);
2289
+ }
2290
+ async setVerificationToken(id, token) {
2291
+ await this.userService.setVerificationToken(id, token);
2292
+ }
2293
+ async getUserByVerificationToken(token) {
2294
+ return this.userService.getUserByVerificationToken(token);
2295
+ }
2296
+ async getUserRoles(userId) {
2297
+ return this.userService.getUserRoles(userId);
2298
+ }
2299
+ async getUserRoleIds(userId) {
2300
+ return this.userService.getUserRoleIds(userId);
2301
+ }
2302
+ async setUserRoles(userId, roleIds) {
2303
+ await this.userService.setUserRoles(userId, roleIds);
2304
+ }
2305
+ async assignDefaultRole(userId, roleId) {
2306
+ await this.userService.assignDefaultRole(userId, roleId);
2307
+ }
2308
+ async getUserWithRoles(userId) {
2309
+ return this.userService.getUserWithRoles(userId);
2310
+ }
2311
+ async getRoleById(id) {
2312
+ return this.roleService.getRoleById(id);
2313
+ }
2314
+ async listRoles() {
2315
+ return this.roleService.listRoles();
2316
+ }
2317
+ async createRole(data) {
2318
+ return this.roleService.createRole(data);
2319
+ }
2320
+ async updateRole(id, data) {
2321
+ return this.roleService.updateRole(id, data);
2322
+ }
2323
+ async deleteRole(id) {
2324
+ await this.roleService.deleteRole(id);
2325
+ }
2326
+ async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
2327
+ await this.tokenRepository.createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
2328
+ }
2329
+ async findRefreshTokenByHash(tokenHash) {
2330
+ return this.tokenRepository.findRefreshTokenByHash(tokenHash);
2331
+ }
2332
+ async deleteRefreshToken(tokenHash) {
2333
+ await this.tokenRepository.deleteRefreshToken(tokenHash);
2334
+ }
2335
+ async deleteAllRefreshTokensForUser(userId) {
2336
+ await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
2337
+ }
2338
+ async listRefreshTokensForUser(userId) {
2339
+ return this.tokenRepository.listRefreshTokensForUser(userId);
2340
+ }
2341
+ async deleteRefreshTokenById(id, userId) {
2342
+ await this.tokenRepository.deleteRefreshTokenById(id, userId);
2343
+ }
2344
+ async createPasswordResetToken(userId, tokenHash, expiresAt) {
2345
+ await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
2346
+ }
2347
+ async findValidPasswordResetToken(tokenHash) {
2348
+ return this.tokenRepository.findValidPasswordResetToken(tokenHash);
2349
+ }
2350
+ async markPasswordResetTokenUsed(tokenHash) {
2351
+ await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
2352
+ }
2353
+ async deleteAllPasswordResetTokensForUser(userId) {
2354
+ await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
2355
+ }
2356
+ async deleteExpiredTokens() {
2357
+ await this.tokenRepository.deleteExpiredTokens();
2358
+ }
2359
+ async createMagicLinkToken(userId, tokenHash, expiresAt) {
2360
+ await this.tokenRepository.createMagicLinkToken(userId, tokenHash, expiresAt);
2361
+ }
2362
+ async findValidMagicLinkToken(tokenHash) {
2363
+ return this.tokenRepository.findValidMagicLinkToken(tokenHash);
2364
+ }
2365
+ async markMagicLinkTokenUsed(tokenHash) {
2366
+ await this.tokenRepository.markMagicLinkTokenUsed(tokenHash);
2367
+ }
2368
+ async createMfaFactor(userId, factorType, secretEncrypted, friendlyName) {
2369
+ throw new Error("MFA is not implemented for MongoDB");
2370
+ }
2371
+ async getMfaFactors(userId) {
2372
+ return [];
2373
+ }
2374
+ async getMfaFactorById(factorId) {
2375
+ return null;
2376
+ }
2377
+ async verifyMfaFactor(factorId) {
2378
+ throw new Error("MFA is not implemented for MongoDB");
2379
+ }
2380
+ async deleteMfaFactor(factorId, userId) {
2381
+ throw new Error("MFA is not implemented for MongoDB");
2382
+ }
2383
+ async createMfaChallenge(factorId, ipAddress) {
2384
+ throw new Error("MFA is not implemented for MongoDB");
2385
+ }
2386
+ async getMfaChallengeById(challengeId) {
2387
+ return null;
2388
+ }
2389
+ async verifyMfaChallenge(challengeId) {
2390
+ throw new Error("MFA is not implemented for MongoDB");
2391
+ }
2392
+ async createRecoveryCodes(userId, codeHashes) {
2393
+ throw new Error("MFA is not implemented for MongoDB");
2394
+ }
2395
+ async useRecoveryCode(userId, codeHash) {
2396
+ return false;
2397
+ }
2398
+ async getUnusedRecoveryCodeCount(userId) {
2399
+ return 0;
2400
+ }
2401
+ async deleteAllRecoveryCodes(userId) {}
2402
+ async hasVerifiedMfaFactors(userId) {
2403
+ return false;
2404
+ }
2405
+ };
2406
+ //#endregion
2407
+ //#region src/auth/ensure-collections.ts
2408
+ var ensure_collections_exports = /* @__PURE__ */ __exportAll({ ensureAuthCollectionsExist: () => ensureAuthCollectionsExist });
2409
+ async function ensureAuthCollectionsExist(db) {
2410
+ _rebasepro_server.logger.info("🔍 Checking MongoDB auth collections and indexes...");
2411
+ try {
2412
+ await db.collection("rebase_users").createIndex({ email: 1 }, { unique: true });
2413
+ const identities = db.collection("rebase_user_identities");
2414
+ await identities.createIndex({
2415
+ provider: 1,
2416
+ provider_id: 1
2417
+ }, { unique: true });
2418
+ await identities.createIndex({ user_id: 1 });
2419
+ const userRoles = db.collection("rebase_user_roles");
2420
+ await userRoles.createIndex({
2421
+ user_id: 1,
2422
+ role_id: 1
2423
+ }, { unique: true });
2424
+ await userRoles.createIndex({ user_id: 1 });
2425
+ const refreshTokens = db.collection("rebase_refresh_tokens");
2426
+ await refreshTokens.createIndex({ token_hash: 1 }, { unique: true });
2427
+ await refreshTokens.createIndex({
2428
+ user_id: 1,
2429
+ user_agent: 1,
2430
+ ip_address: 1
2431
+ }, { unique: true });
2432
+ await refreshTokens.createIndex({ user_id: 1 });
2433
+ const resetTokens = db.collection("rebase_password_reset_tokens");
2434
+ await resetTokens.createIndex({ token_hash: 1 }, { unique: true });
2435
+ await resetTokens.createIndex({ user_id: 1 });
2436
+ await seedDefaultRoles(db);
2437
+ _rebasepro_server.logger.info("✅ MongoDB Auth collections ready");
2438
+ } catch (error) {
2439
+ _rebasepro_server.logger.error("❌ Failed to set up MongoDB auth collections", { error });
2440
+ }
2441
+ }
2442
+ async function seedDefaultRoles(db) {
2443
+ const roles = db.collection("rebase_roles");
2444
+ const count = await roles.countDocuments();
2445
+ if (count > 0) {
2446
+ _rebasepro_server.logger.info(`📋 Found ${count} existing roles`);
2447
+ return;
2448
+ }
2449
+ _rebasepro_server.logger.info("🌱 Seeding default roles...");
2450
+ for (const role of DEFAULT_ROLES) await roles.updateOne({ id: role.id }, { $setOnInsert: role }, { upsert: true });
2451
+ _rebasepro_server.logger.info("✅ Default roles created: admin, editor, viewer");
2452
+ }
2453
+ var DEFAULT_ROLES;
2454
+ var init_ensure_collections = __esmMin((() => {
2455
+ DEFAULT_ROLES = [
2456
+ {
2457
+ _id: "admin",
2458
+ id: "admin",
2459
+ name: "Admin",
2460
+ isAdmin: true,
2461
+ defaultPermissions: {
2462
+ read: true,
2463
+ create: true,
2464
+ edit: true,
2465
+ delete: true
2466
+ },
2467
+ createdAt: /* @__PURE__ */ new Date()
2468
+ },
2469
+ {
2470
+ _id: "editor",
2471
+ id: "editor",
2472
+ name: "Editor",
2473
+ isAdmin: false,
2474
+ defaultPermissions: {
2475
+ read: true,
2476
+ create: true,
2477
+ edit: true,
2478
+ delete: true
2479
+ },
2480
+ createdAt: /* @__PURE__ */ new Date()
2481
+ },
2482
+ {
2483
+ _id: "viewer",
2484
+ id: "viewer",
2485
+ name: "Viewer",
2486
+ isAdmin: false,
2487
+ defaultPermissions: {
2488
+ read: true,
2489
+ create: false,
2490
+ edit: false,
2491
+ delete: false
2492
+ },
2493
+ createdAt: /* @__PURE__ */ new Date()
2494
+ }
2495
+ ];
2496
+ }));
2497
+ //#endregion
2498
+ //#region src/history/ensure-history-collection.ts
2499
+ var ensure_history_collection_exports = /* @__PURE__ */ __exportAll({ ensureHistoryCollectionExists: () => ensureHistoryCollectionExists });
2500
+ async function ensureHistoryCollectionExists(db) {
2501
+ _rebasepro_server.logger.info("🔍 Checking MongoDB history collection and indexes...");
2502
+ try {
2503
+ const history = db.collection("__rebase_history");
2504
+ await history.createIndex({
2505
+ entity_id: 1,
2506
+ table_name: 1,
2507
+ updated_at: -1
2508
+ });
2509
+ await history.createIndex({ updated_at: 1 });
2510
+ _rebasepro_server.logger.info("✅ MongoDB History collection ready");
2511
+ } catch (error) {
2512
+ _rebasepro_server.logger.error("❌ Failed to set up MongoDB history collection", { error });
2513
+ }
2514
+ }
2515
+ var init_ensure_history_collection = __esmMin((() => {}));
2516
+ //#endregion
2517
+ //#region src/websocket.ts
2518
+ var websocket_exports = /* @__PURE__ */ __exportAll({ createMongoWebSocket: () => createMongoWebSocket });
2519
+ function isDriverWithAuth(driver) {
2520
+ return "withAuth" in driver && typeof driver.withAuth === "function";
2521
+ }
2522
+ function isAdminSession(session) {
2523
+ if (!session?.user?.roles) return false;
2524
+ return session.user.roles.includes("admin");
2525
+ }
2526
+ function createMongoWebSocket(server, realtimeService, driver, authConfig, admin) {
2527
+ const isProduction = process.env.NODE_ENV === "production";
2528
+ const wsDebug = (...args) => {
2529
+ if (!isProduction) console.debug(...args);
2530
+ };
2531
+ const wss = new ws.WebSocketServer({ server });
2532
+ wss.on("error", (err) => {
2533
+ if (err.code === "EADDRINUSE") return;
2534
+ _rebasepro_server.logger.error("❌ [WebSocket Server] Error", { error: err });
2535
+ });
2536
+ const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
2537
+ wss.on("connection", (ws$1) => {
2538
+ const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2539
+ wsDebug(`WebSocket client connected: ${clientId}`);
2540
+ clientSessions.set(clientId, {
2541
+ ws: ws$1,
2542
+ authenticated: !requireAuth,
2543
+ messageCount: 0,
2544
+ messageWindowStart: Date.now()
2545
+ });
2546
+ realtimeService.addClient(clientId, ws$1);
2547
+ ws$1.on("close", () => {
2548
+ wsDebug(`WebSocket client disconnected: ${clientId}`);
2549
+ clientSessions.delete(clientId);
2550
+ });
2551
+ ws$1.on("message", async (message) => {
2552
+ let requestId;
2553
+ try {
2554
+ const { type, payload, requestId: reqId } = JSON.parse(message.toString());
2555
+ requestId = reqId;
2556
+ wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : "");
2557
+ const sendError = (errType, code, msg) => {
2558
+ ws$1.send(JSON.stringify({
2559
+ type: errType,
2560
+ requestId,
2561
+ payload: { error: {
2562
+ message: msg,
2563
+ code
2564
+ } }
2565
+ }));
2566
+ };
2567
+ if (type === "AUTHENTICATE") {
2568
+ const { token } = payload || {};
2569
+ if (!token) {
2570
+ sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
2571
+ return;
2572
+ }
2573
+ const user = (0, _rebasepro_server.extractUserFromToken)(token);
2574
+ if (user) {
2575
+ const session = clientSessions.get(clientId);
2576
+ if (session) {
2577
+ session.user = user;
2578
+ session.authenticated = true;
2579
+ }
2580
+ ws$1.send(JSON.stringify({
2581
+ type: "AUTH_SUCCESS",
2582
+ requestId,
2583
+ payload: {
2584
+ userId: user.userId,
2585
+ roles: user.roles
2586
+ }
2587
+ }));
2588
+ } else sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
2589
+ return;
2590
+ }
2591
+ if (requireAuth) {
2592
+ if (!clientSessions.get(clientId)?.authenticated) {
2593
+ sendError("ERROR", "UNAUTHORIZED", "Authentication required");
2594
+ return;
2595
+ }
2596
+ }
2597
+ {
2598
+ const session = clientSessions.get(clientId);
2599
+ if (session) {
2600
+ const now = Date.now();
2601
+ if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {
2602
+ session.messageCount = 0;
2603
+ session.messageWindowStart = now;
2604
+ }
2605
+ session.messageCount++;
2606
+ if (session.messageCount > WS_RATE_LIMIT) {
2607
+ sendError("ERROR", "RATE_LIMITED", "Too many requests. Please slow down.");
2608
+ return;
2609
+ }
2610
+ }
2611
+ }
2612
+ if (ADMIN_ONLY_TYPES.has(type)) {
2613
+ if (!isAdminSession(clientSessions.get(clientId))) {
2614
+ sendError("ERROR", "FORBIDDEN", "Admin access required for this operation");
2615
+ return;
2616
+ }
2617
+ }
2618
+ const getScopedDelegate = async () => {
2619
+ const session = clientSessions.get(clientId);
2620
+ if (session?.user && isDriverWithAuth(driver)) try {
2621
+ const userForAuth = {
2622
+ uid: session.user.userId,
2623
+ email: session.user.email ?? "",
2624
+ displayName: session.user.displayName ?? "",
2625
+ photoURL: session.user.photoURL ?? "",
2626
+ providerId: "jwt",
2627
+ isAnonymous: false,
2628
+ roles: session.user.roles ?? []
2629
+ };
2630
+ return await driver.withAuth(userForAuth);
2631
+ } catch (e) {
2632
+ _rebasepro_server.logger.error("Failed to create authenticated delegate for WS request", { error: e });
2633
+ return driver;
2634
+ }
2635
+ return driver;
2636
+ };
2637
+ switch (type) {
2638
+ case "FETCH_COLLECTION": {
2639
+ const request = payload;
2640
+ const rows = await (await getScopedDelegate()).fetchCollection(request);
2641
+ ws$1.send(JSON.stringify({
2642
+ type: "FETCH_COLLECTION_SUCCESS",
2643
+ payload: { rows },
2644
+ requestId
2645
+ }));
2646
+ break;
2647
+ }
2648
+ case "FETCH_ONE": {
2649
+ const request = payload;
2650
+ const row = await (await getScopedDelegate()).fetchOne(request);
2651
+ ws$1.send(JSON.stringify({
2652
+ type: "FETCH_ONE_SUCCESS",
2653
+ payload: { row },
2654
+ requestId
2655
+ }));
2656
+ break;
2657
+ }
2658
+ case "SAVE": {
2659
+ const request = payload;
2660
+ const row = await (await getScopedDelegate()).save(request);
2661
+ ws$1.send(JSON.stringify({
2662
+ type: "SAVE_SUCCESS",
2663
+ payload: { row },
2664
+ requestId
2665
+ }));
2666
+ break;
2667
+ }
2668
+ case "DELETE": {
2669
+ const request = payload;
2670
+ await (await getScopedDelegate()).delete(request);
2671
+ ws$1.send(JSON.stringify({
2672
+ type: "DELETE_SUCCESS",
2673
+ payload: { success: true },
2674
+ requestId
2675
+ }));
2676
+ break;
2677
+ }
2678
+ case "CHECK_UNIQUE_FIELD": {
2679
+ const { path, name, value, id, collection } = payload;
2680
+ const isUnique = await (await getScopedDelegate()).checkUniqueField(path, name, value, id, collection);
2681
+ ws$1.send(JSON.stringify({
2682
+ type: "CHECK_UNIQUE_FIELD_SUCCESS",
2683
+ payload: { isUnique },
2684
+ requestId
2685
+ }));
2686
+ break;
2687
+ }
2688
+ case "COUNT": {
2689
+ const request = payload;
2690
+ const count = await (await getScopedDelegate()).count(request);
2691
+ ws$1.send(JSON.stringify({
2692
+ type: "COUNT_SUCCESS",
2693
+ payload: { count },
2694
+ requestId
2695
+ }));
2696
+ break;
2697
+ }
2698
+ case "EXECUTE_SQL": {
2699
+ const { sql, options } = payload;
2700
+ if (admin && (0, _rebasepro_types.isDocumentAdmin)(admin) && admin.executeAggregate) {
2701
+ const result = await admin.executeAggregate(sql);
2702
+ ws$1.send(JSON.stringify({
2703
+ type: "EXECUTE_SQL_SUCCESS",
2704
+ payload: { result },
2705
+ requestId
2706
+ }));
2707
+ } else ws$1.send(JSON.stringify({
2708
+ type: "ERROR",
2709
+ requestId,
2710
+ payload: { error: {
2711
+ message: "SQL execution not supported for this driver",
2712
+ code: "NOT_SUPPORTED"
2713
+ } }
2714
+ }));
2715
+ break;
2716
+ }
2717
+ case "FETCH_UNMAPPED_TABLES":
2718
+ if (admin && (0, _rebasepro_types.isSchemaAdmin)(admin)) {
2719
+ const tables = await admin.fetchUnmappedTables?.(payload?.mappedPaths) || [];
2720
+ ws$1.send(JSON.stringify({
2721
+ type: "FETCH_UNMAPPED_TABLES_SUCCESS",
2722
+ payload: { tables },
2723
+ requestId
2724
+ }));
2725
+ } else ws$1.send(JSON.stringify({
2726
+ type: "FETCH_UNMAPPED_TABLES_SUCCESS",
2727
+ payload: { tables: [] },
2728
+ requestId
2729
+ }));
2730
+ break;
2731
+ case "FETCH_TABLE_METADATA": {
2732
+ const { tableName } = payload;
2733
+ if (admin && (0, _rebasepro_types.isSchemaAdmin)(admin)) {
2734
+ const metadata = await admin.fetchTableMetadata?.(tableName);
2735
+ ws$1.send(JSON.stringify({
2736
+ type: "FETCH_TABLE_METADATA_SUCCESS",
2737
+ payload: { metadata },
2738
+ requestId
2739
+ }));
2740
+ } else ws$1.send(JSON.stringify({
2741
+ type: "FETCH_TABLE_METADATA_SUCCESS",
2742
+ payload: { metadata: null },
2743
+ requestId
2744
+ }));
2745
+ break;
2746
+ }
2747
+ case "subscribe_collection":
2748
+ case "subscribe_one":
2749
+ case "unsubscribe": {
2750
+ const session = clientSessions.get(clientId);
2751
+ const authContext = session?.user ? {
2752
+ userId: session.user.userId,
2753
+ roles: session.user.roles ?? []
2754
+ } : void 0;
2755
+ await realtimeService.handleClientMessage(clientId, {
2756
+ type,
2757
+ payload,
2758
+ subscriptionId: payload?.subscriptionId
2759
+ }, authContext);
2760
+ break;
2761
+ }
2762
+ default: _rebasepro_server.logger.error("❌ [WebSocket Server] Unknown message type", { detail: type });
2763
+ }
2764
+ } catch (error) {
2765
+ const errorMessage = process.env.NODE_ENV === "production" ? "An unexpected error occurred" : error instanceof Error ? error.message : "An unexpected error occurred";
2766
+ ws$1.send(JSON.stringify({
2767
+ type: "ERROR",
2768
+ requestId,
2769
+ payload: { error: {
2770
+ message: errorMessage,
2771
+ code: "INTERNAL_ERROR"
2772
+ } }
2773
+ }));
2774
+ }
2775
+ });
2776
+ });
2777
+ }
2778
+ var clientSessions, WS_RATE_LIMIT, WS_RATE_WINDOW_MS, ADMIN_ONLY_TYPES;
2779
+ var init_websocket = __esmMin((() => {
2780
+ clientSessions = /* @__PURE__ */ new Map();
2781
+ WS_RATE_LIMIT = 2e3;
2782
+ WS_RATE_WINDOW_MS = 6e4;
2783
+ ADMIN_ONLY_TYPES = new Set([
2784
+ "EXECUTE_SQL",
2785
+ "FETCH_DATABASES",
2786
+ "FETCH_ROLES",
2787
+ "FETCH_UNMAPPED_TABLES",
2788
+ "FETCH_TABLE_METADATA",
2789
+ "FETCH_CURRENT_DATABASE",
2790
+ "CREATE_BRANCH",
2791
+ "DELETE_BRANCH",
2792
+ "LIST_BRANCHES"
2793
+ ]);
2794
+ }));
2795
+ //#endregion
2796
+ //#region src/MongoBootstrapper.ts
2797
+ function createMongoBootstrapper(mongoConfig) {
2798
+ let cachedAdmin;
2799
+ return {
2800
+ type: "mongodb",
2801
+ async initializeDriver(config) {
2802
+ const { collections } = config;
2803
+ const registry = new MongoCollectionRegistry();
2804
+ if (collections) collections.forEach((collection) => registry.register(collection));
2805
+ const db = mongoConfig.connection;
2806
+ const client = mongoConfig.client;
2807
+ try {
2808
+ await db.command({ ping: 1 });
2809
+ } catch (err) {
2810
+ _rebasepro_server.logger.error("❌ Failed to connect to MongoDB", { error: err });
2811
+ }
2812
+ const realtimeService = new MongoRealtimeService(db);
2813
+ const driver = new MongoDriver(db, realtimeService, void 0, registry);
2814
+ return {
2815
+ driver,
2816
+ realtimeProvider: realtimeService,
2817
+ collectionRegistry: registry,
2818
+ internals: {
2819
+ db,
2820
+ client,
2821
+ registry,
2822
+ realtimeService,
2823
+ driver
2824
+ }
2825
+ };
2826
+ },
2827
+ async initializeAuth(config, driverResult) {
2828
+ const db = driverResult.internals.db;
2829
+ const { ensureAuthCollectionsExist } = await Promise.resolve().then(() => (init_ensure_collections(), ensure_collections_exports));
2830
+ await ensureAuthCollectionsExist(db);
2831
+ const { createEmailService } = await import("@rebasepro/server");
2832
+ const authConfig = config;
2833
+ let emailService;
2834
+ if (authConfig?.email) emailService = createEmailService(authConfig.email);
2835
+ return {
2836
+ userService: new MongoUserService(db),
2837
+ roleService: new MongoRoleService(db),
2838
+ authRepository: new MongoAuthRepository(db),
2839
+ emailService
2840
+ };
2841
+ },
2842
+ async initializeHistory(config, driverResult) {
2843
+ if (!config) return void 0;
2844
+ const db = driverResult.internals.db;
2845
+ const { ensureHistoryCollectionExists } = await Promise.resolve().then(() => (init_ensure_history_collection(), ensure_history_collection_exports));
2846
+ await ensureHistoryCollectionExists(db);
2847
+ const { MongoHistoryService } = await Promise.resolve().then(() => (init_MongoHistoryService(), MongoHistoryService_exports));
2848
+ const retention = typeof config === "object" ? config.retention : void 0;
2849
+ return { historyService: new MongoHistoryService(db, retention ? { ttlDays: retention } : void 0) };
2850
+ },
2851
+ async initializeRealtime(_config, driverResult) {
2852
+ return driverResult.internals.realtimeService;
2853
+ },
2854
+ getAdmin(driverResult) {
2855
+ const db = driverResult.internals.db;
2856
+ const admin = {
2857
+ async executeAggregate(pipeline) {
2858
+ const collName = pipeline[0]?.$from ?? "__admin__";
2859
+ return await db.collection(collName).aggregate(pipeline).toArray();
2860
+ },
2861
+ async fetchCollectionStats(collectionName) {
2862
+ const stats = await db.command({ collStats: collectionName });
2863
+ return {
2864
+ count: stats.count,
2865
+ sizeBytes: stats.size
2866
+ };
2867
+ },
2868
+ async fetchUnmappedTables(mappedPaths) {
2869
+ const names = (await db.listCollections().toArray()).map((c) => c.name).filter((n) => !n.startsWith("system."));
2870
+ if (!mappedPaths || mappedPaths.length === 0) return names;
2871
+ const mappedSet = new Set(mappedPaths.map((p) => p.toLowerCase()));
2872
+ return names.filter((n) => !mappedSet.has(n.toLowerCase()));
2873
+ },
2874
+ async fetchTableMetadata(collectionName) {
2875
+ const sample = await db.collection(collectionName).findOne();
2876
+ if (!sample) return {
2877
+ columns: [],
2878
+ foreignKeys: [],
2879
+ junctions: [],
2880
+ policies: []
2881
+ };
2882
+ return {
2883
+ columns: Object.entries(sample).map(([key, value]) => ({
2884
+ column_name: key,
2885
+ data_type: typeof value,
2886
+ udt_name: typeof value,
2887
+ is_nullable: "YES",
2888
+ column_default: null,
2889
+ character_maximum_length: null
2890
+ })),
2891
+ foreignKeys: [],
2892
+ junctions: [],
2893
+ policies: []
2894
+ };
2895
+ }
2896
+ };
2897
+ cachedAdmin = admin;
2898
+ return admin;
2899
+ },
2900
+ mountRoutes() {},
2901
+ async initializeWebsockets(server, realtimeService, driver, config) {
2902
+ const { createMongoWebSocket } = await Promise.resolve().then(() => (init_websocket(), websocket_exports));
2903
+ createMongoWebSocket(server, realtimeService, driver, config, cachedAdmin);
2904
+ }
2905
+ };
2906
+ }
2907
+ //#endregion
2908
+ exports.AuthenticatedMongoDriver = AuthenticatedMongoDriver;
2909
+ exports.MongoCollectionRegistry = MongoCollectionRegistry;
2910
+ exports.MongoConditionBuilder = MongoConditionBuilder;
2911
+ exports.MongoDBConnection = MongoDBConnection;
2912
+ exports.MongoDataService = MongoDataService;
2913
+ exports.MongoDriver = MongoDriver;
2914
+ exports.MongoRealtimeService = MongoRealtimeService;
2915
+ exports.createMongoBackend = createMongoBackend;
2916
+ exports.createMongoBootstrapper = createMongoBootstrapper;
2917
+ exports.createMongoDBConnection = createMongoDBConnection;
2918
+ exports.createMongoDelegate = createMongoDelegate;
2919
+ exports.createMongoEntityRepository = createMongoEntityRepository;
2920
+ exports.createMongoRealtimeService = createMongoRealtimeService;
2921
+ exports.isMongoBackendConfig = isMongoBackendConfig;
2922
+ exports.isMongoDriverConfig = isMongoDriverConfig;
2923
+ });
2924
+
2925
+ //# sourceMappingURL=index.umd.js.map