@sprucelabs/data-stores 28.1.333 → 28.1.335

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.
@@ -19,13 +19,11 @@ var __rest = (this && this.__rest) || function (s, e) {
19
19
  return t;
20
20
  };
21
21
  import { buildLog } from '@sprucelabs/spruce-skill-utils';
22
- import differenceWith from 'lodash/differenceWith.js';
23
- import isEqual from 'lodash/isEqual.js';
24
22
  import { MongoClient, MongoError, } from 'mongodb';
25
23
  import SpruceError from '../errors/SpruceError.js';
26
24
  import generateId from '../utilities/generateId.js';
27
25
  import mongoUtil from '../utilities/mongo.utility.js';
28
- import normalizeIndex from './normalizeIndex.js';
26
+ import { doesIndexesInclude, generateIndexName, normalizeIndex, pluckMissingIndexes, } from './database.utilities.js';
29
27
  export const MONGO_TEST_URI = 'mongodb://localhost:27017';
30
28
  export default class MongoDatabase {
31
29
  constructor(url, options) {
@@ -250,10 +248,11 @@ export default class MongoDatabase {
250
248
  listIndexes(collection) {
251
249
  return __awaiter(this, void 0, void 0, function* () {
252
250
  try {
253
- return yield this.assertDbWhileAttempingTo('get indexes.', collection)
251
+ const indexes = yield this.assertDbWhileAttempingTo('get indexes.', collection)
254
252
  .collection(collection)
255
253
  .listIndexes()
256
254
  .toArray();
255
+ return indexes.filter((index) => index.name !== '_id_');
257
256
  }
258
257
  catch (err) {
259
258
  return [];
@@ -263,9 +262,10 @@ export default class MongoDatabase {
263
262
  dropIndex(collection, index) {
264
263
  return __awaiter(this, void 0, void 0, function* () {
265
264
  const indexes = yield this.listIndexes(collection);
265
+ const name = this.generateIndexName(this.normalizeIndex(index));
266
266
  let found = false;
267
267
  for (const thisIndex of indexes) {
268
- if (isEqual(Object.keys(thisIndex.key), index)) {
268
+ if (thisIndex.name === name) {
269
269
  yield this.assertDbWhileAttempingTo('drop a index.', collection)
270
270
  .collection(collection)
271
271
  .dropIndex(thisIndex.name);
@@ -288,7 +288,10 @@ export default class MongoDatabase {
288
288
  const uniqueIndexes = [];
289
289
  for (const index of indexes) {
290
290
  if (index.unique) {
291
- uniqueIndexes.push(Object.keys(index.key));
291
+ uniqueIndexes.push({
292
+ fields: Object.keys(index.key),
293
+ filter: index.partialFilterExpression,
294
+ });
292
295
  }
293
296
  }
294
297
  return uniqueIndexes;
@@ -303,12 +306,18 @@ export default class MongoDatabase {
303
306
  try {
304
307
  const indexes = yield this.listIndexes(collection);
305
308
  if (shouldIncludeUnique) {
306
- return indexes;
309
+ return indexes.map((index) => ({
310
+ fields: Object.keys(index.key),
311
+ filter: index.partialFilterExpression,
312
+ }));
307
313
  }
308
314
  const nonUniqueIndexes = [];
309
315
  for (const index of indexes) {
310
316
  if (!index.unique) {
311
- nonUniqueIndexes.push(Object.keys(index.key));
317
+ nonUniqueIndexes.push({
318
+ fields: Object.keys(index.key),
319
+ filter: undefined,
320
+ });
312
321
  }
313
322
  }
314
323
  return nonUniqueIndexes;
@@ -318,24 +327,26 @@ export default class MongoDatabase {
318
327
  }
319
328
  });
320
329
  }
321
- createIndex(collection, fields) {
330
+ createIndex(collection, index) {
322
331
  return __awaiter(this, void 0, void 0, function* () {
323
332
  const currentIndexes = yield this.getIndexes(collection);
324
- yield this.assertIndexDoesNotExist(currentIndexes, fields, collection);
325
- const index = {};
326
- fields.forEach((name) => {
327
- index[name] = 1;
333
+ this.assertIndexDoesNotExist(currentIndexes, index, collection);
334
+ const indexSpec = {};
335
+ this.normalizeIndex(index).fields.forEach((name) => {
336
+ indexSpec[name] = 1;
328
337
  });
329
338
  try {
330
339
  yield this.assertDbWhileAttempingTo('create an index.', collection)
331
340
  .collection(collection)
332
- .createIndex(index);
341
+ .createIndex(indexSpec, {
342
+ name: this.generateIndexName(index),
343
+ });
333
344
  }
334
345
  catch (err) {
335
346
  if ((err === null || err === void 0 ? void 0 : err.code) === 11000) {
336
347
  throw new SpruceError({
337
348
  code: 'DUPLICATE_KEY',
338
- friendlyMessage: `Could not create index! Index on '${collection}' has duplicate key for "${fields.join(',')}"`,
349
+ friendlyMessage: `Could not create index! Index on '${collection}' has duplicate key for "${this.normalizeIndex(index).fields.join(',')}"`,
339
350
  });
340
351
  }
341
352
  else {
@@ -344,33 +355,32 @@ export default class MongoDatabase {
344
355
  }
345
356
  });
346
357
  }
347
- assertIndexDoesNotExist(currentIndexes, fields, collectionName) {
348
- if (this.doesIndexExist(currentIndexes, fields)) {
358
+ assertIndexDoesNotExist(currentIndexes, index, collectionName) {
359
+ if (this.doesInclude(currentIndexes, index)) {
349
360
  throw new SpruceError({
350
361
  code: 'INDEX_EXISTS',
351
- index: fields,
362
+ index: this.normalizeIndex(index).fields,
352
363
  collectionName,
353
364
  });
354
365
  }
355
366
  }
356
- doesIndexExist(currentIndexes, fields) {
357
- for (const index of currentIndexes !== null && currentIndexes !== void 0 ? currentIndexes : []) {
358
- const { fields: normalizedFields } = this.normalizeIndex(index);
359
- if (isEqual(normalizedFields, fields)) {
360
- return true;
361
- }
362
- }
363
- return false;
367
+ doesInclude(haystack, needle) {
368
+ return doesIndexesInclude(haystack, needle);
364
369
  }
365
370
  syncIndexes(collectionName, indexes) {
366
371
  return __awaiter(this, void 0, void 0, function* () {
372
+ yield this._syncIndexes(collectionName, indexes, 'createIndex');
373
+ });
374
+ }
375
+ _syncIndexes(collectionName_1, indexes_1, func_1) {
376
+ return __awaiter(this, arguments, void 0, function* (collectionName, indexes, func, shouldIncludeUnique = false) {
367
377
  var _a;
368
- const currentIndexes = yield this.getIndexes(collectionName);
369
- const extraIndexes = differenceWith(currentIndexes, indexes, isEqual).filter((i) => !(i.length === 1 && i[0] === '_id'));
378
+ const currentIndexes = yield this.getIndexes(collectionName, shouldIncludeUnique);
379
+ const indexesToDelete = pluckMissingIndexes(currentIndexes, indexes);
370
380
  for (const index of indexes) {
371
- if (!this.doesIndexExist(currentIndexes, index)) {
381
+ if (!this.doesInclude(currentIndexes, this.normalizeIndex(index))) {
372
382
  try {
373
- yield this.createIndex(collectionName, index);
383
+ yield this[func](collectionName, index);
374
384
  }
375
385
  catch (err) {
376
386
  if (((_a = err.options) === null || _a === void 0 ? void 0 : _a.code) !== 'INDEX_EXISTS') {
@@ -379,7 +389,7 @@ export default class MongoDatabase {
379
389
  }
380
390
  }
381
391
  }
382
- for (const extra of extraIndexes) {
392
+ for (const extra of indexesToDelete) {
383
393
  yield this.dropIndex(collectionName, extra);
384
394
  }
385
395
  });
@@ -387,16 +397,19 @@ export default class MongoDatabase {
387
397
  createUniqueIndex(collection, index) {
388
398
  return __awaiter(this, void 0, void 0, function* () {
389
399
  const currentIndexes = yield this.getUniqueIndexes(collection);
390
- const { fields, filter } = this.normalizeIndex(index);
391
- this.assertIndexDoesNotExist(currentIndexes, fields, collection);
400
+ const indexWithFilter = this.normalizeIndex(index);
401
+ this.assertIndexDoesNotExist(currentIndexes, indexWithFilter, collection);
392
402
  const created = {};
393
- fields.forEach((name) => {
403
+ indexWithFilter.fields.forEach((name) => {
394
404
  created[name] = 1;
395
405
  });
396
406
  try {
397
- const options = { unique: true };
398
- if (filter) {
399
- options.partialFilterExpression = filter;
407
+ const options = {
408
+ unique: true,
409
+ name: this.generateIndexName(indexWithFilter),
410
+ };
411
+ if (indexWithFilter.filter) {
412
+ options.partialFilterExpression = indexWithFilter.filter;
400
413
  }
401
414
  yield this.assertDbWhileAttempingTo('create a unique index.', collection)
402
415
  .collection(collection)
@@ -406,7 +419,8 @@ export default class MongoDatabase {
406
419
  if ((err === null || err === void 0 ? void 0 : err.code) === 11000) {
407
420
  throw new SpruceError({
408
421
  code: 'DUPLICATE_KEY',
409
- friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${fields.join(',')}"`,
422
+ originalError: err,
423
+ friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${indexWithFilter.fields.join(',')}"\n\nOriginal error:\n\n${err.message}`,
410
424
  });
411
425
  }
412
426
  else {
@@ -415,36 +429,16 @@ export default class MongoDatabase {
415
429
  }
416
430
  });
417
431
  }
432
+ generateIndexName(indexWithFilter) {
433
+ return generateIndexName(this.normalizeIndex(indexWithFilter));
434
+ }
418
435
  normalizeIndex(index) {
419
436
  const { fields, filter } = normalizeIndex(index);
420
437
  return { fields, filter };
421
438
  }
422
439
  syncUniqueIndexes(collectionName, indexes) {
423
440
  return __awaiter(this, void 0, void 0, function* () {
424
- var _a;
425
- const currentIndexes = yield this.getUniqueIndexes(collectionName);
426
- const toDelete = [];
427
- for (const index of currentIndexes) {
428
- if (!this.doesIndexExist(indexes, index)) {
429
- toDelete.push(index);
430
- }
431
- }
432
- for (const index of indexes) {
433
- const { fields } = this.normalizeIndex(index);
434
- if (!this.doesIndexExist(currentIndexes, fields)) {
435
- try {
436
- yield this.createUniqueIndex(collectionName, index);
437
- }
438
- catch (err) {
439
- if (((_a = err.options) === null || _a === void 0 ? void 0 : _a.code) !== 'INDEX_EXISTS') {
440
- throw err;
441
- }
442
- }
443
- }
444
- }
445
- for (const extra of toDelete) {
446
- yield this.dropIndex(collectionName, extra);
447
- }
441
+ yield this._syncIndexes(collectionName, indexes, 'createUniqueIndex', true);
448
442
  });
449
443
  }
450
444
  update(collection, query, updates) {
@@ -1,5 +1,5 @@
1
1
  import AbstractMutexer from '../mutexers/AbstractMutexer';
2
- import { CreateOptions, Database, DatabaseInternalOptions, UniqueIndex } from '../types/database.types';
2
+ import { CreateOptions, Database, DatabaseInternalOptions, Index, IndexWithFilter } from '../types/database.types';
3
3
  import { QueryOptions } from '../types/query.types';
4
4
  export default class NeDbDatabase extends AbstractMutexer implements Database {
5
5
  private collections;
@@ -31,14 +31,15 @@ export default class NeDbDatabase extends AbstractMutexer implements Database {
31
31
  delete(collection: string, query: Record<string, any>): Promise<number>;
32
32
  deleteOne(collection: string, query: Record<string, any>): Promise<number>;
33
33
  private assertPassesUniqueIndexes;
34
- getUniqueIndexes(collection: string): Promise<UniqueIndex[]>;
35
- getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<UniqueIndex[]>;
36
- dropIndex(collection: string, index: UniqueIndex): Promise<void>;
34
+ getUniqueIndexes(collection: string): Promise<IndexWithFilter[]>;
35
+ getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<IndexWithFilter[]>;
36
+ dropIndex(collection: string, index: Index): Promise<void>;
37
37
  private assertIndexDoesNotExist;
38
- private doesIndexExist;
39
- createUniqueIndex(collection: string, index: UniqueIndex): Promise<void>;
38
+ private doesInclude;
39
+ createUniqueIndex(collection: string, index: Index): Promise<void>;
40
40
  createIndex(collection: string, fields: string[]): Promise<void>;
41
- syncUniqueIndexes(collectionName: string, indexes: UniqueIndex[]): Promise<void>;
41
+ private normalizeIndex;
42
+ syncUniqueIndexes(collectionName: string, indexes: Index[]): Promise<void>;
42
43
  syncIndexes(collectionName: string, indexes: string[][]): Promise<void>;
43
44
  query<T>(query: string, params?: any[]): Promise<T[]>;
44
45
  private queryToKey;
@@ -20,7 +20,6 @@ var __rest = (this && this.__rest) || function (s, e) {
20
20
  };
21
21
  var _a;
22
22
  import dotenv from 'dotenv';
23
- import differenceWith from 'lodash/differenceWith.js';
24
23
  import get from 'lodash/get.js';
25
24
  import isEqual from 'lodash/isEqual.js';
26
25
  import isObject from 'lodash/isObject.js';
@@ -30,7 +29,7 @@ import SpruceError from '../errors/SpruceError.js';
30
29
  import AbstractMutexer from '../mutexers/AbstractMutexer.js';
31
30
  import generateId from '../utilities/generateId.js';
32
31
  import mongoUtil from '../utilities/mongo.utility.js';
33
- import normalizeIndex from './normalizeIndex.js';
32
+ import { doesIndexesInclude, normalizeIndex, pluckMissingIndexes, } from './database.utilities.js';
34
33
  dotenv.config();
35
34
  export default class NeDbDatabase extends AbstractMutexer {
36
35
  constructor() {
@@ -394,7 +393,7 @@ export default class NeDbDatabase extends AbstractMutexer {
394
393
  let found = false;
395
394
  let newIndexes = [];
396
395
  for (const uniq of (_a = col._uniqueIndexes) !== null && _a !== void 0 ? _a : []) {
397
- if (!isEqual(uniq, fields)) {
396
+ if (!isEqual(uniq.fields, fields)) {
398
397
  newIndexes.push(uniq);
399
398
  }
400
399
  else {
@@ -408,7 +407,7 @@ export default class NeDbDatabase extends AbstractMutexer {
408
407
  else {
409
408
  newIndexes = [];
410
409
  for (const index of (_b = col._indexes) !== null && _b !== void 0 ? _b : []) {
411
- if (!isEqual(index, fields)) {
410
+ if (!isEqual(index.fields, fields)) {
412
411
  newIndexes.push(index);
413
412
  }
414
413
  else {
@@ -427,22 +426,17 @@ export default class NeDbDatabase extends AbstractMutexer {
427
426
  });
428
427
  });
429
428
  }
430
- assertIndexDoesNotExist(currentIndexes, fields, collectionName) {
431
- if (this.doesIndexExist(currentIndexes, fields)) {
429
+ assertIndexDoesNotExist(currentIndexes, index, collectionName) {
430
+ if (this.doesInclude(currentIndexes, index)) {
432
431
  throw new SpruceError({
433
432
  code: 'INDEX_EXISTS',
434
- index: fields,
433
+ index: index.fields,
435
434
  collectionName,
436
435
  });
437
436
  }
438
437
  }
439
- doesIndexExist(currentIndexes, index) {
440
- for (const existing of currentIndexes !== null && currentIndexes !== void 0 ? currentIndexes : []) {
441
- if (isEqual(existing, index)) {
442
- return true;
443
- }
444
- }
445
- return false;
438
+ doesInclude(haystack, needle) {
439
+ return doesIndexesInclude(haystack, needle);
446
440
  }
447
441
  createUniqueIndex(collection, index) {
448
442
  return __awaiter(this, void 0, void 0, function* () {
@@ -450,12 +444,12 @@ export default class NeDbDatabase extends AbstractMutexer {
450
444
  if (!col._uniqueIndexes) {
451
445
  col._uniqueIndexes = [];
452
446
  }
453
- const { fields, filter } = normalizeIndex(index);
447
+ const indexWithFilter = normalizeIndex(index);
454
448
  yield this.randomDelay();
455
- this.assertIndexDoesNotExist(col._uniqueIndexes, fields, collection);
456
- if (col._uniqueIndexes && !filter) {
449
+ this.assertIndexDoesNotExist(col._uniqueIndexes, indexWithFilter, collection);
450
+ if (col._uniqueIndexes && !indexWithFilter.filter) {
457
451
  const tempUniqueIndexes = [...col._uniqueIndexes];
458
- tempUniqueIndexes.push(fields);
452
+ tempUniqueIndexes.push(indexWithFilter);
459
453
  const documents = (yield this.find(collection)) || [];
460
454
  for (const index of tempUniqueIndexes) {
461
455
  const { fields: uniqueFields } = normalizeIndex(index);
@@ -476,7 +470,7 @@ export default class NeDbDatabase extends AbstractMutexer {
476
470
  }
477
471
  }
478
472
  }
479
- col._uniqueIndexes.push(index);
473
+ col._uniqueIndexes.push(indexWithFilter);
480
474
  });
481
475
  }
482
476
  createIndex(collection, fields) {
@@ -486,22 +480,21 @@ export default class NeDbDatabase extends AbstractMutexer {
486
480
  col._indexes = [];
487
481
  }
488
482
  yield this.randomDelay();
489
- this.assertIndexDoesNotExist(col._indexes, fields, collection);
490
- col._indexes.push(fields);
483
+ this.assertIndexDoesNotExist(col._indexes, this.normalizeIndex(fields), collection);
484
+ col._indexes.push({ fields });
491
485
  });
492
486
  }
487
+ normalizeIndex(index) {
488
+ const { fields, filter } = normalizeIndex(index);
489
+ return { fields, filter };
490
+ }
493
491
  syncUniqueIndexes(collectionName, indexes) {
494
492
  return __awaiter(this, void 0, void 0, function* () {
495
493
  var _a;
496
494
  const currentIndexes = yield this.getUniqueIndexes(collectionName);
497
- const toDelete = [];
498
- for (const index of currentIndexes) {
499
- if (!this.doesIndexExist(indexes, index)) {
500
- toDelete.push(index);
501
- }
502
- }
495
+ const toDelete = pluckMissingIndexes(currentIndexes, indexes);
503
496
  for (const index of indexes) {
504
- if (!this.doesIndexExist(currentIndexes, index)) {
497
+ if (!this.doesInclude(currentIndexes, index)) {
505
498
  try {
506
499
  yield this.createUniqueIndex(collectionName, index);
507
500
  }
@@ -521,9 +514,9 @@ export default class NeDbDatabase extends AbstractMutexer {
521
514
  return __awaiter(this, void 0, void 0, function* () {
522
515
  var _a;
523
516
  const currentIndexes = yield this.getIndexes(collectionName);
524
- const extraIndexes = differenceWith(currentIndexes, indexes, isEqual);
517
+ const extraIndexes = pluckMissingIndexes(currentIndexes, indexes);
525
518
  for (const index of indexes) {
526
- if (!this.doesIndexExist(currentIndexes, index)) {
519
+ if (!this.doesInclude(currentIndexes, index)) {
527
520
  try {
528
521
  yield this.createIndex(collectionName, index);
529
522
  }
@@ -0,0 +1,6 @@
1
+ import { IndexWithFilter, Index } from '../types/database.types';
2
+ export declare function doesIndexesInclude(haystack: Index[], needle: Index): boolean;
3
+ export declare function areIndexesEqual(left: Index, right: Index): boolean;
4
+ export declare function generateIndexName(indexWithFilter: IndexWithFilter): string;
5
+ export declare function normalizeIndex(index: Index): IndexWithFilter;
6
+ export declare function pluckMissingIndexes(left: Index[], right: Index[]): Index[];
@@ -0,0 +1,30 @@
1
+ import differenceWith from 'lodash/differenceWith.js';
2
+ export function doesIndexesInclude(haystack, needle) {
3
+ for (const index of haystack !== null && haystack !== void 0 ? haystack : []) {
4
+ if (areIndexesEqual(index, needle)) {
5
+ return true;
6
+ }
7
+ }
8
+ return false;
9
+ }
10
+ export function areIndexesEqual(left, right) {
11
+ const name1 = generateIndexName(normalizeIndex(left));
12
+ const name2 = generateIndexName(normalizeIndex(right));
13
+ return name1 === name2;
14
+ }
15
+ export function generateIndexName(indexWithFilter) {
16
+ let name = indexWithFilter.fields.join('_');
17
+ if (indexWithFilter.filter) {
18
+ name += '_filtered';
19
+ }
20
+ return name;
21
+ }
22
+ export function normalizeIndex(index) {
23
+ const fields = Array.isArray(index) ? index : index.fields;
24
+ const filter = Array.isArray(index) ? undefined : index.filter;
25
+ fields.sort();
26
+ return { fields, filter };
27
+ }
28
+ export function pluckMissingIndexes(left, right) {
29
+ return differenceWith(left, right, areIndexesEqual);
30
+ }
@@ -21,7 +21,7 @@ export * from './cursors/CursorPager';
21
21
  export { default as CursorPagerFaker } from './cursors/CursorPagerFaker';
22
22
  export * from './cursors/CursorPager';
23
23
  export { default as DatabaseFieldMapperPlugin } from './plugins/DatabaseFieldMapperPlugin';
24
- export { default as normalizeIndex } from './databases/normalizeIndex';
24
+ export * from './databases/database.utilities';
25
25
  /**
26
26
  * @deprecated databaseAssertUtil -> databaseAssert
27
27
  */
@@ -21,7 +21,7 @@ export * from './cursors/CursorPager.js';
21
21
  export { default as CursorPagerFaker } from './cursors/CursorPagerFaker.js';
22
22
  export * from './cursors/CursorPager.js';
23
23
  export { default as DatabaseFieldMapperPlugin } from './plugins/DatabaseFieldMapperPlugin.js';
24
- export { default as normalizeIndex } from './databases/normalizeIndex.js';
24
+ export * from './databases/database.utilities.js';
25
25
  /**
26
26
  * @deprecated databaseAssertUtil -> databaseAssert
27
27
  */
@@ -1,10 +1,10 @@
1
- import { Database, Index, TestConnect, UniqueIndex } from '../types/database.types';
1
+ import { Database, IndexWithFilter, TestConnect } from '../types/database.types';
2
2
  import { DataStore } from '../types/stores.types';
3
3
  declare const databaseAssertUtil: {
4
4
  collectionName: string;
5
5
  runSuite(connect: TestConnect, tests?: string[]): Promise<void>;
6
- _getFilteredIndexes(db: Database): Promise<string[][]>;
7
- _filterIdIndex(allIndexes: UniqueIndex[] | Index[]): UniqueIndex[] | Index[];
6
+ _getIndexesWith_IdFilteredOut(db: Database): Promise<IndexWithFilter[]>;
7
+ _filterOut_Id(allIndexes: IndexWithFilter[]): IndexWithFilter[];
8
8
  _assertUpdateUpdatedRightNumberOfRecords(db: Database, search: Record<string, any>, updates: Record<string, any>, expectedUpdateCount: number): Promise<void>;
9
9
  generateIdDifferentEachTime(connect: TestConnect): Promise<void>;
10
10
  assertCanSortDesc(connect: TestConnect): Promise<void>;
@@ -64,6 +64,7 @@ declare const databaseAssertUtil: {
64
64
  assertCanSaveAndGetNullAndUndefined(connect: TestConnect): Promise<void>;
65
65
  assertSyncIndexesDoesNotRemoveExisting(connect: TestConnect): Promise<void>;
66
66
  assertCanSyncUniqueIndexesWithFilterExpression(connect: TestConnect): Promise<void>;
67
+ assertCanSyncIndexesWithoutPartialThenAgainWithProperlyUpdates(connect: TestConnect): Promise<void>;
67
68
  assertSyncIndexesRemovesExtraIndexes(connect: TestConnect): Promise<void>;
68
69
  assertSyncIndexesSkipsExisting(connect: TestConnect): Promise<void>;
69
70
  assertCantDropCompoundIndexThatDoesNotExist(connect: TestConnect): Promise<void>;