flongo 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flongoCollection.d.ts +29 -5
- package/dist/flongoCollection.js +59 -11
- package/dist/mongo.d.ts +9 -0
- package/dist/mongo.js +10 -0
- package/dist/mongoCollection.d.ts +33 -0
- package/dist/mongoCollection.js +221 -0
- package/dist/mongoCollectionQuery.d.ts +43 -0
- package/dist/mongoCollectionQuery.js +224 -0
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
|
@@ -130,7 +130,27 @@ export declare class FlongoCollection<T> {
|
|
|
130
130
|
* @param attributes - Array of document data
|
|
131
131
|
* @param clientId - Optional client ID for audit trail
|
|
132
132
|
*/
|
|
133
|
-
batchCreate(attributes: T[], clientId?: string): Promise<
|
|
133
|
+
batchCreate(attributes: T[], clientId?: string): Promise<string[]>;
|
|
134
|
+
/**
|
|
135
|
+
* Inserts a document if no document with the same _id exists; otherwise does nothing.
|
|
136
|
+
* Race-safe "first writer wins" semantics — ideal for cache-through writes.
|
|
137
|
+
* @param attributes - Document data including _id
|
|
138
|
+
* @param clientId - Optional client ID for audit trail
|
|
139
|
+
* @returns Promise resolving to the document (newly created or existing)
|
|
140
|
+
*/
|
|
141
|
+
createIfNotExists(attributes: T & {
|
|
142
|
+
_id: string;
|
|
143
|
+
}, clientId?: string): Promise<Entity & T>;
|
|
144
|
+
/**
|
|
145
|
+
* Inserts a document if no document with the same _id exists; otherwise overwrites it.
|
|
146
|
+
* Race-safe "last writer wins" semantics.
|
|
147
|
+
* @param attributes - Document data including _id
|
|
148
|
+
* @param clientId - Optional client ID for audit trail
|
|
149
|
+
* @returns Promise resolving to the document (newly created or updated)
|
|
150
|
+
*/
|
|
151
|
+
createOrUpdate(attributes: T & {
|
|
152
|
+
_id: string;
|
|
153
|
+
}, clientId?: string): Promise<Entity & T>;
|
|
134
154
|
/**
|
|
135
155
|
* Updates multiple documents matching the query
|
|
136
156
|
* @param attributes - Fields to update
|
|
@@ -158,29 +178,33 @@ export declare class FlongoCollection<T> {
|
|
|
158
178
|
* @param id - Document ID
|
|
159
179
|
* @param key - Field name to increment
|
|
160
180
|
* @param amt - Amount to increment by (defaults to 1)
|
|
181
|
+
* @param clientId - Optional client ID for audit trail
|
|
161
182
|
*/
|
|
162
|
-
increment(id: string, key: string, amt?: number): Promise<void>;
|
|
183
|
+
increment(id: string, key: string, amt?: number, clientId?: string): Promise<void>;
|
|
163
184
|
/**
|
|
164
185
|
* Atomically decrements a numeric field
|
|
165
186
|
* @param id - Document ID
|
|
166
187
|
* @param key - Field name to decrement
|
|
167
188
|
* @param amt - Amount to decrement by (defaults to 1)
|
|
189
|
+
* @param clientId - Optional client ID for audit trail
|
|
168
190
|
*/
|
|
169
|
-
decrement(id: string, key: string, amt?: number): Promise<void>;
|
|
191
|
+
decrement(id: string, key: string, amt?: number, clientId?: string): Promise<void>;
|
|
170
192
|
/**
|
|
171
193
|
* Atomically appends items to an array field
|
|
172
194
|
* @param id - Document ID
|
|
173
195
|
* @param key - Array field name
|
|
174
196
|
* @param items - Items to append to the array
|
|
197
|
+
* @param clientId - Optional client ID for audit trail
|
|
175
198
|
*/
|
|
176
|
-
append(id: string, key: string, items: any[]): Promise<void>;
|
|
199
|
+
append(id: string, key: string, items: any[], clientId?: string): Promise<void>;
|
|
177
200
|
/**
|
|
178
201
|
* Atomically removes items from an array field
|
|
179
202
|
* @param id - Document ID
|
|
180
203
|
* @param key - Array field name
|
|
181
204
|
* @param items - Items to remove from the array
|
|
205
|
+
* @param clientId - Optional client ID for audit trail
|
|
182
206
|
*/
|
|
183
|
-
arrRemove(id: string, key: string, items: any[]): Promise<void>;
|
|
207
|
+
arrRemove(id: string, key: string, items: any[], clientId?: string): Promise<void>;
|
|
184
208
|
/**
|
|
185
209
|
* Logs an event to the events collection for audit trails
|
|
186
210
|
* Events are only logged if event logging is enabled in options
|
package/dist/flongoCollection.js
CHANGED
|
@@ -210,6 +210,7 @@ class FlongoCollection {
|
|
|
210
210
|
// Insert document with automatic timestamps
|
|
211
211
|
const created = await this.collection.insertOne({
|
|
212
212
|
...attributes,
|
|
213
|
+
createdBy: clientId,
|
|
213
214
|
createdAt: Date.now(),
|
|
214
215
|
updatedAt: Date.now()
|
|
215
216
|
});
|
|
@@ -236,22 +237,53 @@ class FlongoCollection {
|
|
|
236
237
|
*/
|
|
237
238
|
async batchCreate(attributes, clientId) {
|
|
238
239
|
if (attributes.length === 0)
|
|
239
|
-
return;
|
|
240
|
+
return [];
|
|
240
241
|
const now = Date.now();
|
|
241
242
|
// Prepare entities with timestamps
|
|
242
243
|
const entities = attributes.map((attribute) => ({
|
|
243
244
|
...attribute,
|
|
245
|
+
createdBy: clientId,
|
|
244
246
|
createdAt: now,
|
|
245
247
|
updatedAt: now
|
|
246
248
|
}));
|
|
247
249
|
// Insert all entities at once
|
|
248
|
-
await this.collection.insertMany(entities);
|
|
250
|
+
const result = await this.collection.insertMany(entities);
|
|
249
251
|
// Log batch creation event
|
|
250
252
|
this.logEvent({
|
|
251
253
|
name: types_1.EventName.BatchCreateEntities,
|
|
252
254
|
identity: clientId,
|
|
253
255
|
value: { collectionType: this.name, count: await this.count() }
|
|
254
256
|
});
|
|
257
|
+
return Object.values(result.insertedIds).map((id) => id.toString());
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Inserts a document if no document with the same _id exists; otherwise does nothing.
|
|
261
|
+
* Race-safe "first writer wins" semantics — ideal for cache-through writes.
|
|
262
|
+
* @param attributes - Document data including _id
|
|
263
|
+
* @param clientId - Optional client ID for audit trail
|
|
264
|
+
* @returns Promise resolving to the document (newly created or existing)
|
|
265
|
+
*/
|
|
266
|
+
async createIfNotExists(attributes, clientId) {
|
|
267
|
+
const { _id, ...rest } = attributes;
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
const result = await this.collection.findOneAndUpdate({ _id }, { $setOnInsert: { ...rest, createdBy: clientId, createdAt: now, updatedAt: now } }, { upsert: true, returnDocument: 'after' });
|
|
270
|
+
return this.toEntity(result);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Inserts a document if no document with the same _id exists; otherwise overwrites it.
|
|
274
|
+
* Race-safe "last writer wins" semantics.
|
|
275
|
+
* @param attributes - Document data including _id
|
|
276
|
+
* @param clientId - Optional client ID for audit trail
|
|
277
|
+
* @returns Promise resolving to the document (newly created or updated)
|
|
278
|
+
*/
|
|
279
|
+
async createOrUpdate(attributes, clientId) {
|
|
280
|
+
const { _id, createdAt, createdBy, updatedAt, updatedBy, ...rest } = attributes;
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
const result = await this.collection.findOneAndUpdate({ _id }, {
|
|
283
|
+
$set: { ...rest, updatedAt: now, updatedBy: clientId },
|
|
284
|
+
$setOnInsert: { createdAt: now, createdBy: clientId }
|
|
285
|
+
}, { upsert: true, returnDocument: 'after' });
|
|
286
|
+
return this.toEntity(result);
|
|
255
287
|
}
|
|
256
288
|
// ===========================================
|
|
257
289
|
// UPDATE OPERATIONS
|
|
@@ -268,6 +300,7 @@ class FlongoCollection {
|
|
|
268
300
|
await this.collection.updateMany(mongodbQuery, {
|
|
269
301
|
$set: {
|
|
270
302
|
updatedAt: Date.now(),
|
|
303
|
+
updatedBy: clientId,
|
|
271
304
|
...attributes
|
|
272
305
|
}
|
|
273
306
|
});
|
|
@@ -294,6 +327,7 @@ class FlongoCollection {
|
|
|
294
327
|
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
295
328
|
$set: {
|
|
296
329
|
updatedAt: Date.now(),
|
|
330
|
+
updatedBy: clientId,
|
|
297
331
|
...attributes
|
|
298
332
|
}
|
|
299
333
|
});
|
|
@@ -321,6 +355,7 @@ class FlongoCollection {
|
|
|
321
355
|
const update = {
|
|
322
356
|
$set: {
|
|
323
357
|
updatedAt: Date.now(),
|
|
358
|
+
updatedBy: clientId,
|
|
324
359
|
...attributes
|
|
325
360
|
}
|
|
326
361
|
};
|
|
@@ -340,29 +375,38 @@ class FlongoCollection {
|
|
|
340
375
|
* @param id - Document ID
|
|
341
376
|
* @param key - Field name to increment
|
|
342
377
|
* @param amt - Amount to increment by (defaults to 1)
|
|
378
|
+
* @param clientId - Optional client ID for audit trail
|
|
343
379
|
*/
|
|
344
|
-
async increment(id, key, amt) {
|
|
345
|
-
|
|
346
|
-
|
|
380
|
+
async increment(id, key, amt, clientId) {
|
|
381
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
382
|
+
$inc: { [key]: amt || 1 },
|
|
383
|
+
$set: { updatedAt: Date.now(), updatedBy: clientId }
|
|
384
|
+
});
|
|
347
385
|
}
|
|
348
386
|
/**
|
|
349
387
|
* Atomically decrements a numeric field
|
|
350
388
|
* @param id - Document ID
|
|
351
389
|
* @param key - Field name to decrement
|
|
352
390
|
* @param amt - Amount to decrement by (defaults to 1)
|
|
391
|
+
* @param clientId - Optional client ID for audit trail
|
|
353
392
|
*/
|
|
354
|
-
async decrement(id, key, amt) {
|
|
355
|
-
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
393
|
+
async decrement(id, key, amt, clientId) {
|
|
394
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
395
|
+
$inc: { [key]: -(amt ?? 1) },
|
|
396
|
+
$set: { updatedAt: Date.now(), updatedBy: clientId }
|
|
397
|
+
});
|
|
356
398
|
}
|
|
357
399
|
/**
|
|
358
400
|
* Atomically appends items to an array field
|
|
359
401
|
* @param id - Document ID
|
|
360
402
|
* @param key - Array field name
|
|
361
403
|
* @param items - Items to append to the array
|
|
404
|
+
* @param clientId - Optional client ID for audit trail
|
|
362
405
|
*/
|
|
363
|
-
async append(id, key, items) {
|
|
406
|
+
async append(id, key, items, clientId) {
|
|
364
407
|
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
365
|
-
$push: { [key]: { $each: items } }
|
|
408
|
+
$push: { [key]: { $each: items } },
|
|
409
|
+
$set: { updatedAt: Date.now(), updatedBy: clientId }
|
|
366
410
|
});
|
|
367
411
|
}
|
|
368
412
|
/**
|
|
@@ -370,9 +414,13 @@ class FlongoCollection {
|
|
|
370
414
|
* @param id - Document ID
|
|
371
415
|
* @param key - Array field name
|
|
372
416
|
* @param items - Items to remove from the array
|
|
417
|
+
* @param clientId - Optional client ID for audit trail
|
|
373
418
|
*/
|
|
374
|
-
async arrRemove(id, key, items) {
|
|
375
|
-
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
419
|
+
async arrRemove(id, key, items, clientId) {
|
|
420
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
421
|
+
$pull: { [key]: { $in: items } },
|
|
422
|
+
$set: { updatedAt: Date.now(), updatedBy: clientId }
|
|
423
|
+
});
|
|
376
424
|
}
|
|
377
425
|
// ===========================================
|
|
378
426
|
// EVENT LOGGING
|
package/dist/mongo.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Db, MongoClient } from "mongodb";
|
|
2
|
+
export declare let mongoClient: MongoClient;
|
|
3
|
+
export declare let mongoDb: Db;
|
|
4
|
+
export interface MongoConfig {
|
|
5
|
+
connectionString: string;
|
|
6
|
+
dbName: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function initializeMongo(config: MongoConfig): void;
|
|
9
|
+
//# sourceMappingURL=mongo.d.ts.map
|
package/dist/mongo.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mongoDb = exports.mongoClient = void 0;
|
|
4
|
+
exports.initializeMongo = initializeMongo;
|
|
5
|
+
const mongodb_1 = require("mongodb");
|
|
6
|
+
function initializeMongo(config) {
|
|
7
|
+
exports.mongoClient = new mongodb_1.MongoClient(config.connectionString);
|
|
8
|
+
exports.mongoDb = exports.mongoClient.db(config.dbName);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=mongo.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MongoCollectionQuery } from "./mongoCollectionQuery";
|
|
2
|
+
import { Entity, Event, EventName, Pagination, Repository } from "./types";
|
|
3
|
+
export interface MongoCollectionOptions {
|
|
4
|
+
enableEventLogging?: boolean;
|
|
5
|
+
eventsCollectionName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class MongoCollection<T> {
|
|
8
|
+
private collection;
|
|
9
|
+
private events;
|
|
10
|
+
private name;
|
|
11
|
+
private options;
|
|
12
|
+
constructor(collectionName: Repository, options?: MongoCollectionOptions);
|
|
13
|
+
get(id: string): Promise<Entity & T>;
|
|
14
|
+
private toEntity;
|
|
15
|
+
getAll(query?: MongoCollectionQuery, pagination?: Pagination): Promise<(Entity & T)[]>;
|
|
16
|
+
getSome(query: MongoCollectionQuery, pagination: Pagination): Promise<(Entity & T)[]>;
|
|
17
|
+
getFirst(query: MongoCollectionQuery): Promise<Entity & T>;
|
|
18
|
+
count(query?: MongoCollectionQuery): Promise<number>;
|
|
19
|
+
delete(id: string, clientId: string): Promise<void>;
|
|
20
|
+
batchDelete(ids: string[], clientId: string): Promise<void>;
|
|
21
|
+
exists(query: MongoCollectionQuery): Promise<boolean>;
|
|
22
|
+
logEvent<T extends EventName>(event: Event<T>): Promise<string | null>;
|
|
23
|
+
create(attributes: T, clientId?: string): Promise<Entity & T>;
|
|
24
|
+
batchCreate(attributes: T[], clientId?: string): Promise<void>;
|
|
25
|
+
updateAll(attributes: any, query?: MongoCollectionQuery, clientId?: string): Promise<void>;
|
|
26
|
+
update(id: string, attributes: any, clientId?: string): Promise<void>;
|
|
27
|
+
increment(id: string, key: string, amt?: number): Promise<void>;
|
|
28
|
+
decrement(id: string, key: string, amt?: number): Promise<void>;
|
|
29
|
+
append(id: string, key: string, items: any[]): Promise<void>;
|
|
30
|
+
arrRemove(id: string, key: string, items: any[]): Promise<void>;
|
|
31
|
+
updateFirst(attributes: any, query?: MongoCollectionQuery, clientId?: string): Promise<Entity & T>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=mongoCollection.d.ts.map
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoCollection = void 0;
|
|
4
|
+
const mongo_1 = require("./mongo");
|
|
5
|
+
const mongoCollectionQuery_1 = require("./mongoCollectionQuery");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const mongodb_1 = require("mongodb");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
class MongoCollection {
|
|
10
|
+
constructor(collectionName, options = {}) {
|
|
11
|
+
this.collection = mongo_1.mongoDb.collection(collectionName);
|
|
12
|
+
this.options = { enableEventLogging: true, eventsCollectionName: "events", ...options };
|
|
13
|
+
this.events = this.options.enableEventLogging
|
|
14
|
+
? mongo_1.mongoDb.collection(this.options.eventsCollectionName)
|
|
15
|
+
: null;
|
|
16
|
+
this.name = collectionName;
|
|
17
|
+
}
|
|
18
|
+
async get(id) {
|
|
19
|
+
const objId = new mongodb_1.ObjectId(id);
|
|
20
|
+
const docWithId = await this.collection.findOne({ _id: objId });
|
|
21
|
+
if (docWithId) {
|
|
22
|
+
return this.toEntity(docWithId);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error("Could not find id: " + id);
|
|
26
|
+
throw new errors_1.Error404();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
toEntity(obj) {
|
|
30
|
+
return { ...obj, _id: String(obj._id) };
|
|
31
|
+
}
|
|
32
|
+
async getAll(query, pagination) {
|
|
33
|
+
const mongodbQuery = query?.build() ?? {};
|
|
34
|
+
const mongodbOptions = query?.buildOptions(pagination) ?? {};
|
|
35
|
+
try {
|
|
36
|
+
const res = await this.collection.find(mongodbQuery, mongodbOptions).toArray();
|
|
37
|
+
return res.map((d) => this.toEntity(d));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.log("getAll() Failed: Query: " +
|
|
41
|
+
JSON.stringify(mongodbQuery, null, 2) +
|
|
42
|
+
" err: " +
|
|
43
|
+
JSON.stringify(err, null, 2));
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async getSome(query, pagination) {
|
|
48
|
+
const mongodbQuery = query?.build();
|
|
49
|
+
const mongodbOptions = query?.buildOptions(pagination);
|
|
50
|
+
return (await this.collection.find(mongodbQuery, mongodbOptions).toArray()).map((d) => this.toEntity(d));
|
|
51
|
+
}
|
|
52
|
+
async getFirst(query) {
|
|
53
|
+
const mongodbQuery = query?.build();
|
|
54
|
+
const mongodbOptions = query?.buildOptions();
|
|
55
|
+
return this.toEntity(await this.collection.findOne(mongodbQuery, mongodbOptions));
|
|
56
|
+
}
|
|
57
|
+
async count(query) {
|
|
58
|
+
const mongodbQuery = query?.build() ?? {};
|
|
59
|
+
return this.collection.countDocuments(mongodbQuery);
|
|
60
|
+
}
|
|
61
|
+
async delete(id, clientId) {
|
|
62
|
+
const entity = await this.get(id);
|
|
63
|
+
this.logEvent({
|
|
64
|
+
name: types_1.EventName.DeleteEntity,
|
|
65
|
+
value: {
|
|
66
|
+
id,
|
|
67
|
+
collectionType: this.name,
|
|
68
|
+
count: await this.count(),
|
|
69
|
+
backup: entity,
|
|
70
|
+
deletedBy: clientId
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
await this.collection.deleteOne({ _id: new mongodb_1.ObjectId(id) });
|
|
74
|
+
}
|
|
75
|
+
async batchDelete(ids, clientId) {
|
|
76
|
+
const mongodbQuery = { _id: { $in: ids } };
|
|
77
|
+
const entities = await this.getAll(new mongoCollectionQuery_1.MongoCollectionQuery().where("_id").in(ids));
|
|
78
|
+
this.logEvent({
|
|
79
|
+
name: types_1.EventName.BatchDeleteEntities,
|
|
80
|
+
value: {
|
|
81
|
+
collectionType: this.name,
|
|
82
|
+
count: await this.count(),
|
|
83
|
+
backup: entities,
|
|
84
|
+
deletedBy: clientId
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
await this.collection.deleteMany({ mongodbQuery });
|
|
88
|
+
}
|
|
89
|
+
async exists(query) {
|
|
90
|
+
try {
|
|
91
|
+
const count = await this.collection.countDocuments(query.build());
|
|
92
|
+
return count > 0;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.log("Error getting count... " + JSON.stringify(err, null, 2));
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async logEvent(event) {
|
|
100
|
+
if (!this.events) {
|
|
101
|
+
return null; // Event logging is disabled
|
|
102
|
+
}
|
|
103
|
+
const created = await this.events.insertOne({
|
|
104
|
+
...event,
|
|
105
|
+
createdAt: Date.now(),
|
|
106
|
+
updatedAt: Date.now()
|
|
107
|
+
});
|
|
108
|
+
return String(created.insertedId);
|
|
109
|
+
}
|
|
110
|
+
async create(attributes, clientId) {
|
|
111
|
+
// implement clientId
|
|
112
|
+
try {
|
|
113
|
+
const created = await this.collection.insertOne({
|
|
114
|
+
...attributes,
|
|
115
|
+
// _id: id,
|
|
116
|
+
createdAt: Date.now(),
|
|
117
|
+
updatedAt: Date.now()
|
|
118
|
+
});
|
|
119
|
+
const entity = this.toEntity(await this.get(created.insertedId));
|
|
120
|
+
this.logEvent({
|
|
121
|
+
name: types_1.EventName.CreateEntity,
|
|
122
|
+
identity: clientId,
|
|
123
|
+
value: { id: entity._id, collectionType: this.name, count: await this.count() }
|
|
124
|
+
});
|
|
125
|
+
return entity;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error("Failed create: " + JSON.stringify(err, null, 2));
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async batchCreate(attributes, clientId) {
|
|
133
|
+
// implement clientId
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const entities = attributes.map((attribute) => ({
|
|
136
|
+
...attribute,
|
|
137
|
+
//_id: uuidv4(),
|
|
138
|
+
createdAt: now,
|
|
139
|
+
updatedAt: now
|
|
140
|
+
}));
|
|
141
|
+
if (entities.length > 0) {
|
|
142
|
+
await this.collection.insertMany(entities);
|
|
143
|
+
this.logEvent({
|
|
144
|
+
name: types_1.EventName.BatchCreateEntities,
|
|
145
|
+
identity: clientId,
|
|
146
|
+
value: { collectionType: this.name, count: await this.count() }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async updateAll(attributes, query, clientId) {
|
|
151
|
+
// implement attributes, filters and clientId
|
|
152
|
+
const mongodbQuery = query?.build() ?? {};
|
|
153
|
+
await this.collection.updateMany(mongodbQuery, {
|
|
154
|
+
$set: {
|
|
155
|
+
updatedAt: Date.now(),
|
|
156
|
+
...attributes
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.logEvent({
|
|
160
|
+
name: types_1.EventName.BatchUpdateEntities,
|
|
161
|
+
identity: clientId,
|
|
162
|
+
value: { collectionType: this.name }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async update(id, attributes, clientId) {
|
|
166
|
+
// implement attributes and clientId
|
|
167
|
+
if ("_id" in attributes) {
|
|
168
|
+
delete attributes._id;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
172
|
+
$set: {
|
|
173
|
+
updatedAt: Date.now(),
|
|
174
|
+
...attributes
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
this.logEvent({
|
|
178
|
+
name: types_1.EventName.UpdateEntity,
|
|
179
|
+
identity: clientId,
|
|
180
|
+
value: { collectionType: this.name, id }
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error("Failed updating: " + JSON.stringify(err, null, 2));
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async increment(id, key, amt) {
|
|
189
|
+
const inc = { $inc: { [key]: amt || 1 } };
|
|
190
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, inc);
|
|
191
|
+
}
|
|
192
|
+
async decrement(id, key, amt) {
|
|
193
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, { $inc: { [key]: amt ?? -1 } });
|
|
194
|
+
}
|
|
195
|
+
async append(id, key, items) {
|
|
196
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
197
|
+
$push: { [key]: { $each: items } }
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async arrRemove(id, key, items) {
|
|
201
|
+
await this.collection.updateOne({ _id: new mongodb_1.ObjectId(id) }, { $pull: { [key]: { $in: items } } });
|
|
202
|
+
}
|
|
203
|
+
async updateFirst(attributes, query, clientId) {
|
|
204
|
+
const mongodbQuery = query?.build() ?? {};
|
|
205
|
+
const update = {
|
|
206
|
+
$set: {
|
|
207
|
+
updatedAt: Date.now(),
|
|
208
|
+
...attributes
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const updated = await this.collection.findOneAndUpdate(mongodbQuery, update);
|
|
212
|
+
if (updated) {
|
|
213
|
+
return this.toEntity(updated);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
throw new Error("Failed to update entity");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
exports.MongoCollection = MongoCollection;
|
|
221
|
+
//# sourceMappingURL=mongoCollection.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Bounds, Coordinates, Pagination, ColExpression, ColRange, ICollectionQuery, Logic, SortDirection } from "./types";
|
|
2
|
+
import { Document, Filter, FindOptions } from "mongodb";
|
|
3
|
+
export declare class MongoCollectionQuery implements ICollectionQuery {
|
|
4
|
+
expressions: ColExpression[];
|
|
5
|
+
ranges: ColRange[];
|
|
6
|
+
orderField?: string;
|
|
7
|
+
orderDirection?: SortDirection;
|
|
8
|
+
orQueries: MongoCollectionQuery[];
|
|
9
|
+
andQueries: MongoCollectionQuery[];
|
|
10
|
+
private exp;
|
|
11
|
+
set(op?: string, val?: any): MongoCollectionQuery;
|
|
12
|
+
setRange(key: string, start: any, end: any, orderField: string): void;
|
|
13
|
+
where(key: string): MongoCollectionQuery;
|
|
14
|
+
and(key: string): MongoCollectionQuery;
|
|
15
|
+
eq(val?: any): MongoCollectionQuery;
|
|
16
|
+
neq(val?: any): MongoCollectionQuery;
|
|
17
|
+
lt(val?: any): MongoCollectionQuery;
|
|
18
|
+
ltEq(val?: any): MongoCollectionQuery;
|
|
19
|
+
gt(val?: any): MongoCollectionQuery;
|
|
20
|
+
gtEq(val?: any): MongoCollectionQuery;
|
|
21
|
+
arrContains(val?: any): MongoCollectionQuery;
|
|
22
|
+
arrContainsAll(val?: any): MongoCollectionQuery;
|
|
23
|
+
arrContainsAny(val?: any[]): MongoCollectionQuery;
|
|
24
|
+
startsWith(val: string): MongoCollectionQuery;
|
|
25
|
+
endsWith(val: string): MongoCollectionQuery;
|
|
26
|
+
strContains(val: string): MongoCollectionQuery;
|
|
27
|
+
private applyComparisonOperator;
|
|
28
|
+
in(val?: any[]): MongoCollectionQuery;
|
|
29
|
+
notIn(val?: any[]): MongoCollectionQuery;
|
|
30
|
+
inRange(key: string, min: any, max: any): MongoCollectionQuery;
|
|
31
|
+
inRadius(key: string, center: Coordinates, radius: number): MongoCollectionQuery;
|
|
32
|
+
geoWithin(bounds?: Bounds): MongoCollectionQuery;
|
|
33
|
+
orderBy(field?: string, direction?: SortDirection): MongoCollectionQuery;
|
|
34
|
+
andQuery(query: MongoCollectionQuery): MongoCollectionQuery;
|
|
35
|
+
or(query: MongoCollectionQuery): MongoCollectionQuery;
|
|
36
|
+
build<T>(): Filter<T>;
|
|
37
|
+
buildOptions<T extends Document>(pagination?: Pagination): FindOptions<T>;
|
|
38
|
+
}
|
|
39
|
+
export declare class CollectionQueryBuilder {
|
|
40
|
+
q: MongoCollectionQuery;
|
|
41
|
+
}
|
|
42
|
+
export { Logic };
|
|
43
|
+
//# sourceMappingURL=mongoCollectionQuery.d.ts.map
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logic = exports.CollectionQueryBuilder = exports.MongoCollectionQuery = void 0;
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
Object.defineProperty(exports, "Logic", { enumerable: true, get: function () { return types_1.Logic; } });
|
|
7
|
+
const geofire_common_1 = require("geofire-common");
|
|
8
|
+
const mongodb_1 = require("mongodb");
|
|
9
|
+
class MongoCollectionQuery {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.expressions = [];
|
|
12
|
+
this.ranges = [];
|
|
13
|
+
this.orQueries = [];
|
|
14
|
+
this.andQueries = [];
|
|
15
|
+
}
|
|
16
|
+
exp() {
|
|
17
|
+
return this.expressions[this.expressions.length - 1];
|
|
18
|
+
}
|
|
19
|
+
set(op, val) {
|
|
20
|
+
if (!val) {
|
|
21
|
+
this.expressions.pop();
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
this.exp().op = op;
|
|
25
|
+
this.exp().val = val;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
setRange(key, start, end, orderField) {
|
|
29
|
+
this.orderBy(orderField);
|
|
30
|
+
this.ranges.push({ key: key, start: start, end: end });
|
|
31
|
+
}
|
|
32
|
+
where(key) {
|
|
33
|
+
this.expressions.push(new types_1.ColExpression(key));
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
and(key) {
|
|
37
|
+
this.where(key);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
eq(val) {
|
|
41
|
+
return this.set("$eq", val);
|
|
42
|
+
}
|
|
43
|
+
neq(val) {
|
|
44
|
+
return this.set("$ne", val);
|
|
45
|
+
}
|
|
46
|
+
lt(val) {
|
|
47
|
+
return this.set("$lt", val);
|
|
48
|
+
}
|
|
49
|
+
ltEq(val) {
|
|
50
|
+
return this.set("$lte", val);
|
|
51
|
+
}
|
|
52
|
+
gt(val) {
|
|
53
|
+
return this.set("$gt", val);
|
|
54
|
+
}
|
|
55
|
+
gtEq(val) {
|
|
56
|
+
return this.set("$gte", val);
|
|
57
|
+
}
|
|
58
|
+
arrContains(val) {
|
|
59
|
+
return this.set(undefined, val);
|
|
60
|
+
}
|
|
61
|
+
arrContainsAll(val) {
|
|
62
|
+
return this.set("$all", val);
|
|
63
|
+
}
|
|
64
|
+
arrContainsAny(val) {
|
|
65
|
+
if (val && val.length > 0) {
|
|
66
|
+
this.set("$in", val);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this.expressions.pop();
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
startsWith(val) {
|
|
74
|
+
return this.set("$regex", "^" + val);
|
|
75
|
+
}
|
|
76
|
+
endsWith(val) {
|
|
77
|
+
return this.set("$regex", val + "$");
|
|
78
|
+
}
|
|
79
|
+
strContains(val) {
|
|
80
|
+
return this.set("$regex", val);
|
|
81
|
+
}
|
|
82
|
+
applyComparisonOperator(op, val) {
|
|
83
|
+
if (val) {
|
|
84
|
+
this.set(op, val);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.expressions.pop();
|
|
88
|
+
}
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
in(val) {
|
|
92
|
+
return this.applyComparisonOperator("$in", val);
|
|
93
|
+
}
|
|
94
|
+
notIn(val) {
|
|
95
|
+
return val?.length ?? 0 > 0 ? this.applyComparisonOperator("$nin", val) : this;
|
|
96
|
+
}
|
|
97
|
+
inRange(key, min, max) {
|
|
98
|
+
return this.where(key).gtEq(min).and(key).ltEq(max);
|
|
99
|
+
}
|
|
100
|
+
inRadius(key, center, radius) {
|
|
101
|
+
const geoBounds = (0, geofire_common_1.geohashQueryBounds)([center.latitude, center.longitude], radius);
|
|
102
|
+
this.orderBy(key, types_1.SortDirection.Ascending);
|
|
103
|
+
for (const b of geoBounds) {
|
|
104
|
+
this.or(new MongoCollectionQuery().inRange(key, b[0], b[1]));
|
|
105
|
+
}
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
geoWithin(bounds) {
|
|
109
|
+
if (bounds) {
|
|
110
|
+
this.set("$geoWithin", {
|
|
111
|
+
$geometry: {
|
|
112
|
+
type: "Polygon",
|
|
113
|
+
coordinates: [
|
|
114
|
+
[
|
|
115
|
+
[bounds.ne.longitude, bounds.ne.latitude],
|
|
116
|
+
[bounds.ne.longitude, bounds.sw.latitude],
|
|
117
|
+
[bounds.sw.longitude, bounds.sw.latitude],
|
|
118
|
+
[bounds.sw.longitude, bounds.ne.latitude],
|
|
119
|
+
[bounds.ne.longitude, bounds.ne.latitude]
|
|
120
|
+
]
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.expressions.pop();
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
orderBy(field, direction) {
|
|
131
|
+
this.orderField = field;
|
|
132
|
+
this.orderDirection = direction;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
andQuery(query) {
|
|
136
|
+
if (this.expressions.length > 0) {
|
|
137
|
+
this.andQueries.push(query);
|
|
138
|
+
}
|
|
139
|
+
return query;
|
|
140
|
+
}
|
|
141
|
+
or(query) {
|
|
142
|
+
this.orQueries.push(query);
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
build() {
|
|
146
|
+
let mongodbQuery = {};
|
|
147
|
+
//console.log("Building query: " + JSON.stringify(this, null, 2));
|
|
148
|
+
try {
|
|
149
|
+
if (this.expressions) {
|
|
150
|
+
for (const expression of this.expressions) {
|
|
151
|
+
//console.log("EXPRESSION: " + JSON.stringify(expression, null, 2));
|
|
152
|
+
if (expression.key === "_id") {
|
|
153
|
+
if (Array.isArray(expression.val)) {
|
|
154
|
+
mongodbQuery = {
|
|
155
|
+
_id: {
|
|
156
|
+
[expression.op ?? "$in"]: expression.val.map((element) => new mongodb_1.ObjectId(element))
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
mongodbQuery = { _id: new mongodb_1.ObjectId(expression.val) };
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
mongodbQuery[expression.key] = expression.op
|
|
166
|
+
? expression.op === "$regex"
|
|
167
|
+
? { [expression.op]: expression.val, ["$options"]: "i" }
|
|
168
|
+
: { [expression.op]: expression.val }
|
|
169
|
+
: expression.val;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (this.orQueries.length > 0) {
|
|
173
|
+
//console.log("Found OR queries");
|
|
174
|
+
mongodbQuery["$or"] = [];
|
|
175
|
+
for (const query of this.orQueries) {
|
|
176
|
+
//console.log("OR QUERY: " + JSON.stringify(query, null, 2));
|
|
177
|
+
mongodbQuery["$or"].push(query.build());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (this.andQueries.length > 0) {
|
|
181
|
+
mongodbQuery["$and"] = [];
|
|
182
|
+
for (const query of this.andQueries) {
|
|
183
|
+
mongodbQuery["$and"].push(query.build());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
console.log("Failed building query: " + JSON.stringify(err.message, null, 2));
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
// if (query.ranges) {
|
|
192
|
+
// query.ranges.forEach((range) => {
|
|
193
|
+
// mongodbQuery[range.key as keyof Filter<T>] = {
|
|
194
|
+
// $gte: range.start,
|
|
195
|
+
// $lt: range.end
|
|
196
|
+
// };
|
|
197
|
+
// });
|
|
198
|
+
// }
|
|
199
|
+
//console.log("query: " + JSON.stringify(mongodbQuery, null, 2));
|
|
200
|
+
return mongodbQuery;
|
|
201
|
+
}
|
|
202
|
+
buildOptions(pagination) {
|
|
203
|
+
const mongodbOptions = {};
|
|
204
|
+
if (pagination) {
|
|
205
|
+
mongodbOptions.skip = pagination.offset;
|
|
206
|
+
mongodbOptions.limit = pagination.count;
|
|
207
|
+
}
|
|
208
|
+
if (this.orderField && this.orderDirection) {
|
|
209
|
+
mongodbOptions.sort = {
|
|
210
|
+
[this.orderField]: this.orderDirection === types_1.SortDirection.Ascending ? 1 : -1
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//console.log(this.name + " mongo options: " + JSON.stringify(mongodbOptions, null, 2));
|
|
214
|
+
return mongodbOptions;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.MongoCollectionQuery = MongoCollectionQuery;
|
|
218
|
+
class CollectionQueryBuilder {
|
|
219
|
+
constructor() {
|
|
220
|
+
this.q = new MongoCollectionQuery();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.CollectionQueryBuilder = CollectionQueryBuilder;
|
|
224
|
+
//# sourceMappingURL=mongoCollectionQuery.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export type ICollection<T> = {
|
|
|
76
76
|
batchDelete: (ids: string[], clientId?: string) => Promise<void>;
|
|
77
77
|
exists: (query: ICollectionQuery) => Promise<boolean>;
|
|
78
78
|
create: (attributes: T, clientId?: string) => Promise<Entity & T>;
|
|
79
|
-
batchCreate: (attributes: T[], clientId?: string) => Promise<
|
|
79
|
+
batchCreate: (attributes: T[], clientId?: string) => Promise<string[]>;
|
|
80
80
|
updateAll: (attributes: any, filters?: any, clientId?: string) => Promise<void>;
|
|
81
81
|
update: (id: string, attributes: any, clientId?: string) => Promise<void>;
|
|
82
82
|
increment: (id: string, key: string, amt?: number) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flongo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Firestore-like fluent query API for MongoDB",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test:coverage": "vitest --coverage",
|
|
14
14
|
"lint": "echo 'Linting not configured yet'",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
|
-
"prepublishOnly": "npm run build &&
|
|
16
|
+
"prepublishOnly": "npm run build && vitest --run",
|
|
17
17
|
"semantic-release": "semantic-release"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|