@bedrockio/model 0.1.0 → 0.1.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.
Files changed (73) hide show
  1. package/README.md +24 -0
  2. package/dist/cjs/access.js +4 -0
  3. package/dist/cjs/assign.js +2 -3
  4. package/dist/cjs/disallowed.js +40 -0
  5. package/dist/cjs/include.js +3 -2
  6. package/dist/cjs/load.js +15 -3
  7. package/dist/cjs/schema.js +29 -12
  8. package/dist/cjs/search.js +16 -16
  9. package/dist/cjs/serialization.js +2 -3
  10. package/dist/cjs/soft-delete.js +234 -55
  11. package/dist/cjs/testing.js +8 -0
  12. package/dist/cjs/validation.js +110 -44
  13. package/package.json +9 -7
  14. package/src/access.js +3 -0
  15. package/src/disallowed.js +63 -0
  16. package/src/include.js +1 -0
  17. package/src/load.js +12 -1
  18. package/src/schema.js +25 -5
  19. package/src/search.js +21 -10
  20. package/src/soft-delete.js +238 -85
  21. package/src/testing.js +7 -0
  22. package/src/validation.js +135 -44
  23. package/types/access.d.ts +7 -0
  24. package/types/access.d.ts.map +1 -0
  25. package/types/assign.d.ts +2 -0
  26. package/types/assign.d.ts.map +1 -0
  27. package/types/const.d.ts +9 -0
  28. package/types/const.d.ts.map +1 -0
  29. package/types/disallowed.d.ts +2 -0
  30. package/types/disallowed.d.ts.map +1 -0
  31. package/types/errors.d.ts +9 -0
  32. package/types/errors.d.ts.map +1 -0
  33. package/types/include.d.ts +4 -0
  34. package/types/include.d.ts.map +1 -0
  35. package/types/index.d.ts +6 -0
  36. package/types/index.d.ts.map +1 -0
  37. package/types/load.d.ts +15 -0
  38. package/types/load.d.ts.map +1 -0
  39. package/types/references.d.ts +2 -0
  40. package/types/references.d.ts.map +1 -0
  41. package/types/schema.d.ts +71 -0
  42. package/types/schema.d.ts.map +1 -0
  43. package/types/search.d.ts +303 -0
  44. package/types/search.d.ts.map +1 -0
  45. package/types/serialization.d.ts +6 -0
  46. package/types/serialization.d.ts.map +1 -0
  47. package/types/slug.d.ts +2 -0
  48. package/types/slug.d.ts.map +1 -0
  49. package/types/soft-delete.d.ts +4 -0
  50. package/types/soft-delete.d.ts.map +1 -0
  51. package/types/testing.d.ts +11 -0
  52. package/types/testing.d.ts.map +1 -0
  53. package/types/utils.d.ts +8 -0
  54. package/types/utils.d.ts.map +1 -0
  55. package/types/validation.d.ts +13 -0
  56. package/types/validation.d.ts.map +1 -0
  57. package/types/warn.d.ts +2 -0
  58. package/types/warn.d.ts.map +1 -0
  59. package/babel.config.cjs +0 -41
  60. package/jest.config.js +0 -8
  61. package/test/assign.test.js +0 -225
  62. package/test/definitions/custom-model.json +0 -9
  63. package/test/definitions/special-category.json +0 -18
  64. package/test/include.test.js +0 -896
  65. package/test/load.test.js +0 -47
  66. package/test/references.test.js +0 -71
  67. package/test/schema.test.js +0 -919
  68. package/test/search.test.js +0 -652
  69. package/test/serialization.test.js +0 -748
  70. package/test/setup.js +0 -27
  71. package/test/slug.test.js +0 -112
  72. package/test/soft-delete.test.js +0 -333
  73. package/test/validation.test.js +0 -1925
