objectmodel 4.2.3 → 4.3.1
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/build/{add-banner.cjs → add-banner.js} +2 -2
- package/build/{update-docs.cjs → update-docs.js} +7 -5
- package/dist/object-model.cjs +493 -490
- package/dist/object-model.js +493 -490
- package/dist/object-model.js.map +1 -1
- package/dist/object-model.min.js +2 -2
- package/dist/object-model.min.js.map +1 -1
- package/index.html +1603 -1605
- package/package.json +64 -60
- package/src/object-model.js +479 -476
- package/test/any-model.spec.cjs +68 -68
- package/test/array-model.spec.cjs +0 -1
- package/test/index.html +27 -27
- package/test/object-model.spec.cjs +39 -0
- package/test/umd.html +8 -8
- package/types/index.d.ts +138 -136
- package/test/lib/qunit.css +0 -436
- package/test/lib/qunit.js +0 -6582
- package/tsconfig.json +0 -10
package/src/object-model.js
CHANGED
|
@@ -1,477 +1,480 @@
|
|
|
1
|
-
import {
|
|
2
|
-
bettertypeof, define, extend, getProto, has,
|
|
3
|
-
is, isFunction, isObject, isPlainObject, isString,
|
|
4
|
-
merge, ObjectProto, proxify, setProto
|
|
5
|
-
} from "./helpers.js"
|
|
6
|
-
|
|
7
|
-
export const
|
|
8
|
-
_check = Symbol(),
|
|
9
|
-
_checked = Symbol(), // used to skip validation at instanciation for perf
|
|
10
|
-
_original = Symbol(), // used to bypass proxy
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
model
|
|
24
|
-
model.
|
|
25
|
-
model.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
merge(_this,
|
|
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
|
-
return def
|
|
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
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
result =
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
if (obj
|
|
180
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
privateAccess =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
Any.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
bettertypeof, define, extend, getProto, has,
|
|
3
|
+
is, isFunction, isObject, isPlainObject, isString,
|
|
4
|
+
merge, ObjectProto, proxify, setProto
|
|
5
|
+
} from "./helpers.js"
|
|
6
|
+
|
|
7
|
+
export const
|
|
8
|
+
_check = Symbol(),
|
|
9
|
+
_checked = Symbol(), // used to skip validation at instanciation for perf
|
|
10
|
+
_original = Symbol(), // used to bypass proxy
|
|
11
|
+
CHECK_ONCE = Symbol(),
|
|
12
|
+
|
|
13
|
+
initModel = (def, constructor, parent, init, getTraps, useNew) => {
|
|
14
|
+
const model = function (val = model.default, mode) {
|
|
15
|
+
if (useNew && !is(model, this)) return new model(val)
|
|
16
|
+
if (init) val = init(val, model, this)
|
|
17
|
+
|
|
18
|
+
if (mode === _checked || check(model, val))
|
|
19
|
+
return getTraps && mode !== CHECK_ONCE ? proxify(val, getTraps(model)) : val
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (parent) extend(model, parent)
|
|
23
|
+
setProto(model, constructor.prototype)
|
|
24
|
+
model.constructor = constructor
|
|
25
|
+
model.definition = def
|
|
26
|
+
model.assertions = [...model.assertions]
|
|
27
|
+
define(model, "errors", [])
|
|
28
|
+
delete model.name
|
|
29
|
+
return model
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
initObjectModel = (obj, model, _this) => {
|
|
33
|
+
if (is(model, obj)) return obj
|
|
34
|
+
|
|
35
|
+
if (!isObject(obj) && !isFunction(obj) && obj !== undefined) {
|
|
36
|
+
// short circuit validation if not receiving an object as expected
|
|
37
|
+
return obj
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
merge(_this, model.default)
|
|
41
|
+
if (model.parentClass) merge(obj, new model.parentClass(obj))
|
|
42
|
+
merge(_this, obj)
|
|
43
|
+
return _this
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
extendModel = (child, parent, newProps) => {
|
|
47
|
+
extend(child, parent, newProps)
|
|
48
|
+
child.assertions.push(...parent.assertions)
|
|
49
|
+
return child
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
stackError = (errors, expected, received, path, message) => {
|
|
53
|
+
errors.push({ expected, received, path, message })
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
unstackErrors = (model, collector = model.errorCollector) => {
|
|
57
|
+
const nbErrors = model.errors.length
|
|
58
|
+
if (nbErrors > 0) {
|
|
59
|
+
const errors = model.errors.map(err => {
|
|
60
|
+
if (!err.message) {
|
|
61
|
+
err.message = "expecting " + (err.path ? err.path + " to be " : "") + formatDefinition(err.expected)
|
|
62
|
+
+ ", got " + (err.received != null ? bettertypeof(err.received) + " " : "") + format(err.received)
|
|
63
|
+
}
|
|
64
|
+
return err
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
model.errors.length = 0
|
|
68
|
+
collector.call(model, errors) // throw all errors collected
|
|
69
|
+
}
|
|
70
|
+
return nbErrors
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
isModelInstance = i => i && getProto(i) && is(Model, getProto(i).constructor),
|
|
74
|
+
|
|
75
|
+
parseDefinition = (def) => {
|
|
76
|
+
if (isPlainObject(def)) {
|
|
77
|
+
def = {}
|
|
78
|
+
for (let key in def) { def[key] = parseDefinition(def[key]) }
|
|
79
|
+
}
|
|
80
|
+
else if (!Array.isArray(def)) return [def]
|
|
81
|
+
else if (def.length === 1) return [def[0], undefined, null]
|
|
82
|
+
|
|
83
|
+
return def
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
formatDefinition = (def, stack) => {
|
|
87
|
+
const parts = parseDefinition(def).map(d => format(d, stack))
|
|
88
|
+
return parts.length > 1 ? parts.join(" or ") : parts[0]
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
formatAssertions = fns => fns.length ? `(${fns.map(f => f.name || f.description || f)})` : "",
|
|
92
|
+
|
|
93
|
+
extendDefinition = (def, newParts = []) => {
|
|
94
|
+
if (newParts.length > 0) {
|
|
95
|
+
def = [].concat(def, ...[].concat(newParts))// clone to lose ref
|
|
96
|
+
.filter((value, index, self) => self.indexOf(value) === index) // remove duplicates
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return def
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
check = (model, obj) => {
|
|
103
|
+
model[_check](obj, null, model.errors, [], true);
|
|
104
|
+
return !unstackErrors(model)
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
checkDefinition = (obj, def, path, errors, stack, shouldCast) => {
|
|
108
|
+
const indexFound = stack.indexOf(def)
|
|
109
|
+
if (indexFound !== -1 && stack.indexOf(def, indexFound + 1) !== -1)
|
|
110
|
+
return obj // if found twice in call stack, cycle detected, skip validation
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(def) && def.length === 1 && obj != null) {
|
|
113
|
+
def = def[0] // shorten validation path for optionals
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (is(Model, def)) {
|
|
117
|
+
if (shouldCast) obj = cast(obj, def)
|
|
118
|
+
def[_check](obj, path, errors, stack.concat(def))
|
|
119
|
+
}
|
|
120
|
+
else if (isPlainObject(def)) {
|
|
121
|
+
for (let key in def) {
|
|
122
|
+
const val = obj ? obj[key] : undefined
|
|
123
|
+
checkDefinition(val, def[key], formatPath(path, key), errors, stack, shouldCast)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const pdef = parseDefinition(def)
|
|
128
|
+
if (pdef.some(part => checkDefinitionPart(obj, part, path, stack))) {
|
|
129
|
+
return shouldCast ? cast(obj, def) : obj
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
stackError(errors, def, obj, path)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return obj
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
checkDefinitionPart = (obj, def, path, stack, shouldCast) => {
|
|
139
|
+
if (def === Any) return true
|
|
140
|
+
if (obj == null) return obj === def
|
|
141
|
+
if (isPlainObject(def) || is(Model, def)) { // object or model as part of union type
|
|
142
|
+
const errors = []
|
|
143
|
+
checkDefinition(obj, def, path, errors, stack, shouldCast)
|
|
144
|
+
return !errors.length
|
|
145
|
+
}
|
|
146
|
+
if (is(RegExp, def)) return def.test(obj)
|
|
147
|
+
if (def === Number || def === Date) return obj.constructor === def && !isNaN(obj)
|
|
148
|
+
return obj === def
|
|
149
|
+
|| (isFunction(def) && is(def, obj))
|
|
150
|
+
|| obj.constructor === def
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
checkAssertions = (obj, model, path, errors = model.errors) => {
|
|
154
|
+
for (let assertion of model.assertions) {
|
|
155
|
+
let result
|
|
156
|
+
try {
|
|
157
|
+
result = assertion.call(model, obj)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
result = err
|
|
160
|
+
}
|
|
161
|
+
if (result !== true) {
|
|
162
|
+
const onFail = isFunction(assertion.description) ? assertion.description : (assertionResult, value) =>
|
|
163
|
+
`assertion "${assertion.description}" returned ${format(assertionResult)} `
|
|
164
|
+
+ `for ${path ? path + " =" : "value"} ${format(value)}`
|
|
165
|
+
stackError(errors, assertion, obj, path, onFail.call(model, result, obj, path))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
format = (obj, stack = []) => {
|
|
171
|
+
if (stack.length > 15 || stack.includes(obj)) return "..."
|
|
172
|
+
if (obj === null || obj === undefined) return String(obj)
|
|
173
|
+
if (isString(obj)) return `"${obj}"`
|
|
174
|
+
if (is(Model, obj)) return obj.toString(stack)
|
|
175
|
+
|
|
176
|
+
stack.unshift(obj)
|
|
177
|
+
|
|
178
|
+
if (isFunction(obj)) return obj.name || obj.toString()
|
|
179
|
+
if (is(Map, obj) || is(Set, obj)) return format([...obj])
|
|
180
|
+
if (Array.isArray(obj)) return `[${obj.map(item => format(item, stack)).join(", ")}]`
|
|
181
|
+
if (obj.toString && obj.toString !== ObjectProto.toString) return obj.toString()
|
|
182
|
+
if (isObject(obj)) {
|
|
183
|
+
const props = Object.keys(obj),
|
|
184
|
+
indent = "\t".repeat(stack.length)
|
|
185
|
+
return `{${props.map(
|
|
186
|
+
key => `\n${indent + key}: ${format(obj[key], [...stack])}`
|
|
187
|
+
).join(", ")} ${props.length ? `\n${indent.slice(1)}` : ""}}`
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return String(obj)
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
formatPath = (path, key) => path ? path + "." + key : key,
|
|
194
|
+
|
|
195
|
+
controlMutation = (model, def, path, o, key, privateAccess, applyMutation) => {
|
|
196
|
+
const newPath = formatPath(path, key),
|
|
197
|
+
isPrivate = model.conventionForPrivate(key),
|
|
198
|
+
isConstant = model.conventionForConstant(key),
|
|
199
|
+
isOwnProperty = has(o, key),
|
|
200
|
+
initialPropDescriptor = isOwnProperty && Object.getOwnPropertyDescriptor(o, key)
|
|
201
|
+
|
|
202
|
+
if (key in def && ((isPrivate && !privateAccess) || (isConstant && o[key] !== undefined)))
|
|
203
|
+
cannot(`modify ${isPrivate ? "private" : "constant"} property ${key}`, model)
|
|
204
|
+
|
|
205
|
+
applyMutation()
|
|
206
|
+
if (has(def, key)) checkDefinition(o[key], def[key], newPath, model.errors, [])
|
|
207
|
+
checkAssertions(o, model, newPath)
|
|
208
|
+
|
|
209
|
+
const nbErrors = model.errors.length
|
|
210
|
+
if (nbErrors) {
|
|
211
|
+
if (isOwnProperty) Object.defineProperty(o, key, initialPropDescriptor)
|
|
212
|
+
else delete o[key] // back to the initial property defined in prototype chain
|
|
213
|
+
|
|
214
|
+
unstackErrors(model)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return !nbErrors
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
cannot = (msg, model) => {
|
|
221
|
+
model.errors.push({ message: "cannot " + msg })
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
cast = (obj, defNode = []) => {
|
|
225
|
+
if (!obj || isPlainObject(defNode) || is(BasicModel, defNode) || isModelInstance(obj))
|
|
226
|
+
return obj // no value or not leaf or already a model instance
|
|
227
|
+
|
|
228
|
+
const def = parseDefinition(defNode),
|
|
229
|
+
suitableModels = []
|
|
230
|
+
|
|
231
|
+
for (let part of def) {
|
|
232
|
+
if (is(Model, part) && !is(BasicModel, part) && part.test(obj))
|
|
233
|
+
suitableModels.push(part)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (suitableModels.length === 1) {
|
|
237
|
+
// automatically cast to suitable model when explicit (autocasting)
|
|
238
|
+
return new suitableModels[0](obj, _checked)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (suitableModels.length > 1)
|
|
242
|
+
console.warn(`Ambiguous model for value ${format(obj)}, could be ${suitableModels.join(" or ")}`)
|
|
243
|
+
|
|
244
|
+
return obj
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
getProp = (val, model, def, path, privateAccess) => {
|
|
249
|
+
if (!isPlainObject(def)) return cast(val, def)
|
|
250
|
+
return proxify(val, getTraps(model, def, path, privateAccess))
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
getTraps = (model, def, path, privateAccess) => {
|
|
254
|
+
const grantPrivateAccess = f => proxify(f, {
|
|
255
|
+
apply(fn, ctx, args) {
|
|
256
|
+
privateAccess = true
|
|
257
|
+
const result = Reflect.apply(fn, ctx, args)
|
|
258
|
+
privateAccess = false
|
|
259
|
+
return result
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
get(o, key) {
|
|
265
|
+
if (key === _original) return o
|
|
266
|
+
|
|
267
|
+
if (!isString(key)) return Reflect.get(o, key)
|
|
268
|
+
|
|
269
|
+
const newPath = formatPath(path, key)
|
|
270
|
+
const inDef = has(def, key)
|
|
271
|
+
const defPart = def[key]
|
|
272
|
+
|
|
273
|
+
if (!privateAccess && inDef && model.conventionForPrivate(key)) {
|
|
274
|
+
cannot(`access to private property ${newPath}`, model)
|
|
275
|
+
unstackErrors(model)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let value = o[key]
|
|
280
|
+
|
|
281
|
+
if (inDef && value && has(o, key) && !isPlainObject(defPart) && !isModelInstance(value)) {
|
|
282
|
+
Reflect.set(o, key, value = cast(value, defPart)) // cast nested models
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (isFunction(value) && key !== "constructor" && !privateAccess) {
|
|
286
|
+
return grantPrivateAccess(value)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (isPlainObject(defPart) && !value) {
|
|
290
|
+
o[key] = value = {} // null-safe traversal
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return getProp(value, model, defPart, newPath, privateAccess)
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
set(o, key, val) {
|
|
297
|
+
return controlMutation(model, def, path, o, key, privateAccess, () => Reflect.set(o, key, cast(val, def[key])))
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
deleteProperty(o, key) {
|
|
301
|
+
return controlMutation(model, def, path, o, key, privateAccess, () => Reflect.deleteProperty(o, key))
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
defineProperty(o, key, args) {
|
|
305
|
+
return controlMutation(model, def, path, o, key, privateAccess, () => Reflect.defineProperty(o, key, args))
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
has(o, key) {
|
|
309
|
+
return Reflect.has(o, key) && Reflect.has(def, key) && !model.conventionForPrivate(key)
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
ownKeys(o) {
|
|
313
|
+
return Reflect.ownKeys(o).filter(key => Reflect.has(def, key) && !model.conventionForPrivate(key))
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
getOwnPropertyDescriptor(o, key) {
|
|
317
|
+
let descriptor
|
|
318
|
+
if (!model.conventionForPrivate(key)) {
|
|
319
|
+
descriptor = Object.getOwnPropertyDescriptor(def, key)
|
|
320
|
+
if (descriptor !== undefined) descriptor.value = o[key]
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return descriptor
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
export function Model(def) {
|
|
330
|
+
return isPlainObject(def) ? new ObjectModel(def) : new BasicModel(def)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
Object.assign(Model.prototype, {
|
|
334
|
+
name: "Model",
|
|
335
|
+
assertions: [],
|
|
336
|
+
|
|
337
|
+
conventionForConstant: key => key.toUpperCase() === key,
|
|
338
|
+
conventionForPrivate: key => key[0] === "_",
|
|
339
|
+
|
|
340
|
+
toString(stack) {
|
|
341
|
+
return has(this, "name") ? this.name : formatDefinition(this.definition, stack) + formatAssertions(this.assertions)
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
as(name) {
|
|
345
|
+
define(this, "name", name)
|
|
346
|
+
return this
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
defaultTo(val) {
|
|
350
|
+
this.default = val
|
|
351
|
+
return this
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
[_check](obj, path, errors, stack) {
|
|
355
|
+
checkDefinition(obj, this.definition, path, errors, stack)
|
|
356
|
+
checkAssertions(obj, this, path, errors)
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
test(obj, errorCollector) {
|
|
360
|
+
let model = this
|
|
361
|
+
while (!has(model, "errorCollector")) {
|
|
362
|
+
model = getProto(model)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const initialErrorCollector = model.errorCollector
|
|
366
|
+
let failed
|
|
367
|
+
|
|
368
|
+
model.errorCollector = errors => {
|
|
369
|
+
failed = true
|
|
370
|
+
if (errorCollector) errorCollector.call(this, errors)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
new this(obj) // may trigger errorCollector
|
|
374
|
+
|
|
375
|
+
model.errorCollector = initialErrorCollector
|
|
376
|
+
return !failed
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
errorCollector(errors) {
|
|
380
|
+
const e = new TypeError(errors.map(e => e.message).join("\n"))
|
|
381
|
+
e.stack = e.stack.replace(/\n.*object-model(.|\n)*object-model.*/, "") // blackbox objectmodel in stacktrace
|
|
382
|
+
throw e
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
assert(assertion, description = format(assertion)) {
|
|
386
|
+
define(assertion, "description", description)
|
|
387
|
+
this.assertions = this.assertions.concat(assertion)
|
|
388
|
+
return this
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
Model.CHECK_ONCE = CHECK_ONCE
|
|
393
|
+
|
|
394
|
+
export function BasicModel(def) {
|
|
395
|
+
return initModel(def, BasicModel)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
extend(BasicModel, Model, {
|
|
399
|
+
extend(...newParts) {
|
|
400
|
+
const child = extendModel(new BasicModel(extendDefinition(this.definition, newParts)), this)
|
|
401
|
+
for (let part of newParts) {
|
|
402
|
+
if (is(BasicModel, part)) child.assertions.push(...part.assertions)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return child
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
export function ObjectModel(def) {
|
|
410
|
+
return initModel(def, ObjectModel, Object, initObjectModel, model => getTraps(model, def), true)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
extend(ObjectModel, Model, {
|
|
414
|
+
defaultTo(obj) {
|
|
415
|
+
const def = this.definition
|
|
416
|
+
for (let key in obj) {
|
|
417
|
+
if (has(def, key)) {
|
|
418
|
+
obj[key] = checkDefinition(obj[key], def[key], key, this.errors, [], true)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
unstackErrors(this)
|
|
422
|
+
this.default = obj;
|
|
423
|
+
return this
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
toString(stack) {
|
|
427
|
+
return format(this.definition, stack)
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
extend(...newParts) {
|
|
431
|
+
const definition = { ...this.definition }
|
|
432
|
+
const proto = { ...this.prototype }
|
|
433
|
+
const defaults = { ...this.default }
|
|
434
|
+
const newAssertions = []
|
|
435
|
+
|
|
436
|
+
for (let part of newParts) {
|
|
437
|
+
if (is(Model, part)) {
|
|
438
|
+
merge(definition, part.definition)
|
|
439
|
+
merge(defaults, part.default)
|
|
440
|
+
newAssertions.push(...part.assertions)
|
|
441
|
+
}
|
|
442
|
+
if (isFunction(part)) merge(proto, part.prototype)
|
|
443
|
+
if (isObject(part)) merge(definition, part)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const submodel = extendModel(new ObjectModel(definition), this, proto).defaultTo(defaults)
|
|
447
|
+
submodel.assertions = [...this.assertions, ...newAssertions]
|
|
448
|
+
|
|
449
|
+
if (getProto(this) !== ObjectModel.prototype) { // extended class
|
|
450
|
+
submodel.parentClass = this
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return submodel
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
[_check](obj, path, errors, stack, shouldCast) {
|
|
457
|
+
if (isObject(obj)) {
|
|
458
|
+
checkDefinition(obj[_original] || obj, this.definition, path, errors, stack, shouldCast)
|
|
459
|
+
}
|
|
460
|
+
else stackError(errors, this, obj, path)
|
|
461
|
+
|
|
462
|
+
checkAssertions(obj, this, path, errors)
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
export const Any = proxify(BasicModel(), {
|
|
467
|
+
apply(target, ctx, [def]) {
|
|
468
|
+
const anyOf = Object.create(Any)
|
|
469
|
+
anyOf.definition = def;
|
|
470
|
+
return anyOf
|
|
471
|
+
}
|
|
472
|
+
})
|
|
473
|
+
Any.definition = Any
|
|
474
|
+
Any.toString = () => "Any"
|
|
475
|
+
|
|
476
|
+
Any.remaining = function (def) { this.definition = def }
|
|
477
|
+
extend(Any.remaining, Any, {
|
|
478
|
+
toString() { return "..." + formatDefinition(this.definition) }
|
|
479
|
+
})
|
|
477
480
|
Any[Symbol.iterator] = function* () { yield new Any.remaining(this.definition) }
|