@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.
@@ -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
- filters: [['role', '=', 'user']]
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
- filters: [['age', '>', 25]]
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
- filters: [
200
- ['role', '=', 'admin'],
201
- 'or',
202
- ['age', '>', 30]
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
- sort: [['age', 'asc']]
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
- sort: [['age', 'asc']],
220
- skip: 1,
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, [['role', '=', 'user']]);
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
- [['role', '=', 'user']],
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
  });