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