@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 +32 -32
- package/dist/cjs/search.js +5 -49
- package/package.json +1 -1
- package/src/search.js +7 -58
- package/types/search.d.ts.map +1 -1
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
|
-
"
|
|
633
|
+
"userName": {
|
|
633
634
|
"type": "String",
|
|
634
635
|
"path": "user.name"
|
|
635
636
|
}
|
|
636
637
|
},
|
|
637
|
-
"fields": ["
|
|
638
|
+
"fields": ["userName"]
|
|
638
639
|
}
|
|
639
640
|
}
|
|
640
641
|
```
|
|
641
642
|
|
|
642
|
-
The above example is equivalent to creating a field called `
|
|
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
|
-
|
|
648
|
+
userName: 'String',
|
|
648
649
|
});
|
|
649
650
|
schema.pre('save', function () {
|
|
650
651
|
await this.populate('user');
|
|
651
|
-
this.
|
|
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
|
-
"
|
|
708
|
-
"
|
|
709
|
-
"path": "user.name"
|
|
690
|
+
"userName": {
|
|
691
|
+
"type": "String",
|
|
692
|
+
"path": "user.name",
|
|
693
|
+
"lazy": true
|
|
710
694
|
}
|
|
711
695
|
},
|
|
712
|
-
"fields": ["
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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.
|
package/dist/cjs/search.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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
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.
|
|
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
|
|
385
|
-
const { fields
|
|
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
|
-
|
|
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) {
|
package/types/search.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAgBA,gEAyDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}
|