@objectql/driver-memory 4.0.1 → 4.0.3
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +114 -25
- package/dist/index.js +306 -115
- package/dist/index.js.map +1 -1
- package/jest.config.js +13 -1
- package/package.json +3 -3
- package/src/index.ts +375 -134
- package/test/index.test.ts +286 -16
- package/tsconfig.tsbuildinfo +1 -1
package/test/index.test.ts
CHANGED
|
@@ -180,7 +180,7 @@ describe('MemoryDriver', () => {
|
|
|
180
180
|
|
|
181
181
|
it('should filter records with = operator', async () => {
|
|
182
182
|
const results = await driver.find(TEST_OBJECT, {
|
|
183
|
-
|
|
183
|
+
where: { role: 'user' }
|
|
184
184
|
});
|
|
185
185
|
expect(results).toHaveLength(2);
|
|
186
186
|
expect(results.every(r => r.role === 'user')).toBe(true);
|
|
@@ -188,7 +188,7 @@ describe('MemoryDriver', () => {
|
|
|
188
188
|
|
|
189
189
|
it('should filter records with > operator', async () => {
|
|
190
190
|
const results = await driver.find(TEST_OBJECT, {
|
|
191
|
-
|
|
191
|
+
where: { age: { $gt: 25 } }
|
|
192
192
|
});
|
|
193
193
|
expect(results).toHaveLength(2);
|
|
194
194
|
expect(results.every(r => r.age > 25)).toBe(true);
|
|
@@ -196,18 +196,19 @@ describe('MemoryDriver', () => {
|
|
|
196
196
|
|
|
197
197
|
it('should combine filters with OR', async () => {
|
|
198
198
|
const results = await driver.find(TEST_OBJECT, {
|
|
199
|
-
|
|
200
|
-
[
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
where: {
|
|
200
|
+
$or: [
|
|
201
|
+
{ role: 'admin' },
|
|
202
|
+
{ age: { $gt: 30 } }
|
|
203
|
+
]
|
|
204
|
+
}
|
|
204
205
|
});
|
|
205
206
|
expect(results).toHaveLength(2); // Alice (admin) and Charlie (age > 30)
|
|
206
207
|
});
|
|
207
208
|
|
|
208
209
|
it('should sort records ascending', async () => {
|
|
209
210
|
const results = await driver.find(TEST_OBJECT, {
|
|
210
|
-
|
|
211
|
+
orderBy: [{ field: 'age', order: 'asc' }]
|
|
211
212
|
});
|
|
212
213
|
expect(results[0].age).toBe(25);
|
|
213
214
|
expect(results[1].age).toBe(30);
|
|
@@ -216,8 +217,8 @@ describe('MemoryDriver', () => {
|
|
|
216
217
|
|
|
217
218
|
it('should support pagination with skip and limit', async () => {
|
|
218
219
|
const results = await driver.find(TEST_OBJECT, {
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
orderBy: [{ field: 'age', order: 'asc' }],
|
|
221
|
+
offset: 1,
|
|
221
222
|
limit: 1
|
|
222
223
|
});
|
|
223
224
|
expect(results).toHaveLength(1);
|
|
@@ -233,12 +234,12 @@ describe('MemoryDriver', () => {
|
|
|
233
234
|
});
|
|
234
235
|
|
|
235
236
|
it('should count all records', async () => {
|
|
236
|
-
const count = await driver.count(TEST_OBJECT,
|
|
237
|
+
const count = await driver.count(TEST_OBJECT, {});
|
|
237
238
|
expect(count).toBe(3);
|
|
238
239
|
});
|
|
239
240
|
|
|
240
241
|
it('should count filtered records', async () => {
|
|
241
|
-
const count = await driver.count(TEST_OBJECT,
|
|
242
|
+
const count = await driver.count(TEST_OBJECT, { role: 'user' });
|
|
242
243
|
expect(count).toBe(2);
|
|
243
244
|
});
|
|
244
245
|
});
|
|
@@ -261,7 +262,7 @@ describe('MemoryDriver', () => {
|
|
|
261
262
|
|
|
262
263
|
const result = await driver.updateMany(
|
|
263
264
|
TEST_OBJECT,
|
|
264
|
-
|
|
265
|
+
{ role: 'user' },
|
|
265
266
|
{ status: 'active' }
|
|
266
267
|
);
|
|
267
268
|
|
|
@@ -273,11 +274,280 @@ describe('MemoryDriver', () => {
|
|
|
273
274
|
await driver.create(TEST_OBJECT, { id: '2', role: 'user' });
|
|
274
275
|
await driver.create(TEST_OBJECT, { id: '3', role: 'admin' });
|
|
275
276
|
|
|
276
|
-
const result = await driver.deleteMany(TEST_OBJECT,
|
|
277
|
-
['role', '=', 'user']
|
|
278
|
-
]);
|
|
277
|
+
const result = await driver.deleteMany(TEST_OBJECT, { role: 'user' });
|
|
279
278
|
|
|
280
279
|
expect(result.deletedCount).toBe(2);
|
|
281
280
|
});
|
|
282
281
|
});
|
|
282
|
+
|
|
283
|
+
describe('Aggregate Method', () => {
|
|
284
|
+
beforeEach(async () => {
|
|
285
|
+
// Create test data for aggregation
|
|
286
|
+
await driver.create('orders', { id: '1', customer: 'Alice', product: 'Laptop', amount: 1200, quantity: 1, status: 'completed' });
|
|
287
|
+
await driver.create('orders', { id: '2', customer: 'Bob', product: 'Mouse', amount: 25, quantity: 2, status: 'completed' });
|
|
288
|
+
await driver.create('orders', { id: '3', customer: 'Alice', product: 'Keyboard', amount: 75, quantity: 1, status: 'pending' });
|
|
289
|
+
await driver.create('orders', { id: '4', customer: 'Charlie', product: 'Monitor', amount: 350, quantity: 1, status: 'completed' });
|
|
290
|
+
await driver.create('orders', { id: '5', customer: 'Bob', product: 'Laptop', amount: 1200, quantity: 1, status: 'cancelled' });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should aggregate with $match stage', async () => {
|
|
294
|
+
const results = await driver.aggregate('orders', [
|
|
295
|
+
{ $match: { status: 'completed' } }
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
expect(results).toBeDefined();
|
|
299
|
+
expect(results.length).toBe(3);
|
|
300
|
+
expect(results.every((r: any) => r.status === 'completed')).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should aggregate with $group and $sum', async () => {
|
|
304
|
+
const results = await driver.aggregate('orders', [
|
|
305
|
+
{ $match: { status: 'completed' } },
|
|
306
|
+
{
|
|
307
|
+
$group: {
|
|
308
|
+
_id: '$customer',
|
|
309
|
+
totalAmount: { $sum: '$amount' }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
expect(results).toBeDefined();
|
|
315
|
+
expect(results.length).toBeGreaterThan(0);
|
|
316
|
+
|
|
317
|
+
const alice = results.find((r: any) => r._id === 'Alice');
|
|
318
|
+
expect(alice).toBeDefined();
|
|
319
|
+
expect(alice.totalAmount).toBe(1200);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should aggregate with $group and $avg', async () => {
|
|
323
|
+
const results = await driver.aggregate('orders', [
|
|
324
|
+
{
|
|
325
|
+
$group: {
|
|
326
|
+
_id: null,
|
|
327
|
+
avgAmount: { $avg: '$amount' }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
expect(results).toBeDefined();
|
|
333
|
+
expect(results.length).toBe(1);
|
|
334
|
+
expect(results[0].avgAmount).toBeCloseTo(570, 0);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should aggregate with $project stage', async () => {
|
|
338
|
+
const results = await driver.aggregate('orders', [
|
|
339
|
+
{ $match: { status: 'completed' } },
|
|
340
|
+
{
|
|
341
|
+
$project: {
|
|
342
|
+
customer: 1,
|
|
343
|
+
product: 1,
|
|
344
|
+
total: { $multiply: ['$amount', '$quantity'] }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
expect(results).toBeDefined();
|
|
350
|
+
expect(results.length).toBe(3);
|
|
351
|
+
expect(results[0]).toHaveProperty('total');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should aggregate with $sort stage', async () => {
|
|
355
|
+
const results = await driver.aggregate('orders', [
|
|
356
|
+
{ $match: { status: 'completed' } },
|
|
357
|
+
{ $sort: { amount: -1 } }
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
expect(results).toBeDefined();
|
|
361
|
+
expect(results.length).toBe(3);
|
|
362
|
+
expect(results[0].amount).toBe(1200);
|
|
363
|
+
expect(results[2].amount).toBe(25);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe('Transaction Support', () => {
|
|
368
|
+
it('should begin and commit a transaction', async () => {
|
|
369
|
+
const tx = await driver.beginTransaction();
|
|
370
|
+
expect(tx).toBeDefined();
|
|
371
|
+
expect(tx.id).toBeDefined();
|
|
372
|
+
|
|
373
|
+
await driver.create(TEST_OBJECT, { id: '1', name: 'Alice' });
|
|
374
|
+
await driver.commitTransaction(tx);
|
|
375
|
+
|
|
376
|
+
const result = await driver.findOne(TEST_OBJECT, '1');
|
|
377
|
+
expect(result).toBeDefined();
|
|
378
|
+
expect(result.name).toBe('Alice');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should rollback a transaction', async () => {
|
|
382
|
+
await driver.create(TEST_OBJECT, { id: '1', name: 'Alice' });
|
|
383
|
+
|
|
384
|
+
const tx = await driver.beginTransaction();
|
|
385
|
+
|
|
386
|
+
await driver.update(TEST_OBJECT, '1', { name: 'Bob' });
|
|
387
|
+
await driver.create(TEST_OBJECT, { id: '2', name: 'Charlie' });
|
|
388
|
+
|
|
389
|
+
await driver.rollbackTransaction(tx);
|
|
390
|
+
|
|
391
|
+
const result1 = await driver.findOne(TEST_OBJECT, '1');
|
|
392
|
+
expect(result1.name).toBe('Alice'); // Should be reverted
|
|
393
|
+
|
|
394
|
+
const result2 = await driver.findOne(TEST_OBJECT, '2');
|
|
395
|
+
expect(result2).toBeNull(); // Should not exist
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should handle multiple transactions', async () => {
|
|
399
|
+
const tx1 = await driver.beginTransaction();
|
|
400
|
+
const tx2 = await driver.beginTransaction();
|
|
401
|
+
|
|
402
|
+
expect(tx1.id).not.toBe(tx2.id);
|
|
403
|
+
|
|
404
|
+
await driver.commitTransaction(tx1);
|
|
405
|
+
await driver.commitTransaction(tx2);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should throw error for invalid transaction', async () => {
|
|
409
|
+
await expect(
|
|
410
|
+
driver.commitTransaction({ id: 'invalid-tx-id' })
|
|
411
|
+
).rejects.toThrow();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('Indexing', () => {
|
|
416
|
+
it('should build indexes on initialization', () => {
|
|
417
|
+
const indexedDriver = new MemoryDriver({
|
|
418
|
+
initialData: {
|
|
419
|
+
users: [
|
|
420
|
+
{ id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin' },
|
|
421
|
+
{ id: '2', name: 'Bob', email: 'bob@example.com', role: 'user' },
|
|
422
|
+
{ id: '3', name: 'Charlie', email: 'charlie@example.com', role: 'user' }
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
indexes: {
|
|
426
|
+
users: ['email', 'role']
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(indexedDriver).toBeDefined();
|
|
431
|
+
expect(indexedDriver.getSize()).toBe(3);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should update indexes when creating records', async () => {
|
|
435
|
+
const indexedDriver = new MemoryDriver({
|
|
436
|
+
indexes: {
|
|
437
|
+
users: ['email']
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await indexedDriver.create('users', { id: '1', name: 'Alice', email: 'alice@example.com' });
|
|
442
|
+
await indexedDriver.create('users', { id: '2', name: 'Bob', email: 'bob@example.com' });
|
|
443
|
+
|
|
444
|
+
const results = await indexedDriver.find('users', {});
|
|
445
|
+
expect(results.length).toBe(2);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should update indexes when updating records', async () => {
|
|
449
|
+
const indexedDriver = new MemoryDriver({
|
|
450
|
+
indexes: {
|
|
451
|
+
users: ['email']
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await indexedDriver.create('users', { id: '1', name: 'Alice', email: 'alice@example.com' });
|
|
456
|
+
await indexedDriver.update('users', '1', { email: 'alice.new@example.com' });
|
|
457
|
+
|
|
458
|
+
const result = await indexedDriver.findOne('users', '1');
|
|
459
|
+
expect(result.email).toBe('alice.new@example.com');
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should remove from indexes when deleting records', async () => {
|
|
463
|
+
const indexedDriver = new MemoryDriver({
|
|
464
|
+
indexes: {
|
|
465
|
+
users: ['email']
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
await indexedDriver.create('users', { id: '1', name: 'Alice', email: 'alice@example.com' });
|
|
470
|
+
await indexedDriver.delete('users', '1');
|
|
471
|
+
|
|
472
|
+
const result = await indexedDriver.findOne('users', '1');
|
|
473
|
+
expect(result).toBeNull();
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('Persistence', () => {
|
|
478
|
+
const testFilePath = '/tmp/objectql-memory-test.json';
|
|
479
|
+
|
|
480
|
+
afterEach(() => {
|
|
481
|
+
// Clean up test file
|
|
482
|
+
try {
|
|
483
|
+
const fs = require('fs');
|
|
484
|
+
if (fs.existsSync(testFilePath)) {
|
|
485
|
+
fs.unlinkSync(testFilePath);
|
|
486
|
+
}
|
|
487
|
+
} catch (e) {
|
|
488
|
+
// Ignore errors
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should save data to disk on disconnect', async () => {
|
|
493
|
+
const persistentDriver = new MemoryDriver({
|
|
494
|
+
persistence: {
|
|
495
|
+
filePath: testFilePath,
|
|
496
|
+
autoSaveInterval: 10000 // Long interval, we'll manually trigger
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
await persistentDriver.create('users', { id: '1', name: 'Alice' });
|
|
501
|
+
await persistentDriver.disconnect();
|
|
502
|
+
|
|
503
|
+
const fs = require('fs');
|
|
504
|
+
expect(fs.existsSync(testFilePath)).toBe(true);
|
|
505
|
+
|
|
506
|
+
const fileData = JSON.parse(fs.readFileSync(testFilePath, 'utf8'));
|
|
507
|
+
expect(fileData.store).toBeDefined();
|
|
508
|
+
expect(fileData.store['users:1']).toBeDefined();
|
|
509
|
+
expect(fileData.store['users:1'].name).toBe('Alice');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should load data from disk on initialization', async () => {
|
|
513
|
+
// First, create a driver and save data
|
|
514
|
+
const driver1 = new MemoryDriver({
|
|
515
|
+
persistence: {
|
|
516
|
+
filePath: testFilePath,
|
|
517
|
+
autoSaveInterval: 10000
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await driver1.create('users', { id: '1', name: 'Alice' });
|
|
522
|
+
await driver1.create('users', { id: '2', name: 'Bob' });
|
|
523
|
+
await driver1.disconnect();
|
|
524
|
+
|
|
525
|
+
// Now create a new driver that should load the data
|
|
526
|
+
const driver2 = new MemoryDriver({
|
|
527
|
+
persistence: {
|
|
528
|
+
filePath: testFilePath,
|
|
529
|
+
autoSaveInterval: 10000
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const users = await driver2.find('users', {});
|
|
534
|
+
expect(users.length).toBe(2);
|
|
535
|
+
expect(users.some(u => u.name === 'Alice')).toBe(true);
|
|
536
|
+
expect(users.some(u => u.name === 'Bob')).toBe(true);
|
|
537
|
+
|
|
538
|
+
await driver2.disconnect();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should handle missing persistence file gracefully', () => {
|
|
542
|
+
const driver = new MemoryDriver({
|
|
543
|
+
persistence: {
|
|
544
|
+
filePath: '/tmp/nonexistent-file.json',
|
|
545
|
+
autoSaveInterval: 10000
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
expect(driver).toBeDefined();
|
|
550
|
+
expect(driver.getSize()).toBe(0);
|
|
551
|
+
});
|
|
552
|
+
});
|
|
283
553
|
});
|