@rebasepro/server-mongodb 0.0.1-canary.4d4fb3e

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 (125) hide show
  1. package/LICENSE +6 -0
  2. package/dist/index.es.js +875 -0
  3. package/dist/index.es.js.map +1 -0
  4. package/dist/index.umd.js +878 -0
  5. package/dist/index.umd.js.map +1 -0
  6. package/dist/server-mongodb/src/connection.d.ts +35 -0
  7. package/dist/server-mongodb/src/connection.d.ts.map +1 -0
  8. package/dist/server-mongodb/src/db/MongoConditionBuilder.d.ts +64 -0
  9. package/dist/server-mongodb/src/db/MongoConditionBuilder.d.ts.map +1 -0
  10. package/dist/server-mongodb/src/db/MongoEntityService.d.ts +98 -0
  11. package/dist/server-mongodb/src/db/MongoEntityService.d.ts.map +1 -0
  12. package/dist/server-mongodb/src/factory.d.ts +139 -0
  13. package/dist/server-mongodb/src/factory.d.ts.map +1 -0
  14. package/dist/server-mongodb/src/index.d.ts +17 -0
  15. package/dist/server-mongodb/src/index.d.ts.map +1 -0
  16. package/dist/server-mongodb/src/services/MongoDriver.d.ts +81 -0
  17. package/dist/server-mongodb/src/services/MongoDriver.d.ts.map +1 -0
  18. package/dist/server-mongodb/src/services/MongoRealtimeService.d.ts +65 -0
  19. package/dist/server-mongodb/src/services/MongoRealtimeService.d.ts.map +1 -0
  20. package/dist/server-mongodb/src/useMongoDriver.d.ts +18 -0
  21. package/dist/server-mongodb/src/useMongoDriver.d.ts.map +1 -0
  22. package/dist/server-mongodb/src/utils.d.ts +10 -0
  23. package/dist/server-mongodb/src/utils.d.ts.map +1 -0
  24. package/dist/types/src/controllers/analytics_controller.d.ts +8 -0
  25. package/dist/types/src/controllers/analytics_controller.d.ts.map +1 -0
  26. package/dist/types/src/controllers/auth.d.ts +118 -0
  27. package/dist/types/src/controllers/auth.d.ts.map +1 -0
  28. package/dist/types/src/controllers/client.d.ts +59 -0
  29. package/dist/types/src/controllers/client.d.ts.map +1 -0
  30. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  31. package/dist/types/src/controllers/collection_registry.d.ts.map +1 -0
  32. package/dist/types/src/controllers/customization_controller.d.ts +55 -0
  33. package/dist/types/src/controllers/customization_controller.d.ts.map +1 -0
  34. package/dist/types/src/controllers/data.d.ts +142 -0
  35. package/dist/types/src/controllers/data.d.ts.map +1 -0
  36. package/dist/types/src/controllers/data_driver.d.ts +169 -0
  37. package/dist/types/src/controllers/data_driver.d.ts.map +1 -0
  38. package/dist/types/src/controllers/database_admin.d.ts +12 -0
  39. package/dist/types/src/controllers/database_admin.d.ts.map +1 -0
  40. package/dist/types/src/controllers/dialogs_controller.d.ts +37 -0
  41. package/dist/types/src/controllers/dialogs_controller.d.ts.map +1 -0
  42. package/dist/types/src/controllers/effective_role.d.ts +5 -0
  43. package/dist/types/src/controllers/effective_role.d.ts.map +1 -0
  44. package/dist/types/src/controllers/index.d.ts +18 -0
  45. package/dist/types/src/controllers/index.d.ts.map +1 -0
  46. package/dist/types/src/controllers/local_config_persistence.d.ts +21 -0
  47. package/dist/types/src/controllers/local_config_persistence.d.ts.map +1 -0
  48. package/dist/types/src/controllers/navigation.d.ts +214 -0
  49. package/dist/types/src/controllers/navigation.d.ts.map +1 -0
  50. package/dist/types/src/controllers/registry.d.ts +52 -0
  51. package/dist/types/src/controllers/registry.d.ts.map +1 -0
  52. package/dist/types/src/controllers/side_dialogs_controller.d.ts +68 -0
  53. package/dist/types/src/controllers/side_dialogs_controller.d.ts.map +1 -0
  54. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  55. package/dist/types/src/controllers/side_entity_controller.d.ts.map +1 -0
  56. package/dist/types/src/controllers/snackbar.d.ts +25 -0
  57. package/dist/types/src/controllers/snackbar.d.ts.map +1 -0
  58. package/dist/types/src/controllers/storage.d.ts +174 -0
  59. package/dist/types/src/controllers/storage.d.ts.map +1 -0
  60. package/dist/types/src/index.d.ts +5 -0
  61. package/dist/types/src/index.d.ts.map +1 -0
  62. package/dist/types/src/rebase_context.d.ts +102 -0
  63. package/dist/types/src/rebase_context.d.ts.map +1 -0
  64. package/dist/types/src/types/backend.d.ts +534 -0
  65. package/dist/types/src/types/backend.d.ts.map +1 -0
  66. package/dist/types/src/types/builders.d.ts +15 -0
  67. package/dist/types/src/types/builders.d.ts.map +1 -0
  68. package/dist/types/src/types/chips.d.ts +6 -0
  69. package/dist/types/src/types/chips.d.ts.map +1 -0
  70. package/dist/types/src/types/collections.d.ts +813 -0
  71. package/dist/types/src/types/collections.d.ts.map +1 -0
  72. package/dist/types/src/types/data_source.d.ts +65 -0
  73. package/dist/types/src/types/data_source.d.ts.map +1 -0
  74. package/dist/types/src/types/entities.d.ts +146 -0
  75. package/dist/types/src/types/entities.d.ts.map +1 -0
  76. package/dist/types/src/types/entity_actions.d.ts +99 -0
  77. package/dist/types/src/types/entity_actions.d.ts.map +1 -0
  78. package/dist/types/src/types/entity_callbacks.d.ts +174 -0
  79. package/dist/types/src/types/entity_callbacks.d.ts.map +1 -0
  80. package/dist/types/src/types/entity_link_builder.d.ts +8 -0
  81. package/dist/types/src/types/entity_link_builder.d.ts.map +1 -0
  82. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  83. package/dist/types/src/types/entity_overrides.d.ts.map +1 -0
  84. package/dist/types/src/types/entity_views.d.ts +62 -0
  85. package/dist/types/src/types/entity_views.d.ts.map +1 -0
  86. package/dist/types/src/types/export_import.d.ts +22 -0
  87. package/dist/types/src/types/export_import.d.ts.map +1 -0
  88. package/dist/types/src/types/index.d.ts +23 -0
  89. package/dist/types/src/types/index.d.ts.map +1 -0
  90. package/dist/types/src/types/locales.d.ts +5 -0
  91. package/dist/types/src/types/locales.d.ts.map +1 -0
  92. package/dist/types/src/types/modify_collections.d.ts +6 -0
  93. package/dist/types/src/types/modify_collections.d.ts.map +1 -0
  94. package/dist/types/src/types/plugins.d.ts +226 -0
  95. package/dist/types/src/types/plugins.d.ts.map +1 -0
  96. package/dist/types/src/types/properties.d.ts +1092 -0
  97. package/dist/types/src/types/properties.d.ts.map +1 -0
  98. package/dist/types/src/types/property_config.d.ts +71 -0
  99. package/dist/types/src/types/property_config.d.ts.map +1 -0
  100. package/dist/types/src/types/relations.d.ts +337 -0
  101. package/dist/types/src/types/relations.d.ts.map +1 -0
  102. package/dist/types/src/types/slots.d.ts +229 -0
  103. package/dist/types/src/types/slots.d.ts.map +1 -0
  104. package/dist/types/src/types/translations.d.ts +827 -0
  105. package/dist/types/src/types/translations.d.ts.map +1 -0
  106. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  107. package/dist/types/src/types/user_management_delegate.d.ts.map +1 -0
  108. package/dist/types/src/types/websockets.d.ts +79 -0
  109. package/dist/types/src/types/websockets.d.ts.map +1 -0
  110. package/dist/types/src/users/index.d.ts +3 -0
  111. package/dist/types/src/users/index.d.ts.map +1 -0
  112. package/dist/types/src/users/roles.d.ts +23 -0
  113. package/dist/types/src/users/roles.d.ts.map +1 -0
  114. package/dist/types/src/users/user.d.ts +47 -0
  115. package/dist/types/src/users/user.d.ts.map +1 -0
  116. package/package.json +81 -0
  117. package/src/connection.ts +60 -0
  118. package/src/db/MongoConditionBuilder.ts +181 -0
  119. package/src/db/MongoEntityService.ts +350 -0
  120. package/src/factory.ts +274 -0
  121. package/src/index.ts +24 -0
  122. package/src/services/MongoDriver.ts +267 -0
  123. package/src/services/MongoRealtimeService.ts +295 -0
  124. package/src/useMongoDriver.ts +516 -0
  125. package/src/utils.ts +28 -0
