@sprucelabs/data-stores 28.0.26 → 28.1.1

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.
@@ -1,5 +1,5 @@
1
1
  import { MongoClientOptions, MongoClient } from 'mongodb';
2
- import { Database, DatabaseOptions } from '../types/database.types';
2
+ import { Database, DatabaseOptions, IndexWithFilter, UniqueIndex } from '../types/database.types';
3
3
  import { QueryOptions } from '../types/query.types';
4
4
  export declare const MONGO_TEST_URI = "mongodb://localhost:27017";
5
5
  export default class MongoDatabase implements Database {
@@ -33,8 +33,9 @@ export default class MongoDatabase implements Database {
33
33
  private assertIndexDoesNotExist;
34
34
  private doesIndexExist;
35
35
  syncIndexes(collectionName: string, indexes: string[][]): Promise<void>;
36
- createUniqueIndex(collection: string, fields: string[]): Promise<void>;
37
- syncUniqueIndexes(collectionName: string, indexes: string[][]): Promise<void>;
36
+ createUniqueIndex(collection: string, index: string[] | IndexWithFilter): Promise<void>;
37
+ private normalizeIndex;
38
+ syncUniqueIndexes(collectionName: string, indexes: UniqueIndex[]): Promise<void>;
38
39
  update(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<number>;
39
40
  updateOne(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<Record<string, any>>;
40
41
  upsertOne(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<Record<string, any>>;
@@ -22,6 +22,7 @@ const mongodb_1 = require("mongodb");
22
22
  const SpruceError_1 = __importDefault(require("../errors/SpruceError"));
23
23
  const generateId_1 = __importDefault(require("../utilities/generateId"));
24
24
  const mongo_utility_1 = __importDefault(require("../utilities/mongo.utility"));
25
+ const normalizeIndex_1 = __importDefault(require("./normalizeIndex"));
25
26
  exports.MONGO_TEST_URI = 'mongodb://localhost:27017';
26
27
  class MongoDatabase {
27
28
  constructor(url, options) {
@@ -321,7 +322,8 @@ class MongoDatabase {
321
322
  }
322
323
  doesIndexExist(currentIndexes, fields) {
323
324
  for (const index of currentIndexes !== null && currentIndexes !== void 0 ? currentIndexes : []) {
324
- if ((0, isEqual_1.default)(index, fields)) {
325
+ const { fields: normalizedFields } = this.normalizeIndex(index);
326
+ if ((0, isEqual_1.default)(normalizedFields, fields)) {
325
327
  return true;
326
328
  }
327
329
  }
@@ -339,17 +341,22 @@ class MongoDatabase {
339
341
  await this.dropIndex(collectionName, extra);
340
342
  }
341
343
  }
342
- async createUniqueIndex(collection, fields) {
344
+ async createUniqueIndex(collection, index) {
343
345
  const currentIndexes = await this.getUniqueIndexes(collection);
344
- await this.assertIndexDoesNotExist(currentIndexes, fields, collection);
345
- const index = {};
346
+ const { fields, filter } = this.normalizeIndex(index);
347
+ this.assertIndexDoesNotExist(currentIndexes, fields, collection);
348
+ const created = {};
346
349
  fields.forEach((name) => {
347
- index[name] = 1;
350
+ created[name] = 1;
348
351
  });
349
352
  try {
353
+ const options = { unique: true };
354
+ if (filter) {
355
+ options.partialFilterExpression = filter;
356
+ }
350
357
  await this.assertDbWhileAttempingTo('create a unique index.', collection)
351
358
  .collection(collection)
352
- .createIndex(index, { unique: true });
359
+ .createIndex(created, options);
353
360
  }
354
361
  catch (err) {
355
362
  if ((err === null || err === void 0 ? void 0 : err.code) === 11000) {
@@ -363,12 +370,25 @@ class MongoDatabase {
363
370
  }
364
371
  }
365
372
  }
373
+ normalizeIndex(index) {
374
+ const { fields, filter } = (0, normalizeIndex_1.default)(index);
375
+ return { fields, filter };
376
+ }
366
377
  async syncUniqueIndexes(collectionName, indexes) {
378
+ var _a;
367
379
  const currentIndexes = await this.getUniqueIndexes(collectionName);
368
380
  const extraIndexes = (0, differenceWith_1.default)(currentIndexes, indexes, isEqual_1.default);
369
381
  for (const index of indexes) {
370
- if (!this.doesIndexExist(currentIndexes, index)) {
371
- await this.createUniqueIndex(collectionName, index);
382
+ const { fields } = this.normalizeIndex(index);
383
+ if (!this.doesIndexExist(currentIndexes, fields)) {
384
+ try {
385
+ await this.createUniqueIndex(collectionName, index);
386
+ }
387
+ catch (err) {
388
+ if (((_a = err.options) === null || _a === void 0 ? void 0 : _a.code) !== 'INDEX_EXISTS') {
389
+ throw err;
390
+ }
391
+ }
372
392
  }
373
393
  }
374
394
  for (const extra of extraIndexes) {
@@ -1,5 +1,5 @@
1
1
  import AbstractMutexer from '../mutexers/AbstractMutexer';
2
- import { CreateOptions, Database, DatabaseInternalOptions } from '../types/database.types';
2
+ import { CreateOptions, Database, DatabaseInternalOptions, UniqueIndex } 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,14 @@ 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<string[][]>;
35
- getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<string[][]>;
36
- dropIndex(collection: string, fields: string[]): Promise<void>;
34
+ getUniqueIndexes(collection: string): Promise<UniqueIndex[]>;
35
+ getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<UniqueIndex[]>;
36
+ dropIndex(collection: string, index: UniqueIndex): Promise<void>;
37
37
  private assertIndexDoesNotExist;
38
38
  private doesIndexExist;
39
- createUniqueIndex(collection: string, fields: string[]): Promise<void>;
39
+ createUniqueIndex(collection: string, index: UniqueIndex): Promise<void>;
40
40
  createIndex(collection: string, fields: string[]): Promise<void>;
41
- syncUniqueIndexes(collectionName: string, indexes: string[][]): Promise<void>;
41
+ syncUniqueIndexes(collectionName: string, indexes: UniqueIndex[]): Promise<void>;
42
42
  syncIndexes(collectionName: string, indexes: string[][]): Promise<void>;
43
43
  query<T>(query: string, params?: any[]): Promise<T[]>;
44
44
  private queryToKey;
@@ -26,6 +26,7 @@ const SpruceError_1 = __importDefault(require("../errors/SpruceError"));
26
26
  const AbstractMutexer_1 = __importDefault(require("../mutexers/AbstractMutexer"));
27
27
  const generateId_1 = __importDefault(require("../utilities/generateId"));
28
28
  const mongo_utility_1 = __importDefault(require("../utilities/mongo.utility"));
29
+ const normalizeIndex_1 = __importDefault(require("./normalizeIndex"));
29
30
  dotenv_1.default.config();
30
31
  const NULL_PLACEHOLDER = '_____NULL_____';
31
32
  const UNDEFINED_PLACEHOLDER = '_____UNDEFINED_____';
@@ -292,7 +293,11 @@ class NeDbDatabase extends AbstractMutexer_1.default {
292
293
  const col = this.loadCollection(collection);
293
294
  await this.randomDelay();
294
295
  if (col._uniqueIndexes) {
295
- for (const fields of col._uniqueIndexes) {
296
+ for (const index of col._uniqueIndexes) {
297
+ const { fields, filter } = (0, normalizeIndex_1.default)(index);
298
+ if (filter) {
299
+ return;
300
+ }
296
301
  const existing = query
297
302
  ? await this.findOne(collection, query)
298
303
  : null;
@@ -333,9 +338,10 @@ class NeDbDatabase extends AbstractMutexer_1.default {
333
338
  }
334
339
  return (_c = col._indexes) !== null && _c !== void 0 ? _c : [];
335
340
  }
336
- async dropIndex(collection, fields) {
341
+ async dropIndex(collection, index) {
337
342
  var _a, _b;
338
343
  const col = this.loadCollection(collection);
344
+ const { fields } = (0, normalizeIndex_1.default)(index);
339
345
  await this.randomDelay();
340
346
  let found = false;
341
347
  let newIndexes = [];
@@ -389,18 +395,20 @@ class NeDbDatabase extends AbstractMutexer_1.default {
389
395
  }
390
396
  return false;
391
397
  }
392
- async createUniqueIndex(collection, fields) {
398
+ async createUniqueIndex(collection, index) {
393
399
  const col = this.loadCollection(collection);
394
400
  if (!col._uniqueIndexes) {
395
401
  col._uniqueIndexes = [];
396
402
  }
403
+ const { fields, filter } = (0, normalizeIndex_1.default)(index);
397
404
  await this.randomDelay();
398
405
  this.assertIndexDoesNotExist(col._uniqueIndexes, fields, collection);
399
- if (col._uniqueIndexes) {
406
+ if (col._uniqueIndexes && !filter) {
400
407
  const tempUniqueIndexes = [...col._uniqueIndexes];
401
408
  tempUniqueIndexes.push(fields);
402
- const documents = (await this.find(collection, {})) || [];
403
- for (const uniqueFields of tempUniqueIndexes) {
409
+ const documents = (await this.find(collection)) || [];
410
+ for (const index of tempUniqueIndexes) {
411
+ const { fields: uniqueFields } = (0, normalizeIndex_1.default)(index);
404
412
  let parsedExisting = [];
405
413
  for (const doc of documents) {
406
414
  const tempDoc = {};
@@ -413,12 +421,12 @@ class NeDbDatabase extends AbstractMutexer_1.default {
413
421
  if (parsedExisting.length != uniqued.length) {
414
422
  throw new SpruceError_1.default({
415
423
  code: 'DUPLICATE_KEY',
416
- friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${fields.join(',')}"`,
424
+ friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${uniqueFields.join(',')}"`,
417
425
  });
418
426
  }
419
427
  }
420
428
  }
421
- col._uniqueIndexes.push(fields);
429
+ col._uniqueIndexes.push(index);
422
430
  }
423
431
  async createIndex(collection, fields) {
424
432
  const col = this.loadCollection(collection);
@@ -434,7 +442,8 @@ class NeDbDatabase extends AbstractMutexer_1.default {
434
442
  const currentIndexes = await this.getUniqueIndexes(collectionName);
435
443
  const extraIndexes = (0, differenceWith_1.default)(currentIndexes, indexes, isEqual_1.default);
436
444
  for (const index of indexes) {
437
- if (!this.doesIndexExist(currentIndexes, index)) {
445
+ const { fields } = (0, normalizeIndex_1.default)(index);
446
+ if (!this.doesIndexExist(currentIndexes, fields)) {
438
447
  try {
439
448
  await this.createUniqueIndex(collectionName, index);
440
449
  }
@@ -0,0 +1,5 @@
1
+ import { IndexWithFilter } from '../types/database.types';
2
+ export default function normalizeIndex(index: string[] | IndexWithFilter): {
3
+ fields: string[];
4
+ filter: Record<string, any> | undefined;
5
+ };
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function normalizeIndex(index) {
4
+ const fields = Array.isArray(index) ? index : index.fields;
5
+ const filter = Array.isArray(index) ? undefined : index.filter;
6
+ return { fields, filter };
7
+ }
8
+ exports.default = normalizeIndex;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const error_1 = __importDefault(require("@sprucelabs/error"));
7
7
  class SpruceError extends error_1.default {
8
8
  friendlyMessage() {
9
- var _a, _b, _c, _d;
9
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
10
10
  const { options } = this;
11
11
  let message;
12
12
  switch (options === null || options === void 0 ? void 0 : options.code) {
@@ -25,7 +25,7 @@ class SpruceError extends error_1.default {
25
25
  return `One or more ${options.collectionName} already have the ${duplicates.join(' and ')} you provided.`;
26
26
  }
27
27
  case 'UNKNOWN_STORE_ERROR':
28
- message = `An unknown error occurred in the ${options.storeName} store. The original error is: \n\n${(_a = options.originalError) === null || _a === void 0 ? void 0 : _a.stack}`;
28
+ message = `An unknown error occurred in the ${options.storeName} store. The original error is: \n\n${(_b = (_a = options.originalError) === null || _a === void 0 ? void 0 : _a.stack) !== null && _b !== void 0 ? _b : (_c = options.originalError) === null || _c === void 0 ? void 0 : _c.message}`;
29
29
  break;
30
30
  case 'RECORD_NOT_FOUND':
31
31
  message = `I looked through all ${options.storeName} and couldn't find what you're looking for.`;
@@ -38,7 +38,7 @@ class SpruceError extends error_1.default {
38
38
  break;
39
39
  case 'INVALID_STORE':
40
40
  message =
41
- (_b = options.friendlyMessage) !== null && _b !== void 0 ? _b : `The store you passed does not extend AbstractStore!`;
41
+ (_d = options.friendlyMessage) !== null && _d !== void 0 ? _d : `The store you passed does not extend AbstractStore!`;
42
42
  break;
43
43
  case 'INVALID_STORE_NAME':
44
44
  message = `I couldn't find a store named '${options.suppliedName}'. `;
@@ -98,7 +98,7 @@ If you are on a mac, using brew is recommended: https://brew.sh`;
98
98
  message = 'Unknown error!';
99
99
  break;
100
100
  case 'DUPLICATE_KEY':
101
- message = `An index was trying to be created that exists. Original error is:\n\n${(_c = options.friendlyMessage) !== null && _c !== void 0 ? _c : '**missing**'}`;
101
+ message = `An index was trying to be created that exists. Original error is:\n\n${(_g = (_e = options.friendlyMessage) !== null && _e !== void 0 ? _e : (_f = options.originalError) === null || _f === void 0 ? void 0 : _f.stack) !== null && _g !== void 0 ? _g : (_h = options.originalError) === null || _h === void 0 ? void 0 : _h.message}`;
102
102
  break;
103
103
  case 'INVALID_CONNECTION_STRING_SCHEME':
104
104
  message = `There is no database adapter setup for: ${options.connectionString.split('://')[0]}`;
@@ -115,7 +115,7 @@ If you are on a mac, using brew is recommended: https://brew.sh`;
115
115
  default:
116
116
  message = super.friendlyMessage();
117
117
  }
118
- const fullMessage = (_d = options.friendlyMessage) !== null && _d !== void 0 ? _d : message;
118
+ const fullMessage = (_j = options.friendlyMessage) !== null && _j !== void 0 ? _j : message;
119
119
  return fullMessage;
120
120
  }
121
121
  }
@@ -1,5 +1,5 @@
1
1
  import { MongoClientOptions, MongoClient } from 'mongodb';
2
- import { Database, DatabaseOptions } from '../types/database.types';
2
+ import { Database, DatabaseOptions, IndexWithFilter, UniqueIndex } from '../types/database.types';
3
3
  import { QueryOptions } from '../types/query.types';
4
4
  export declare const MONGO_TEST_URI = "mongodb://localhost:27017";
5
5
  export default class MongoDatabase implements Database {
@@ -33,8 +33,9 @@ export default class MongoDatabase implements Database {
33
33
  private assertIndexDoesNotExist;
34
34
  private doesIndexExist;
35
35
  syncIndexes(collectionName: string, indexes: string[][]): Promise<void>;
36
- createUniqueIndex(collection: string, fields: string[]): Promise<void>;
37
- syncUniqueIndexes(collectionName: string, indexes: string[][]): Promise<void>;
36
+ createUniqueIndex(collection: string, index: string[] | IndexWithFilter): Promise<void>;
37
+ private normalizeIndex;
38
+ syncUniqueIndexes(collectionName: string, indexes: UniqueIndex[]): Promise<void>;
38
39
  update(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<number>;
39
40
  updateOne(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<Record<string, any>>;
40
41
  upsertOne(collection: string, query: Record<string, any>, updates: Record<string, any>): Promise<Record<string, any>>;
@@ -21,10 +21,11 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  import { buildLog } from '@sprucelabs/spruce-skill-utils';
22
22
  import differenceWith from 'lodash/differenceWith.js';
23
23
  import isEqual from 'lodash/isEqual.js';
24
- import { MongoClient, MongoError } from 'mongodb';
24
+ import { MongoClient, MongoError, } from 'mongodb';
25
25
  import SpruceError from '../errors/SpruceError.js';
26
26
  import generateId from '../utilities/generateId.js';
27
27
  import mongoUtil from '../utilities/mongo.utility.js';
28
+ import normalizeIndex from './normalizeIndex.js';
28
29
  export const MONGO_TEST_URI = 'mongodb://localhost:27017';
29
30
  export default class MongoDatabase {
30
31
  constructor(url, options) {
@@ -354,7 +355,8 @@ export default class MongoDatabase {
354
355
  }
355
356
  doesIndexExist(currentIndexes, fields) {
356
357
  for (const index of currentIndexes !== null && currentIndexes !== void 0 ? currentIndexes : []) {
357
- if (isEqual(index, fields)) {
358
+ const { fields: normalizedFields } = this.normalizeIndex(index);
359
+ if (isEqual(normalizedFields, fields)) {
358
360
  return true;
359
361
  }
360
362
  }
@@ -374,18 +376,23 @@ export default class MongoDatabase {
374
376
  }
375
377
  });
376
378
  }
377
- createUniqueIndex(collection, fields) {
379
+ createUniqueIndex(collection, index) {
378
380
  return __awaiter(this, void 0, void 0, function* () {
379
381
  const currentIndexes = yield this.getUniqueIndexes(collection);
380
- yield this.assertIndexDoesNotExist(currentIndexes, fields, collection);
381
- const index = {};
382
+ const { fields, filter } = this.normalizeIndex(index);
383
+ this.assertIndexDoesNotExist(currentIndexes, fields, collection);
384
+ const created = {};
382
385
  fields.forEach((name) => {
383
- index[name] = 1;
386
+ created[name] = 1;
384
387
  });
385
388
  try {
389
+ const options = { unique: true };
390
+ if (filter) {
391
+ options.partialFilterExpression = filter;
392
+ }
386
393
  yield this.assertDbWhileAttempingTo('create a unique index.', collection)
387
394
  .collection(collection)
388
- .createIndex(index, { unique: true });
395
+ .createIndex(created, options);
389
396
  }
390
397
  catch (err) {
391
398
  if ((err === null || err === void 0 ? void 0 : err.code) === 11000) {
@@ -400,13 +407,26 @@ export default class MongoDatabase {
400
407
  }
401
408
  });
402
409
  }
410
+ normalizeIndex(index) {
411
+ const { fields, filter } = normalizeIndex(index);
412
+ return { fields, filter };
413
+ }
403
414
  syncUniqueIndexes(collectionName, indexes) {
404
415
  return __awaiter(this, void 0, void 0, function* () {
416
+ var _a;
405
417
  const currentIndexes = yield this.getUniqueIndexes(collectionName);
406
418
  const extraIndexes = differenceWith(currentIndexes, indexes, isEqual);
407
419
  for (const index of indexes) {
408
- if (!this.doesIndexExist(currentIndexes, index)) {
409
- yield this.createUniqueIndex(collectionName, index);
420
+ const { fields } = this.normalizeIndex(index);
421
+ if (!this.doesIndexExist(currentIndexes, fields)) {
422
+ try {
423
+ yield this.createUniqueIndex(collectionName, index);
424
+ }
425
+ catch (err) {
426
+ if (((_a = err.options) === null || _a === void 0 ? void 0 : _a.code) !== 'INDEX_EXISTS') {
427
+ throw err;
428
+ }
429
+ }
410
430
  }
411
431
  }
412
432
  for (const extra of extraIndexes) {
@@ -1,5 +1,5 @@
1
1
  import AbstractMutexer from '../mutexers/AbstractMutexer';
2
- import { CreateOptions, Database, DatabaseInternalOptions } from '../types/database.types';
2
+ import { CreateOptions, Database, DatabaseInternalOptions, UniqueIndex } 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,14 @@ 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<string[][]>;
35
- getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<string[][]>;
36
- dropIndex(collection: string, fields: string[]): Promise<void>;
34
+ getUniqueIndexes(collection: string): Promise<UniqueIndex[]>;
35
+ getIndexes(collection: string, shouldIncludeUnique?: boolean): Promise<UniqueIndex[]>;
36
+ dropIndex(collection: string, index: UniqueIndex): Promise<void>;
37
37
  private assertIndexDoesNotExist;
38
38
  private doesIndexExist;
39
- createUniqueIndex(collection: string, fields: string[]): Promise<void>;
39
+ createUniqueIndex(collection: string, index: UniqueIndex): Promise<void>;
40
40
  createIndex(collection: string, fields: string[]): Promise<void>;
41
- syncUniqueIndexes(collectionName: string, indexes: string[][]): Promise<void>;
41
+ syncUniqueIndexes(collectionName: string, indexes: UniqueIndex[]): Promise<void>;
42
42
  syncIndexes(collectionName: string, indexes: string[][]): Promise<void>;
43
43
  query<T>(query: string, params?: any[]): Promise<T[]>;
44
44
  private queryToKey;
@@ -30,6 +30,7 @@ import SpruceError from '../errors/SpruceError.js';
30
30
  import AbstractMutexer from '../mutexers/AbstractMutexer.js';
31
31
  import generateId from '../utilities/generateId.js';
32
32
  import mongoUtil from '../utilities/mongo.utility.js';
33
+ import normalizeIndex from './normalizeIndex.js';
33
34
  dotenv.config();
34
35
  const NULL_PLACEHOLDER = '_____NULL_____';
35
36
  const UNDEFINED_PLACEHOLDER = '_____UNDEFINED_____';
@@ -327,7 +328,11 @@ export default class NeDbDatabase extends AbstractMutexer {
327
328
  const col = this.loadCollection(collection);
328
329
  yield this.randomDelay();
329
330
  if (col._uniqueIndexes) {
330
- for (const fields of col._uniqueIndexes) {
331
+ for (const index of col._uniqueIndexes) {
332
+ const { fields, filter } = normalizeIndex(index);
333
+ if (filter) {
334
+ return;
335
+ }
331
336
  const existing = query
332
337
  ? yield this.findOne(collection, query)
333
338
  : null;
@@ -373,10 +378,11 @@ export default class NeDbDatabase extends AbstractMutexer {
373
378
  return (_c = col._indexes) !== null && _c !== void 0 ? _c : [];
374
379
  });
375
380
  }
376
- dropIndex(collection, fields) {
381
+ dropIndex(collection, index) {
377
382
  return __awaiter(this, void 0, void 0, function* () {
378
383
  var _a, _b;
379
384
  const col = this.loadCollection(collection);
385
+ const { fields } = normalizeIndex(index);
380
386
  yield this.randomDelay();
381
387
  let found = false;
382
388
  let newIndexes = [];
@@ -431,19 +437,21 @@ export default class NeDbDatabase extends AbstractMutexer {
431
437
  }
432
438
  return false;
433
439
  }
434
- createUniqueIndex(collection, fields) {
440
+ createUniqueIndex(collection, index) {
435
441
  return __awaiter(this, void 0, void 0, function* () {
436
442
  const col = this.loadCollection(collection);
437
443
  if (!col._uniqueIndexes) {
438
444
  col._uniqueIndexes = [];
439
445
  }
446
+ const { fields, filter } = normalizeIndex(index);
440
447
  yield this.randomDelay();
441
448
  this.assertIndexDoesNotExist(col._uniqueIndexes, fields, collection);
442
- if (col._uniqueIndexes) {
449
+ if (col._uniqueIndexes && !filter) {
443
450
  const tempUniqueIndexes = [...col._uniqueIndexes];
444
451
  tempUniqueIndexes.push(fields);
445
- const documents = (yield this.find(collection, {})) || [];
446
- for (const uniqueFields of tempUniqueIndexes) {
452
+ const documents = (yield this.find(collection)) || [];
453
+ for (const index of tempUniqueIndexes) {
454
+ const { fields: uniqueFields } = normalizeIndex(index);
447
455
  let parsedExisting = [];
448
456
  for (const doc of documents) {
449
457
  const tempDoc = {};
@@ -456,12 +464,12 @@ export default class NeDbDatabase extends AbstractMutexer {
456
464
  if (parsedExisting.length != uniqued.length) {
457
465
  throw new SpruceError({
458
466
  code: 'DUPLICATE_KEY',
459
- friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${fields.join(',')}"`,
467
+ friendlyMessage: `Could not create index! Unique index on '${collection}' has duplicate key for "${uniqueFields.join(',')}"`,
460
468
  });
461
469
  }
462
470
  }
463
471
  }
464
- col._uniqueIndexes.push(fields);
472
+ col._uniqueIndexes.push(index);
465
473
  });
466
474
  }
467
475
  createIndex(collection, fields) {
@@ -481,7 +489,8 @@ export default class NeDbDatabase extends AbstractMutexer {
481
489
  const currentIndexes = yield this.getUniqueIndexes(collectionName);
482
490
  const extraIndexes = differenceWith(currentIndexes, indexes, isEqual);
483
491
  for (const index of indexes) {
484
- if (!this.doesIndexExist(currentIndexes, index)) {
492
+ const { fields } = normalizeIndex(index);
493
+ if (!this.doesIndexExist(currentIndexes, fields)) {
485
494
  try {
486
495
  yield this.createUniqueIndex(collectionName, index);
487
496
  }
@@ -0,0 +1,5 @@
1
+ import { IndexWithFilter } from '../types/database.types';
2
+ export default function normalizeIndex(index: string[] | IndexWithFilter): {
3
+ fields: string[];
4
+ filter: Record<string, any> | undefined;
5
+ };
@@ -0,0 +1,5 @@
1
+ export default function normalizeIndex(index) {
2
+ const fields = Array.isArray(index) ? index : index.fields;
3
+ const filter = Array.isArray(index) ? undefined : index.filter;
4
+ return { fields, filter };
5
+ }
@@ -1,7 +1,7 @@
1
1
  import AbstractSpruceError from '@sprucelabs/error';
2
2
  export default class SpruceError extends AbstractSpruceError {
3
3
  friendlyMessage() {
4
- var _a, _b, _c, _d;
4
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
5
5
  const { options } = this;
6
6
  let message;
7
7
  switch (options === null || options === void 0 ? void 0 : options.code) {
@@ -20,7 +20,7 @@ export default class SpruceError extends AbstractSpruceError {
20
20
  return `One or more ${options.collectionName} already have the ${duplicates.join(' and ')} you provided.`;
21
21
  }
22
22
  case 'UNKNOWN_STORE_ERROR':
23
- message = `An unknown error occurred in the ${options.storeName} store. The original error is: \n\n${(_a = options.originalError) === null || _a === void 0 ? void 0 : _a.stack}`;
23
+ message = `An unknown error occurred in the ${options.storeName} store. The original error is: \n\n${(_b = (_a = options.originalError) === null || _a === void 0 ? void 0 : _a.stack) !== null && _b !== void 0 ? _b : (_c = options.originalError) === null || _c === void 0 ? void 0 : _c.message}`;
24
24
  break;
25
25
  case 'RECORD_NOT_FOUND':
26
26
  message = `I looked through all ${options.storeName} and couldn't find what you're looking for.`;
@@ -33,7 +33,7 @@ export default class SpruceError extends AbstractSpruceError {
33
33
  break;
34
34
  case 'INVALID_STORE':
35
35
  message =
36
- (_b = options.friendlyMessage) !== null && _b !== void 0 ? _b : `The store you passed does not extend AbstractStore!`;
36
+ (_d = options.friendlyMessage) !== null && _d !== void 0 ? _d : `The store you passed does not extend AbstractStore!`;
37
37
  break;
38
38
  case 'INVALID_STORE_NAME':
39
39
  message = `I couldn't find a store named '${options.suppliedName}'. `;
@@ -93,7 +93,7 @@ If you are on a mac, using brew is recommended: https://brew.sh`;
93
93
  message = 'Unknown error!';
94
94
  break;
95
95
  case 'DUPLICATE_KEY':
96
- message = `An index was trying to be created that exists. Original error is:\n\n${(_c = options.friendlyMessage) !== null && _c !== void 0 ? _c : '**missing**'}`;
96
+ message = `An index was trying to be created that exists. Original error is:\n\n${(_g = (_e = options.friendlyMessage) !== null && _e !== void 0 ? _e : (_f = options.originalError) === null || _f === void 0 ? void 0 : _f.stack) !== null && _g !== void 0 ? _g : (_h = options.originalError) === null || _h === void 0 ? void 0 : _h.message}`;
97
97
  break;
98
98
  case 'INVALID_CONNECTION_STRING_SCHEME':
99
99
  message = `There is no database adapter setup for: ${options.connectionString.split('://')[0]}`;
@@ -110,7 +110,7 @@ If you are on a mac, using brew is recommended: https://brew.sh`;
110
110
  default:
111
111
  message = super.friendlyMessage();
112
112
  }
113
- const fullMessage = (_d = options.friendlyMessage) !== null && _d !== void 0 ? _d : message;
113
+ const fullMessage = (_j = options.friendlyMessage) !== null && _j !== void 0 ? _j : message;
114
114
  return fullMessage;
115
115
  }
116
116
  }
@@ -3,7 +3,7 @@ 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<UniqueIndex[] | Index[]>;
6
+ _getFilteredIndexes(db: Database): Promise<string[][]>;
7
7
  _filterIdIndex(allIndexes: UniqueIndex[] | Index[]): UniqueIndex[] | Index[];
8
8
  _assertUpdateUpdatedRightNumberOfRecords(db: Database, search: Record<string, any>, updates: Record<string, any>, expectedUpdateCount: number): Promise<void>;
9
9
  generateIdDifferentEachTime(connect: TestConnect): Promise<void>;
@@ -63,6 +63,7 @@ declare const databaseAssertUtil: {
63
63
  assertCanUpsertNull(connect: TestConnect): Promise<void>;
64
64
  assertCanSaveAndGetNullAndUndefined(connect: TestConnect): Promise<void>;
65
65
  assertSyncIndexesDoesNotRemoveExisting(connect: TestConnect): Promise<void>;
66
+ assertCanSyncUniqueIndexesWithFilterExpression(connect: TestConnect): Promise<void>;
66
67
  assertSyncIndexesRemovesExtraIndexes(connect: TestConnect): Promise<void>;
67
68
  assertSyncIndexesSkipsExisting(connect: TestConnect): Promise<void>;
68
69
  assertCantDropCompoundIndexThatDoesNotExist(connect: TestConnect): Promise<void>;
@@ -49,6 +49,7 @@ const databaseAssertUtil = {
49
49
  'assertCanUpsertOne',
50
50
  'assertCanUpsertNull',
51
51
  'assertCanPushToArrayOnUpsert',
52
+ 'assertCanSyncUniqueIndexesWithFilterExpression',
52
53
  //finding
53
54
  'assertEmptyDatabaseReturnsEmptyArray',
54
55
  'assertFindOneOnEmptyDatabaseReturnsNull',
@@ -122,6 +123,7 @@ const databaseAssertUtil = {
122
123
  });
123
124
  },
124
125
  _filterIdIndex(allIndexes) {
126
+ //@ts-ignore
125
127
  return allIndexes.filter((i) => i[0] !== '_id');
126
128
  },
127
129
  _assertUpdateUpdatedRightNumberOfRecords(db, search, updates, expectedUpdateCount) {
@@ -243,19 +245,19 @@ const databaseAssertUtil = {
243
245
  return __awaiter(this, void 0, void 0, function* () {
244
246
  const db = yield connectToDabatase(connect);
245
247
  yield db.createUniqueIndex(this.collectionName, ['uniqueField']);
246
- let indexes = yield db.getUniqueIndexes(this.collectionName);
248
+ let indexes = (yield db.getUniqueIndexes(this.collectionName));
247
249
  assert.isLength(indexes, 1, 'getUniqueIndexes() did not return the unique index I tried to create!');
248
250
  assert.isLength(indexes[0], 1, 'getUniqueIndexes() needs to return an array of arrays. Each item in the array should be an array of fields that make up the unique index.');
249
251
  assert.isEqual(indexes[0][0].toLowerCase(), 'uniqueField'.toLowerCase(), 'getUniqueIndexes() did not add the expected field to the first unique index.');
250
252
  yield db.createUniqueIndex(this.collectionName, ['uniqueField2']);
251
- indexes = yield db.getUniqueIndexes(this.collectionName);
253
+ indexes = (yield db.getUniqueIndexes(this.collectionName));
252
254
  assert.isLength(indexes, 2);
253
255
  assert.isEqual(indexes[1][0].toLowerCase(), 'uniqueField2'.toLowerCase());
254
256
  yield db.createUniqueIndex(this.collectionName, [
255
257
  'uniqueField3',
256
258
  'uniqueField4',
257
259
  ]);
258
- indexes = yield db.getUniqueIndexes(this.collectionName);
260
+ indexes = (yield db.getUniqueIndexes(this.collectionName));
259
261
  assert.isLength(indexes, 3);
260
262
  assert.isEqual(indexes[2][0].toLowerCase(), 'uniqueField3'.toLowerCase());
261
263
  assert.isEqual(indexes[2][1].toLowerCase(), 'uniqueField4'.toLowerCase());
@@ -745,10 +747,10 @@ const databaseAssertUtil = {
745
747
  ['someField'],
746
748
  ['otherField', 'otherField2'],
747
749
  ]);
748
- let indexes = yield db.getUniqueIndexes(this.collectionName);
750
+ let indexes = (yield db.getUniqueIndexes(this.collectionName));
749
751
  assert.isLength(indexes, 3, 'syncUniqueIndexes() should have created 3 indexes.');
750
752
  yield db.syncUniqueIndexes(this.collectionName, [['uniqueField']]);
751
- indexes = yield db.getUniqueIndexes(this.collectionName);
753
+ indexes = (yield db.getUniqueIndexes(this.collectionName));
752
754
  assert.isLength(indexes, 1);
753
755
  assert.isEqual(indexes[0][0].toLowerCase(), 'uniquefield');
754
756
  yield this.shutdown(db);
@@ -1446,6 +1448,87 @@ const databaseAssertUtil = {
1446
1448
  yield this.shutdown(db);
1447
1449
  });
1448
1450
  },
1451
+ assertCanSyncUniqueIndexesWithFilterExpression(connect) {
1452
+ return __awaiter(this, void 0, void 0, function* () {
1453
+ var _a, _b, _c, _d;
1454
+ const db = yield connectToDabatase(connect);
1455
+ try {
1456
+ yield db.syncUniqueIndexes(this.collectionName, [
1457
+ {
1458
+ fields: ['username', 'dateScrambled'],
1459
+ filter: {
1460
+ username: { $exists: true },
1461
+ },
1462
+ },
1463
+ ]);
1464
+ }
1465
+ catch (err) {
1466
+ assert.fail((_a = `syncUniqueIndexes() should not have thrown an error when syncing a unique index with a filter expression.\n\n` +
1467
+ err.stack) !== null && _a !== void 0 ? _a : err.message);
1468
+ }
1469
+ try {
1470
+ yield db.createOne(this.collectionName, {
1471
+ username: 'test',
1472
+ phone: null,
1473
+ dateScrambled: 'test',
1474
+ });
1475
+ }
1476
+ catch (err) {
1477
+ assert.fail((_b = `Creating a record should not have thrown an error after syncing a unique index with a filter expression.\n\n` +
1478
+ err.stack) !== null && _b !== void 0 ? _b : err.message);
1479
+ }
1480
+ yield assert.doesThrowAsync(() => db.createOne(this.collectionName, {
1481
+ username: 'test',
1482
+ phone: null,
1483
+ dateScrambled: 'test',
1484
+ }));
1485
+ yield db.createOne(this.collectionName, {
1486
+ phone: '555-000-0000',
1487
+ dateScrambled: 'test',
1488
+ });
1489
+ yield db.createOne(this.collectionName, {
1490
+ phone: '555-000-0001',
1491
+ dateScrambled: 'test',
1492
+ });
1493
+ try {
1494
+ yield db.syncUniqueIndexes(this.collectionName, [
1495
+ {
1496
+ fields: ['username', 'dateScrambled'],
1497
+ filter: {
1498
+ username: { $exists: true },
1499
+ },
1500
+ },
1501
+ {
1502
+ fields: ['phone', 'dateScrambled'],
1503
+ filter: {
1504
+ phone: { $exists: true, $type: 'string' },
1505
+ },
1506
+ },
1507
+ ]);
1508
+ }
1509
+ catch (err) {
1510
+ assert.fail((_c = `syncUniqueIndexes() should not have thrown an error when syncing multiple unique indexes with filter expressions.\n\n` +
1511
+ err.stack) !== null && _c !== void 0 ? _c : err.message);
1512
+ }
1513
+ yield db.createOne(this.collectionName, {
1514
+ username: 'test',
1515
+ phone: null,
1516
+ dateScrambled: 'next',
1517
+ });
1518
+ try {
1519
+ yield db.createOne(this.collectionName, {
1520
+ username: 'test2',
1521
+ phone: null,
1522
+ dateScrambled: 'next',
1523
+ });
1524
+ }
1525
+ catch (err) {
1526
+ assert.fail((_d = `createOne() should not throw if index has filter { \$exists: true, \$type: 'string' }.\n\n` +
1527
+ err.stack) !== null && _d !== void 0 ? _d : err.message);
1528
+ }
1529
+ yield this.shutdown(db);
1530
+ });
1531
+ },
1449
1532
  assertSyncIndexesRemovesExtraIndexes(connect) {
1450
1533
  return __awaiter(this, void 0, void 0, function* () {
1451
1534
  const db = yield connectToDabatase(connect);
@@ -1,2 +1,2 @@
1
1
  import databaseAssertUtil from './databaseAssertUtil';
2
- export default function pluckAssertionMethods(util: typeof databaseAssertUtil): ("collectionName" | "assertThrowsWithInvalidConnectionString" | "assertThrowsWhenCantConnect" | "assertThrowsWithBadDatabaseName" | "assertEmptyDatabaseReturnsEmptyArray" | "assertKnowsIfConnectionClosed" | "assertFindOneOnEmptyDatabaseReturnsNull" | "assertCanSortDesc" | "assertCanSortAsc" | "assertCanSortById" | "assertCanQueryWithOr" | "generateIdDifferentEachTime" | "assertInsertingGeneratesId" | "assertCanCreateMany" | "assertCanLimitResults" | "assertCanCreateWithObjectField" | "assertCanCountOnId" | "assertCanCount" | "assertThrowsWhenUpdatingRecordNotFound" | "assertCanUpdate" | "assertCanUpdateMany" | "assertCanPushOntoArrayValue" | "assertCanUpdateWithObjectField" | "assertCanUpdateFieldInObjectFieldWithTargettedWhere" | "assertCanSaveAndGetNullAndUndefined" | "assertCanUpsertOne" | "assertCanUpsertNull" | "assertCanPushToArrayOnUpsert" | "assertCanLimitResultsToZero" | "assertCanFindWithBooleanField" | "assertCanQueryByGtLtGteLteNe" | "assertCanQueryPathWithDotSyntax" | "assertCanReturnOnlySelectFields" | "assertCanSearchByRegex" | "assertCanFindWithNe" | "assertCanFindWithIn" | "assertCanDeleteRecord" | "assertCanDeleteOne" | "assertHasNoUniqueIndexToStart" | "assertCanCreateUniqueIndex" | "assertCanCreateMultiFieldUniqueIndex" | "assertCantCreateUniqueIndexTwice" | "assertCanDropUniqueIndex" | "assertCanDropCompoundUniqueIndex" | "assertCantDropUniqueIndexThatDoesntExist" | "assertCantDropIndexWhenNoIndexExists" | "assertCantDropCompoundUniqueIndexThatDoesntExist" | "assertSyncingUniqueIndexsAddsMissingIndexes" | "assertSyncingUniqueIndexsSkipsExistingIndexs" | "assertSyncingUniqueIndexesRemovesExtraIndexes" | "assertSyncingUniqueIndexesIsRaceProof" | "assertSyncingIndexesDoesNotAddAndRemove" | "assertUniqueIndexBlocksDuplicates" | "assertDuplicateKeyThrowsOnInsert" | "assertSettingUniqueIndexViolationThrowsSpruceError" | "assertCanCreateUniqueIndexOnNestedField" | "assertUpsertWithUniqueIndex" | "assertNestedFieldIndexUpdates" | "assertHasNoIndexToStart" | "assertCanCreateIndex" | "assertCantCreateSameIndexTwice" | "assertCanCreateMultiFieldIndex" | "assertCanDropIndex" | "assertCanDropCompoundIndex" | "assertCantDropCompoundIndexThatDoesNotExist" | "assertSyncIndexesSkipsExisting" | "assertSyncIndexesRemovesExtraIndexes" | "assertSyncIndexesHandlesRaceConditions" | "assertSyncIndexesDoesNotRemoveExisting" | "assertDuplicateFieldsWithMultipleUniqueIndexesWorkAsExpected" | "runSuite" | "_getFilteredIndexes" | "_filterIdIndex" | "_assertUpdateUpdatedRightNumberOfRecords" | "shutdown" | "_assertThrowsExpectedNotFoundOnUpdateOne" | "_assert$orReturnsExpectedTotalRecords" | "_assertCanCreateMultiFieldIndex" | "assertHasLowerCaseToCamelCaseMappingEnabled")[];
2
+ export default function pluckAssertionMethods(util: typeof databaseAssertUtil): ("collectionName" | "assertThrowsWithInvalidConnectionString" | "assertThrowsWhenCantConnect" | "assertThrowsWithBadDatabaseName" | "assertEmptyDatabaseReturnsEmptyArray" | "assertKnowsIfConnectionClosed" | "assertFindOneOnEmptyDatabaseReturnsNull" | "assertCanSortDesc" | "assertCanSortAsc" | "assertCanSortById" | "assertCanQueryWithOr" | "generateIdDifferentEachTime" | "assertInsertingGeneratesId" | "assertCanCreateMany" | "assertCanLimitResults" | "assertCanCreateWithObjectField" | "assertCanCountOnId" | "assertCanCount" | "assertThrowsWhenUpdatingRecordNotFound" | "assertCanUpdate" | "assertCanUpdateMany" | "assertCanPushOntoArrayValue" | "assertCanUpdateWithObjectField" | "assertCanUpdateFieldInObjectFieldWithTargettedWhere" | "assertCanSaveAndGetNullAndUndefined" | "assertCanUpsertOne" | "assertCanUpsertNull" | "assertCanPushToArrayOnUpsert" | "assertCanSyncUniqueIndexesWithFilterExpression" | "assertCanLimitResultsToZero" | "assertCanFindWithBooleanField" | "assertCanQueryByGtLtGteLteNe" | "assertCanQueryPathWithDotSyntax" | "assertCanReturnOnlySelectFields" | "assertCanSearchByRegex" | "assertCanFindWithNe" | "assertCanFindWithIn" | "assertCanDeleteRecord" | "assertCanDeleteOne" | "assertHasNoUniqueIndexToStart" | "assertCanCreateUniqueIndex" | "assertCanCreateMultiFieldUniqueIndex" | "assertCantCreateUniqueIndexTwice" | "assertCanDropUniqueIndex" | "assertCanDropCompoundUniqueIndex" | "assertCantDropUniqueIndexThatDoesntExist" | "assertCantDropIndexWhenNoIndexExists" | "assertCantDropCompoundUniqueIndexThatDoesntExist" | "assertSyncingUniqueIndexsAddsMissingIndexes" | "assertSyncingUniqueIndexsSkipsExistingIndexs" | "assertSyncingUniqueIndexesRemovesExtraIndexes" | "assertSyncingUniqueIndexesIsRaceProof" | "assertSyncingIndexesDoesNotAddAndRemove" | "assertUniqueIndexBlocksDuplicates" | "assertDuplicateKeyThrowsOnInsert" | "assertSettingUniqueIndexViolationThrowsSpruceError" | "assertCanCreateUniqueIndexOnNestedField" | "assertUpsertWithUniqueIndex" | "assertNestedFieldIndexUpdates" | "assertHasNoIndexToStart" | "assertCanCreateIndex" | "assertCantCreateSameIndexTwice" | "assertCanCreateMultiFieldIndex" | "assertCanDropIndex" | "assertCanDropCompoundIndex" | "assertCantDropCompoundIndexThatDoesNotExist" | "assertSyncIndexesSkipsExisting" | "assertSyncIndexesRemovesExtraIndexes" | "assertSyncIndexesHandlesRaceConditions" | "assertSyncIndexesDoesNotRemoveExisting" | "assertDuplicateFieldsWithMultipleUniqueIndexesWorkAsExpected" | "runSuite" | "_getFilteredIndexes" | "_filterIdIndex" | "_assertUpdateUpdatedRightNumberOfRecords" | "shutdown" | "_assertThrowsExpectedNotFoundOnUpdateOne" | "_assert$orReturnsExpectedTotalRecords" | "_assertCanCreateMultiFieldIndex" | "assertHasLowerCaseToCamelCaseMappingEnabled")[];
@@ -38,7 +38,11 @@ export type TestConnect = (connectionString?: string, dbName?: string) => Promis
38
38
  connectionStringWithRandomBadDatabaseName: string;
39
39
  badDatabaseName: string;
40
40
  }>;
41
- export type UniqueIndex = string[];
41
+ export interface IndexWithFilter {
42
+ fields: string[];
43
+ filter: Record<string, any>;
44
+ }
45
+ export type UniqueIndex = string[] | IndexWithFilter;
42
46
  export type Index = string[];
43
47
  export interface CreateOptions extends DatabaseInternalOptions {
44
48
  }
@@ -3,7 +3,7 @@ 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<UniqueIndex[] | Index[]>;
6
+ _getFilteredIndexes(db: Database): Promise<string[][]>;
7
7
  _filterIdIndex(allIndexes: UniqueIndex[] | Index[]): UniqueIndex[] | Index[];
8
8
  _assertUpdateUpdatedRightNumberOfRecords(db: Database, search: Record<string, any>, updates: Record<string, any>, expectedUpdateCount: number): Promise<void>;
9
9
  generateIdDifferentEachTime(connect: TestConnect): Promise<void>;
@@ -63,6 +63,7 @@ declare const databaseAssertUtil: {
63
63
  assertCanUpsertNull(connect: TestConnect): Promise<void>;
64
64
  assertCanSaveAndGetNullAndUndefined(connect: TestConnect): Promise<void>;
65
65
  assertSyncIndexesDoesNotRemoveExisting(connect: TestConnect): Promise<void>;
66
+ assertCanSyncUniqueIndexesWithFilterExpression(connect: TestConnect): Promise<void>;
66
67
  assertSyncIndexesRemovesExtraIndexes(connect: TestConnect): Promise<void>;
67
68
  assertSyncIndexesSkipsExisting(connect: TestConnect): Promise<void>;
68
69
  assertCantDropCompoundIndexThatDoesNotExist(connect: TestConnect): Promise<void>;
@@ -44,6 +44,7 @@ const databaseAssertUtil = {
44
44
  'assertCanUpsertOne',
45
45
  'assertCanUpsertNull',
46
46
  'assertCanPushToArrayOnUpsert',
47
+ 'assertCanSyncUniqueIndexesWithFilterExpression',
47
48
  //finding
48
49
  'assertEmptyDatabaseReturnsEmptyArray',
49
50
  'assertFindOneOnEmptyDatabaseReturnsNull',
@@ -114,6 +115,7 @@ const databaseAssertUtil = {
114
115
  return this._filterIdIndex(await db.getIndexes(this.collectionName));
115
116
  },
116
117
  _filterIdIndex(allIndexes) {
118
+ //@ts-ignore
117
119
  return allIndexes.filter((i) => i[0] !== '_id');
118
120
  },
119
121
  async _assertUpdateUpdatedRightNumberOfRecords(db, search, updates, expectedUpdateCount) {
@@ -220,19 +222,19 @@ const databaseAssertUtil = {
220
222
  async assertCanCreateUniqueIndex(connect) {
221
223
  const db = await connectToDabatase(connect);
222
224
  await db.createUniqueIndex(this.collectionName, ['uniqueField']);
223
- let indexes = await db.getUniqueIndexes(this.collectionName);
225
+ let indexes = (await db.getUniqueIndexes(this.collectionName));
224
226
  test_utils_1.assert.isLength(indexes, 1, 'getUniqueIndexes() did not return the unique index I tried to create!');
225
227
  test_utils_1.assert.isLength(indexes[0], 1, 'getUniqueIndexes() needs to return an array of arrays. Each item in the array should be an array of fields that make up the unique index.');
226
228
  test_utils_1.assert.isEqual(indexes[0][0].toLowerCase(), 'uniqueField'.toLowerCase(), 'getUniqueIndexes() did not add the expected field to the first unique index.');
227
229
  await db.createUniqueIndex(this.collectionName, ['uniqueField2']);
228
- indexes = await db.getUniqueIndexes(this.collectionName);
230
+ indexes = (await db.getUniqueIndexes(this.collectionName));
229
231
  test_utils_1.assert.isLength(indexes, 2);
230
232
  test_utils_1.assert.isEqual(indexes[1][0].toLowerCase(), 'uniqueField2'.toLowerCase());
231
233
  await db.createUniqueIndex(this.collectionName, [
232
234
  'uniqueField3',
233
235
  'uniqueField4',
234
236
  ]);
235
- indexes = await db.getUniqueIndexes(this.collectionName);
237
+ indexes = (await db.getUniqueIndexes(this.collectionName));
236
238
  test_utils_1.assert.isLength(indexes, 3);
237
239
  test_utils_1.assert.isEqual(indexes[2][0].toLowerCase(), 'uniqueField3'.toLowerCase());
238
240
  test_utils_1.assert.isEqual(indexes[2][1].toLowerCase(), 'uniqueField4'.toLowerCase());
@@ -678,10 +680,10 @@ const databaseAssertUtil = {
678
680
  ['someField'],
679
681
  ['otherField', 'otherField2'],
680
682
  ]);
681
- let indexes = await db.getUniqueIndexes(this.collectionName);
683
+ let indexes = (await db.getUniqueIndexes(this.collectionName));
682
684
  test_utils_1.assert.isLength(indexes, 3, 'syncUniqueIndexes() should have created 3 indexes.');
683
685
  await db.syncUniqueIndexes(this.collectionName, [['uniqueField']]);
684
- indexes = await db.getUniqueIndexes(this.collectionName);
686
+ indexes = (await db.getUniqueIndexes(this.collectionName));
685
687
  test_utils_1.assert.isLength(indexes, 1);
686
688
  test_utils_1.assert.isEqual(indexes[0][0].toLowerCase(), 'uniquefield');
687
689
  await this.shutdown(db);
@@ -1322,6 +1324,85 @@ const databaseAssertUtil = {
1322
1324
  ]);
1323
1325
  await this.shutdown(db);
1324
1326
  },
1327
+ async assertCanSyncUniqueIndexesWithFilterExpression(connect) {
1328
+ var _a, _b, _c, _d;
1329
+ const db = await connectToDabatase(connect);
1330
+ try {
1331
+ await db.syncUniqueIndexes(this.collectionName, [
1332
+ {
1333
+ fields: ['username', 'dateScrambled'],
1334
+ filter: {
1335
+ username: { $exists: true },
1336
+ },
1337
+ },
1338
+ ]);
1339
+ }
1340
+ catch (err) {
1341
+ test_utils_1.assert.fail((_a = `syncUniqueIndexes() should not have thrown an error when syncing a unique index with a filter expression.\n\n` +
1342
+ err.stack) !== null && _a !== void 0 ? _a : err.message);
1343
+ }
1344
+ try {
1345
+ await db.createOne(this.collectionName, {
1346
+ username: 'test',
1347
+ phone: null,
1348
+ dateScrambled: 'test',
1349
+ });
1350
+ }
1351
+ catch (err) {
1352
+ test_utils_1.assert.fail((_b = `Creating a record should not have thrown an error after syncing a unique index with a filter expression.\n\n` +
1353
+ err.stack) !== null && _b !== void 0 ? _b : err.message);
1354
+ }
1355
+ await test_utils_1.assert.doesThrowAsync(() => db.createOne(this.collectionName, {
1356
+ username: 'test',
1357
+ phone: null,
1358
+ dateScrambled: 'test',
1359
+ }));
1360
+ await db.createOne(this.collectionName, {
1361
+ phone: '555-000-0000',
1362
+ dateScrambled: 'test',
1363
+ });
1364
+ await db.createOne(this.collectionName, {
1365
+ phone: '555-000-0001',
1366
+ dateScrambled: 'test',
1367
+ });
1368
+ try {
1369
+ await db.syncUniqueIndexes(this.collectionName, [
1370
+ {
1371
+ fields: ['username', 'dateScrambled'],
1372
+ filter: {
1373
+ username: { $exists: true },
1374
+ },
1375
+ },
1376
+ {
1377
+ fields: ['phone', 'dateScrambled'],
1378
+ filter: {
1379
+ phone: { $exists: true, $type: 'string' },
1380
+ },
1381
+ },
1382
+ ]);
1383
+ }
1384
+ catch (err) {
1385
+ test_utils_1.assert.fail((_c = `syncUniqueIndexes() should not have thrown an error when syncing multiple unique indexes with filter expressions.\n\n` +
1386
+ err.stack) !== null && _c !== void 0 ? _c : err.message);
1387
+ }
1388
+ await db.createOne(this.collectionName, {
1389
+ username: 'test',
1390
+ phone: null,
1391
+ dateScrambled: 'next',
1392
+ });
1393
+ try {
1394
+ await db.createOne(this.collectionName, {
1395
+ username: 'test2',
1396
+ phone: null,
1397
+ dateScrambled: 'next',
1398
+ });
1399
+ }
1400
+ catch (err) {
1401
+ test_utils_1.assert.fail((_d = `createOne() should not throw if index has filter { \$exists: true, \$type: 'string' }.\n\n` +
1402
+ err.stack) !== null && _d !== void 0 ? _d : err.message);
1403
+ }
1404
+ await this.shutdown(db);
1405
+ },
1325
1406
  async assertSyncIndexesRemovesExtraIndexes(connect) {
1326
1407
  const db = await connectToDabatase(connect);
1327
1408
  await db.syncIndexes(this.collectionName, [
@@ -1,2 +1,2 @@
1
1
  import databaseAssertUtil from './databaseAssertUtil';
2
- export default function pluckAssertionMethods(util: typeof databaseAssertUtil): ("collectionName" | "assertThrowsWithInvalidConnectionString" | "assertThrowsWhenCantConnect" | "assertThrowsWithBadDatabaseName" | "assertEmptyDatabaseReturnsEmptyArray" | "assertKnowsIfConnectionClosed" | "assertFindOneOnEmptyDatabaseReturnsNull" | "assertCanSortDesc" | "assertCanSortAsc" | "assertCanSortById" | "assertCanQueryWithOr" | "generateIdDifferentEachTime" | "assertInsertingGeneratesId" | "assertCanCreateMany" | "assertCanLimitResults" | "assertCanCreateWithObjectField" | "assertCanCountOnId" | "assertCanCount" | "assertThrowsWhenUpdatingRecordNotFound" | "assertCanUpdate" | "assertCanUpdateMany" | "assertCanPushOntoArrayValue" | "assertCanUpdateWithObjectField" | "assertCanUpdateFieldInObjectFieldWithTargettedWhere" | "assertCanSaveAndGetNullAndUndefined" | "assertCanUpsertOne" | "assertCanUpsertNull" | "assertCanPushToArrayOnUpsert" | "assertCanLimitResultsToZero" | "assertCanFindWithBooleanField" | "assertCanQueryByGtLtGteLteNe" | "assertCanQueryPathWithDotSyntax" | "assertCanReturnOnlySelectFields" | "assertCanSearchByRegex" | "assertCanFindWithNe" | "assertCanFindWithIn" | "assertCanDeleteRecord" | "assertCanDeleteOne" | "assertHasNoUniqueIndexToStart" | "assertCanCreateUniqueIndex" | "assertCanCreateMultiFieldUniqueIndex" | "assertCantCreateUniqueIndexTwice" | "assertCanDropUniqueIndex" | "assertCanDropCompoundUniqueIndex" | "assertCantDropUniqueIndexThatDoesntExist" | "assertCantDropIndexWhenNoIndexExists" | "assertCantDropCompoundUniqueIndexThatDoesntExist" | "assertSyncingUniqueIndexsAddsMissingIndexes" | "assertSyncingUniqueIndexsSkipsExistingIndexs" | "assertSyncingUniqueIndexesRemovesExtraIndexes" | "assertSyncingUniqueIndexesIsRaceProof" | "assertSyncingIndexesDoesNotAddAndRemove" | "assertUniqueIndexBlocksDuplicates" | "assertDuplicateKeyThrowsOnInsert" | "assertSettingUniqueIndexViolationThrowsSpruceError" | "assertCanCreateUniqueIndexOnNestedField" | "assertUpsertWithUniqueIndex" | "assertNestedFieldIndexUpdates" | "assertHasNoIndexToStart" | "assertCanCreateIndex" | "assertCantCreateSameIndexTwice" | "assertCanCreateMultiFieldIndex" | "assertCanDropIndex" | "assertCanDropCompoundIndex" | "assertCantDropCompoundIndexThatDoesNotExist" | "assertSyncIndexesSkipsExisting" | "assertSyncIndexesRemovesExtraIndexes" | "assertSyncIndexesHandlesRaceConditions" | "assertSyncIndexesDoesNotRemoveExisting" | "assertDuplicateFieldsWithMultipleUniqueIndexesWorkAsExpected" | "runSuite" | "_getFilteredIndexes" | "_filterIdIndex" | "_assertUpdateUpdatedRightNumberOfRecords" | "shutdown" | "_assertThrowsExpectedNotFoundOnUpdateOne" | "_assert$orReturnsExpectedTotalRecords" | "_assertCanCreateMultiFieldIndex" | "assertHasLowerCaseToCamelCaseMappingEnabled")[];
2
+ export default function pluckAssertionMethods(util: typeof databaseAssertUtil): ("collectionName" | "assertThrowsWithInvalidConnectionString" | "assertThrowsWhenCantConnect" | "assertThrowsWithBadDatabaseName" | "assertEmptyDatabaseReturnsEmptyArray" | "assertKnowsIfConnectionClosed" | "assertFindOneOnEmptyDatabaseReturnsNull" | "assertCanSortDesc" | "assertCanSortAsc" | "assertCanSortById" | "assertCanQueryWithOr" | "generateIdDifferentEachTime" | "assertInsertingGeneratesId" | "assertCanCreateMany" | "assertCanLimitResults" | "assertCanCreateWithObjectField" | "assertCanCountOnId" | "assertCanCount" | "assertThrowsWhenUpdatingRecordNotFound" | "assertCanUpdate" | "assertCanUpdateMany" | "assertCanPushOntoArrayValue" | "assertCanUpdateWithObjectField" | "assertCanUpdateFieldInObjectFieldWithTargettedWhere" | "assertCanSaveAndGetNullAndUndefined" | "assertCanUpsertOne" | "assertCanUpsertNull" | "assertCanPushToArrayOnUpsert" | "assertCanSyncUniqueIndexesWithFilterExpression" | "assertCanLimitResultsToZero" | "assertCanFindWithBooleanField" | "assertCanQueryByGtLtGteLteNe" | "assertCanQueryPathWithDotSyntax" | "assertCanReturnOnlySelectFields" | "assertCanSearchByRegex" | "assertCanFindWithNe" | "assertCanFindWithIn" | "assertCanDeleteRecord" | "assertCanDeleteOne" | "assertHasNoUniqueIndexToStart" | "assertCanCreateUniqueIndex" | "assertCanCreateMultiFieldUniqueIndex" | "assertCantCreateUniqueIndexTwice" | "assertCanDropUniqueIndex" | "assertCanDropCompoundUniqueIndex" | "assertCantDropUniqueIndexThatDoesntExist" | "assertCantDropIndexWhenNoIndexExists" | "assertCantDropCompoundUniqueIndexThatDoesntExist" | "assertSyncingUniqueIndexsAddsMissingIndexes" | "assertSyncingUniqueIndexsSkipsExistingIndexs" | "assertSyncingUniqueIndexesRemovesExtraIndexes" | "assertSyncingUniqueIndexesIsRaceProof" | "assertSyncingIndexesDoesNotAddAndRemove" | "assertUniqueIndexBlocksDuplicates" | "assertDuplicateKeyThrowsOnInsert" | "assertSettingUniqueIndexViolationThrowsSpruceError" | "assertCanCreateUniqueIndexOnNestedField" | "assertUpsertWithUniqueIndex" | "assertNestedFieldIndexUpdates" | "assertHasNoIndexToStart" | "assertCanCreateIndex" | "assertCantCreateSameIndexTwice" | "assertCanCreateMultiFieldIndex" | "assertCanDropIndex" | "assertCanDropCompoundIndex" | "assertCantDropCompoundIndexThatDoesNotExist" | "assertSyncIndexesSkipsExisting" | "assertSyncIndexesRemovesExtraIndexes" | "assertSyncIndexesHandlesRaceConditions" | "assertSyncIndexesDoesNotRemoveExisting" | "assertDuplicateFieldsWithMultipleUniqueIndexesWorkAsExpected" | "runSuite" | "_getFilteredIndexes" | "_filterIdIndex" | "_assertUpdateUpdatedRightNumberOfRecords" | "shutdown" | "_assertThrowsExpectedNotFoundOnUpdateOne" | "_assert$orReturnsExpectedTotalRecords" | "_assertCanCreateMultiFieldIndex" | "assertHasLowerCaseToCamelCaseMappingEnabled")[];
@@ -38,7 +38,11 @@ export type TestConnect = (connectionString?: string, dbName?: string) => Promis
38
38
  connectionStringWithRandomBadDatabaseName: string;
39
39
  badDatabaseName: string;
40
40
  }>;
41
- export type UniqueIndex = string[];
41
+ export interface IndexWithFilter {
42
+ fields: string[];
43
+ filter: Record<string, any>;
44
+ }
45
+ export type UniqueIndex = string[] | IndexWithFilter;
42
46
  export type Index = string[];
43
47
  export interface CreateOptions extends DatabaseInternalOptions {
44
48
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "28.0.26",
6
+ "version": "28.1.1",
7
7
  "files": [
8
8
  "build/**/*",
9
9
  "!build/__tests__",
@@ -68,24 +68,24 @@
68
68
  },
69
69
  "dependencies": {
70
70
  "@sprucelabs/error": "^6.0.34",
71
- "@sprucelabs/globby": "^2.0.15",
72
- "@sprucelabs/schema": "^30.0.61",
73
- "@sprucelabs/spruce-skill-utils": "^31.0.66",
71
+ "@sprucelabs/globby": "^2.0.16",
72
+ "@sprucelabs/schema": "^30.0.62",
73
+ "@sprucelabs/spruce-skill-utils": "^31.0.67",
74
74
  "just-clone": "^6.2.0",
75
75
  "lodash": "^4.17.21",
76
- "mongodb": "^6.6.2",
76
+ "mongodb": "^6.7.0",
77
77
  "nedb": "^1.8.0"
78
78
  },
79
79
  "devDependencies": {
80
- "@sprucelabs/esm-postbuild": "^6.0.26",
81
- "@sprucelabs/jest-json-reporter": "^8.0.37",
82
- "@sprucelabs/resolve-path-aliases": "^2.0.27",
80
+ "@sprucelabs/esm-postbuild": "^6.0.27",
81
+ "@sprucelabs/jest-json-reporter": "^8.0.38",
82
+ "@sprucelabs/resolve-path-aliases": "^2.0.28",
83
83
  "@sprucelabs/semantic-release": "^5.0.1",
84
84
  "@sprucelabs/test": "^9.0.18",
85
- "@sprucelabs/test-utils": "^5.0.50",
85
+ "@sprucelabs/test-utils": "^5.0.51",
86
86
  "@types/lodash": "^4.17.4",
87
87
  "@types/nedb": "^1.8.16",
88
- "@types/node": "^20.12.12",
88
+ "@types/node": "^20.12.13",
89
89
  "chokidar-cli": "^3.0.0",
90
90
  "concurrently": "^8.2.2",
91
91
  "dotenv": "^16.4.5",