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/dist/object-model.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ObjectModel v4.
|
|
1
|
+
// ObjectModel v4.3.1 - http://objectmodel.js.org
|
|
2
2
|
// MIT License - Sylvain Pollet-Villard
|
|
3
3
|
const
|
|
4
4
|
ObjectProto = Object.prototype,
|
|
@@ -45,476 +45,479 @@ const
|
|
|
45
45
|
setProto(child, parent);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
const
|
|
49
|
-
_check = Symbol(),
|
|
50
|
-
_checked = Symbol(), // used to skip validation at instanciation for perf
|
|
51
|
-
_original = Symbol(), // used to bypass proxy
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
model
|
|
65
|
-
model.
|
|
66
|
-
model.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
merge(_this,
|
|
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
|
-
return def
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
result =
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
if (obj
|
|
221
|
-
if (
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
privateAccess =
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
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
|
-
|
|
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
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
Any.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
48
|
+
const
|
|
49
|
+
_check = Symbol(),
|
|
50
|
+
_checked = Symbol(), // used to skip validation at instanciation for perf
|
|
51
|
+
_original = Symbol(), // used to bypass proxy
|
|
52
|
+
CHECK_ONCE = Symbol(),
|
|
53
|
+
|
|
54
|
+
initModel = (def, constructor, parent, init, getTraps, useNew) => {
|
|
55
|
+
const model = function (val = model.default, mode) {
|
|
56
|
+
if (useNew && !is(model, this)) return new model(val)
|
|
57
|
+
if (init) val = init(val, model, this);
|
|
58
|
+
|
|
59
|
+
if (mode === _checked || check(model, val))
|
|
60
|
+
return getTraps && mode !== CHECK_ONCE ? proxify(val, getTraps(model)) : val
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (parent) extend(model, parent);
|
|
64
|
+
setProto(model, constructor.prototype);
|
|
65
|
+
model.constructor = constructor;
|
|
66
|
+
model.definition = def;
|
|
67
|
+
model.assertions = [...model.assertions];
|
|
68
|
+
define(model, "errors", []);
|
|
69
|
+
delete model.name;
|
|
70
|
+
return model
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
initObjectModel = (obj, model, _this) => {
|
|
74
|
+
if (is(model, obj)) return obj
|
|
75
|
+
|
|
76
|
+
if (!isObject(obj) && !isFunction(obj) && obj !== undefined) {
|
|
77
|
+
// short circuit validation if not receiving an object as expected
|
|
78
|
+
return obj
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
merge(_this, model.default);
|
|
82
|
+
if (model.parentClass) merge(obj, new model.parentClass(obj));
|
|
83
|
+
merge(_this, obj);
|
|
84
|
+
return _this
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
extendModel = (child, parent, newProps) => {
|
|
88
|
+
extend(child, parent, newProps);
|
|
89
|
+
child.assertions.push(...parent.assertions);
|
|
90
|
+
return child
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
stackError = (errors, expected, received, path, message) => {
|
|
94
|
+
errors.push({ expected, received, path, message });
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
unstackErrors = (model, collector = model.errorCollector) => {
|
|
98
|
+
const nbErrors = model.errors.length;
|
|
99
|
+
if (nbErrors > 0) {
|
|
100
|
+
const errors = model.errors.map(err => {
|
|
101
|
+
if (!err.message) {
|
|
102
|
+
err.message = "expecting " + (err.path ? err.path + " to be " : "") + formatDefinition(err.expected)
|
|
103
|
+
+ ", got " + (err.received != null ? bettertypeof(err.received) + " " : "") + format$1(err.received);
|
|
104
|
+
}
|
|
105
|
+
return err
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
model.errors.length = 0;
|
|
109
|
+
collector.call(model, errors); // throw all errors collected
|
|
110
|
+
}
|
|
111
|
+
return nbErrors
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
isModelInstance = i => i && getProto(i) && is(Model, getProto(i).constructor),
|
|
115
|
+
|
|
116
|
+
parseDefinition = (def) => {
|
|
117
|
+
if (isPlainObject(def)) {
|
|
118
|
+
def = {};
|
|
119
|
+
for (let key in def) { def[key] = parseDefinition(def[key]); }
|
|
120
|
+
}
|
|
121
|
+
else if (!Array.isArray(def)) return [def]
|
|
122
|
+
else if (def.length === 1) return [def[0], undefined, null]
|
|
123
|
+
|
|
124
|
+
return def
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
formatDefinition = (def, stack) => {
|
|
128
|
+
const parts = parseDefinition(def).map(d => format$1(d, stack));
|
|
129
|
+
return parts.length > 1 ? parts.join(" or ") : parts[0]
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
formatAssertions = fns => fns.length ? `(${fns.map(f => f.name || f.description || f)})` : "",
|
|
133
|
+
|
|
134
|
+
extendDefinition = (def, newParts = []) => {
|
|
135
|
+
if (newParts.length > 0) {
|
|
136
|
+
def = [].concat(def, ...[].concat(newParts))// clone to lose ref
|
|
137
|
+
.filter((value, index, self) => self.indexOf(value) === index); // remove duplicates
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return def
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
check = (model, obj) => {
|
|
144
|
+
model[_check](obj, null, model.errors, [], true);
|
|
145
|
+
return !unstackErrors(model)
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
checkDefinition = (obj, def, path, errors, stack, shouldCast) => {
|
|
149
|
+
const indexFound = stack.indexOf(def);
|
|
150
|
+
if (indexFound !== -1 && stack.indexOf(def, indexFound + 1) !== -1)
|
|
151
|
+
return obj // if found twice in call stack, cycle detected, skip validation
|
|
152
|
+
|
|
153
|
+
if (Array.isArray(def) && def.length === 1 && obj != null) {
|
|
154
|
+
def = def[0]; // shorten validation path for optionals
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (is(Model, def)) {
|
|
158
|
+
if (shouldCast) obj = cast(obj, def);
|
|
159
|
+
def[_check](obj, path, errors, stack.concat(def));
|
|
160
|
+
}
|
|
161
|
+
else if (isPlainObject(def)) {
|
|
162
|
+
for (let key in def) {
|
|
163
|
+
const val = obj ? obj[key] : undefined;
|
|
164
|
+
checkDefinition(val, def[key], formatPath(path, key), errors, stack, shouldCast);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const pdef = parseDefinition(def);
|
|
169
|
+
if (pdef.some(part => checkDefinitionPart(obj, part, path, stack))) {
|
|
170
|
+
return shouldCast ? cast(obj, def) : obj
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
stackError(errors, def, obj, path);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return obj
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
checkDefinitionPart = (obj, def, path, stack, shouldCast) => {
|
|
180
|
+
if (def === Any) return true
|
|
181
|
+
if (obj == null) return obj === def
|
|
182
|
+
if (isPlainObject(def) || is(Model, def)) { // object or model as part of union type
|
|
183
|
+
const errors = [];
|
|
184
|
+
checkDefinition(obj, def, path, errors, stack, shouldCast);
|
|
185
|
+
return !errors.length
|
|
186
|
+
}
|
|
187
|
+
if (is(RegExp, def)) return def.test(obj)
|
|
188
|
+
if (def === Number || def === Date) return obj.constructor === def && !isNaN(obj)
|
|
189
|
+
return obj === def
|
|
190
|
+
|| (isFunction(def) && is(def, obj))
|
|
191
|
+
|| obj.constructor === def
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
checkAssertions = (obj, model, path, errors = model.errors) => {
|
|
195
|
+
for (let assertion of model.assertions) {
|
|
196
|
+
let result;
|
|
197
|
+
try {
|
|
198
|
+
result = assertion.call(model, obj);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
result = err;
|
|
201
|
+
}
|
|
202
|
+
if (result !== true) {
|
|
203
|
+
const onFail = isFunction(assertion.description) ? assertion.description : (assertionResult, value) =>
|
|
204
|
+
`assertion "${assertion.description}" returned ${format$1(assertionResult)} `
|
|
205
|
+
+ `for ${path ? path + " =" : "value"} ${format$1(value)}`;
|
|
206
|
+
stackError(errors, assertion, obj, path, onFail.call(model, result, obj, path));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
format$1 = (obj, stack = []) => {
|
|
212
|
+
if (stack.length > 15 || stack.includes(obj)) return "..."
|
|
213
|
+
if (obj === null || obj === undefined) return String(obj)
|
|
214
|
+
if (isString(obj)) return `"${obj}"`
|
|
215
|
+
if (is(Model, obj)) return obj.toString(stack)
|
|
216
|
+
|
|
217
|
+
stack.unshift(obj);
|
|
218
|
+
|
|
219
|
+
if (isFunction(obj)) return obj.name || obj.toString()
|
|
220
|
+
if (is(Map, obj) || is(Set, obj)) return format$1([...obj])
|
|
221
|
+
if (Array.isArray(obj)) return `[${obj.map(item => format$1(item, stack)).join(", ")}]`
|
|
222
|
+
if (obj.toString && obj.toString !== ObjectProto.toString) return obj.toString()
|
|
223
|
+
if (isObject(obj)) {
|
|
224
|
+
const props = Object.keys(obj),
|
|
225
|
+
indent = "\t".repeat(stack.length);
|
|
226
|
+
return `{${props.map(
|
|
227
|
+
key => `\n${indent + key}: ${format$1(obj[key], [...stack])}`
|
|
228
|
+
).join(", ")} ${props.length ? `\n${indent.slice(1)}` : ""}}`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return String(obj)
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
formatPath = (path, key) => path ? path + "." + key : key,
|
|
235
|
+
|
|
236
|
+
controlMutation$1 = (model, def, path, o, key, privateAccess, applyMutation) => {
|
|
237
|
+
const newPath = formatPath(path, key),
|
|
238
|
+
isPrivate = model.conventionForPrivate(key),
|
|
239
|
+
isConstant = model.conventionForConstant(key),
|
|
240
|
+
isOwnProperty = has(o, key),
|
|
241
|
+
initialPropDescriptor = isOwnProperty && Object.getOwnPropertyDescriptor(o, key);
|
|
242
|
+
|
|
243
|
+
if (key in def && ((isPrivate && !privateAccess) || (isConstant && o[key] !== undefined)))
|
|
244
|
+
cannot(`modify ${isPrivate ? "private" : "constant"} property ${key}`, model);
|
|
245
|
+
|
|
246
|
+
applyMutation();
|
|
247
|
+
if (has(def, key)) checkDefinition(o[key], def[key], newPath, model.errors, []);
|
|
248
|
+
checkAssertions(o, model, newPath);
|
|
249
|
+
|
|
250
|
+
const nbErrors = model.errors.length;
|
|
251
|
+
if (nbErrors) {
|
|
252
|
+
if (isOwnProperty) Object.defineProperty(o, key, initialPropDescriptor);
|
|
253
|
+
else delete o[key]; // back to the initial property defined in prototype chain
|
|
254
|
+
|
|
255
|
+
unstackErrors(model);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return !nbErrors
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
cannot = (msg, model) => {
|
|
262
|
+
model.errors.push({ message: "cannot " + msg });
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
cast = (obj, defNode = []) => {
|
|
266
|
+
if (!obj || isPlainObject(defNode) || is(BasicModel, defNode) || isModelInstance(obj))
|
|
267
|
+
return obj // no value or not leaf or already a model instance
|
|
268
|
+
|
|
269
|
+
const def = parseDefinition(defNode),
|
|
270
|
+
suitableModels = [];
|
|
271
|
+
|
|
272
|
+
for (let part of def) {
|
|
273
|
+
if (is(Model, part) && !is(BasicModel, part) && part.test(obj))
|
|
274
|
+
suitableModels.push(part);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (suitableModels.length === 1) {
|
|
278
|
+
// automatically cast to suitable model when explicit (autocasting)
|
|
279
|
+
return new suitableModels[0](obj, _checked)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (suitableModels.length > 1)
|
|
283
|
+
console.warn(`Ambiguous model for value ${format$1(obj)}, could be ${suitableModels.join(" or ")}`);
|
|
284
|
+
|
|
285
|
+
return obj
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
getProp = (val, model, def, path, privateAccess) => {
|
|
290
|
+
if (!isPlainObject(def)) return cast(val, def)
|
|
291
|
+
return proxify(val, getTraps(model, def, path, privateAccess))
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
getTraps = (model, def, path, privateAccess) => {
|
|
295
|
+
const grantPrivateAccess = f => proxify(f, {
|
|
296
|
+
apply(fn, ctx, args) {
|
|
297
|
+
privateAccess = true;
|
|
298
|
+
const result = Reflect.apply(fn, ctx, args);
|
|
299
|
+
privateAccess = false;
|
|
300
|
+
return result
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
get(o, key) {
|
|
306
|
+
if (key === _original) return o
|
|
307
|
+
|
|
308
|
+
if (!isString(key)) return Reflect.get(o, key)
|
|
309
|
+
|
|
310
|
+
const newPath = formatPath(path, key);
|
|
311
|
+
const inDef = has(def, key);
|
|
312
|
+
const defPart = def[key];
|
|
313
|
+
|
|
314
|
+
if (!privateAccess && inDef && model.conventionForPrivate(key)) {
|
|
315
|
+
cannot(`access to private property ${newPath}`, model);
|
|
316
|
+
unstackErrors(model);
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let value = o[key];
|
|
321
|
+
|
|
322
|
+
if (inDef && value && has(o, key) && !isPlainObject(defPart) && !isModelInstance(value)) {
|
|
323
|
+
Reflect.set(o, key, value = cast(value, defPart)); // cast nested models
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (isFunction(value) && key !== "constructor" && !privateAccess) {
|
|
327
|
+
return grantPrivateAccess(value)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (isPlainObject(defPart) && !value) {
|
|
331
|
+
o[key] = value = {}; // null-safe traversal
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return getProp(value, model, defPart, newPath, privateAccess)
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
set(o, key, val) {
|
|
338
|
+
return controlMutation$1(model, def, path, o, key, privateAccess, () => Reflect.set(o, key, cast(val, def[key])))
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
deleteProperty(o, key) {
|
|
342
|
+
return controlMutation$1(model, def, path, o, key, privateAccess, () => Reflect.deleteProperty(o, key))
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
defineProperty(o, key, args) {
|
|
346
|
+
return controlMutation$1(model, def, path, o, key, privateAccess, () => Reflect.defineProperty(o, key, args))
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
has(o, key) {
|
|
350
|
+
return Reflect.has(o, key) && Reflect.has(def, key) && !model.conventionForPrivate(key)
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
ownKeys(o) {
|
|
354
|
+
return Reflect.ownKeys(o).filter(key => Reflect.has(def, key) && !model.conventionForPrivate(key))
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
getOwnPropertyDescriptor(o, key) {
|
|
358
|
+
let descriptor;
|
|
359
|
+
if (!model.conventionForPrivate(key)) {
|
|
360
|
+
descriptor = Object.getOwnPropertyDescriptor(def, key);
|
|
361
|
+
if (descriptor !== undefined) descriptor.value = o[key];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return descriptor
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
function Model(def) {
|
|
371
|
+
return isPlainObject(def) ? new ObjectModel(def) : new BasicModel(def)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
Object.assign(Model.prototype, {
|
|
375
|
+
name: "Model",
|
|
376
|
+
assertions: [],
|
|
377
|
+
|
|
378
|
+
conventionForConstant: key => key.toUpperCase() === key,
|
|
379
|
+
conventionForPrivate: key => key[0] === "_",
|
|
380
|
+
|
|
381
|
+
toString(stack) {
|
|
382
|
+
return has(this, "name") ? this.name : formatDefinition(this.definition, stack) + formatAssertions(this.assertions)
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
as(name) {
|
|
386
|
+
define(this, "name", name);
|
|
387
|
+
return this
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
defaultTo(val) {
|
|
391
|
+
this.default = val;
|
|
392
|
+
return this
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
[_check](obj, path, errors, stack) {
|
|
396
|
+
checkDefinition(obj, this.definition, path, errors, stack);
|
|
397
|
+
checkAssertions(obj, this, path, errors);
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
test(obj, errorCollector) {
|
|
401
|
+
let model = this;
|
|
402
|
+
while (!has(model, "errorCollector")) {
|
|
403
|
+
model = getProto(model);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const initialErrorCollector = model.errorCollector;
|
|
407
|
+
let failed;
|
|
408
|
+
|
|
409
|
+
model.errorCollector = errors => {
|
|
410
|
+
failed = true;
|
|
411
|
+
if (errorCollector) errorCollector.call(this, errors);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
new this(obj); // may trigger errorCollector
|
|
415
|
+
|
|
416
|
+
model.errorCollector = initialErrorCollector;
|
|
417
|
+
return !failed
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
errorCollector(errors) {
|
|
421
|
+
const e = new TypeError(errors.map(e => e.message).join("\n"));
|
|
422
|
+
e.stack = e.stack.replace(/\n.*object-model(.|\n)*object-model.*/, ""); // blackbox objectmodel in stacktrace
|
|
423
|
+
throw e
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
assert(assertion, description = format$1(assertion)) {
|
|
427
|
+
define(assertion, "description", description);
|
|
428
|
+
this.assertions = this.assertions.concat(assertion);
|
|
429
|
+
return this
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
Model.CHECK_ONCE = CHECK_ONCE;
|
|
434
|
+
|
|
435
|
+
function BasicModel(def) {
|
|
436
|
+
return initModel(def, BasicModel)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
extend(BasicModel, Model, {
|
|
440
|
+
extend(...newParts) {
|
|
441
|
+
const child = extendModel(new BasicModel(extendDefinition(this.definition, newParts)), this);
|
|
442
|
+
for (let part of newParts) {
|
|
443
|
+
if (is(BasicModel, part)) child.assertions.push(...part.assertions);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return child
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
function ObjectModel(def) {
|
|
451
|
+
return initModel(def, ObjectModel, Object, initObjectModel, model => getTraps(model, def), true)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
extend(ObjectModel, Model, {
|
|
455
|
+
defaultTo(obj) {
|
|
456
|
+
const def = this.definition;
|
|
457
|
+
for (let key in obj) {
|
|
458
|
+
if (has(def, key)) {
|
|
459
|
+
obj[key] = checkDefinition(obj[key], def[key], key, this.errors, [], true);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
unstackErrors(this);
|
|
463
|
+
this.default = obj;
|
|
464
|
+
return this
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
toString(stack) {
|
|
468
|
+
return format$1(this.definition, stack)
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
extend(...newParts) {
|
|
472
|
+
const definition = { ...this.definition };
|
|
473
|
+
const proto = { ...this.prototype };
|
|
474
|
+
const defaults = { ...this.default };
|
|
475
|
+
const newAssertions = [];
|
|
476
|
+
|
|
477
|
+
for (let part of newParts) {
|
|
478
|
+
if (is(Model, part)) {
|
|
479
|
+
merge(definition, part.definition);
|
|
480
|
+
merge(defaults, part.default);
|
|
481
|
+
newAssertions.push(...part.assertions);
|
|
482
|
+
}
|
|
483
|
+
if (isFunction(part)) merge(proto, part.prototype);
|
|
484
|
+
if (isObject(part)) merge(definition, part);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const submodel = extendModel(new ObjectModel(definition), this, proto).defaultTo(defaults);
|
|
488
|
+
submodel.assertions = [...this.assertions, ...newAssertions];
|
|
489
|
+
|
|
490
|
+
if (getProto(this) !== ObjectModel.prototype) { // extended class
|
|
491
|
+
submodel.parentClass = this;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return submodel
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
[_check](obj, path, errors, stack, shouldCast) {
|
|
498
|
+
if (isObject(obj)) {
|
|
499
|
+
checkDefinition(obj[_original] || obj, this.definition, path, errors, stack, shouldCast);
|
|
500
|
+
}
|
|
501
|
+
else stackError(errors, this, obj, path);
|
|
502
|
+
|
|
503
|
+
checkAssertions(obj, this, path, errors);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const Any = proxify(BasicModel(), {
|
|
508
|
+
apply(target, ctx, [def]) {
|
|
509
|
+
const anyOf = Object.create(Any);
|
|
510
|
+
anyOf.definition = def;
|
|
511
|
+
return anyOf
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
Any.definition = Any;
|
|
515
|
+
Any.toString = () => "Any";
|
|
516
|
+
|
|
517
|
+
Any.remaining = function (def) { this.definition = def; };
|
|
518
|
+
extend(Any.remaining, Any, {
|
|
519
|
+
toString() { return "..." + formatDefinition(this.definition) }
|
|
520
|
+
});
|
|
518
521
|
Any[Symbol.iterator] = function* () { yield new Any.remaining(this.definition); };
|
|
519
522
|
|
|
520
523
|
const initListModel = (base, constructor, def, init, clone, mutators, otherTraps) => {
|
|
@@ -578,11 +581,11 @@ function ArrayModel(initialDefinition) {
|
|
|
578
581
|
},
|
|
579
582
|
{
|
|
580
583
|
set(arr, key, val) {
|
|
581
|
-
return controlMutation
|
|
584
|
+
return controlMutation(model, arr, key, val, (a, v) => a[key] = v, true)
|
|
582
585
|
},
|
|
583
586
|
|
|
584
587
|
deleteProperty(arr, key) {
|
|
585
|
-
return controlMutation
|
|
588
|
+
return controlMutation(model, arr, key, undefined, a => delete a[key])
|
|
586
589
|
}
|
|
587
590
|
}
|
|
588
591
|
);
|
|
@@ -608,7 +611,7 @@ extend(ArrayModel, Model, {
|
|
|
608
611
|
}
|
|
609
612
|
});
|
|
610
613
|
|
|
611
|
-
const controlMutation
|
|
614
|
+
const controlMutation = (model, array, key, value, applyMutation, canBeExtended) => {
|
|
612
615
|
const path = `Array[${key}]`;
|
|
613
616
|
const isInDef = (+key >= 0 && (canBeExtended || key in array));
|
|
614
617
|
if (isInDef) value = checkDefinition(value, model.definition, path, model.errors, [], true);
|
|
@@ -685,7 +688,7 @@ extend(MapModel, Model, {
|
|
|
685
688
|
path = path || "Map";
|
|
686
689
|
for (let [key, value] of map) {
|
|
687
690
|
checkDefinition(key, this.definition.key, `${path} key`, errors, stack);
|
|
688
|
-
checkDefinition(value, this.definition.value, `${path}[${format(key)}]`, errors, stack);
|
|
691
|
+
checkDefinition(value, this.definition.value, `${path}[${format$1(key)}]`, errors, stack);
|
|
689
692
|
}
|
|
690
693
|
} else stackError(errors, this, map, path);
|
|
691
694
|
|
|
@@ -792,7 +795,7 @@ const getModel = (instance) => {
|
|
|
792
795
|
|
|
793
796
|
const span = (style, ...children) => ["span", { style }, ...children];
|
|
794
797
|
|
|
795
|
-
const format
|
|
798
|
+
const format = (x, config = {}) => {
|
|
796
799
|
if (x === null || x === undefined)
|
|
797
800
|
return span(styles.null, "" + x);
|
|
798
801
|
|
|
@@ -806,7 +809,7 @@ const format$1 = (x, config = {}) => {
|
|
|
806
809
|
return span(styles.string, `"${x}"`);
|
|
807
810
|
|
|
808
811
|
if (Array.isArray(x) && config.isModelDefinition) {
|
|
809
|
-
return span("", ...x.flatMap(part => [format
|
|
812
|
+
return span("", ...x.flatMap(part => [format(part, config), " or "]).slice(0, -1))
|
|
810
813
|
}
|
|
811
814
|
|
|
812
815
|
if (isPlainObject(x))
|
|
@@ -821,7 +824,7 @@ const format$1 = (x, config = {}) => {
|
|
|
821
824
|
const formatObject = (o, model, config) => span("",
|
|
822
825
|
"{",
|
|
823
826
|
["ol", { style: styles.list }, ...Object.keys(o).map(prop =>
|
|
824
|
-
["li", { style: styles.listItem }, span(styles.property, prop), ": ", format
|
|
827
|
+
["li", { style: styles.listItem }, span(styles.property, prop), ": ", format(o[prop], config)])
|
|
825
828
|
],
|
|
826
829
|
"}"
|
|
827
830
|
);
|
|
@@ -833,13 +836,13 @@ const formatModel = model => {
|
|
|
833
836
|
formatList = (list, map) => list.flatMap(e => [map(e), ", "]).slice(0, -1);
|
|
834
837
|
let parts = [];
|
|
835
838
|
|
|
836
|
-
if (is(BasicModel, model)) parts = [format
|
|
837
|
-
if (is(ArrayModel, model)) parts = ["Array of ", format
|
|
838
|
-
if (is(SetModel, model)) parts = ["Set of ", format
|
|
839
|
-
if (is(MapModel, model)) parts = ["Map of ", format
|
|
839
|
+
if (is(BasicModel, model)) parts = [format(def, cfg)];
|
|
840
|
+
if (is(ArrayModel, model)) parts = ["Array of ", format(def, cfg)];
|
|
841
|
+
if (is(SetModel, model)) parts = ["Set of ", format(def, cfg)];
|
|
842
|
+
if (is(MapModel, model)) parts = ["Map of ", format(def.key, cfg), " : ", format(def.value, cfg)];
|
|
840
843
|
if (is(FunctionModel, model)) {
|
|
841
|
-
parts = ["Function(", ...formatList(def.arguments, arg => format
|
|
842
|
-
if ("return" in def) parts.push(" => ", format
|
|
844
|
+
parts = ["Function(", ...formatList(def.arguments, arg => format(arg, cfg)), ")"];
|
|
845
|
+
if ("return" in def) parts.push(" => ", format(def.return, cfg));
|
|
843
846
|
}
|
|
844
847
|
|
|
845
848
|
if (model.assertions.length > 0) {
|
|
@@ -855,7 +858,7 @@ const ModelFormatter = {
|
|
|
855
858
|
return span(styles.model, "Any")
|
|
856
859
|
|
|
857
860
|
if (is(Any.remaining, x))
|
|
858
|
-
return span(styles.model, "...", format
|
|
861
|
+
return span(styles.model, "...", format(x.definition, { isModelDefinition: true }))
|
|
859
862
|
|
|
860
863
|
if (is(ObjectModel, x))
|
|
861
864
|
return span(styles.model, x.name)
|
|
@@ -865,7 +868,7 @@ const ModelFormatter = {
|
|
|
865
868
|
}
|
|
866
869
|
|
|
867
870
|
if (config.isModelDefinition && isPlainObject(x))
|
|
868
|
-
return format
|
|
871
|
+
return format(x, config)
|
|
869
872
|
|
|
870
873
|
return null;
|
|
871
874
|
},
|
|
@@ -888,8 +891,8 @@ const ModelFormatter = {
|
|
|
888
891
|
}
|
|
889
892
|
|
|
890
893
|
return ["li", { style: styles.listItem },
|
|
891
|
-
span(style, prop), ": ", format
|
|
892
|
-
hasDefault ? span(styles.proto, " = ", format
|
|
894
|
+
span(style, prop), ": ", format(model.definition[prop], { isModelDefinition: true }),
|
|
895
|
+
hasDefault ? span(styles.proto, " = ", format(model.default[prop])) : ""
|
|
893
896
|
]
|
|
894
897
|
})],
|
|
895
898
|
"}"
|
|
@@ -900,7 +903,7 @@ const ModelFormatter = {
|
|
|
900
903
|
const ModelInstanceFormatter = {
|
|
901
904
|
header(x, config = {}) {
|
|
902
905
|
if (config.isInstanceProperty && isPlainObject(x)) {
|
|
903
|
-
return format
|
|
906
|
+
return format(x, config)
|
|
904
907
|
}
|
|
905
908
|
|
|
906
909
|
const model = getModel(x);
|
|
@@ -937,7 +940,7 @@ const ModelInstanceFormatter = {
|
|
|
937
940
|
}
|
|
938
941
|
|
|
939
942
|
return ["li", { style: styles.listItem },
|
|
940
|
-
span(style, prop), ": ", format
|
|
943
|
+
span(style, prop), ": ", format(o[prop], { isInstanceProperty: true })
|
|
941
944
|
]
|
|
942
945
|
}),
|
|
943
946
|
["li", { style: styles.listItem },
|