@bedrockio/model 0.11.3 → 0.12.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.12.0
2
+
3
+ - Handle aggregate pipelines in search.
4
+
1
5
  ## 0.11.3
2
6
 
3
7
  - Added warning when id field not passed for unique check.
package/dist/cjs/const.js CHANGED
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.SEARCH_DEFAULTS = exports.POPULATE_MAX_DEPTH = void 0;
7
7
  const SEARCH_DEFAULTS = exports.SEARCH_DEFAULTS = {
8
+ skip: 0,
8
9
  limit: 50,
9
10
  sort: {
10
11
  field: '_id',
@@ -25,44 +25,12 @@ function applySearch(schema, definition) {
25
25
  const {
26
26
  search: config = {}
27
27
  } = definition;
28
- schema.static('search', function search(body = {}) {
29
- const options = mergeOptions(_const.SEARCH_DEFAULTS, config.query, body);
30
- const {
31
- ids,
32
- keyword,
33
- skip = 0,
34
- limit,
35
- sort,
36
- ...rest
37
- } = options;
38
- let query = normalizeQuery(rest, schema.obj);
39
- if (ids?.length) {
40
- query = {
41
- ...query,
42
- _id: {
43
- $in: ids
44
- }
45
- };
46
- }
47
- if (keyword) {
48
- const keywordQuery = buildKeywordQuery(schema, keyword, config);
49
- query = (0, _query.mergeQuery)(query, keywordQuery);
50
- }
51
- if (_env.debug) {
52
- _logger.default.info(`Search query for ${this.modelName}:\n`, JSON.stringify(query, null, 2));
28
+ schema.static('search', function search(...args) {
29
+ if (Array.isArray(args[0])) {
30
+ return searchPipeline(this, args[0], args[1]);
31
+ } else {
32
+ return searchQuery(this, args[0], config);
53
33
  }
54
- const mQuery = this.find(query).sort(resolveSort(sort, schema)).skip(skip).limit(limit);
55
- return (0, _query.wrapQuery)(mQuery, async promise => {
56
- const [data, total] = await Promise.all([promise, this.countDocuments(query)]);
57
- return {
58
- data,
59
- meta: {
60
- total,
61
- skip,
62
- limit
63
- }
64
- };
65
- });
66
34
  });
67
35
  }
68
36
  function searchValidation(options = {}) {
@@ -89,6 +57,90 @@ function searchValidation(options = {}) {
89
57
  ...appendSchema
90
58
  });
91
59
  }
60
+ function searchQuery(Model, options, config) {
61
+ const {
62
+ schema
63
+ } = Model;
64
+ options = mergeOptions(_const.SEARCH_DEFAULTS, options);
65
+ let {
66
+ ids,
67
+ keyword,
68
+ skip,
69
+ limit,
70
+ sort,
71
+ ...rest
72
+ } = options;
73
+ sort = resolveSort(sort, schema);
74
+ let query = normalizeQuery(rest, schema.obj);
75
+ if (ids?.length) {
76
+ query = (0, _query.mergeQuery)(query, {
77
+ _id: {
78
+ $in: ids
79
+ }
80
+ });
81
+ }
82
+ if (keyword) {
83
+ const keywordQuery = buildKeywordQuery(schema, keyword, config);
84
+ query = (0, _query.mergeQuery)(query, keywordQuery);
85
+ }
86
+ if (_env.debug) {
87
+ _logger.default.info(`Search query for ${Model.modelName}:\n`, JSON.stringify(query, null, 2));
88
+ }
89
+ const mQuery = Model.find(query).sort(sort).skip(skip).limit(limit);
90
+ return (0, _query.wrapQuery)(mQuery, async promise => {
91
+ const [data, total] = await Promise.all([promise, Model.countDocuments(query)]);
92
+ return {
93
+ data,
94
+ meta: {
95
+ total,
96
+ skip,
97
+ limit
98
+ }
99
+ };
100
+ });
101
+ }
102
+ function searchPipeline(Model, pipeline, options) {
103
+ const {
104
+ schema
105
+ } = Model;
106
+ options = mergeOptions(_const.SEARCH_DEFAULTS, options);
107
+ let {
108
+ skip,
109
+ limit,
110
+ sort
111
+ } = options;
112
+ sort = resolveSort(sort, schema);
113
+ if (_env.debug) {
114
+ _logger.default.info(`Search pipeline for ${Model.modelName}:\n`, JSON.stringify(pipeline, null, 2));
115
+ }
116
+ const aggregate = Model.aggregate([...pipeline, {
117
+ $facet: {
118
+ data: [{
119
+ $sort: sort
120
+ }, {
121
+ $skip: skip
122
+ }, {
123
+ $limit: limit
124
+ }],
125
+ meta: [{
126
+ $count: 'total'
127
+ }]
128
+ }
129
+ }]);
130
+ return (0, _query.wrapQuery)(aggregate, async promise => {
131
+ const result = await promise;
132
+ const data = result[0].data;
133
+ const total = result[0].meta[0]?.total ?? 0;
134
+ return {
135
+ data,
136
+ meta: {
137
+ skip,
138
+ limit,
139
+ total
140
+ }
141
+ };
142
+ });
143
+ }
92
144
  function getSortSchema(sort) {
93
145
  const schema = _yada.default.object({
94
146
  field: _yada.default.string().required(),
@@ -104,13 +156,18 @@ function validateDefinition(definition) {
104
156
  }
105
157
  function resolveSort(sort, schema) {
106
158
  if (!sort) {
107
- sort = [];
108
- } else if (!Array.isArray(sort)) {
159
+ return {
160
+ _id: 1
161
+ };
162
+ }
163
+ const result = {};
164
+ if (!Array.isArray(sort)) {
109
165
  sort = [sort];
110
166
  }
111
167
  for (let {
112
168
  name,
113
- field
169
+ field,
170
+ order
114
171
  } of sort) {
115
172
  if (name) {
116
173
  throw new Error('Sort property "name" is not allowed. Use "field" instead.');
@@ -118,13 +175,9 @@ function resolveSort(sort, schema) {
118
175
  if (!field.startsWith('$') && !schema.path(field)) {
119
176
  throw new Error(`Unknown sort field "${field}".`);
120
177
  }
178
+ result[field] = order === 'desc' ? -1 : 1;
121
179
  }
122
- return sort.map(({
123
- field,
124
- order
125
- }) => {
126
- return [field, order === 'desc' ? -1 : 1];
127
- });
180
+ return result;
128
181
  }
129
182
 
130
183
  // Keyword queries
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/const.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const SEARCH_DEFAULTS = {
2
+ skip: 0,
2
3
  limit: 50,
3
4
  sort: {
4
5
  field: '_id',
package/src/search.js CHANGED
@@ -26,51 +26,12 @@ export function applySearch(schema, definition) {
26
26
 
27
27
  const { search: config = {} } = definition;
28
28
 
29
- schema.static('search', function search(body = {}) {
30
- const options = mergeOptions(SEARCH_DEFAULTS, config.query, body);
31
-
32
- const { ids, keyword, skip = 0, limit, sort, ...rest } = options;
33
-
34
- let query = normalizeQuery(rest, schema.obj);
35
-
36
- if (ids?.length) {
37
- query = {
38
- ...query,
39
- _id: { $in: ids },
40
- };
41
- }
42
-
43
- if (keyword) {
44
- const keywordQuery = buildKeywordQuery(schema, keyword, config);
45
- query = mergeQuery(query, keywordQuery);
46
- }
47
-
48
- if (debug) {
49
- logger.info(
50
- `Search query for ${this.modelName}:\n`,
51
- JSON.stringify(query, null, 2),
52
- );
29
+ schema.static('search', function search(...args) {
30
+ if (Array.isArray(args[0])) {
31
+ return searchPipeline(this, args[0], args[1]);
32
+ } else {
33
+ return searchQuery(this, args[0], config);
53
34
  }
54
-
55
- const mQuery = this.find(query)
56
- .sort(resolveSort(sort, schema))
57
- .skip(skip)
58
- .limit(limit);
59
-
60
- return wrapQuery(mQuery, async (promise) => {
61
- const [data, total] = await Promise.all([
62
- promise,
63
- this.countDocuments(query),
64
- ]);
65
- return {
66
- data,
67
- meta: {
68
- total,
69
- skip,
70
- limit,
71
- },
72
- };
73
- });
74
35
  });
75
36
  }
76
37
 
@@ -101,6 +62,105 @@ export function searchValidation(options = {}) {
101
62
  });
102
63
  }
103
64
 
65
+ function searchQuery(Model, options, config) {
66
+ const { schema } = Model;
67
+
68
+ options = mergeOptions(SEARCH_DEFAULTS, options);
69
+ let { ids, keyword, skip, limit, sort, ...rest } = options;
70
+
71
+ sort = resolveSort(sort, schema);
72
+
73
+ let query = normalizeQuery(rest, schema.obj);
74
+
75
+ if (ids?.length) {
76
+ query = mergeQuery(query, {
77
+ _id: { $in: ids },
78
+ });
79
+ }
80
+
81
+ if (keyword) {
82
+ const keywordQuery = buildKeywordQuery(schema, keyword, config);
83
+ query = mergeQuery(query, keywordQuery);
84
+ }
85
+
86
+ if (debug) {
87
+ logger.info(
88
+ `Search query for ${Model.modelName}:\n`,
89
+ JSON.stringify(query, null, 2),
90
+ );
91
+ }
92
+
93
+ const mQuery = Model.find(query).sort(sort).skip(skip).limit(limit);
94
+
95
+ return wrapQuery(mQuery, async (promise) => {
96
+ const [data, total] = await Promise.all([
97
+ promise,
98
+ Model.countDocuments(query),
99
+ ]);
100
+ return {
101
+ data,
102
+ meta: {
103
+ total,
104
+ skip,
105
+ limit,
106
+ },
107
+ };
108
+ });
109
+ }
110
+
111
+ function searchPipeline(Model, pipeline, options) {
112
+ const { schema } = Model;
113
+ options = mergeOptions(SEARCH_DEFAULTS, options);
114
+
115
+ let { skip, limit, sort } = options;
116
+ sort = resolveSort(sort, schema);
117
+
118
+ if (debug) {
119
+ logger.info(
120
+ `Search pipeline for ${Model.modelName}:\n`,
121
+ JSON.stringify(pipeline, null, 2),
122
+ );
123
+ }
124
+
125
+ const aggregate = Model.aggregate([
126
+ ...pipeline,
127
+ {
128
+ $facet: {
129
+ data: [
130
+ {
131
+ $sort: sort,
132
+ },
133
+ {
134
+ $skip: skip,
135
+ },
136
+ {
137
+ $limit: limit,
138
+ },
139
+ ],
140
+ meta: [
141
+ {
142
+ $count: 'total',
143
+ },
144
+ ],
145
+ },
146
+ },
147
+ ]);
148
+
149
+ return wrapQuery(aggregate, async (promise) => {
150
+ const result = await promise;
151
+ const data = result[0].data;
152
+ const total = result[0].meta[0]?.total ?? 0;
153
+ return {
154
+ data,
155
+ meta: {
156
+ skip,
157
+ limit,
158
+ total,
159
+ },
160
+ };
161
+ });
162
+ }
163
+
104
164
  function getSortSchema(sort) {
105
165
  const schema = yd
106
166
  .object({
@@ -125,11 +185,16 @@ function validateDefinition(definition) {
125
185
 
126
186
  function resolveSort(sort, schema) {
127
187
  if (!sort) {
128
- sort = [];
129
- } else if (!Array.isArray(sort)) {
188
+ return { _id: 1 };
189
+ }
190
+
191
+ const result = {};
192
+
193
+ if (!Array.isArray(sort)) {
130
194
  sort = [sort];
131
195
  }
132
- for (let { name, field } of sort) {
196
+
197
+ for (let { name, field, order } of sort) {
133
198
  if (name) {
134
199
  throw new Error(
135
200
  'Sort property "name" is not allowed. Use "field" instead.',
@@ -138,10 +203,10 @@ function resolveSort(sort, schema) {
138
203
  if (!field.startsWith('$') && !schema.path(field)) {
139
204
  throw new Error(`Unknown sort field "${field}".`);
140
205
  }
206
+
207
+ result[field] = order === 'desc' ? -1 : 1;
141
208
  }
142
- return sort.map(({ field, order }) => {
143
- return [field, order === 'desc' ? -1 : 1];
144
- });
209
+ return result;
145
210
  }
146
211
 
147
212
  // Keyword queries
package/types/const.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export namespace SEARCH_DEFAULTS {
2
+ let skip: number;
2
3
  let limit: number;
3
4
  namespace sort {
4
5
  let field: string;
@@ -1 +1 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.js"],"names":[],"mappings":";;;;;;;AAQA,iCAAkC,CAAC,CAAC"}
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.js"],"names":[],"mappings":";;;;;;;;AASA,iCAAkC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,gEAoDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAYa,CAAC;;;;;;;;;;;;;;;;;EAab"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,gEAaC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA4CyD,CAAC;;;;;;;;;;;;;;;;;EAnBzD"}