@bedrockio/model 0.11.2 → 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 +8 -0
- package/dist/cjs/const.js +1 -0
- package/dist/cjs/search.js +99 -46
- package/dist/cjs/soft-delete.js +11 -2
- package/package.json +1 -1
- package/src/const.js +1 -0
- package/src/search.js +115 -50
- package/src/soft-delete.js +9 -2
- package/types/const.d.ts +1 -0
- package/types/const.d.ts.map +1 -1
- package/types/search.d.ts.map +1 -1
- package/types/soft-delete.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
package/dist/cjs/const.js
CHANGED
package/dist/cjs/search.js
CHANGED
|
@@ -25,44 +25,12 @@ function applySearch(schema, definition) {
|
|
|
25
25
|
const {
|
|
26
26
|
search: config = {}
|
|
27
27
|
} = definition;
|
|
28
|
-
schema.static('search', function search(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
|
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/dist/cjs/soft-delete.js
CHANGED
|
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.applySoftDelete = applySoftDelete;
|
|
7
7
|
exports.assertUnique = assertUnique;
|
|
8
8
|
var _lodash = require("lodash");
|
|
9
|
+
var _warn = _interopRequireDefault(require("./warn"));
|
|
9
10
|
var _query = require("./query");
|
|
10
11
|
var _errors = require("./errors");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
13
|
function applySoftDelete(schema) {
|
|
12
14
|
applyQueries(schema);
|
|
13
15
|
applyUniqueConstraints(schema);
|
|
@@ -32,7 +34,7 @@ async function assertUnique(options) {
|
|
|
32
34
|
};
|
|
33
35
|
const exists = await model.exists(query);
|
|
34
36
|
if (exists) {
|
|
35
|
-
const message = getUniqueErrorMessage(
|
|
37
|
+
const message = getUniqueErrorMessage(field, options);
|
|
36
38
|
throw new _errors.UniqueConstraintError(message, {
|
|
37
39
|
model,
|
|
38
40
|
field,
|
|
@@ -40,7 +42,11 @@ async function assertUnique(options) {
|
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
function getUniqueErrorMessage(
|
|
45
|
+
function getUniqueErrorMessage(field, options) {
|
|
46
|
+
const {
|
|
47
|
+
id,
|
|
48
|
+
model
|
|
49
|
+
} = options;
|
|
44
50
|
const {
|
|
45
51
|
modelName
|
|
46
52
|
} = model;
|
|
@@ -48,6 +54,9 @@ function getUniqueErrorMessage(model, field) {
|
|
|
48
54
|
const name = field === 'phone' ? 'phone number' : field;
|
|
49
55
|
return `A user with that ${name} already exists.`;
|
|
50
56
|
} else {
|
|
57
|
+
if (!id) {
|
|
58
|
+
(0, _warn.default)('Note "id" field was not passed to exclude self for unique check.');
|
|
59
|
+
}
|
|
51
60
|
return `"${field}" already exists.`;
|
|
52
61
|
}
|
|
53
62
|
}
|
package/package.json
CHANGED
package/src/const.js
CHANGED
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(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
129
|
-
}
|
|
188
|
+
return { _id: 1 };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = {};
|
|
192
|
+
|
|
193
|
+
if (!Array.isArray(sort)) {
|
|
130
194
|
sort = [sort];
|
|
131
195
|
}
|
|
132
|
-
|
|
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
|
|
143
|
-
return [field, order === 'desc' ? -1 : 1];
|
|
144
|
-
});
|
|
209
|
+
return result;
|
|
145
210
|
}
|
|
146
211
|
|
|
147
212
|
// Keyword queries
|
package/src/soft-delete.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { isEqual } from 'lodash';
|
|
2
2
|
|
|
3
|
+
import warn from './warn';
|
|
4
|
+
|
|
3
5
|
import { wrapQuery } from './query';
|
|
4
6
|
import { UniqueConstraintError } from './errors';
|
|
5
7
|
|
|
@@ -24,8 +26,9 @@ export async function assertUnique(options) {
|
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const exists = await model.exists(query);
|
|
29
|
+
|
|
27
30
|
if (exists) {
|
|
28
|
-
const message = getUniqueErrorMessage(
|
|
31
|
+
const message = getUniqueErrorMessage(field, options);
|
|
29
32
|
throw new UniqueConstraintError(message, {
|
|
30
33
|
model,
|
|
31
34
|
field,
|
|
@@ -34,12 +37,16 @@ export async function assertUnique(options) {
|
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
function getUniqueErrorMessage(
|
|
40
|
+
function getUniqueErrorMessage(field, options) {
|
|
41
|
+
const { id, model } = options;
|
|
38
42
|
const { modelName } = model;
|
|
39
43
|
if (modelName === 'User' && !field.includes('.')) {
|
|
40
44
|
const name = field === 'phone' ? 'phone number' : field;
|
|
41
45
|
return `A user with that ${name} already exists.`;
|
|
42
46
|
} else {
|
|
47
|
+
if (!id) {
|
|
48
|
+
warn('Note "id" field was not passed to exclude self for unique check.');
|
|
49
|
+
}
|
|
43
50
|
return `"${field}" already exists.`;
|
|
44
51
|
}
|
|
45
52
|
}
|
package/types/const.d.ts
CHANGED
package/types/const.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.js"],"names":[],"mappings":";;;;;;;;AASA,iCAAkC,CAAC,CAAC"}
|
package/types/search.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,gEAaC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA4CyD,CAAC;;;;;;;;;;;;;;;;;EAnBzD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"AAOA,mDAIC;AAED,0DAwBC"}
|