@bedrockio/model 0.5.1 → 0.5.2

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
@@ -21,6 +21,7 @@ Bedrock utilities for model creation.
21
21
  - [Assign](#assign)
22
22
  - [Slugs](#slugs)
23
23
  - [Testing](#testing)
24
+ - [Troubleshooting](#troubleshooting)
24
25
 
25
26
  ## Install
26
27
 
@@ -629,47 +630,29 @@ cache foreign fields on the model to allow filtering on them.
629
630
  },
630
631
  "search": {
631
632
  "cache": {
632
- "cachedUserName": {
633
+ "userName": {
633
634
  "type": "String",
634
635
  "path": "user.name"
635
636
  }
636
637
  },
637
- "fields": ["cachedUserName"]
638
+ "fields": ["userName"]
638
639
  }
639
640
  }
640
641
  ```
641
642
 
642
- The above example is equivalent to creating a field called `cachedUserName` and
643
+ The above example is equivalent to creating a field called `userName` and
643
644
  updating it when a document is saved:
644
645
 
645
646
  ```js
646
647
  schema.add({
647
- cachedUserName: 'String',
648
+ userName: 'String',
648
649
  });
649
650
  schema.pre('save', function () {
650
651
  await this.populate('user');
651
- this.cachedUserName = this.user.name;
652
+ this.userName = this.user.name;
652
653
  });
653
654
  ```
654
655
 
655
- Specifying a foreign path in `fields` serves as a shortcut to manually defining
656
- the cached fields:
657
-
658
- ```json
659
- // Equivalent to the above example.
660
- {
661
- "attributes": {
662
- "user": {
663
- "type": "ObjectId",
664
- "ref": "User"
665
- }
666
- },
667
- "search": {
668
- "fields": ["user.name"]
669
- }
670
- }
671
- ```
672
-
673
656
  ##### Syncing Search Fields
674
657
 
675
658
  When first applying or making changes to defined cached search fields, existing
@@ -704,12 +687,13 @@ Cached fields can be made lazy:
704
687
  },
705
688
  "search": {
706
689
  "cache": {
707
- "cachedUserName": {
708
- "lazy": true,
709
- "path": "user.name"
690
+ "userName": {
691
+ "type": "String",
692
+ "path": "user.name",
693
+ "lazy": true
710
694
  }
711
695
  },
712
- "fields": ["user.name"]
696
+ "fields": ["userName"]
713
697
  }
714
698
  }
715
699
  ```
@@ -875,7 +859,7 @@ Example 2:
875
859
  await Product.findById(id).include('shop.^user.name').
876
860
  ```
877
861
 
878
- ```json
862
+ ```jsonc
879
863
  {
880
864
  "name": "Product Name",
881
865
  "cost": 10,
@@ -897,7 +881,7 @@ Example 3:
897
881
  await Product.findById(id).include('shop.user.^name').
898
882
  ```
899
883
 
900
- ```json
884
+ ```jsonc
901
885
  {
902
886
  "name": "Product Name",
903
887
  "cost": 10,
@@ -944,7 +928,7 @@ Example 1: Single wildcard
944
928
  const user = await User.findById(id).include('*Name');
945
929
  ```
946
930
 
947
- ```json
931
+ ```jsonc
948
932
  {
949
933
  "firstName": "Frank",
950
934
  "lastName": "Reynolds"
@@ -1381,7 +1365,7 @@ for (let shop of shops) {
1381
1365
 
1382
1366
  Operations may filter on additional fields with `query`:
1383
1367
 
1384
- ```json
1368
+ ```jsonc
1385
1369
  // user.json
1386
1370
  {
1387
1371
  "onDelete": {
@@ -1421,7 +1405,7 @@ operators have special behavior with multiple paths (see note below).
1421
1405
  An operation that specified an array of `paths` will implicitly run an `$or`
1422
1406
  query:
1423
1407
 
1424
- ```json
1408
+ ```jsonc
1425
1409
  // user.json
1426
1410
  {
1427
1411
  "onDelete": {
@@ -1558,3 +1542,19 @@ const Post = createTestModel('Post', {
1558
1542
  ```
1559
1543
 
1560
1544
  Make sure in this case that the model name is unique.
1545
+
1546
+ ## Troubleshooting
1547
+
1548
+ Q: I'm seeing `scopes were requested but not provided` messages everywhere. What
1549
+ is this?
1550
+
1551
+ A: When a model has fields with `readAccess`, documents require extra context to
1552
+ be able to serialize properly. In practice this means calling `toObject` on the
1553
+ document without the `scopes` option will generate this warning. This also means
1554
+ that functions like `console.log` that internally call `toString` on the
1555
+ document will also show warnings. Possible causes include:
1556
+
1557
+ - `console.log(document)` - solution here is to remove the log or use the
1558
+ `serializeDocument` helper in bedrock core.
1559
+ - A bug in Mongoose (observed in v8.3.2) prevents serialize options from being
1560
+ passed down to nested documents. Bumping the Mongoose version should fix this.
@@ -24,6 +24,7 @@ const {
24
24
  } = _mongoose.default.Types;
25
25
  function applySearch(schema, definition) {
26
26
  validateDefinition(definition);
27
+ validateSearchFields(schema, definition);
27
28
  applySearchCache(schema, definition);
28
29
  const {
29
30
  query: searchQuery,
@@ -299,7 +300,6 @@ function parseRegexQuery(str) {
299
300
  // Search field caching
300
301
 
301
302
  function applySearchCache(schema, definition) {
302
- normalizeCacheFields(schema, definition);
303
303
  if (!definition.search?.cache) {
304
304
  return;
305
305
  }
@@ -320,15 +320,8 @@ function applySearchCache(schema, definition) {
320
320
  }
321
321
  const query = {};
322
322
  if (!force) {
323
- const $or = Object.entries(cache).map(entry => {
324
- const [cachedField, def] = entry;
325
- const {
326
- base
327
- } = def;
323
+ const $or = Object.keys(cache).map(cachedField => {
328
324
  return {
329
- [base]: {
330
- $exists: true
331
- },
332
325
  [cachedField]: {
333
326
  $exists: false
334
327
  }
@@ -352,32 +345,18 @@ function applySearchCache(schema, definition) {
352
345
  return await this.bulkWrite(ops);
353
346
  });
354
347
  }
355
- function normalizeCacheFields(schema, definition) {
348
+ function validateSearchFields(schema, definition) {
356
349
  const {
357
- fields,
358
- cache = {}
350
+ fields
359
351
  } = definition.search || {};
360
352
  if (!fields) {
361
353
  return;
362
354
  }
363
- const normalized = [];
364
355
  for (let path of fields) {
365
356
  if (isForeignField(schema, path)) {
366
- const cacheName = generateCacheFieldName(path);
367
- const type = resolveSchemaType(schema, path);
368
- const base = getRefBase(schema, path);
369
- cache[cacheName] = {
370
- type,
371
- base,
372
- path: path
373
- };
374
- normalized.push(cacheName);
375
- } else {
376
- normalized.push(path);
357
+ throw new Error(`Foreign field "${path}" not allowed in search.`);
377
358
  }
378
359
  }
379
- definition.search.cache = cache;
380
- definition.search.fields = normalized;
381
360
  }
382
361
  function createCacheFields(schema, definition) {
383
362
  for (let [cachedField, def] of Object.entries(definition.search.cache)) {
@@ -410,32 +389,12 @@ function applyCacheHook(schema, definition) {
410
389
  this.assign(getUpdates(this, paths, definition));
411
390
  });
412
391
  }
413
- function resolveSchemaType(schema, path) {
414
- if (!path.includes('.')) {
415
- return (0, _lodash.get)(schema.obj, path)?.type;
416
- }
417
- const field = getRefField(schema, path);
418
- if (field) {
419
- const {
420
- type,
421
- rest
422
- } = field;
423
- const Model = _mongoose.default.models[type.options.ref];
424
- return resolveSchemaType(Model.schema, rest.join('.'));
425
- }
426
- }
427
392
  function isForeignField(schema, path) {
428
393
  if (!path.includes('.')) {
429
394
  return false;
430
395
  }
431
396
  return !!getRefField(schema, path);
432
397
  }
433
- function getRefBase(schema, path) {
434
- const field = getRefField(schema, path);
435
- if (field) {
436
- return field.base.join('.');
437
- }
438
- }
439
398
  function getRefField(schema, path) {
440
399
  const split = path.split('.');
441
400
  for (let i = 1; i < split.length; i++) {
@@ -475,9 +434,6 @@ function getCachePaths(definition, filter) {
475
434
  return entry[1].path;
476
435
  });
477
436
  }
478
- function generateCacheFieldName(field) {
479
- return `cached${(0, _lodash.upperFirst)((0, _lodash.camelCase)(field))}`;
480
- }
481
437
 
482
438
  // Assertions
483
439
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/search.js CHANGED
@@ -1,15 +1,7 @@
1
1
  import yd from '@bedrockio/yada';
2
2
  import logger from '@bedrockio/logger';
3
3
  import mongoose from 'mongoose';
4
- import {
5
- get,
6
- pick,
7
- isEmpty,
8
- camelCase,
9
- upperFirst,
10
- escapeRegExp,
11
- isPlainObject,
12
- } from 'lodash';
4
+ import { get, pick, isEmpty, escapeRegExp, isPlainObject } from 'lodash';
13
5
 
14
6
  import { isDateField, isNumberField, getField } from './utils';
15
7
  import { SEARCH_DEFAULTS } from './const';
@@ -24,6 +16,7 @@ const { ObjectId } = mongoose.Types;
24
16
 
25
17
  export function applySearch(schema, definition) {
26
18
  validateDefinition(definition);
19
+ validateSearchFields(schema, definition);
27
20
  applySearchCache(schema, definition);
28
21
 
29
22
  const { query: searchQuery, fields: searchFields } = definition.search || {};
@@ -318,8 +311,6 @@ function parseRegexQuery(str) {
318
311
  // Search field caching
319
312
 
320
313
  function applySearchCache(schema, definition) {
321
- normalizeCacheFields(schema, definition);
322
-
323
314
  if (!definition.search?.cache) {
324
315
  return;
325
316
  }
@@ -346,13 +337,8 @@ function applySearchCache(schema, definition) {
346
337
  const query = {};
347
338
 
348
339
  if (!force) {
349
- const $or = Object.entries(cache).map((entry) => {
350
- const [cachedField, def] = entry;
351
- const { base } = def;
340
+ const $or = Object.keys(cache).map((cachedField) => {
352
341
  return {
353
- [base]: {
354
- $exists: true,
355
- },
356
342
  [cachedField]: {
357
343
  $exists: false,
358
344
  },
@@ -381,32 +367,18 @@ function applySearchCache(schema, definition) {
381
367
  );
382
368
  }
383
369
 
384
- function normalizeCacheFields(schema, definition) {
385
- const { fields, cache = {} } = definition.search || {};
370
+ function validateSearchFields(schema, definition) {
371
+ const { fields } = definition.search || {};
372
+
386
373
  if (!fields) {
387
374
  return;
388
375
  }
389
376
 
390
- const normalized = [];
391
-
392
377
  for (let path of fields) {
393
378
  if (isForeignField(schema, path)) {
394
- const cacheName = generateCacheFieldName(path);
395
- const type = resolveSchemaType(schema, path);
396
- const base = getRefBase(schema, path);
397
- cache[cacheName] = {
398
- type,
399
- base,
400
- path: path,
401
- };
402
- normalized.push(cacheName);
403
- } else {
404
- normalized.push(path);
379
+ throw new Error(`Foreign field "${path}" not allowed in search.`);
405
380
  }
406
381
  }
407
-
408
- definition.search.cache = cache;
409
- definition.search.fields = normalized;
410
382
  }
411
383
 
412
384
  function createCacheFields(schema, definition) {
@@ -442,18 +414,6 @@ function applyCacheHook(schema, definition) {
442
414
  });
443
415
  }
444
416
 
445
- function resolveSchemaType(schema, path) {
446
- if (!path.includes('.')) {
447
- return get(schema.obj, path)?.type;
448
- }
449
- const field = getRefField(schema, path);
450
- if (field) {
451
- const { type, rest } = field;
452
- const Model = mongoose.models[type.options.ref];
453
- return resolveSchemaType(Model.schema, rest.join('.'));
454
- }
455
- }
456
-
457
417
  function isForeignField(schema, path) {
458
418
  if (!path.includes('.')) {
459
419
  return false;
@@ -461,13 +421,6 @@ function isForeignField(schema, path) {
461
421
  return !!getRefField(schema, path);
462
422
  }
463
423
 
464
- function getRefBase(schema, path) {
465
- const field = getRefField(schema, path);
466
- if (field) {
467
- return field.base.join('.');
468
- }
469
- }
470
-
471
424
  function getRefField(schema, path) {
472
425
  const split = path.split('.');
473
426
  for (let i = 1; i < split.length; i++) {
@@ -511,10 +464,6 @@ function getCachePaths(definition, filter) {
511
464
  });
512
465
  }
513
466
 
514
- function generateCacheFieldName(field) {
515
- return `cached${upperFirst(camelCase(field))}`;
516
- }
517
-
518
467
  // Assertions
519
468
 
520
469
  function assertIncludeModule(Model) {
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAwBA,gEAwDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAgBA,gEAyDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}