dobo 2.30.7 → 2.32.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/.jsdoc.conf.json +1 -1
- package/docs/Dobo.html +1 -24
- package/docs/DoboAction.html +13 -0
- package/docs/DoboAdapter.html +3 -0
- package/docs/DoboConnection.html +3 -0
- package/docs/DoboFeature.html +3 -0
- package/docs/DoboModel.html +51 -0
- package/docs/data/search.json +1 -1
- package/docs/extend_bajo_hook-docs.js.html +241 -0
- package/docs/external-Tools.html +3 -0
- package/docs/global.html +1 -5
- package/docs/index.html +1 -1
- package/docs/index.js.html +434 -380
- package/docs/lib_factory_action.js.html +290 -0
- package/docs/lib_factory_adapter.js.html +1057 -0
- package/docs/lib_factory_connection.js.html +112 -0
- package/docs/lib_factory_feature.js.html +55 -0
- package/docs/lib_factory_model.js.html +503 -0
- package/docs/lib_factory_model_build.js.html +28 -0
- package/docs/lib_factory_model_clear-record.js.html +33 -0
- package/docs/lib_factory_model_count-record.js.html +45 -0
- package/docs/lib_factory_model_create-record.js.html +52 -0
- package/docs/lib_factory_model_drop.js.html +29 -0
- package/docs/lib_factory_model_exists.js.html +29 -0
- package/docs/lib_factory_model_find-all-record.js.html +115 -0
- package/docs/lib_factory_model_find-one-record.js.html +41 -0
- package/docs/lib_factory_model_find-record.js.html +85 -0
- package/docs/lib_factory_model_get-record.js.html +55 -0
- package/docs/lib_factory_model_helper.js.html +579 -0
- package/docs/lib_factory_model_remove-record.js.html +47 -0
- package/docs/lib_factory_model_sanitize-body.js.html +60 -0
- package/docs/lib_factory_model_sanitize-fixture.js.html +59 -0
- package/docs/lib_factory_model_sanitize-record.js.html +73 -0
- package/docs/lib_factory_model_update-record.js.html +61 -0
- package/docs/lib_factory_model_upsert-record.js.html +74 -0
- package/docs/{method_validate.js.html → lib_factory_model_validate.js.html} +109 -73
- package/docs/lib_helper.js.html +466 -0
- package/docs/module-Helper.html +3 -0
- package/docs/module-Helper_Model.html +3 -0
- package/docs/module-Hook.html +15 -0
- package/docs/scripts/core.js +477 -476
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +285 -282
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1202 -1195
- package/docs/scripts/third-party/hljs.js +1 -1
- package/docs/scripts/third-party/popper.js +1 -1
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +509 -508
- package/docs/static/home.md +3 -1
- package/extend/bajo/hook-docs.js +238 -0
- package/extend/bajo/intl/en-US.json +9 -9
- package/extend/bajo/intl/id.json +9 -9
- package/extend/bajoCli/applet/rebuild-model.js +2 -2
- package/extend/dobo/{driver → adapter}/memory.js +6 -6
- package/extend/dobo/feature/immutable.js +2 -2
- package/extend/dobo/feature/removed-at.js +1 -1
- package/index.js +33 -30
- package/lib/factory/action.js +145 -18
- package/lib/factory/adapter.js +1054 -0
- package/lib/factory/connection.js +46 -22
- package/lib/factory/feature.js +23 -3
- package/lib/factory/model/build.js +11 -2
- package/lib/factory/model/bulk-create-record.js +2 -2
- package/lib/factory/model/clear-record.js +13 -3
- package/lib/factory/model/count-record.js +13 -2
- package/lib/factory/model/create-aggregate.js +2 -2
- package/lib/factory/model/create-attachment.js +1 -1
- package/lib/factory/model/create-histogram.js +2 -2
- package/lib/factory/model/create-record.js +15 -2
- package/lib/factory/model/drop.js +12 -2
- package/lib/factory/model/exists.js +10 -6
- package/lib/factory/model/find-all-record.js +14 -3
- package/lib/factory/model/find-attachment.js +1 -1
- package/lib/factory/model/find-one-record.js +13 -1
- package/lib/factory/model/find-record.js +17 -53
- package/lib/factory/model/get-record.js +11 -39
- package/lib/factory/model/{_util.js → helper.js} +176 -16
- package/lib/factory/model/remove-attachment.js +1 -1
- package/lib/factory/model/remove-record.js +11 -28
- package/lib/factory/model/sanitize-body.js +14 -12
- package/lib/factory/model/sanitize-fixture.js +17 -4
- package/lib/factory/model/sanitize-record.js +9 -6
- package/lib/factory/model/transaction.js +2 -2
- package/lib/factory/model/update-record.js +12 -40
- package/lib/factory/model/upsert-record.js +13 -42
- package/lib/factory/model/validate.js +47 -14
- package/lib/factory/model.js +301 -23
- package/lib/{collect-models.js → helper.js} +463 -364
- package/package.json +1 -1
- package/wiki/CHANGES.md +17 -7
- package/wiki/ECOSYSTEM.md +7 -7
- package/wiki/GETTING-STARTED.md +3 -3
- package/wiki/QUERY-LANGUAGE.md +1 -1
- package/docs/lib_collect-connections.js.html +0 -39
- package/docs/lib_collect-drivers.js.html +0 -52
- package/docs/lib_collect-features.js.html +0 -36
- package/docs/lib_collect-schemas.js.html +0 -94
- package/docs/lib_index.js.html +0 -6
- package/docs/method_model_create.js.html +0 -35
- package/docs/method_model_drop.js.html +0 -34
- package/docs/method_model_exists.js.html +0 -40
- package/docs/method_record_count.js.html +0 -69
- package/docs/method_record_create.js.html +0 -114
- package/docs/method_record_find-all.js.html +0 -44
- package/docs/method_record_find-one.js.html +0 -73
- package/docs/method_record_find.js.html +0 -118
- package/docs/method_record_get.js.html +0 -92
- package/docs/method_record_remove.js.html +0 -75
- package/docs/method_record_update.js.html +0 -107
- package/docs/method_record_upsert.js.html +0 -54
- package/docs/method_sanitize_body.js.html +0 -88
- package/docs/method_sanitize_date.js.html +0 -30
- package/docs/method_sanitize_id.js.html +0 -20
- package/docs/module-Lib.html +0 -3
- package/lib/collect-connections.js +0 -56
- package/lib/collect-drivers.js +0 -35
- package/lib/collect-features.js +0 -40
- package/lib/factory/driver.js +0 -574
- package/lib/factory/model/sanitize-id.js +0 -7
- package/lib/index.js +0 -3
|
@@ -1,364 +1,463 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} else
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
ref.
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (item.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
item
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
await
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import adapterFactory from './factory/adapter.js'
|
|
3
|
+
import connectionFactory from './factory/connection.js'
|
|
4
|
+
import featureFactory from './factory/feature.js'
|
|
5
|
+
import modelFactory from './factory/model.js'
|
|
6
|
+
import actionFactory from './factory/action.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @module Helper
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sanitizes a property definition for a model, ensuring it adheres to the expected structure and types.
|
|
14
|
+
* It handles various property attributes such as type, index, required status, and more.
|
|
15
|
+
*
|
|
16
|
+
* If the property is virtual, it processes it accordingly; otherwise, it adds it to the model's properties
|
|
17
|
+
* and indexes.
|
|
18
|
+
* @param {DoboModel} model
|
|
19
|
+
* @param {DoboModel.TProperty} prop
|
|
20
|
+
* @param {DoboModel.TIndex[]} indexes
|
|
21
|
+
*/
|
|
22
|
+
async function sanitizeProp (model, prop, indexes) {
|
|
23
|
+
const { isEmpty, isString, keys, pick, isArray, isPlainObject, omit, isFunction } = this.app.lib._
|
|
24
|
+
const allPropKeys = this.getAllPropertyKeys(model.connection.adapter)
|
|
25
|
+
const propType = this.constructor.propertyType
|
|
26
|
+
if (isString(prop)) {
|
|
27
|
+
let [name, type, maxLength, idx, required] = prop.split(',').map(m => m.trim())
|
|
28
|
+
if (isEmpty(type)) type = 'string'
|
|
29
|
+
maxLength = isEmpty(maxLength) ? propType.string.maxLength : parseInt(maxLength)
|
|
30
|
+
prop = { name, type, maxLength }
|
|
31
|
+
if (!isEmpty(idx)) prop.index = idx
|
|
32
|
+
prop.required = required === 'true'
|
|
33
|
+
}
|
|
34
|
+
prop.type = prop.type ?? 'string'
|
|
35
|
+
if (isArray(prop.values)) {
|
|
36
|
+
prop.values = prop.values.map(item => {
|
|
37
|
+
if (isPlainObject(item)) return pick(item, ['value', 'text'])
|
|
38
|
+
return { value: item, text: item }
|
|
39
|
+
})
|
|
40
|
+
} else if (!(isString(prop.values) || isFunction(prop.values))) delete prop.values
|
|
41
|
+
if (prop.hidden) model.hidden.push(prop.name)
|
|
42
|
+
if (prop.scanable) model.scanables.push(prop.scanable)
|
|
43
|
+
if (prop.virtual) {
|
|
44
|
+
const keys = Object.keys(propType)
|
|
45
|
+
if (!keys.includes(prop.type)) this.fatal('unknownPropType%s%s', `${prop.name}:${prop.type}`, model.name)
|
|
46
|
+
for (const key of ['required', 'rules', 'index', 'validator', 'ref', 'rulesMsg', 'immutable']) {
|
|
47
|
+
delete prop[key]
|
|
48
|
+
}
|
|
49
|
+
model.properties.push(prop)
|
|
50
|
+
} else {
|
|
51
|
+
if (prop.index) {
|
|
52
|
+
if (prop.index === true || prop.index === 'true') prop.index = 'index'
|
|
53
|
+
const [idx, idxName] = prop.index.split(':')
|
|
54
|
+
const index = { name: idxName ?? `${model.collName}_${prop.name}_${idx}`, fields: [prop.name], type: idx }
|
|
55
|
+
indexes.push(index)
|
|
56
|
+
}
|
|
57
|
+
if (keys(propType).includes(prop.type)) model.properties.push(pick(prop, allPropKeys))
|
|
58
|
+
else {
|
|
59
|
+
const feature = this.getFeature(prop.type)
|
|
60
|
+
if (!feature) this.fatal('unknownPropType%s%s', prop.type, model.name)
|
|
61
|
+
const opts = omit(prop, ['name', 'type'])
|
|
62
|
+
opts.field = prop.name
|
|
63
|
+
await applyFeature.call(this, model, feature, opts, indexes)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Finds and sanitizes all properties for a given model schema, ensuring that the 'id' property is included and
|
|
70
|
+
* that all properties are properly processed.
|
|
71
|
+
* @param {object} schema - The model schema
|
|
72
|
+
* @param {array} inputs - The list of input properties to process
|
|
73
|
+
* @param {array} indexes - The list of indexes to update
|
|
74
|
+
*/
|
|
75
|
+
async function findAllProps (schema, inputs = [], indexes = []) {
|
|
76
|
+
const { isPlainObject, cloneDeep } = this.app.lib._
|
|
77
|
+
const isIdProp = inputs.find(p => {
|
|
78
|
+
return isPlainObject(p) ? p.name === 'id' : p.startsWith('id,')
|
|
79
|
+
})
|
|
80
|
+
if (!isIdProp) {
|
|
81
|
+
const idField = cloneDeep(schema.connection.adapter.idField)
|
|
82
|
+
idField.name = 'id'
|
|
83
|
+
inputs.unshift(idField)
|
|
84
|
+
}
|
|
85
|
+
for (const prop of inputs) {
|
|
86
|
+
await sanitizeProp.call(this, schema, prop, indexes)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Applies a feature to a model schema, adding properties, rules, hooks, and other
|
|
92
|
+
* configurations as defined by the feature.
|
|
93
|
+
* @param {object} schema - The model schema
|
|
94
|
+
* @param {object} feature - The feature to apply
|
|
95
|
+
* @param {object} options - The options for the feature
|
|
96
|
+
* @param {array} indexes - The list of indexes to update
|
|
97
|
+
*/
|
|
98
|
+
async function applyFeature (schema, feature, options, indexes) {
|
|
99
|
+
const { isArray, findIndex } = this.app.lib._
|
|
100
|
+
if (feature.name === 'dobo:unique' && options.field === 'id') {
|
|
101
|
+
const idx = findIndex(schema.properties, { name: 'id' })
|
|
102
|
+
if (idx > -1) schema.properties.pullAt(idx)
|
|
103
|
+
}
|
|
104
|
+
const item = await feature.handler(options)
|
|
105
|
+
if (item.rules) schema.rules.push(...item.rules)
|
|
106
|
+
if (item.scanables) schema.scanables.push(...item.scanables)
|
|
107
|
+
if (!isArray(item.properties)) item.properties = [item.properties]
|
|
108
|
+
for (const prop of item.properties) {
|
|
109
|
+
prop.feature = `${feature.plugin.ns}:${feature.name}`
|
|
110
|
+
await sanitizeProp.call(this, schema, prop, indexes)
|
|
111
|
+
}
|
|
112
|
+
if (item.hooks) {
|
|
113
|
+
item.hooks = item.hooks.map(hook => {
|
|
114
|
+
hook.level = hook.level ?? 999
|
|
115
|
+
return hook
|
|
116
|
+
})
|
|
117
|
+
schema.hooks.push(...item.hooks)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Finds and applies all features for a given model schema, ensuring that each feature is properly processed
|
|
123
|
+
* @param {object} schema - The model schema
|
|
124
|
+
* @param {array} inputs - The list of features to apply
|
|
125
|
+
* @param {array} indexes - The list of indexes to update
|
|
126
|
+
*/
|
|
127
|
+
async function findAllFeats (schema, inputs = [], indexes = []) {
|
|
128
|
+
const { isString, omit } = this.app.lib._
|
|
129
|
+
for (let feat of inputs) {
|
|
130
|
+
if (isString(feat)) feat = { name: feat }
|
|
131
|
+
const featName = feat.name.indexOf(':') === -1 ? `dobo:${feat.name}` : feat.name
|
|
132
|
+
const feature = this.app.dobo.getFeature(featName)
|
|
133
|
+
if (!feature) this.fatal('invalidFeature%s%s', schema.name, featName)
|
|
134
|
+
await applyFeature.call(this, schema, feature, omit(feat, 'name'), indexes)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Finds and applies all indexes for a given model schema, ensuring that each index is properly processed
|
|
140
|
+
* @param {object} schema - The model schema
|
|
141
|
+
* @param {array} inputs - The list of indexes to apply
|
|
142
|
+
* @param {array} indexes - The list of existing indexes to update
|
|
143
|
+
*/
|
|
144
|
+
async function findAllIndexes (schema, inputs = [], indexes = []) {
|
|
145
|
+
const items = []
|
|
146
|
+
for (const index of inputs) {
|
|
147
|
+
index.type = index.type ?? 'index'
|
|
148
|
+
index.fields = index.fields ?? []
|
|
149
|
+
if (!index.name) index.name = `${schema.name}_${index.fields.join('_')}_${index.type}`
|
|
150
|
+
items.push(index)
|
|
151
|
+
}
|
|
152
|
+
schema.indexes = [...items, ...indexes]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Sanitizes the references in a model schema, ensuring that all references point to valid models and properties.
|
|
157
|
+
* It checks for duplicate reference keys and validates the existence of referenced models and properties.
|
|
158
|
+
* @async
|
|
159
|
+
* @param {object} schema - The model schema to sanitize
|
|
160
|
+
* @param {object[]} schemas - The list of all model schemas
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
*/
|
|
163
|
+
export async function sanitizeRef (schema, schemas) {
|
|
164
|
+
const { find, isString, pullAt } = this.app.lib._
|
|
165
|
+
const _refKeys = []
|
|
166
|
+
for (const prop of schema.properties) {
|
|
167
|
+
const ignored = []
|
|
168
|
+
for (const key in prop.ref ?? {}) {
|
|
169
|
+
_refKeys.push(key)
|
|
170
|
+
let ref = prop.ref[key]
|
|
171
|
+
if (isString(ref)) {
|
|
172
|
+
ref = { field: ref }
|
|
173
|
+
}
|
|
174
|
+
ref.field = ref.field ?? 'id'
|
|
175
|
+
ref.type = ref.type ?? '1:1'
|
|
176
|
+
ref.searchField = ref.searchField ?? ref.field
|
|
177
|
+
ref.labelField = ref.labelField ?? ref.searchField
|
|
178
|
+
const rModel = find(schemas, { name: ref.model })
|
|
179
|
+
if (!rModel) {
|
|
180
|
+
ignored.push(key)
|
|
181
|
+
this.log.warn('notFound%s%s', this.t('model'), ref.model)
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
const rProp = find(rModel.properties, { name: ref.field })
|
|
185
|
+
if (!rProp) {
|
|
186
|
+
ignored.push(key)
|
|
187
|
+
this.log.warn('notFound%s%s', this.t('property'), `${ref.field}@${ref.model}`)
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
ref.fields = ref.fields ?? '*'
|
|
191
|
+
if (['*', 'all'].includes(ref.fields)) ref.fields = rModel.properties.map(p => p.name)
|
|
192
|
+
if (ref.fields.length > 0 && !ref.fields.includes('id')) ref.fields.unshift('id')
|
|
193
|
+
const removed = []
|
|
194
|
+
for (const idx in ref.fields) {
|
|
195
|
+
const p = find(rModel.properties, { name: ref.fields[idx] })
|
|
196
|
+
if (!p) removed.push(ref.fields[idx])
|
|
197
|
+
}
|
|
198
|
+
pullAt(ref.fields, removed)
|
|
199
|
+
prop.ref[key] = ref
|
|
200
|
+
}
|
|
201
|
+
for (const key of ignored) {
|
|
202
|
+
delete prop.ref[key]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const dupes = _refKeys.filter((item, index) => _refKeys.indexOf(item) !== index)
|
|
206
|
+
if (dupes.length > 0) throw this.error('duplicateRefKeys%s%s', schema.name, dupes.join(', '))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Sanitizes all model schemas, ensuring that properties, indexes, and references are properly defined and validated.
|
|
211
|
+
* It also determines the sortable fields based on the defined indexes.
|
|
212
|
+
* @async
|
|
213
|
+
* @param {object} schema - The model schema
|
|
214
|
+
* @returns {Promise<void>}
|
|
215
|
+
*/
|
|
216
|
+
export async function sanitizeAll (schema) {
|
|
217
|
+
const { runHook } = this.app.bajo
|
|
218
|
+
const { pick, keys, map, uniq, camelCase, filter } = this.app.lib._
|
|
219
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
220
|
+
const allPropNames = uniq(map(schema.properties, 'name'))
|
|
221
|
+
const propType = this.constructor.propertyType
|
|
222
|
+
const indexTypes = this.constructor.indexTypes
|
|
223
|
+
|
|
224
|
+
await runHook(`dobo.${camelCase(schema.name)}:beforeSanitizeModel`, schema)
|
|
225
|
+
// properties
|
|
226
|
+
for (const idx in schema.properties) {
|
|
227
|
+
let prop = schema.properties[idx]
|
|
228
|
+
const def = propType[prop.type]
|
|
229
|
+
prop = pick(defaultsDeep(prop, def), this.getPropertyKeysByType(prop.type))
|
|
230
|
+
if (!keys(propType).includes(prop.type)) this.fatal('unknownPropType%s%s', `${prop.name}.${def.name}`, prop.type)
|
|
231
|
+
if (prop.type === 'string') {
|
|
232
|
+
prop.minLength = parseInt(prop.minLength) ?? 0
|
|
233
|
+
prop.maxLength = parseInt(prop.maxLength) ?? 255
|
|
234
|
+
if (prop.minLength > 0) prop.required = true
|
|
235
|
+
if (prop.minLength === 0) delete prop.minLength
|
|
236
|
+
}
|
|
237
|
+
schema.properties[idx] = prop
|
|
238
|
+
}
|
|
239
|
+
// indexes
|
|
240
|
+
for (const index of schema.indexes) {
|
|
241
|
+
if (!indexTypes.includes(index.type)) this.fatal('unknownIndexType%s%s', index.type, schema.name)
|
|
242
|
+
for (const field of index.fields) {
|
|
243
|
+
if (!allPropNames.includes(field)) this.fatal('unknownPropNameOnIndex%s%s', field, schema.name)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
await runHook(`dobo.${camelCase(schema.name)}:afterSanitizeModel`, schema)
|
|
247
|
+
// sorting only possible using these fields
|
|
248
|
+
schema.sortables = []
|
|
249
|
+
for (const index of schema.indexes) {
|
|
250
|
+
schema.sortables.push(...index.fields)
|
|
251
|
+
}
|
|
252
|
+
schema.hidden = filter(uniq(schema.hidden), prop => allPropNames.includes(prop))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Creates a model schema by sanitizing its properties, features, and indexes, and ensuring that all references are valid.
|
|
257
|
+
* It also applies any buildStart and buildEnd handlers defined in the schema.
|
|
258
|
+
* @param {object} item - The model definition object
|
|
259
|
+
* @returns {object} - The sanitized model schema
|
|
260
|
+
*/
|
|
261
|
+
async function createSchema (item) {
|
|
262
|
+
const { find, orderBy, get } = this.app.lib._
|
|
263
|
+
const { mergeObjectsByKey, defaultsDeep, parseObject } = this.app.lib.aneka
|
|
264
|
+
const feats = item.features ?? []
|
|
265
|
+
const props = item.properties ?? []
|
|
266
|
+
const indexes = item.indexes ?? []
|
|
267
|
+
item.features = []
|
|
268
|
+
item.properties = []
|
|
269
|
+
item.indexes = []
|
|
270
|
+
item.hidden = item.hidden ?? []
|
|
271
|
+
item.rules = item.rules ?? []
|
|
272
|
+
item.buildLevel = item.buildLevel ?? 999
|
|
273
|
+
item.hooks = item.hooks ?? []
|
|
274
|
+
item.disabled = item.disabled ?? []
|
|
275
|
+
item.scanables = item.scanables ?? []
|
|
276
|
+
if (item.disabled === 'all') item.disabled = ['find', 'get', 'create', 'update', 'remove']
|
|
277
|
+
else if (item.disabled === 'readonly') item.disabled = ['create', 'update', 'remove']
|
|
278
|
+
const conn = item.connection ?? 'default'
|
|
279
|
+
if (!(conn instanceof this.app.baseClass.DoboConnection)) {
|
|
280
|
+
item.connection = null
|
|
281
|
+
// Is there any overwritten connection?
|
|
282
|
+
const newConn = find(this.connections, c => c.options.models.includes(item.name))
|
|
283
|
+
if (newConn) item.connection = newConn
|
|
284
|
+
else {
|
|
285
|
+
item.connection = this.getConnection(conn, true)
|
|
286
|
+
if (!item.connection && conn === 'default') item.connection = this.getConnection('memory')
|
|
287
|
+
}
|
|
288
|
+
if (!item.connection) this.fatal('unknownConn%s%s', conn, item.name)
|
|
289
|
+
}
|
|
290
|
+
// cache settings
|
|
291
|
+
const defCache = defaultsDeep({}, item.connection.options.cache, get(this, 'app.bajoCache.config.default', this.config.default.cache))
|
|
292
|
+
if (item.cache === false) item.cache = { ttlDur: 0 }
|
|
293
|
+
else if (item.cache === true) item.cache = defCache
|
|
294
|
+
else item.cache = defaultsDeep({}, item.cache, defCache)
|
|
295
|
+
item.cache = parseObject(item.cache)
|
|
296
|
+
if (item.connection.name === 'memory') item.cache.ttlDur = 0
|
|
297
|
+
|
|
298
|
+
// let's run
|
|
299
|
+
await findAllProps.call(this, item, props, indexes)
|
|
300
|
+
await findAllFeats.call(this, item, feats, indexes)
|
|
301
|
+
await findAllIndexes.call(this, item, indexes)
|
|
302
|
+
for (const key of ['properties', 'indexes']) {
|
|
303
|
+
item[key] = mergeObjectsByKey(item[key], 'name')
|
|
304
|
+
}
|
|
305
|
+
item.hooks = orderBy(item.hooks, ['name', 'level'])
|
|
306
|
+
delete item.features
|
|
307
|
+
await sanitizeAll.call(this, item)
|
|
308
|
+
return item
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Collects all model schemas from the application, sanitizes them, and creates model instances.
|
|
313
|
+
* It also handles any buildStart and buildEnd handlers defined in the model definitions.
|
|
314
|
+
* @async
|
|
315
|
+
* @method
|
|
316
|
+
* @returns {Promise<void>}
|
|
317
|
+
*/
|
|
318
|
+
export async function collectModels () {
|
|
319
|
+
const { eachPlugins, callHandler } = this.app.bajo
|
|
320
|
+
const { orderBy, has, omit } = this.app.lib._
|
|
321
|
+
await actionFactory.call(this)
|
|
322
|
+
await modelFactory.call(this)
|
|
323
|
+
|
|
324
|
+
this.log.trace('collecting%s', this.t('model'))
|
|
325
|
+
const me = this
|
|
326
|
+
let schemas = []
|
|
327
|
+
await eachPlugins(async function ({ file }) {
|
|
328
|
+
const { readConfig, callHandler } = this.app.bajo
|
|
329
|
+
const { pascalCase } = this.app.lib.aneka
|
|
330
|
+
const { isPlainObject, isEmpty, isArray } = this.app.lib._
|
|
331
|
+
|
|
332
|
+
let items = await readConfig(file, { ns: this.ns, baseNs: me.ns, merge: true })
|
|
333
|
+
if (isEmpty(items)) return undefined
|
|
334
|
+
if (isPlainObject(items)) {
|
|
335
|
+
items.baseName = items.baseName ?? path.basename(file, path.extname(file))
|
|
336
|
+
items.name = items.name ?? pascalCase(`${this.alias} ${items.baseName}`)
|
|
337
|
+
}
|
|
338
|
+
if (!isArray(items)) items = [items]
|
|
339
|
+
for (const item of items) {
|
|
340
|
+
if (!item.baseName) me.fatal('missing%s%s', 'baseName', file)
|
|
341
|
+
item.name = item.name ?? pascalCase(`${this.alias} ${item.baseName}`)
|
|
342
|
+
me.log.trace('- %s', item.name)
|
|
343
|
+
item.collName = item.collName ?? item.name
|
|
344
|
+
item.options = item.options ?? {}
|
|
345
|
+
item.options.attachment = item.options.attachment ?? true
|
|
346
|
+
item.options.persistence = item.options.persistence ?? true
|
|
347
|
+
item.ns = this.ns
|
|
348
|
+
if (item.buildStart) await callHandler(this, item.buildStart, item)
|
|
349
|
+
const schema = await createSchema.call(me, item)
|
|
350
|
+
schemas.push(schema)
|
|
351
|
+
}
|
|
352
|
+
}, { glob: ['model/*.*', 'model.*'], prefix: this.ns })
|
|
353
|
+
schemas = orderBy(schemas, ['buildLevel', 'name'])
|
|
354
|
+
for (const schema of schemas) {
|
|
355
|
+
const idProp = schema.properties.find(p => p.name === 'id')
|
|
356
|
+
if (!this.constructor.idTypes.includes(idProp.type)) this.fatal('invalidIdType%s%s', schema.name, this.constructor.idTypes.join(', '))
|
|
357
|
+
if (idProp.type === 'string' && !has(idProp, 'maxLength')) idProp.maxLength = 50
|
|
358
|
+
const plugin = this.app[schema.ns]
|
|
359
|
+
await sanitizeRef.call(this, schema, schemas)
|
|
360
|
+
for (const item of schema.indexes) {
|
|
361
|
+
for (const field of item.fields) {
|
|
362
|
+
const prop = schema.properties.find(p => p.name === field)
|
|
363
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s%s', field, 'index', schema.name)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const field of schema.scanables) {
|
|
367
|
+
const prop = schema.properties.find(p => p.name === field)
|
|
368
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s%s', field, 'scanable', schema.name)
|
|
369
|
+
}
|
|
370
|
+
const model = new this.app.baseClass.DoboModel(plugin, omit(schema, ['beforeCreate', 'afterCreate']))
|
|
371
|
+
me.models.push(model)
|
|
372
|
+
if (schema.buildEnd) await callHandler(plugin, schema.buildEnd, model)
|
|
373
|
+
}
|
|
374
|
+
schemas = []
|
|
375
|
+
this.log.debug('collected%s%d', this.t('model'), this.models.length)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Collects all model features from loaded plugins
|
|
380
|
+
* @async
|
|
381
|
+
* @method
|
|
382
|
+
* @returns {Promise<void>}
|
|
383
|
+
*/
|
|
384
|
+
export async function collectFeatures () {
|
|
385
|
+
const { eachPlugins } = this.app.bajo
|
|
386
|
+
await featureFactory.call(this)
|
|
387
|
+
|
|
388
|
+
this.log.trace('collecting%s', this.t('feature'))
|
|
389
|
+
const me = this
|
|
390
|
+
await eachPlugins(async function ({ file }) {
|
|
391
|
+
const { importModule } = this.app.bajo
|
|
392
|
+
const { camelCase, isFunction } = this.app.lib._
|
|
393
|
+
|
|
394
|
+
const name = camelCase(path.basename(file, '.js'))
|
|
395
|
+
const handler = await importModule(file)
|
|
396
|
+
if (!isFunction(handler)) this.fatal('invalidFeatureHandler%s%s', this.ns, name)
|
|
397
|
+
me.features.push(new this.app.baseClass.DoboFeature(this, { name, handler }))
|
|
398
|
+
me.log.trace('- %s:%s', this.ns, name)
|
|
399
|
+
}, { glob: 'feature/*.js', prefix: this.ns })
|
|
400
|
+
this.log.debug('collected%s%d', this.t('feature'), this.features.length)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Collects all database connections from loaded plugins, initializes their adapters, and ensures that a default connection is available.
|
|
405
|
+
* It also handles any memory-based connections and merges their models into a single memory connection.
|
|
406
|
+
* @async
|
|
407
|
+
* @method
|
|
408
|
+
* @returns {Promise<void>}
|
|
409
|
+
*/
|
|
410
|
+
export async function collectConnections () {
|
|
411
|
+
const { buildCollections } = this.app.bajo
|
|
412
|
+
const { pullAt } = this.app.lib._
|
|
413
|
+
const { filterIndex } = this.app.lib.aneka
|
|
414
|
+
await connectionFactory.call(this)
|
|
415
|
+
|
|
416
|
+
async function handler ({ item }) {
|
|
417
|
+
const { has } = this.app.lib._
|
|
418
|
+
if (!has(item, 'adapter')) this.fatal('unknownDbAdapter%s')
|
|
419
|
+
const conn = new this.app.baseClass.DoboConnection(this, item)
|
|
420
|
+
await conn.initAdapter(item.adapter)
|
|
421
|
+
return conn
|
|
422
|
+
}
|
|
423
|
+
const memIndexes = filterIndex(this.config.connections, current => current.adapter === 'dobo:memory' || current.name === 'memory')
|
|
424
|
+
const models = memIndexes.map(idx => [...(this.config.connections[idx].models ?? [])])
|
|
425
|
+
pullAt(this.config.connections, memIndexes)
|
|
426
|
+
this.config.connections.push({
|
|
427
|
+
adapter: 'dobo:memory',
|
|
428
|
+
name: 'memory',
|
|
429
|
+
models
|
|
430
|
+
})
|
|
431
|
+
this.connections = await buildCollections({ ns: this.ns, container: 'connections', handler, dupChecks: ['name'] })
|
|
432
|
+
const defConn = this.connections.find(conn => conn.name === 'default')
|
|
433
|
+
if (!defConn) this.log.warn('noDefaultConnection')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Collects all database adapters from loaded plugins.
|
|
438
|
+
* @async
|
|
439
|
+
* @method
|
|
440
|
+
* @returns {Promise<void>}
|
|
441
|
+
*/
|
|
442
|
+
export async function collectAdapters () {
|
|
443
|
+
const { eachPlugins, runHook } = this.app.bajo
|
|
444
|
+
const { importModule } = this.app.bajo
|
|
445
|
+
const { camelCase, isFunction } = this.app.lib._
|
|
446
|
+
await adapterFactory.call(this)
|
|
447
|
+
|
|
448
|
+
this.log.trace('collecting%s', this.t('adapter'))
|
|
449
|
+
const me = this
|
|
450
|
+
await runHook(`${this.ns}:beforeCollectAdapters`)
|
|
451
|
+
await eachPlugins(async function ({ file }) {
|
|
452
|
+
const name = camelCase(path.basename(file, '.js'))
|
|
453
|
+
const factory = await importModule(file)
|
|
454
|
+
if (!isFunction(factory)) this.fatal('invalidAdapterClassFactory%s%s', this.ns, name)
|
|
455
|
+
const Cls = await factory.call(this)
|
|
456
|
+
const instance = new Cls(this, name)
|
|
457
|
+
if (!(instance instanceof this.app.baseClass.DoboAdapter)) this.fatal('invalidAdapterClass%s%s', this.ns, name)
|
|
458
|
+
me.adapters.push(instance)
|
|
459
|
+
me.log.trace('- %s:%s', this.ns, name)
|
|
460
|
+
}, { glob: 'adapter/*.js', prefix: this.ns })
|
|
461
|
+
await runHook(`${this.ns}:afterCollectAdapters`)
|
|
462
|
+
this.log.debug('collected%s%d', this.t('adapter'), this.adapters.length)
|
|
463
|
+
}
|