@bedrockio/model 0.2.18 → 0.2.20

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/README.md CHANGED
@@ -11,7 +11,7 @@ Bedrock utilities for model creation.
11
11
  - [Scopes](#scopes)
12
12
  - [Tuples](#tuples)
13
13
  - [Array Extensions](#array-extensions)
14
- - [Features](#features)
14
+ - [Modules](#modules)
15
15
  - [Soft Delete](#soft-delete)
16
16
  - [Validation](#validation)
17
17
  - [Search](#search)
@@ -352,7 +352,7 @@ unfortunately cannot be disambiguated in this case.
352
352
 
353
353
  This will manually create a new nested subschema.
354
354
 
355
- ## Features
355
+ ## Modules
356
356
 
357
357
  ### Soft Delete
358
358
 
@@ -531,8 +531,6 @@ The method takes the following options:
531
531
  - `include` - Allows [include](#includes) based population.
532
532
  - `keyword` - A keyword to perform a [keyword search](#keyword-search).
533
533
  - `ids` - An array of document ids to search on.
534
- - `fields` - Used by [keyword search](#keyword-search). Generally for internal
535
- use.
536
534
 
537
535
  Any other fields passed in will be forwarded to `find`. The return value
538
536
  contains the found documents in `data` and `meta` which contains metadata about
@@ -603,8 +601,8 @@ this feature a `fields` key must be present on the model definition:
603
601
  }
604
602
  ```
605
603
 
606
- This will use the `$or` operator to search on multiple fields. If `fields` is
607
- not defined then a Mongo text query will be attempted:
604
+ This will use the `$or` operator to search on multiple fields. If the model has
605
+ a text index applied, then a Mongo text query will be attempted:
608
606
 
609
607
  ```
610
608
  {
@@ -614,7 +612,118 @@ not defined then a Mongo text query will be attempted:
614
612
  }
615
613
  ```
616
614
 
617
- Note that this will fail unless a text index is defined on the model.
615
+ #### Keyword Field Caching
616
+
617
+ A common problem with search is filtering on fields belonging to foreign models.
618
+ The search module helps to alleviate this issue by allowing a simple way to
619
+ cache foreign fields on the model to allow filtering on them.
620
+
621
+ ```json
622
+ {
623
+ "attributes": {
624
+ "user": {
625
+ "type": "ObjectId",
626
+ "ref": "User"
627
+ }
628
+ },
629
+ "search": {
630
+ "cache": {
631
+ "cachedUserName": {
632
+ "type": "String",
633
+ "path": "user.name"
634
+ }
635
+ },
636
+ "fields": ["cachedUserName"]
637
+ }
638
+ }
639
+ ```
640
+
641
+ The above example is equivalent to creating a field called `cachedUserName` and
642
+ updating it when a document is saved:
643
+
644
+ ```js
645
+ schema.add({
646
+ cachedUserName: 'String',
647
+ });
648
+ schema.pre('save', function () {
649
+ await this.populate('user');
650
+ this.cachedUserName = this.user.name;
651
+ });
652
+ ```
653
+
654
+ Specifying a foreign path in `fields` serves as a shortcut to manually defining
655
+ the cached fields:
656
+
657
+ ```json
658
+ // Equivalent to the above example.
659
+ {
660
+ "attributes": {
661
+ "user": {
662
+ "type": "ObjectId",
663
+ "ref": "User"
664
+ }
665
+ },
666
+ "search": {
667
+ "fields": ["user.name"]
668
+ }
669
+ }
670
+ ```
671
+
672
+ ##### Syncing Search Fields
673
+
674
+ When first applying or making changes to defined cached search fields, existing
675
+ documents will be out of sync. The static method `syncSearchFields` is provided
676
+ to synchronize them:
677
+
678
+ ```js
679
+ // Find and update any documents that do not have
680
+ // existing cached fields. Generally called when
681
+ // adding a cached field.
682
+ await Model.syncSearchFields();
683
+
684
+ // Force an update on ALL documents to resync their
685
+ // cached fields. Generally called to force a cache
686
+ // refresh.
687
+ await Model.syncSearchFields({
688
+ force: true,
689
+ });
690
+ ```
691
+
692
+ ##### Lazy Cached Fields
693
+
694
+ Cached fields can be made lazy:
695
+
696
+ ```json
697
+ {
698
+ "attributes": {
699
+ "user": {
700
+ "type": "ObjectId",
701
+ "ref": "User"
702
+ }
703
+ },
704
+ "search": {
705
+ "cache": {
706
+ "cachedUserName": {
707
+ "lazy": true,
708
+ "path": "user.name"
709
+ }
710
+ },
711
+ "fields": ["user.name"]
712
+ }
713
+ }
714
+ ```
715
+
716
+ Lazy cached fields will not update themselves once set. They can only be updated
717
+ by forcing a sync:
718
+
719
+ ```js
720
+ await Model.syncSearchFields({
721
+ force: true,
722
+ });
723
+ ```
724
+
725
+ Making fields lazy alleviates performance impact on writes and allows caches to
726
+ be updated at another time (such as a background job).
618
727
 
619
728
  #### Search Validation
620
729
 
@@ -242,15 +242,19 @@ function setNodePath(node, options) {
242
242
  node[key] = null;
243
243
  }
244
244
  } else if (type === 'virtual') {
245
- node[key] ||= {};
246
245
  const virtual = schema.virtual(key);
247
- setNodePath(node[key], {
248
- // @ts-ignore
249
- modelName: virtual.options.ref,
250
- path: path.slice(parts.length),
251
- depth: depth + 1,
252
- exclude
253
- });
246
+ // @ts-ignore
247
+ const ref = virtual.options.ref;
248
+ if (ref) {
249
+ node[key] ||= {};
250
+ setNodePath(node[key], {
251
+ // @ts-ignore
252
+ modelName: ref,
253
+ path: path.slice(parts.length),
254
+ depth: depth + 1,
255
+ exclude
256
+ });
257
+ }
254
258
  halt = true;
255
259
  } else if (type !== 'nested') {
256
260
  throw new Error(`Unknown path on ${modelName}: ${key}.`);
@@ -227,13 +227,13 @@ function applyScopeExtension(typedef, definition) {
227
227
  ...options
228
228
  };
229
229
  } else {
230
- val = attributesToMongoose({
230
+ val = {
231
231
  type: 'Object',
232
232
  attributes: val,
233
233
  ...options
234
- });
234
+ };
235
235
  }
236
- definition[key] = val;
236
+ definition[key] = attributesToMongoose(val);
237
237
  }
238
238
  }
239
239
 
@@ -16,15 +16,19 @@ var _env = require("./env");
16
16
  var _query = require("./query");
17
17
  var _warn = _interopRequireDefault(require("./warn"));
18
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+ const {
20
+ SchemaTypes
21
+ } = _mongoose.default;
19
22
  const {
20
23
  ObjectId
21
24
  } = _mongoose.default.Types;
22
25
  function applySearch(schema, definition) {
23
26
  validateDefinition(definition);
27
+ applySearchCache(schema, definition);
24
28
  schema.static('search', function search(body = {}) {
25
29
  const options = {
26
30
  ..._const.SEARCH_DEFAULTS,
27
- ...definition.search,
31
+ ...definition.search?.query,
28
32
  ...body
29
33
  };
30
34
  const {
@@ -33,7 +37,6 @@ function applySearch(schema, definition) {
33
37
  skip = 0,
34
38
  limit,
35
39
  sort,
36
- fields,
37
40
  ...rest
38
41
  } = options;
39
42
  const query = {};
@@ -43,7 +46,7 @@ function applySearch(schema, definition) {
43
46
  };
44
47
  }
45
48
  if (keyword) {
46
- Object.assign(query, buildKeywordQuery(schema, keyword, fields));
49
+ Object.assign(query, buildKeywordQuery(schema, keyword, definition.search?.fields));
47
50
  }
48
51
  Object.assign(query, normalizeQuery(rest, schema.obj));
49
52
  if (_env.debug) {
@@ -162,7 +165,7 @@ function buildKeywordQuery(schema, keyword, fields) {
162
165
  } else if (hasTextIndex(schema)) {
163
166
  queries = [getTextQuery(keyword)];
164
167
  } else {
165
- queries = [];
168
+ throw new Error('No keyword fields defined.');
166
169
  }
167
170
  if (ObjectId.isValid(keyword)) {
168
171
  queries.push({
@@ -284,4 +287,200 @@ function parseRegexQuery(str) {
284
287
  $regex,
285
288
  $options
286
289
  };
290
+ }
291
+
292
+ // Search field caching
293
+
294
+ function applySearchCache(schema, definition) {
295
+ normalizeCacheFields(schema, definition);
296
+ if (!definition.search?.cache) {
297
+ return;
298
+ }
299
+ createCacheFields(schema, definition);
300
+ applyCacheHook(schema, definition);
301
+ schema.static('syncSearchFields', async function syncSearchFields(options = {}) {
302
+ assertIncludeModule(this);
303
+ const {
304
+ force
305
+ } = options;
306
+ const {
307
+ cache = {}
308
+ } = definition.search || {};
309
+ const paths = getCachePaths(definition);
310
+ const cachedFields = Object.keys(cache);
311
+ if (!cachedFields.length) {
312
+ throw new Error('No search fields to sync.');
313
+ }
314
+ const query = {};
315
+ if (!force) {
316
+ const $or = Object.entries(cache).map(entry => {
317
+ const [cachedField, def] = entry;
318
+ const {
319
+ base
320
+ } = def;
321
+ return {
322
+ [base]: {
323
+ $exists: true
324
+ },
325
+ [cachedField]: {
326
+ $exists: false
327
+ }
328
+ };
329
+ });
330
+ query.$or = $or;
331
+ }
332
+ const docs = await this.find(query).include(paths);
333
+ const ops = docs.map(doc => {
334
+ return {
335
+ updateOne: {
336
+ filter: {
337
+ _id: doc._id
338
+ },
339
+ update: {
340
+ $set: getUpdates(doc, paths, definition)
341
+ }
342
+ }
343
+ };
344
+ });
345
+ return await this.bulkWrite(ops);
346
+ });
347
+ }
348
+ function normalizeCacheFields(schema, definition) {
349
+ const {
350
+ fields,
351
+ cache = {}
352
+ } = definition.search || {};
353
+ if (!fields) {
354
+ return;
355
+ }
356
+ const normalized = [];
357
+ for (let path of fields) {
358
+ if (isForeignField(schema, path)) {
359
+ const cacheName = generateCacheFieldName(path);
360
+ const type = resolveSchemaType(schema, path);
361
+ const base = getRefBase(schema, path);
362
+ cache[cacheName] = {
363
+ type,
364
+ base,
365
+ path: path
366
+ };
367
+ normalized.push(cacheName);
368
+ } else {
369
+ normalized.push(path);
370
+ }
371
+ }
372
+ definition.search.cache = cache;
373
+ definition.search.fields = normalized;
374
+ }
375
+ function createCacheFields(schema, definition) {
376
+ for (let [cachedField, def] of Object.entries(definition.search.cache)) {
377
+ // Fall back to string type for virtuals or not defined.
378
+ const {
379
+ type = 'String'
380
+ } = def;
381
+ schema.add({
382
+ [cachedField]: type
383
+ });
384
+ schema.obj[cachedField] = {
385
+ type,
386
+ readAccess: 'none'
387
+ };
388
+ }
389
+ }
390
+ function applyCacheHook(schema, definition) {
391
+ schema.pre('save', async function () {
392
+ assertIncludeModule(this.constructor);
393
+ assertAssignModule(this.constructor);
394
+ const doc = this;
395
+ const paths = getCachePaths(definition, (cachedField, def) => {
396
+ if (def.lazy) {
397
+ return !(0, _lodash.get)(doc, cachedField);
398
+ } else {
399
+ return true;
400
+ }
401
+ });
402
+ await this.include(paths);
403
+ this.assign(getUpdates(this, paths, definition));
404
+ });
405
+ }
406
+ function resolveSchemaType(schema, path) {
407
+ if (!path.includes('.')) {
408
+ return (0, _lodash.get)(schema.obj, path)?.type;
409
+ }
410
+ const field = getRefField(schema, path);
411
+ if (field) {
412
+ const {
413
+ type,
414
+ rest
415
+ } = field;
416
+ const Model = _mongoose.default.models[type.options.ref];
417
+ return resolveSchemaType(Model.schema, rest.join('.'));
418
+ }
419
+ }
420
+ function isForeignField(schema, path) {
421
+ if (!path.includes('.')) {
422
+ return false;
423
+ }
424
+ return !!getRefField(schema, path);
425
+ }
426
+ function getRefBase(schema, path) {
427
+ const field = getRefField(schema, path);
428
+ if (field) {
429
+ return field.base.join('.');
430
+ }
431
+ }
432
+ function getRefField(schema, path) {
433
+ const split = path.split('.');
434
+ for (let i = 1; i < split.length; i++) {
435
+ const base = split.slice(0, i);
436
+ const rest = split.slice(i);
437
+ const type = schema.path(base);
438
+ if (type instanceof SchemaTypes.ObjectId) {
439
+ return {
440
+ type,
441
+ base,
442
+ rest
443
+ };
444
+ }
445
+ }
446
+ }
447
+ function getUpdates(doc, paths, definition) {
448
+ const updates = {};
449
+ const entries = Object.entries(definition.search.cache).filter(entry => {
450
+ return paths.includes(entry[1].path);
451
+ });
452
+ for (let [cachedField, def] of entries) {
453
+ // doc.get will not return virtuals (even with specified options),
454
+ // so use lodash to ensure they are included here.
455
+ // https://mongoosejs.com/docs/api/document.html#Document.prototype.get()
456
+ updates[cachedField] = (0, _lodash.get)(doc, def.path);
457
+ }
458
+ return updates;
459
+ }
460
+ function getCachePaths(definition, filter) {
461
+ filter ||= () => true;
462
+ const {
463
+ cache
464
+ } = definition.search || {};
465
+ return Object.entries(cache).filter(entry => {
466
+ return filter(...entry);
467
+ }).map(entry => {
468
+ return entry[1].path;
469
+ });
470
+ }
471
+ function generateCacheFieldName(field) {
472
+ return `cached${(0, _lodash.upperFirst)((0, _lodash.camelCase)(field))}`;
473
+ }
474
+
475
+ // Assertions
476
+
477
+ function assertIncludeModule(Model) {
478
+ if (!Model.schema.methods.include) {
479
+ throw new Error('Include module is required for cached search fields.');
480
+ }
481
+ }
482
+ function assertAssignModule(Model) {
483
+ if (!Model.schema.methods.assign) {
484
+ throw new Error('Assign module is required for cached search fields.');
485
+ }
287
486
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@bedrockio/yada": "^1.0.39",
34
- "mongoose": "^6.9.0 || ^7.6.4"
34
+ "mongoose": "^7.6.4"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/cli": "^7.20.7",
package/src/include.js CHANGED
@@ -228,15 +228,20 @@ function setNodePath(node, options) {
228
228
  node[key] = null;
229
229
  }
230
230
  } else if (type === 'virtual') {
231
- node[key] ||= {};
232
231
  const virtual = schema.virtual(key);
233
- setNodePath(node[key], {
234
- // @ts-ignore
235
- modelName: virtual.options.ref,
236
- path: path.slice(parts.length),
237
- depth: depth + 1,
238
- exclude,
239
- });
232
+ // @ts-ignore
233
+ const ref = virtual.options.ref;
234
+
235
+ if (ref) {
236
+ node[key] ||= {};
237
+ setNodePath(node[key], {
238
+ // @ts-ignore
239
+ modelName: ref,
240
+ path: path.slice(parts.length),
241
+ depth: depth + 1,
242
+ exclude,
243
+ });
244
+ }
240
245
  halt = true;
241
246
  } else if (type !== 'nested') {
242
247
  throw new Error(`Unknown path on ${modelName}: ${key}.`);
package/src/schema.js CHANGED
@@ -236,13 +236,13 @@ function applyScopeExtension(typedef, definition) {
236
236
  ...options,
237
237
  };
238
238
  } else {
239
- val = attributesToMongoose({
239
+ val = {
240
240
  type: 'Object',
241
241
  attributes: val,
242
242
  ...options,
243
- });
243
+ };
244
244
  }
245
- definition[key] = val;
245
+ definition[key] = attributesToMongoose(val);
246
246
  }
247
247
  }
248
248
 
package/src/search.js CHANGED
@@ -1,7 +1,15 @@
1
1
  import yd from '@bedrockio/yada';
2
2
  import logger from '@bedrockio/logger';
3
3
  import mongoose from 'mongoose';
4
- import { pick, isEmpty, escapeRegExp, isPlainObject } from 'lodash';
4
+ import {
5
+ get,
6
+ pick,
7
+ isEmpty,
8
+ camelCase,
9
+ upperFirst,
10
+ escapeRegExp,
11
+ isPlainObject,
12
+ } from 'lodash';
5
13
 
6
14
  import { isDateField, isNumberField, getField } from './utils';
7
15
  import { SEARCH_DEFAULTS } from './const';
@@ -11,19 +19,21 @@ import { wrapQuery } from './query';
11
19
 
12
20
  import warn from './warn';
13
21
 
22
+ const { SchemaTypes } = mongoose;
14
23
  const { ObjectId } = mongoose.Types;
15
24
 
16
25
  export function applySearch(schema, definition) {
17
26
  validateDefinition(definition);
27
+ applySearchCache(schema, definition);
18
28
 
19
29
  schema.static('search', function search(body = {}) {
20
30
  const options = {
21
31
  ...SEARCH_DEFAULTS,
22
- ...definition.search,
32
+ ...definition.search?.query,
23
33
  ...body,
24
34
  };
25
35
 
26
- const { ids, keyword, skip = 0, limit, sort, fields, ...rest } = options;
36
+ const { ids, keyword, skip = 0, limit, sort, ...rest } = options;
27
37
 
28
38
  const query = {};
29
39
 
@@ -32,7 +42,10 @@ export function applySearch(schema, definition) {
32
42
  }
33
43
 
34
44
  if (keyword) {
35
- Object.assign(query, buildKeywordQuery(schema, keyword, fields));
45
+ Object.assign(
46
+ query,
47
+ buildKeywordQuery(schema, keyword, definition.search?.fields)
48
+ );
36
49
  }
37
50
 
38
51
  Object.assign(query, normalizeQuery(rest, schema.obj));
@@ -172,7 +185,7 @@ function buildKeywordQuery(schema, keyword, fields) {
172
185
  } else if (hasTextIndex(schema)) {
173
186
  queries = [getTextQuery(keyword)];
174
187
  } else {
175
- queries = [];
188
+ throw new Error('No keyword fields defined.');
176
189
  }
177
190
 
178
191
  if (ObjectId.isValid(keyword)) {
@@ -300,3 +313,217 @@ function parseRegexQuery(str) {
300
313
  $options,
301
314
  };
302
315
  }
316
+
317
+ // Search field caching
318
+
319
+ function applySearchCache(schema, definition) {
320
+ normalizeCacheFields(schema, definition);
321
+
322
+ if (!definition.search?.cache) {
323
+ return;
324
+ }
325
+
326
+ createCacheFields(schema, definition);
327
+ applyCacheHook(schema, definition);
328
+
329
+ schema.static(
330
+ 'syncSearchFields',
331
+ async function syncSearchFields(options = {}) {
332
+ assertIncludeModule(this);
333
+
334
+ const { force } = options;
335
+ const { cache = {} } = definition.search || {};
336
+
337
+ const paths = getCachePaths(definition);
338
+
339
+ const cachedFields = Object.keys(cache);
340
+
341
+ if (!cachedFields.length) {
342
+ throw new Error('No search fields to sync.');
343
+ }
344
+
345
+ const query = {};
346
+
347
+ if (!force) {
348
+ const $or = Object.entries(cache).map((entry) => {
349
+ const [cachedField, def] = entry;
350
+ const { base } = def;
351
+ return {
352
+ [base]: {
353
+ $exists: true,
354
+ },
355
+ [cachedField]: {
356
+ $exists: false,
357
+ },
358
+ };
359
+ });
360
+ query.$or = $or;
361
+ }
362
+
363
+ const docs = await this.find(query).include(paths);
364
+
365
+ const ops = docs.map((doc) => {
366
+ return {
367
+ updateOne: {
368
+ filter: {
369
+ _id: doc._id,
370
+ },
371
+ update: {
372
+ $set: getUpdates(doc, paths, definition),
373
+ },
374
+ },
375
+ };
376
+ });
377
+
378
+ return await this.bulkWrite(ops);
379
+ }
380
+ );
381
+ }
382
+
383
+ function normalizeCacheFields(schema, definition) {
384
+ const { fields, cache = {} } = definition.search || {};
385
+ if (!fields) {
386
+ return;
387
+ }
388
+
389
+ const normalized = [];
390
+
391
+ for (let path of fields) {
392
+ if (isForeignField(schema, path)) {
393
+ const cacheName = generateCacheFieldName(path);
394
+ const type = resolveSchemaType(schema, path);
395
+ const base = getRefBase(schema, path);
396
+ cache[cacheName] = {
397
+ type,
398
+ base,
399
+ path: path,
400
+ };
401
+ normalized.push(cacheName);
402
+ } else {
403
+ normalized.push(path);
404
+ }
405
+ }
406
+
407
+ definition.search.cache = cache;
408
+ definition.search.fields = normalized;
409
+ }
410
+
411
+ function createCacheFields(schema, definition) {
412
+ for (let [cachedField, def] of Object.entries(definition.search.cache)) {
413
+ // Fall back to string type for virtuals or not defined.
414
+ const { type = 'String' } = def;
415
+ schema.add({
416
+ [cachedField]: type,
417
+ });
418
+ schema.obj[cachedField] = {
419
+ type,
420
+ readAccess: 'none',
421
+ };
422
+ }
423
+ }
424
+
425
+ function applyCacheHook(schema, definition) {
426
+ schema.pre('save', async function () {
427
+ assertIncludeModule(this.constructor);
428
+ assertAssignModule(this.constructor);
429
+
430
+ const doc = this;
431
+ const paths = getCachePaths(definition, (cachedField, def) => {
432
+ if (def.lazy) {
433
+ return !get(doc, cachedField);
434
+ } else {
435
+ return true;
436
+ }
437
+ });
438
+
439
+ await this.include(paths);
440
+ this.assign(getUpdates(this, paths, definition));
441
+ });
442
+ }
443
+
444
+ function resolveSchemaType(schema, path) {
445
+ if (!path.includes('.')) {
446
+ return get(schema.obj, path)?.type;
447
+ }
448
+ const field = getRefField(schema, path);
449
+ if (field) {
450
+ const { type, rest } = field;
451
+ const Model = mongoose.models[type.options.ref];
452
+ return resolveSchemaType(Model.schema, rest.join('.'));
453
+ }
454
+ }
455
+
456
+ function isForeignField(schema, path) {
457
+ if (!path.includes('.')) {
458
+ return false;
459
+ }
460
+ return !!getRefField(schema, path);
461
+ }
462
+
463
+ function getRefBase(schema, path) {
464
+ const field = getRefField(schema, path);
465
+ if (field) {
466
+ return field.base.join('.');
467
+ }
468
+ }
469
+
470
+ function getRefField(schema, path) {
471
+ const split = path.split('.');
472
+ for (let i = 1; i < split.length; i++) {
473
+ const base = split.slice(0, i);
474
+ const rest = split.slice(i);
475
+ const type = schema.path(base);
476
+ if (type instanceof SchemaTypes.ObjectId) {
477
+ return {
478
+ type,
479
+ base,
480
+ rest,
481
+ };
482
+ }
483
+ }
484
+ }
485
+
486
+ function getUpdates(doc, paths, definition) {
487
+ const updates = {};
488
+
489
+ const entries = Object.entries(definition.search.cache).filter((entry) => {
490
+ return paths.includes(entry[1].path);
491
+ });
492
+ for (let [cachedField, def] of entries) {
493
+ // doc.get will not return virtuals (even with specified options),
494
+ // so use lodash to ensure they are included here.
495
+ // https://mongoosejs.com/docs/api/document.html#Document.prototype.get()
496
+ updates[cachedField] = get(doc, def.path);
497
+ }
498
+ return updates;
499
+ }
500
+
501
+ function getCachePaths(definition, filter) {
502
+ filter ||= () => true;
503
+ const { cache } = definition.search || {};
504
+ return Object.entries(cache)
505
+ .filter((entry) => {
506
+ return filter(...entry);
507
+ })
508
+ .map((entry) => {
509
+ return entry[1].path;
510
+ });
511
+ }
512
+
513
+ function generateCacheFieldName(field) {
514
+ return `cached${upperFirst(camelCase(field))}`;
515
+ }
516
+
517
+ // Assertions
518
+
519
+ function assertIncludeModule(Model) {
520
+ if (!Model.schema.methods.include) {
521
+ throw new Error('Include module is required for cached search fields.');
522
+ }
523
+ }
524
+
525
+ function assertAssignModule(Model) {
526
+ if (!Model.schema.methods.assign) {
527
+ throw new Error('Assign module is required for cached search fields.');
528
+ }
529
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAeA,gEAmDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAwBA,gEAuDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}