@@ -0,0 +1,878 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("mongodb")) : typeof define === "function" && define.amd ? define(["exports", "mongodb"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase MongoDB"] = {}, global.mongodb));
3
+ })(this, function(exports2, mongodb) {
4
+ "use strict";
5
+ class MongoDBConnection {
6
+ constructor(db, client) {
7
+ this.db = db;
8
+ this.client = client;
9
+ }
10
+ type = "mongodb";
11
+ get isConnected() {
12
+ try {
13
+ const clientInternal = this.client;
14
+ return clientInternal.topology?.isConnected?.() ?? false;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ async close() {
20
+ await this.client.close();
21
+ }
22
+ }
23
+ async function createMongoDBConnection(connectionString, databaseName) {
24
+ const client = new mongodb.MongoClient(connectionString);
25
+ await client.connect();
26
+ const db = client.db(databaseName);
27
+ return new MongoDBConnection(db, client);
28
+ }
29
+ const REBASE_TO_MONGO_OP = {
30
+ "<": "$lt",
31
+ "<=": "$lte",
32
+ "==": "$eq",
33
+ "!=": "$ne",
34
+ ">=": "$gte",
35
+ ">": "$gt",
36
+ "array-contains": "$elemMatch",
37
+ "array-contains-any": "$in",
38
+ "in": "$in",
39
+ "not-in": "$nin"
40
+ };
41
+ class MongoConditionBuilder {
42
+ /**
43
+ * Build MongoDB filter conditions from Rebase FilterValues
44
+ *
45
+ * @param filter - Rebase filter values
46
+ * @returns Array of MongoDB filter objects
47
+ */
48
+ static buildFilterConditions(filter) {
49
+ if (!filter) return [];
50
+ const conditions = [];
51
+ for (const [field, filterParam] of Object.entries(filter)) {
52
+ if (!filterParam) continue;
53
+ const [op, value] = filterParam;
54
+ const mongoOp = REBASE_TO_MONGO_OP[op];
55
+ if (!mongoOp) {
56
+ console.warn(`Unsupported filter operator: ${op}`);
57
+ continue;
58
+ }
59
+ if (op === "array-contains") {
60
+ conditions.push({
61
+ [field]: { $elemMatch: { $eq: value } }
62
+ });
63
+ } else {
64
+ conditions.push({
65
+ [field]: { [mongoOp]: value }
66
+ });
67
+ }
68
+ }
69
+ return conditions;
70
+ }
71
+ /**
72
+ * Build search conditions for text search
73
+ *
74
+ * @param searchString - Text to search for
75
+ * @param properties - Properties to search in
76
+ * @returns Array of MongoDB filter objects for text search
77
+ */
78
+ static buildSearchConditions(searchString, properties) {
79
+ if (!searchString) return [];
80
+ const orConditions = [];
81
+ const searchRegex = new RegExp(searchString, "i");
82
+ for (const [key, prop] of Object.entries(properties)) {
83
+ if (prop?.dataType === "string" || typeof prop === "string") {
84
+ orConditions.push({
85
+ [key]: { $regex: searchRegex }
86
+ });
87
+ }
88
+ }
89
+ if (orConditions.length === 0) {
90
+ return [{ $text: { $search: searchString } }];
91
+ }
92
+ return orConditions;
93
+ }
94
+ /**
95
+ * Combine multiple conditions with AND operator
96
+ *
97
+ * @param conditions - Array of filter conditions
98
+ * @returns Combined filter or undefined if empty
99
+ */
100
+ static combineConditionsWithAnd(conditions) {
101
+ if (conditions.length === 0) return void 0;
102
+ if (conditions.length === 1) return conditions[0];
103
+ return { $and: conditions };
104
+ }
105
+ /**
106
+ * Combine multiple conditions with OR operator
107
+ *
108
+ * @param conditions - Array of filter conditions
109
+ * @returns Combined filter or undefined if empty
110
+ */
111
+ static combineConditionsWithOr(conditions) {
112
+ if (conditions.length === 0) return void 0;
113
+ if (conditions.length === 1) return conditions[0];
114
+ return { $or: conditions };
115
+ }
116
+ /**
117
+ * Build a complete MongoDB query from Rebase options
118
+ *
119
+ * @param options - Rebase fetch options
120
+ * @returns MongoDB filter object
121
+ */
122
+ static buildQuery(options) {
123
+ const conditions = [];
124
+ if (options.filter) {
125
+ const filterConditions = this.buildFilterConditions(options.filter);
126
+ conditions.push(...filterConditions);
127
+ }
128
+ if (options.searchString && options.properties) {
129
+ const searchConditions = this.buildSearchConditions(
130
+ options.searchString,
131
+ options.properties
132
+ );
133
+ if (searchConditions.length > 0) {
134
+ const searchFilter = this.combineConditionsWithOr(searchConditions);
135
+ if (searchFilter) {
136
+ conditions.push(searchFilter);
137
+ }
138
+ }
139
+ }
140
+ return this.combineConditionsWithAnd(conditions) ?? {};
141
+ }
142
+ /**
143
+ * Build MongoDB sort options from Rebase options
144
+ *
145
+ * @param orderBy - Field to order by
146
+ * @param order - Sort direction
147
+ * @returns MongoDB sort object
148
+ */
149
+ static buildSort(orderBy, order) {
150
+ if (!orderBy) return void 0;
151
+ return { [orderBy]: order === "desc" ? -1 : 1 };
152
+ }
153
+ }
154
+ class MongoEntityService {
155
+ constructor(db) {
156
+ this.db = db;
157
+ }
158
+ /**
159
+ * Get a MongoDB collection by its path
160
+ */
161
+ getCollection(collectionPath) {
162
+ const collectionName = collectionPath.replace(/\//g, "_");
163
+ return this.db.collection(collectionName);
164
+ }
165
+ /**
166
+ * Convert a string ID to ObjectId if it's a valid ObjectId string
167
+ */
168
+ toObjectId(id) {
169
+ if (typeof id === "string" && mongodb.ObjectId.isValid(id) && id.length === 24) {
170
+ return new mongodb.ObjectId(id);
171
+ }
172
+ return id;
173
+ }
174
+ /**
175
+ * Convert a MongoDB document to a Rebase Entity
176
+ */
177
+ documentToEntity(doc, path) {
178
+ const { _id, ...values } = doc;
179
+ return {
180
+ id: _id.toString(),
181
+ path,
182
+ values: this.convertFromMongoValues(values)
183
+ };
184
+ }
185
+ /**
186
+ * Convert values from MongoDB format to Rebase format
187
+ */
188
+ convertFromMongoValues(values) {
189
+ const result = {};
190
+ for (const [key, value] of Object.entries(values)) {
191
+ result[key] = this.convertFromMongoValue(value);
192
+ }
193
+ return result;
194
+ }
195
+ /**
196
+ * Convert a single value from MongoDB format
197
+ */
198
+ convertFromMongoValue(value) {
199
+ if (value === null || value === void 0) return value;
200
+ if (value instanceof mongodb.ObjectId) {
201
+ return value.toString();
202
+ }
203
+ if (value instanceof Date) {
204
+ return value;
205
+ }
206
+ if (Array.isArray(value)) {
207
+ return value.map((v) => this.convertFromMongoValue(v));
208
+ }
209
+ if (typeof value === "object" && "path" in value && "id" in value) {
210
+ return {
211
+ path: value.path,
212
+ id: value.id instanceof mongodb.ObjectId ? value.id.toString() : value.id
213
+ };
214
+ }
215
+ if (typeof value === "object") {
216
+ return this.convertFromMongoValues(value);
217
+ }
218
+ return value;
219
+ }
220
+ /**
221
+ * Convert values to MongoDB format for storage
222
+ */
223
+ convertToMongoValues(values) {
224
+ const result = {};
225
+ for (const [key, value] of Object.entries(values)) {
226
+ result[key] = this.convertToMongoValue(value);
227
+ }
228
+ return result;
229
+ }
230
+ /**
231
+ * Convert a single value to MongoDB format
232
+ */
233
+ convertToMongoValue(value) {
234
+ if (value === null || value === void 0) return value;
235
+ if (typeof value === "object" && value.isEntityReference?.()) {
236
+ return {
237
+ id: mongodb.ObjectId.isValid(value.id) ? new mongodb.ObjectId(value.id) : value.id,
238
+ path: value.path
239
+ };
240
+ }
241
+ if (value instanceof Date) {
242
+ return value;
243
+ }
244
+ if (Array.isArray(value)) {
245
+ return value.map((v) => this.convertToMongoValue(v));
246
+ }
247
+ if (typeof value === "object") {
248
+ return this.convertToMongoValues(value);
249
+ }
250
+ return value;
251
+ }
252
+ // =============================================================
253
+ // EntityRepository Implementation
254
+ // =============================================================
255
+ /**
256
+ * Fetch a single entity by ID
257
+ */
258
+ async fetchEntity(collectionPath, entityId, _databaseId) {
259
+ const collection = this.getCollection(collectionPath);
260
+ const id = this.toObjectId(entityId);
261
+ const doc = await collection.findOne({ _id: id });
262
+ if (!doc) return void 0;
263
+ return this.documentToEntity(doc, collectionPath);
264
+ }
265
+ /**
266
+ * Fetch a collection of entities with optional filtering, ordering, and pagination
267
+ */
268
+ async fetchCollection(collectionPath, options = {}) {
269
+ const collection = this.getCollection(collectionPath);
270
+ const query = MongoConditionBuilder.buildQuery({
271
+ filter: options.filter,
272
+ searchString: options.searchString,
273
+ properties: options.collection?.properties ?? {}
274
+ });
275
+ const findOptions = {};
276
+ const sort = MongoConditionBuilder.buildSort(options.orderBy, options.order);
277
+ if (sort) {
278
+ findOptions.sort = sort;
279
+ }
280
+ if (options.limit) {
281
+ findOptions.limit = options.limit;
282
+ }
283
+ if (options.startAfter !== void 0) {
284
+ findOptions.skip = Number(options.startAfter);
285
+ }
286
+ const docs = await collection.find(query, findOptions).toArray();
287
+ return docs.map((doc) => this.documentToEntity(doc, collectionPath));
288
+ }
289
+ /**
290
+ * Search entities by text
291
+ */
292
+ async searchEntities(collectionPath, searchString, options = {}) {
293
+ return this.fetchCollection(collectionPath, {
294
+ ...options,
295
+ searchString
296
+ });
297
+ }
298
+ /**
299
+ * Count entities in a collection
300
+ */
301
+ async countEntities(collectionPath, options = {}) {
302
+ const collection = this.getCollection(collectionPath);
303
+ const query = options.filter ? MongoConditionBuilder.buildQuery({ filter: options.filter }) : {};
304
+ return collection.countDocuments(query);
305
+ }
306
+ /**
307
+ * Save an entity (create or update)
308
+ */
309
+ async saveEntity(collectionPath, values, entityId, _databaseId) {
310
+ const collection = this.getCollection(collectionPath);
311
+ const mongoValues = this.convertToMongoValues(values);
312
+ if (entityId) {
313
+ const id = this.toObjectId(entityId);
314
+ await collection.updateOne(
315
+ { _id: id },
316
+ { $set: mongoValues },
317
+ { upsert: true }
318
+ );
319
+ return {
320
+ id: entityId.toString(),
321
+ path: collectionPath,
322
+ values
323
+ };
324
+ } else {
325
+ const newId = new mongodb.ObjectId();
326
+ await collection.insertOne({
327
+ _id: newId,
328
+ ...mongoValues
329
+ });
330
+ return {
331
+ id: newId.toString(),
332
+ path: collectionPath,
333
+ values
334
+ };
335
+ }
336
+ }
337
+ /**
338
+ * Delete an entity by ID
339
+ */
340
+ async deleteEntity(collectionPath, entityId, _databaseId) {
341
+ const collection = this.getCollection(collectionPath);
342
+ const id = this.toObjectId(entityId);
343
+ const result = await collection.deleteOne({ _id: id });
344
+ if (result.deletedCount === 0) {
345
+ console.warn(`Entity ${entityId} not found in collection ${collectionPath}`);
346
+ }
347
+ }
348
+ /**
349
+ * Check if a field value is unique in a collection
350
+ */
351
+ async checkUniqueField(collectionPath, fieldName, value, excludeEntityId, _databaseId) {
352
+ const collection = this.getCollection(collectionPath);
353
+ const query = { [fieldName]: value };
354
+ if (excludeEntityId) {
355
+ const id = this.toObjectId(excludeEntityId);
356
+ query._id = { $ne: id };
357
+ }
358
+ const count = await collection.countDocuments(query);
359
+ return count === 0;
360
+ }
361
+ /**
362
+ * Generate a new entity ID
363
+ */
364
+ generateEntityId() {
365
+ return new mongodb.ObjectId().toString();
366
+ }
367
+ }
368
+ class MongoRealtimeService {
369
+ constructor(db) {
370
+ this.db = db;
371
+ this.entityService = new MongoEntityService(db);
372
+ }
373
+ subscriptions = /* @__PURE__ */ new Map();
374
+ entityService;
375
+ /**
376
+ * Get the collection name from a path
377
+ */
378
+ getCollectionName(path) {
379
+ return path.replace(/\//g, "_");
380
+ }
381
+ /**
382
+ * Subscribe to collection changes
383
+ */
384
+ subscribeToCollection(subscriptionId, config, callback) {
385
+ this.unsubscribe(subscriptionId);
386
+ const collectionName = this.getCollectionName(config.path);
387
+ const collection = this.db.collection(collectionName);
388
+ const pipeline = [];
389
+ pipeline.push({
390
+ $match: {
391
+ operationType: { $in: ["insert", "update", "replace", "delete"] }
392
+ }
393
+ });
394
+ try {
395
+ const changeStream = collection.watch(pipeline, {
396
+ fullDocument: "updateLookup"
397
+ });
398
+ const subscription = {
399
+ type: "collection",
400
+ config,
401
+ changeStream,
402
+ callback
403
+ };
404
+ this.subscriptions.set(subscriptionId, subscription);
405
+ this.fetchAndNotifyCollection(subscriptionId, config, callback);
406
+ changeStream.on("change", async (change) => {
407
+ await this.fetchAndNotifyCollection(subscriptionId, config, callback);
408
+ });
409
+ changeStream.on("error", (error) => {
410
+ console.error(`Change stream error for subscription ${subscriptionId}:`, error);
411
+ });
412
+ } catch (error) {
413
+ console.warn("Change streams not available, falling back to polling:", error);
414
+ const subscription = {
415
+ type: "collection",
416
+ config,
417
+ callback
418
+ };
419
+ this.subscriptions.set(subscriptionId, subscription);
420
+ this.fetchAndNotifyCollection(subscriptionId, config, callback);
421
+ }
422
+ }
423
+ /**
424
+ * Fetch collection and notify callback
425
+ */
426
+ async fetchAndNotifyCollection(subscriptionId, config, callback) {
427
+ try {
428
+ const entities = await this.entityService.fetchCollection(config.path, {
429
+ filter: config.filter,
430
+ orderBy: config.orderBy,
431
+ order: config.order,
432
+ limit: config.limit,
433
+ startAfter: config.startAfter,
434
+ searchString: config.searchString
435
+ });
436
+ if (callback) {
437
+ callback(entities);
438
+ }
439
+ } catch (error) {
440
+ console.error(`Error fetching collection for subscription ${subscriptionId}:`, error);
441
+ }
442
+ }
443
+ /**
444
+ * Subscribe to single entity changes
445
+ */
446
+ subscribeToEntity(subscriptionId, config, callback) {
447
+ this.unsubscribe(subscriptionId);
448
+ const collectionName = this.getCollectionName(config.path);
449
+ const collection = this.db.collection(collectionName);
450
+ const entityId = typeof config.entityId === "string" && mongodb.ObjectId.isValid(config.entityId) ? new mongodb.ObjectId(config.entityId) : config.entityId;
451
+ const pipeline = [
452
+ {
453
+ $match: {
454
+ "documentKey._id": entityId,
455
+ operationType: { $in: ["insert", "update", "replace", "delete"] }
456
+ }
457
+ }
458
+ ];
459
+ try {
460
+ const changeStream = collection.watch(pipeline, {
461
+ fullDocument: "updateLookup"
462
+ });
463
+ const subscription = {
464
+ type: "entity",
465
+ config,
466
+ changeStream,
467
+ callback
468
+ };
469
+ this.subscriptions.set(subscriptionId, subscription);
470
+ this.fetchAndNotifyEntity(subscriptionId, config, callback);
471
+ changeStream.on("change", async (change) => {
472
+ if (change.operationType === "delete") {
473
+ if (callback) {
474
+ callback(null);
475
+ }
476
+ } else {
477
+ await this.fetchAndNotifyEntity(subscriptionId, config, callback);
478
+ }
479
+ });
480
+ changeStream.on("error", (error) => {
481
+ console.error(`Change stream error for subscription ${subscriptionId}:`, error);
482
+ });
483
+ } catch (error) {
484
+ console.warn("Change streams not available, falling back to polling:", error);
485
+ const subscription = {
486
+ type: "entity",
487
+ config,
488
+ callback
489
+ };
490
+ this.subscriptions.set(subscriptionId, subscription);
491
+ this.fetchAndNotifyEntity(subscriptionId, config, callback);
492
+ }
493
+ }
494
+ /**
495
+ * Fetch entity and notify callback
496
+ */
497
+ async fetchAndNotifyEntity(subscriptionId, config, callback) {
498
+ try {
499
+ const entity = await this.entityService.fetchEntity(config.path, config.entityId);
500
+ if (callback) {
501
+ callback(entity || null);
502
+ }
503
+ } catch (error) {
504
+ console.error(`Error fetching entity for subscription ${subscriptionId}:`, error);
505
+ }
506
+ }
507
+ /**
508
+ * Unsubscribe from a subscription
509
+ */
510
+ unsubscribe(subscriptionId) {
511
+ const subscription = this.subscriptions.get(subscriptionId);
512
+ if (subscription) {
513
+ if (subscription.changeStream) {
514
+ subscription.changeStream.close().catch(console.error);
515
+ }
516
+ this.subscriptions.delete(subscriptionId);
517
+ }
518
+ }
519
+ /**
520
+ * Notify all relevant subscribers of an entity update
521
+ * This is called after save/delete operations to push updates
522
+ */
523
+ async notifyEntityUpdate(path, entityId, entity, _databaseId) {
524
+ for (const [subscriptionId, subscription] of this.subscriptions) {
525
+ if (subscription.type === "entity") {
526
+ const config = subscription.config;
527
+ if (config.path === path && config.entityId.toString() === entityId) {
528
+ if (subscription.callback) {
529
+ subscription.callback(entity);
530
+ }
531
+ }
532
+ } else if (subscription.type === "collection") {
533
+ const config = subscription.config;
534
+ if (config.path === path) {
535
+ await this.fetchAndNotifyCollection(subscriptionId, config, subscription.callback);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ /**
541
+ * Get all active subscriptions (for debugging)
542
+ */
543
+ getSubscriptions() {
544
+ return this.subscriptions;
545
+ }
546
+ /**
547
+ * Close all subscriptions
548
+ */
549
+ async closeAll() {
550
+ for (const [subscriptionId] of this.subscriptions) {
551
+ this.unsubscribe(subscriptionId);
552
+ }
553
+ }
554
+ }
555
+ class MongoDriver {
556
+ constructor(db, realtimeService) {
557
+ this.db = db;
558
+ this.entityService = new MongoEntityService(db);
559
+ this.realtimeService = realtimeService ?? new MongoRealtimeService(db);
560
+ }
561
+ key = "mongodb";
562
+ initialised = true;
563
+ entityService;
564
+ realtimeService;
565
+ /**
566
+ * Get the current timestamp
567
+ */
568
+ currentTime() {
569
+ return /* @__PURE__ */ new Date();
570
+ }
571
+ /**
572
+ * Fetch a collection of entities
573
+ */
574
+ async fetchCollection({
575
+ path,
576
+ collection,
577
+ filter,
578
+ limit,
579
+ startAfter,
580
+ orderBy,
581
+ searchString,
582
+ order
583
+ }) {
584
+ return this.entityService.fetchCollection(path, {
585
+ filter,
586
+ limit,
587
+ startAfter,
588
+ orderBy,
589
+ order,
590
+ searchString,
591
+ collection
592
+ });
593
+ }
594
+ /**
595
+ * Listen to collection changes
596
+ */
597
+ listenCollection({
598
+ path,
599
+ collection,
600
+ filter,
601
+ limit,
602
+ startAfter,
603
+ orderBy,
604
+ searchString,
605
+ order,
606
+ onUpdate,
607
+ onError
608
+ }) {
609
+ const subscriptionId = this.generateSubscriptionId();
610
+ const callback = (entities) => {
611
+ try {
612
+ onUpdate(entities);
613
+ } catch (error) {
614
+ console.error("Error in collection update callback:", error);
615
+ if (onError) {
616
+ onError(error instanceof Error ? error : new Error(String(error)));
617
+ }
618
+ }
619
+ };
620
+ this.realtimeService.subscribeToCollection(
621
+ subscriptionId,
622
+ {
623
+ clientId: "driver",
624
+ path,
625
+ filter,
626
+ orderBy,
627
+ order,
628
+ limit,
629
+ startAfter,
630
+ searchString
631
+ },
632
+ callback
633
+ );
634
+ return () => {
635
+ this.realtimeService.unsubscribe(subscriptionId);
636
+ };
637
+ }
638
+ /**
639
+ * Fetch a single entity
640
+ */
641
+ async fetchEntity({
642
+ path,
643
+ entityId,
644
+ databaseId,
645
+ collection
646
+ }) {
647
+ return this.entityService.fetchEntity(path, entityId, databaseId);
648
+ }
649
+ /**
650
+ * Listen to entity changes
651
+ */
652
+ listenEntity({
653
+ path,
654
+ entityId,
655
+ collection,
656
+ onUpdate,
657
+ onError
658
+ }) {
659
+ const subscriptionId = this.generateSubscriptionId();
660
+ const callback = (entity) => {
661
+ try {
662
+ onUpdate(entity);
663
+ } catch (error) {
664
+ console.error("Error in entity update callback:", error);
665
+ if (onError) {
666
+ onError(error instanceof Error ? error : new Error(String(error)));
667
+ }
668
+ }
669
+ };
670
+ this.realtimeService.subscribeToEntity(
671
+ subscriptionId,
672
+ {
673
+ clientId: "driver",
674
+ path,
675
+ entityId
676
+ },
677
+ callback
678
+ );
679
+ return () => {
680
+ this.realtimeService.unsubscribe(subscriptionId);
681
+ };
682
+ }
683
+ /**
684
+ * Save an entity (create or update)
685
+ */
686
+ async saveEntity({
687
+ path,
688
+ entityId,
689
+ values,
690
+ collection,
691
+ status
692
+ }) {
693
+ const entity = await this.entityService.saveEntity(path, values, entityId);
694
+ await this.realtimeService.notifyEntityUpdate(path, String(entity.id), entity);
695
+ return entity;
696
+ }
697
+ /**
698
+ * Delete an entity
699
+ */
700
+ async deleteEntity({
701
+ entity,
702
+ collection
703
+ }) {
704
+ await this.entityService.deleteEntity(entity.path, entity.id);
705
+ await this.realtimeService.notifyEntityUpdate(entity.path, String(entity.id), null);
706
+ }
707
+ /**
708
+ * Check if a field value is unique
709
+ */
710
+ async checkUniqueField(path, name, value, entityId, collection) {
711
+ return this.entityService.checkUniqueField(path, name, value, entityId);
712
+ }
713
+ /**
714
+ * Generate a new entity ID
715
+ */
716
+ generateEntityId(path, collection) {
717
+ return this.entityService.generateEntityId();
718
+ }
719
+ /**
720
+ * Count entities in a collection
721
+ */
722
+ async countEntities({
723
+ path,
724
+ collection,
725
+ filter
726
+ }) {
727
+ return this.entityService.countEntities(path, { filter });
728
+ }
729
+ /**
730
+ * Generate a unique subscription ID
731
+ */
732
+ generateSubscriptionId() {
733
+ return `mongo_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
734
+ }
735
+ /**
736
+ * Check if the delegate is ready
737
+ */
738
+ isReady() {
739
+ return this.initialised;
740
+ }
741
+ /**
742
+ * Get the underlying entity service for direct access
743
+ */
744
+ getEntityService() {
745
+ return this.entityService;
746
+ }
747
+ /**
748
+ * Get the underlying realtime service for direct access
749
+ */
750
+ getRealtimeService() {
751
+ return this.realtimeService;
752
+ }
753
+ }
754
+ class MongoCollectionRegistry {
755
+ collections = /* @__PURE__ */ new Map();
756
+ /**
757
+ * Register a collection
758
+ */
759
+ register(collection) {
760
+ this.collections.set(collection.name, collection);
761
+ }
762
+ /**
763
+ * Get a collection by its path
764
+ */
765
+ getCollectionByPath(path) {
766
+ return this.collections.get(path);
767
+ }
768
+ /**
769
+ * Get all registered collections
770
+ */
771
+ getCollections() {
772
+ return Array.from(this.collections.values());
773
+ }
774
+ }
775
+ function createMongoBackend(config) {
776
+ const { connection: db, client, collections } = config;
777
+ const collectionRegistry = new MongoCollectionRegistry();
778
+ if (collections) {
779
+ collections.forEach((collection) => collectionRegistry.register(collection));
780
+ }
781
+ const entityService = new MongoEntityService(db);
782
+ const realtimeService = new MongoRealtimeService(db);
783
+ const driver = new MongoDriver(db, realtimeService);
784
+ const mongoConnection = new MongoDBConnection(db, client);
785
+ const admin = {
786
+ async executeAggregate(pipeline) {
787
+ const firstStage = pipeline[0];
788
+ const collName = firstStage?.$from ?? "__admin__";
789
+ const cursor = db.collection(collName).aggregate(pipeline);
790
+ return await cursor.toArray();
791
+ },
792
+ async fetchCollectionStats(collectionName) {
793
+ const stats = await db.command({ collStats: collectionName });
794
+ return { count: stats.count, sizeBytes: stats.size };
795
+ },
796
+ async fetchUnmappedTables(mappedPaths) {
797
+ const allCollections = await db.listCollections().toArray();
798
+ const names = allCollections.map((c) => c.name).filter((n) => !n.startsWith("system."));
799
+ if (!mappedPaths || mappedPaths.length === 0) return names;
800
+ const mappedSet = new Set(mappedPaths.map((p) => p.toLowerCase()));
801
+ return names.filter((n) => !mappedSet.has(n.toLowerCase()));
802
+ },
803
+ async fetchTableMetadata(collectionName) {
804
+ const sample = await db.collection(collectionName).findOne();
805
+ if (!sample) return { columns: [], foreignKeys: [], junctions: [], policies: [] };
806
+ const columns = Object.entries(sample).map(([key, value]) => ({
807
+ column_name: key,
808
+ data_type: typeof value,
809
+ udt_name: typeof value,
810
+ is_nullable: "YES",
811
+ column_default: null,
812
+ character_maximum_length: null
813
+ }));
814
+ return { columns, foreignKeys: [], junctions: [], policies: [] };
815
+ }
816
+ };
817
+ return {
818
+ // Abstract interface implementations
819
+ connection: mongoConnection,
820
+ entityRepository: entityService,
821
+ realtimeProvider: realtimeService,
822
+ collectionRegistry,
823
+ admin,
824
+ // Lifecycle
825
+ async initialize() {
826
+ },
827
+ async healthCheck() {
828
+ const start = Date.now();
829
+ try {
830
+ await db.command({ ping: 1 });
831
+ return { healthy: true, latencyMs: Date.now() - start };
832
+ } catch {
833
+ return { healthy: false, latencyMs: Date.now() - start };
834
+ }
835
+ },
836
+ async destroy() {
837
+ await client.close();
838
+ },
839
+ // MongoDB-specific accessors
840
+ db,
841
+ client,
842
+ driver,
843
+ entityService,
844
+ realtimeService
845
+ };
846
+ }
847
+ function createMongoDelegate(db, realtimeService) {
848
+ const realtime = realtimeService ?? new MongoRealtimeService(db);
849
+ return new MongoDriver(db, realtime);
850
+ }
851
+ function createMongoRealtimeService(db) {
852
+ return new MongoRealtimeService(db);
853
+ }
854
+ function createMongoEntityRepository(db) {
855
+ return new MongoEntityService(db);
856
+ }
857
+ function isMongoBackendConfig(config) {
858
+ return config.type === "mongodb" && typeof config.connection !== "undefined" && typeof config.client !== "undefined";
859
+ }
860
+ function isMongoDriverConfig(obj) {
861
+ return typeof obj === "object" && obj !== null && "type" in obj && obj.type === "mongodb" && "connection" in obj && "client" in obj;
862
+ }
863
+ exports2.MongoCollectionRegistry = MongoCollectionRegistry;
864
+ exports2.MongoConditionBuilder = MongoConditionBuilder;
865
+ exports2.MongoDBConnection = MongoDBConnection;
866
+ exports2.MongoDriver = MongoDriver;
867
+ exports2.MongoEntityService = MongoEntityService;
868
+ exports2.MongoRealtimeService = MongoRealtimeService;
869
+ exports2.createMongoBackend = createMongoBackend;
870
+ exports2.createMongoDBConnection = createMongoDBConnection;
871
+ exports2.createMongoDelegate = createMongoDelegate;
872
+ exports2.createMongoEntityRepository = createMongoEntityRepository;
873
+ exports2.createMongoRealtimeService = createMongoRealtimeService;
874
+ exports2.isMongoBackendConfig = isMongoBackendConfig;
875
+ exports2.isMongoDriverConfig = isMongoDriverConfig;
876
+ Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
877
+ });
878
+ //# sourceMappingURL=index.umd.js.map