@@ -1,652 +0,0 @@
1
- import mongoose from 'mongoose';
2
-
3
- import { createSchema } from '../src/schema';
4
- import { createTestModel } from '../src/testing';
5
-
6
- describe('search', () => {
7
- it('should search on name', async () => {
8
- const User = createTestModel({
9
- name: {
10
- type: 'String',
11
- required: true,
12
- },
13
- });
14
- await Promise.all([
15
- User.create({ name: 'Billy' }),
16
- User.create({ name: 'Willy' }),
17
- ]);
18
- const { data, meta } = await User.search({ name: 'Billy' });
19
- expect(data).toMatchObject([{ name: 'Billy' }]);
20
- expect(meta).toEqual({
21
- total: 1,
22
- limit: 50,
23
- skip: 0,
24
- });
25
- });
26
-
27
- it('should search on name as a keyword', async () => {
28
- const User = createTestModel({
29
- name: {
30
- type: 'String',
31
- required: true,
32
- },
33
- });
34
- User.schema.index({
35
- name: 'text',
36
- });
37
- await User.createIndexes();
38
- await Promise.all([
39
- User.create({ name: 'Billy' }),
40
- User.create({ name: 'Willy' }),
41
- ]);
42
- const { data, meta } = await User.search({ keyword: 'billy' });
43
- expect(data).toMatchObject([{ name: 'Billy' }]);
44
- expect(meta.total).toBe(1);
45
- });
46
-
47
- it('should allow partial text match when search fields are defined', async () => {
48
- let result;
49
- const schema = createSchema({
50
- attributes: {
51
- name: {
52
- type: 'String',
53
- required: true,
54
- },
55
- },
56
- search: {
57
- fields: ['name'],
58
- },
59
- });
60
- schema.index({
61
- name: 'text',
62
- });
63
- const User = createTestModel(schema);
64
- await Promise.all([
65
- User.create({ name: 'Billy' }),
66
- User.create({ name: 'Willy' }),
67
- ]);
68
-
69
- result = await User.search({
70
- keyword: 'bil',
71
- sort: {
72
- field: 'name',
73
- order: 'asc',
74
- },
75
- });
76
- expect(result.data).toMatchObject([{ name: 'Billy' }]);
77
- expect(result.meta.total).toBe(1);
78
-
79
- result = await User.search({
80
- keyword: 'lly',
81
- sort: {
82
- field: 'name',
83
- order: 'asc',
84
- },
85
- });
86
- expect(result.data).toMatchObject([{ name: 'Billy' }, { name: 'Willy' }]);
87
- expect(result.meta.total).toBe(2);
88
- });
89
-
90
- it('should allow partial text match on multiple fields', async () => {
91
- let result;
92
- const schema = createSchema({
93
- attributes: {
94
- name: {
95
- type: 'String',
96
- required: true,
97
- },
98
- role: {
99
- type: 'String',
100
- required: true,
101
- },
102
- },
103
- search: {
104
- fields: ['name', 'role'],
105
- },
106
- });
107
- schema.index({
108
- name: 'text',
109
- });
110
- const User = createTestModel(schema);
111
- await Promise.all([
112
- User.create({ name: 'Billy', role: 'user' }),
113
- User.create({ name: 'Willy', role: 'manager' }),
114
- ]);
115
-
116
- result = await User.search({ keyword: 'use' });
117
- expect(result.data).toMatchObject([{ name: 'Billy', role: 'user' }]);
118
- expect(result.meta.total).toBe(1);
119
-
120
- result = await User.search({ keyword: 'man' });
121
- expect(result.data).toMatchObject([{ name: 'Willy', role: 'manager' }]);
122
- expect(result.meta.total).toBe(1);
123
- });
124
-
125
- it('should allow id search with partial text match', async () => {
126
- let result;
127
- const schema = createSchema({
128
- attributes: {
129
- name: {
130
- type: 'String',
131
- required: true,
132
- },
133
- },
134
- search: {
135
- fields: ['name'],
136
- },
137
- });
138
- const User = createTestModel(schema);
139
- const [billy] = await Promise.all([
140
- User.create({ name: 'Billy', role: 'user' }),
141
- User.create({ name: 'Willy', role: 'manager' }),
142
- ]);
143
-
144
- result = await User.search({ keyword: billy.id });
145
- expect(result.data).toMatchObject([{ name: 'Billy' }]);
146
- expect(result.meta.total).toBe(1);
147
- });
148
-
149
- it('should search on an array field', async () => {
150
- const User = createTestModel({
151
- order: 'Number',
152
- categories: ['String'],
153
- });
154
- const [user1, user2] = await Promise.all([
155
- User.create({ order: 1, categories: ['owner', 'member'] }),
156
- User.create({ order: 2, categories: ['owner'] }),
157
- ]);
158
-
159
- let result;
160
- result = await User.search({
161
- categories: ['member'],
162
- });
163
- expect(result.data).toMatchObject([{ id: user1.id }]);
164
- expect(result.meta.total).toBe(1);
165
-
166
- result = await User.search({
167
- categories: ['owner'],
168
- sort: {
169
- field: 'order',
170
- order: 'asc',
171
- },
172
- });
173
- expect(result.data).toMatchObject([{ id: user1.id }, { id: user2.id }]);
174
- expect(result.meta.total).toBe(2);
175
- });
176
-
177
- it('should allow shorthand for a regex query', async () => {
178
- const User = createTestModel({
179
- name: 'String',
180
- });
181
- await Promise.all([
182
- User.create({ name: 'Willy' }),
183
- User.create({ name: 'Billy' }),
184
- ]);
185
-
186
- let result;
187
-
188
- result = await User.search({
189
- name: '/bi/i',
190
- });
191
- expect(result.data).toMatchObject([{ name: 'Billy' }]);
192
- expect(result.meta.total).toBe(1);
193
- });
194
-
195
- it('should behave like $in when empty array passed', async () => {
196
- const User = createTestModel({
197
- categories: ['String'],
198
- });
199
- await Promise.all([
200
- User.create({ categories: ['owner', 'member'] }),
201
- User.create({ categories: ['owner'] }),
202
- ]);
203
-
204
- const result = await User.search({
205
- categories: [],
206
- });
207
- expect(result.data).toMatchObject([]);
208
- expect(result.meta.total).toBe(0);
209
- });
210
-
211
- it('should perform a search on a nested field', async () => {
212
- const User = createTestModel({
213
- order: 'Number',
214
- roles: [
215
- {
216
- role: {
217
- type: 'String',
218
- required: true,
219
- },
220
- scope: {
221
- type: 'String',
222
- required: true,
223
- },
224
- },
225
- ],
226
- });
227
- const [user1, user2] = await Promise.all([
228
- User.create({
229
- order: 1,
230
- roles: [
231
- { role: 'owner', scope: 'global' },
232
- { role: 'member', scope: 'global' },
233
- ],
234
- }),
235
- User.create({
236
- order: 2,
237
- roles: [{ role: 'member', scope: 'global' }],
238
- }),
239
- ]);
240
-
241
- let result;
242
-
243
- result = await User.search({
244
- roles: {
245
- role: 'member',
246
- },
247
- sort: {
248
- field: 'order',
249
- order: 'asc',
250
- },
251
- });
252
- expect(result.data).toMatchObject([{ id: user1.id }, { id: user2.id }]);
253
- expect(result.meta.total).toBe(2);
254
-
255
- result = await User.search({
256
- roles: {
257
- role: ['owner', 'member'],
258
- },
259
- sort: {
260
- field: 'order',
261
- order: 'asc',
262
- },
263
- });
264
- expect(result.data).toMatchObject([{ id: user1.id }, { id: user2.id }]);
265
- expect(result.meta.total).toBe(2);
266
-
267
- result = await User.search({
268
- roles: {
269
- role: ['owner'],
270
- },
271
- sort: {
272
- field: 'order',
273
- order: 'asc',
274
- },
275
- });
276
- expect(result.data).toMatchObject([{ id: user1.id }]);
277
- expect(result.meta.total).toBe(1);
278
- });
279
-
280
- it('should perform a search on a complex nested field', async () => {
281
- const User = createTestModel({
282
- name: 'String',
283
- profile: {
284
- roles: [
285
- {
286
- role: {
287
- functions: ['String'],
288
- },
289
- },
290
- ],
291
- },
292
- });
293
- await Promise.all([
294
- User.create({
295
- name: 'Bob',
296
- profile: {
297
- roles: [
298
- {
299
- role: {
300
- functions: ['owner', 'spectator'],
301
- },
302
- },
303
- ],
304
- },
305
- }),
306
- User.create({
307
- name: 'Fred',
308
- profile: {
309
- roles: [
310
- {
311
- role: {
312
- functions: ['manager', 'spectator'],
313
- },
314
- },
315
- ],
316
- },
317
- }),
318
- ]);
319
-
320
- let result;
321
- result = await User.search({
322
- profile: {
323
- roles: {
324
- role: {
325
- functions: ['owner'],
326
- },
327
- },
328
- },
329
- });
330
- expect(result.data).toMatchObject([
331
- {
332
- name: 'Bob',
333
- },
334
- ]);
335
- });
336
-
337
- it('should mixin operator queries', async () => {
338
- let result;
339
-
340
- const User = createTestModel({
341
- name: 'String',
342
- age: 'Number',
343
- });
344
- await Promise.all([
345
- User.create({ name: 'Billy', age: 20 }),
346
- User.create({ name: 'Willy', age: 32 }),
347
- User.create({ name: 'Chilly', age: 10 }),
348
- ]);
349
-
350
- result = await User.search({
351
- $or: [{ name: 'Billy' }, { age: 10 }],
352
- sort: {
353
- field: 'name',
354
- order: 'asc',
355
- },
356
- });
357
- expect(result.data).toMatchObject([
358
- { name: 'Billy', age: 20 },
359
- { name: 'Chilly', age: 10 },
360
- ]);
361
-
362
- result = await User.search({
363
- name: { $ne: 'Billy' },
364
- sort: {
365
- field: 'name',
366
- order: 'asc',
367
- },
368
- });
369
- expect(result.data).toMatchObject([
370
- { name: 'Chilly', age: 10 },
371
- { name: 'Willy', age: 32 },
372
- ]);
373
- });
374
-
375
- it('should mixin nested operator queries', async () => {
376
- const User = createTestModel({
377
- name: 'String',
378
- });
379
- await Promise.all([
380
- User.create({ name: 'Billy' }),
381
- User.create({ name: 'Willy' }),
382
- ]);
383
- const { data, meta } = await User.search({ name: { $ne: 'Billy' } });
384
- expect(data).toMatchObject([
385
- {
386
- name: 'Willy',
387
- },
388
- ]);
389
- expect(meta).toEqual({
390
- total: 1,
391
- limit: 50,
392
- skip: 0,
393
- });
394
- });
395
-
396
- it('should allow custom dot path in query', async () => {
397
- const User = createTestModel({
398
- roles: [
399
- {
400
- role: {
401
- type: 'String',
402
- required: true,
403
- },
404
- scope: {
405
- type: 'String',
406
- required: true,
407
- },
408
- scopeRef: {
409
- type: 'ObjectId',
410
- ref: 'Organization',
411
- },
412
- },
413
- ],
414
- });
415
- const ref1 = mongoose.Types.ObjectId();
416
- const ref2 = mongoose.Types.ObjectId();
417
-
418
- await User.create(
419
- {
420
- roles: [
421
- {
422
- role: 'admin',
423
- scope: 'organization',
424
- scopeRef: ref1,
425
- },
426
- ],
427
- },
428
- {
429
- roles: [
430
- {
431
- role: 'admin',
432
- scope: 'organization',
433
- scopeRef: ref2,
434
- },
435
- ],
436
- }
437
- );
438
- const { data } = await User.search({
439
- 'roles.scope': 'organization',
440
- 'roles.scopeRef': ref1,
441
- });
442
-
443
- expect(data.length).toBe(1);
444
- });
445
-
446
- it('should allow date range search', async () => {
447
- let result;
448
- const User = createTestModel({
449
- name: 'String',
450
- archivedAt: 'Date',
451
- });
452
- await Promise.all([
453
- User.create({ name: 'Billy', archivedAt: '2020-01-01' }),
454
- User.create({ name: 'Willy', archivedAt: '2021-01-01' }),
455
- ]);
456
-
457
- result = await User.search({ archivedAt: { lte: '2020-06-01' } });
458
- expect(result.data).toMatchObject([{ name: 'Billy' }]);
459
- expect(result.meta.total).toBe(1);
460
-
461
- result = await User.search({ archivedAt: { gte: '2020-06-01' } });
462
- expect(result.data).toMatchObject([{ name: 'Willy' }]);
463
- expect(result.meta.total).toBe(1);
464
-
465
- result = await User.search({
466
- archivedAt: { gte: '2019-06-01' },
467
- sort: {
468
- field: 'name',
469
- order: 'asc',
470
- },
471
- });
472
- expect(result.data).toMatchObject([{ name: 'Billy' }, { name: 'Willy' }]);
473
- expect(result.meta.total).toBe(2);
474
-
475
- result = await User.search({
476
- archivedAt: {},
477
- sort: {
478
- field: 'name',
479
- order: 'asc',
480
- },
481
- });
482
- expect(result.data).toMatchObject([{ name: 'Billy' }, { name: 'Willy' }]);
483
- expect(result.meta.total).toBe(2);
484
- });
485
-
486
- it('should allow date range search on dot path', async () => {
487
- let result;
488
- const User = createTestModel({
489
- user: {
490
- name: 'String',
491
- archivedAt: 'Date',
492
- },
493
- });
494
- await Promise.all([
495
- User.create({ user: { name: 'Billy', archivedAt: '2020-01-01' } }),
496
- User.create({ user: { name: 'Willy', archivedAt: '2021-01-01' } }),
497
- ]);
498
-
499
- result = await User.search({ 'user.archivedAt': { lte: '2020-06-01' } });
500
- expect(result.data).toMatchObject([{ user: { name: 'Billy' } }]);
501
- expect(result.meta.total).toBe(1);
502
- });
503
-
504
- it('should allow number range search', async () => {
505
- let result;
506
- const User = createTestModel({
507
- name: 'String',
508
- age: 'Number',
509
- });
510
- await Promise.all([
511
- User.create({ name: 'Billy', age: 22 }),
512
- User.create({ name: 'Willy', age: 54 }),
513
- ]);
514
-
515
- result = await User.search({ age: { lte: 25 } });
516
- expect(result.data).toMatchObject([{ name: 'Billy' }]);
517
- expect(result.meta.total).toBe(1);
518
-
519
- result = await User.search({ age: { gte: 25 } });
520
- expect(result.data).toMatchObject([{ name: 'Willy' }]);
521
- expect(result.meta.total).toBe(1);
522
-
523
- result = await User.search({
524
- age: { gte: 10 },
525
- sort: {
526
- field: 'name',
527
- order: 'asc',
528
- },
529
- });
530
- expect(result.data).toMatchObject([{ name: 'Billy' }, { name: 'Willy' }]);
531
- expect(result.meta.total).toBe(2);
532
-
533
- result = await User.search({
534
- age: {},
535
- sort: {
536
- field: 'name',
537
- order: 'asc',
538
- },
539
- });
540
- expect(result.data).toMatchObject([{ name: 'Billy' }, { name: 'Willy' }]);
541
- expect(result.meta.total).toBe(2);
542
- });
543
-
544
- it('should return the query to allow population', async () => {
545
- const User = createTestModel({
546
- name: 'String',
547
- });
548
- const Shop = createTestModel({
549
- user: {
550
- ref: User.modelName,
551
- type: mongoose.Schema.Types.ObjectId,
552
- },
553
- });
554
-
555
- const user = await User.create({ name: 'Billy' });
556
- await Shop.create({
557
- user: user.id,
558
- });
559
-
560
- const { data, meta } = await Shop.search().populate('user');
561
-
562
- expect(data).toMatchObject([{ user: { name: 'Billy' } }]);
563
- expect(meta).toEqual({
564
- total: 1,
565
- limit: 50,
566
- skip: 0,
567
- });
568
- });
569
-
570
- it('should error on bad queries', async () => {
571
- const User = createTestModel({
572
- name: 'String',
573
- });
574
- await expect(async () => {
575
- await User.search({ _id: 'bad' });
576
- }).rejects.toThrow();
577
- });
578
-
579
- it('should allow model-level override of search defaults', async () => {
580
- const schema = createSchema({
581
- attributes: {
582
- name: {
583
- type: 'String',
584
- required: true,
585
- },
586
- },
587
- search: {
588
- limit: 2,
589
- sort: {
590
- field: 'name',
591
- order: 'asc',
592
- },
593
- },
594
- });
595
- const User = createTestModel(schema);
596
- await Promise.all([
597
- User.create({ name: 'Welly' }),
598
- User.create({ name: 'Willy' }),
599
- User.create({ name: 'Chilly' }),
600
- User.create({ name: 'Smelly' }),
601
- User.create({ name: 'Billy' }),
602
- ]);
603
- const { data, meta } = await User.search();
604
- expect(data).toMatchObject([{ name: 'Billy' }, { name: 'Chilly' }]);
605
- expect(data.length).toBe(2);
606
- expect(meta.total).toBe(5);
607
- });
608
-
609
- it('should allow sorting on multiple fields', async () => {
610
- const User = createTestModel({
611
- name: 'String',
612
- age: 'Number',
613
- });
614
- await Promise.all([
615
- User.create({ name: 'Billy', age: 5 }),
616
- User.create({ name: 'Billy', age: 6 }),
617
- User.create({ name: 'Willy', age: 7 }),
618
- User.create({ name: 'Willy', age: 8 }),
619
- ]);
620
-
621
- const result = await User.search({
622
- sort: [
623
- {
624
- field: 'name',
625
- order: 'asc',
626
- },
627
- {
628
- field: 'age',
629
- order: 'desc',
630
- },
631
- ],
632
- });
633
- expect(result.data).toMatchObject([
634
- {
635
- name: 'Billy',
636
- age: 6,
637
- },
638
- {
639
- name: 'Billy',
640
- age: 5,
641
- },
642
- {
643
- name: 'Willy',
644
- age: 8,
645
- },
646
- {
647
- name: 'Willy',
648
- age: 7,
649
- },
650
- ]);
651
- });
652
- });