velocious 1.0.448 → 1.0.449
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/database/record/index.js +13 -8
- package/build/environment-handlers/node/cli/commands/generate/base-models.js +1 -16
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +125 -73
- package/build/frontend-model-controller.js +9 -0
- package/build/frontend-model-resource/base-resource.js +266 -53
- package/build/frontend-models/base.js +241 -97
- package/build/frontend-models/preloader.js +3 -2
- package/build/src/background-jobs/job-record.d.ts +2 -1
- package/build/src/background-jobs/job-record.d.ts.map +1 -1
- package/build/src/database/query/preloader/belongs-to.d.ts +2 -2
- package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.d.ts +1 -1
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-one.d.ts +2 -2
- package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
- package/build/src/database/query/preloader.d.ts +1 -1
- package/build/src/database/query/preloader.d.ts.map +1 -1
- package/build/src/database/record/attachments/handle.d.ts +1 -1
- package/build/src/database/record/attachments/handle.d.ts.map +1 -1
- package/build/src/database/record/index.d.ts +23 -13
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +14 -9
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +2 -15
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +89 -32
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +123 -72
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +8 -1
- package/build/src/frontend-model-resource/base-resource.d.ts +203 -64
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +237 -54
- package/build/src/frontend-models/base.d.ts +173 -110
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +218 -102
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +4 -3
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/build/src/testing/expect.d.ts +6 -0
- package/build/src/testing/expect.d.ts.map +1 -1
- package/build/src/testing/expect.js +9 -1
- package/build/testing/browser-frontend-model-event-hook-scenarios.js +1 -1
- package/build/testing/expect.js +9 -0
- package/package.json +1 -1
- package/src/database/record/index.js +13 -8
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +1 -16
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +125 -73
- package/src/frontend-model-controller.js +9 -0
- package/src/frontend-model-resource/base-resource.js +266 -53
- package/src/frontend-models/base.js +241 -97
- package/src/frontend-models/preloader.js +3 -2
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +1 -1
- package/src/testing/expect.js +9 -0
|
@@ -20,6 +20,28 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
20
20
|
/**
|
|
21
21
|
* FrontendModelRequestCommandType type.
|
|
22
22
|
@typedef {FrontendModelCommandType | string} FrontendModelRequestCommandType */
|
|
23
|
+
/**
|
|
24
|
+
* Model-like instance value supported by frontend-model transport.
|
|
25
|
+
* @typedef {{attributes: () => Record<string, unknown>}} FrontendModelTransportModelValue
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Special scalar values restored by frontend-model transport.
|
|
29
|
+
* @typedef {undefined | null | boolean | number | string | bigint | Date | FrontendModelTransportModelValue} FrontendModelTransportScalarValue
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Plain object supported by frontend-model transport values.
|
|
33
|
+
* Nested values are intentionally opaque because TypeScript rejects recursive
|
|
34
|
+
* JSDoc typedefs for this transport value contract.
|
|
35
|
+
* @typedef {Record<string, unknown>} FrontendModelTransportObject
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Value supported by frontend-model transport serialization and deserialization.
|
|
39
|
+
* @typedef {FrontendModelTransportScalarValue | FrontendModelTransportObject | Array<unknown>} FrontendModelTransportValue
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Frontend model attribute value used when generated metadata cannot infer a narrower type.
|
|
43
|
+
* @typedef {FrontendModelTransportValue} FrontendModelAttributeValue
|
|
44
|
+
*/
|
|
23
45
|
/**
|
|
24
46
|
* Defines this typedef.
|
|
25
47
|
* @typedef {{type: "hasOne" | "hasMany"}} FrontendModelAttachmentDefinition
|
|
@@ -34,11 +56,15 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
34
56
|
*/
|
|
35
57
|
/**
|
|
36
58
|
* Frontend model constructor type.
|
|
37
|
-
* @
|
|
59
|
+
* @template {FrontendModelBase} [T=FrontendModelBase]
|
|
60
|
+
* @typedef {{new (attributes?: Record<string, FrontendModelAttributeValue>): T}} FrontendModelConstructor
|
|
38
61
|
*/
|
|
39
62
|
/**
|
|
40
|
-
* Frontend model static side
|
|
41
|
-
* @
|
|
63
|
+
* Frontend model static side.
|
|
64
|
+
* @template {FrontendModelBase} [T=FrontendModelBase]
|
|
65
|
+
* @template {Record<string, FrontendModelAttributeValue>} [Attributes=Record<string, FrontendModelAttributeValue>]
|
|
66
|
+
* @template {Record<string, FrontendModelAttributeValue>} [CreateAttributes=Record<string, FrontendModelAttributeValue>]
|
|
67
|
+
* @typedef {{new (attributes?: Attributes | CreateAttributes): T, create(attributes?: CreateAttributes): Promise<T>} & Omit<typeof FrontendModelBase, "create">} FrontendModelClass
|
|
42
68
|
*/
|
|
43
69
|
/**
|
|
44
70
|
* FrontendModelTransportConfig type.
|
|
@@ -238,27 +264,29 @@ export class AttributeNotSelectedError extends Error {
|
|
|
238
264
|
|
|
239
265
|
/**
|
|
240
266
|
* Lightweight singular relationship state holder for frontend model instances.
|
|
241
|
-
* @template {
|
|
242
|
-
* @template {
|
|
267
|
+
* @template {FrontendModelBase} S
|
|
268
|
+
* @template {FrontendModelBase} T
|
|
269
|
+
* @template {Record<string, FrontendModelAttributeValue>} [TargetCreateAttributes=Record<string, FrontendModelAttributeValue>]
|
|
243
270
|
*/
|
|
244
271
|
export class FrontendModelSingularRelationship {
|
|
245
272
|
/**
|
|
246
273
|
* Runs constructor.
|
|
247
|
-
* @param {
|
|
274
|
+
* @param {S} model - Parent model.
|
|
248
275
|
* @param {string} relationshipName - Relationship name.
|
|
249
|
-
* @param {T | null} targetModelClass - Target model class.
|
|
276
|
+
* @param {FrontendModelClass<T, Record<string, FrontendModelAttributeValue>, TargetCreateAttributes> | null} targetModelClass - Target model class.
|
|
250
277
|
*/
|
|
251
278
|
constructor(model, relationshipName, targetModelClass) {
|
|
252
279
|
this.model = model
|
|
253
280
|
this.relationshipName = relationshipName
|
|
254
281
|
this.targetModelClass = targetModelClass
|
|
255
282
|
this._preloaded = false
|
|
283
|
+
/** @type {T | null} */
|
|
256
284
|
this._loadedValue = null
|
|
257
285
|
}
|
|
258
286
|
|
|
259
287
|
/**
|
|
260
288
|
* Runs set loaded.
|
|
261
|
-
* @param {
|
|
289
|
+
* @param {T | null | undefined} loadedValue - Loaded relationship value.
|
|
262
290
|
* @returns {void}
|
|
263
291
|
*/
|
|
264
292
|
setLoaded(loadedValue) {
|
|
@@ -276,7 +304,7 @@ export class FrontendModelSingularRelationship {
|
|
|
276
304
|
|
|
277
305
|
/**
|
|
278
306
|
* Runs loaded.
|
|
279
|
-
* @returns {
|
|
307
|
+
* @returns {T | null} - Loaded relationship value.
|
|
280
308
|
*/
|
|
281
309
|
loaded() {
|
|
282
310
|
if (!this._preloaded) {
|
|
@@ -286,42 +314,91 @@ export class FrontendModelSingularRelationship {
|
|
|
286
314
|
return this._loadedValue
|
|
287
315
|
}
|
|
288
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Copies loaded value from another singular relationship helper.
|
|
319
|
+
* @param {FrontendModelRelationship} sourceRelationship - Source relationship helper.
|
|
320
|
+
* @returns {void}
|
|
321
|
+
*/
|
|
322
|
+
copyLoadedFrom(sourceRelationship) {
|
|
323
|
+
if (sourceRelationship instanceof FrontendModelHasManyRelationship) {
|
|
324
|
+
throw new Error(`Expected ${this.model.constructor.name}#${this.relationshipName} source relationship to be singular`)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Narrows the runtime value to the target relationship's documented model type.
|
|
328
|
+
const loadedValue = /** @type {T | null} */ (sourceRelationship.loaded())
|
|
329
|
+
|
|
330
|
+
this.setLoaded(loadedValue)
|
|
331
|
+
}
|
|
332
|
+
|
|
289
333
|
/**
|
|
290
334
|
* Runs build.
|
|
291
|
-
* @param {
|
|
292
|
-
* @returns {
|
|
335
|
+
* @param {TargetCreateAttributes} [attributes] - New model attributes.
|
|
336
|
+
* @returns {T} - Built model.
|
|
293
337
|
*/
|
|
294
|
-
build(attributes = {}) {
|
|
338
|
+
build(attributes = /** @type {TargetCreateAttributes} */ ({})) {
|
|
295
339
|
if (!this.targetModelClass) {
|
|
296
340
|
throw new Error(`No target model class configured for ${this.model.constructor.name}#${this.relationshipName}`)
|
|
297
341
|
}
|
|
298
342
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
@type {InstanceType<T>} */ (new this.targetModelClass(attributes))
|
|
343
|
+
// Narrows the runtime value to the documented relationship model type.
|
|
344
|
+
const model = /** @type {T} */ (new this.targetModelClass(attributes))
|
|
302
345
|
|
|
303
346
|
this.setLoaded(model)
|
|
304
347
|
|
|
305
348
|
return model
|
|
306
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Force-reload the relationship.
|
|
353
|
+
* @returns {Promise<T | null>} - Loaded relationship model.
|
|
354
|
+
*/
|
|
355
|
+
async load() {
|
|
356
|
+
this._preloaded = false
|
|
357
|
+
this._loadedValue = null
|
|
358
|
+
|
|
359
|
+
const batched = await this.model._tryCohortPreload(this.relationshipName)
|
|
360
|
+
|
|
361
|
+
if (batched) return this.loaded()
|
|
362
|
+
|
|
363
|
+
await this.model.loadRelationship(this.relationshipName)
|
|
364
|
+
|
|
365
|
+
return this.loaded()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns the loaded relationship or loads it.
|
|
370
|
+
* @returns {Promise<T | null>} - Loaded relationship model.
|
|
371
|
+
*/
|
|
372
|
+
async orLoad() {
|
|
373
|
+
if (this.getPreloaded()) return this.loaded()
|
|
374
|
+
|
|
375
|
+
const batched = await this.model._tryCohortPreload(this.relationshipName)
|
|
376
|
+
|
|
377
|
+
if (batched) return this.loaded()
|
|
378
|
+
|
|
379
|
+
await this.model.loadRelationship(this.relationshipName)
|
|
380
|
+
|
|
381
|
+
return this.loaded()
|
|
382
|
+
}
|
|
307
383
|
}
|
|
308
384
|
|
|
309
385
|
/**
|
|
310
386
|
* Lightweight has-many relationship state holder for frontend model instances.
|
|
311
|
-
* @template {
|
|
312
|
-
* @template {
|
|
387
|
+
* @template {FrontendModelBase} S
|
|
388
|
+
* @template {FrontendModelBase} T
|
|
389
|
+
* @template {Record<string, FrontendModelAttributeValue>} [TargetCreateAttributes=Record<string, FrontendModelAttributeValue>]
|
|
313
390
|
*/
|
|
314
391
|
export class FrontendModelHasManyRelationship {
|
|
315
392
|
/**
|
|
316
393
|
* Narrows the runtime value to the documented type.
|
|
317
|
-
@type {Array<
|
|
394
|
+
@type {Array<T>} */
|
|
318
395
|
_loadedValue
|
|
319
396
|
|
|
320
397
|
/**
|
|
321
398
|
* Runs constructor.
|
|
322
|
-
* @param {
|
|
399
|
+
* @param {S} model - Parent model.
|
|
323
400
|
* @param {string} relationshipName - Relationship name.
|
|
324
|
-
* @param {T | null} targetModelClass - Target model class.
|
|
401
|
+
* @param {FrontendModelClass<T, Record<string, FrontendModelAttributeValue>, TargetCreateAttributes> | null} targetModelClass - Target model class.
|
|
325
402
|
*/
|
|
326
403
|
constructor(model, relationshipName, targetModelClass) {
|
|
327
404
|
this.model = model
|
|
@@ -333,11 +410,15 @@ export class FrontendModelHasManyRelationship {
|
|
|
333
410
|
|
|
334
411
|
/**
|
|
335
412
|
* Runs set loaded.
|
|
336
|
-
* @param {Array<
|
|
413
|
+
* @param {Array<T>} loadedValue - Loaded relationship value.
|
|
337
414
|
* @returns {void}
|
|
338
415
|
*/
|
|
339
416
|
setLoaded(loadedValue) {
|
|
340
|
-
|
|
417
|
+
if (!Array.isArray(loadedValue)) {
|
|
418
|
+
throw new Error(`Expected ${this.model.constructor.name}#${this.relationshipName} to be loaded with an array`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this._loadedValue = loadedValue
|
|
341
422
|
this._preloaded = true
|
|
342
423
|
}
|
|
343
424
|
|
|
@@ -351,7 +432,7 @@ export class FrontendModelHasManyRelationship {
|
|
|
351
432
|
|
|
352
433
|
/**
|
|
353
434
|
* Runs loaded.
|
|
354
|
-
* @returns {Array<
|
|
435
|
+
* @returns {Array<T>} - Loaded relationship values.
|
|
355
436
|
*/
|
|
356
437
|
loaded() {
|
|
357
438
|
if (!this._preloaded) {
|
|
@@ -361,9 +442,25 @@ export class FrontendModelHasManyRelationship {
|
|
|
361
442
|
return this._loadedValue
|
|
362
443
|
}
|
|
363
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Copies loaded value from another has-many relationship helper.
|
|
447
|
+
* @param {FrontendModelRelationship} sourceRelationship - Source relationship helper.
|
|
448
|
+
* @returns {void}
|
|
449
|
+
*/
|
|
450
|
+
copyLoadedFrom(sourceRelationship) {
|
|
451
|
+
if (!(sourceRelationship instanceof FrontendModelHasManyRelationship)) {
|
|
452
|
+
throw new Error(`Expected ${this.model.constructor.name}#${this.relationshipName} source relationship to be has-many`)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Narrows the runtime value to the target relationship's documented model type.
|
|
456
|
+
const loadedValue = /** @type {Array<T>} */ (sourceRelationship.loaded())
|
|
457
|
+
|
|
458
|
+
this.setLoaded(loadedValue)
|
|
459
|
+
}
|
|
460
|
+
|
|
364
461
|
/**
|
|
365
462
|
* Runs add to loaded.
|
|
366
|
-
* @param {Array<
|
|
463
|
+
* @param {Array<T>} models - Models to append.
|
|
367
464
|
* @returns {void}
|
|
368
465
|
*/
|
|
369
466
|
addToLoaded(models) {
|
|
@@ -374,17 +471,16 @@ export class FrontendModelHasManyRelationship {
|
|
|
374
471
|
|
|
375
472
|
/**
|
|
376
473
|
* Runs build.
|
|
377
|
-
* @param {
|
|
378
|
-
* @returns {
|
|
474
|
+
* @param {TargetCreateAttributes} [attributes] - New model attributes.
|
|
475
|
+
* @returns {T} - Built model.
|
|
379
476
|
*/
|
|
380
|
-
build(attributes = {}) {
|
|
477
|
+
build(attributes = /** @type {TargetCreateAttributes} */ ({})) {
|
|
381
478
|
if (!this.targetModelClass) {
|
|
382
479
|
throw new Error(`No target model class configured for ${this.model.constructor.name}#${this.relationshipName}`)
|
|
383
480
|
}
|
|
384
481
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
@type {InstanceType<T>} */ (new this.targetModelClass(attributes))
|
|
482
|
+
// Narrows the runtime value to the documented relationship model type.
|
|
483
|
+
const model = /** @type {T} */ (new this.targetModelClass(attributes))
|
|
388
484
|
|
|
389
485
|
this.addToLoaded([model])
|
|
390
486
|
|
|
@@ -397,25 +493,25 @@ export class FrontendModelHasManyRelationship {
|
|
|
397
493
|
* batched into one request via the cohort preloader. The scoped query path
|
|
398
494
|
* (`Model.where(...).preload([name]).toArray()` directly from user code)
|
|
399
495
|
* bypasses cohort batching by design.
|
|
400
|
-
* @returns {Promise<Array<
|
|
496
|
+
* @returns {Promise<Array<T>>} - Loaded relationship models.
|
|
401
497
|
*/
|
|
402
498
|
async load() {
|
|
403
499
|
// Reset so the cohort preloader (or single-record fallback) repopulates.
|
|
404
500
|
this._preloaded = false
|
|
405
501
|
this._loadedValue = []
|
|
406
502
|
|
|
407
|
-
const batched = await
|
|
408
|
-
* Narrows the runtime value to the documented type.
|
|
409
|
-
@type {?} */ (this.model)._tryCohortPreload(this.relationshipName)
|
|
503
|
+
const batched = await this.model._tryCohortPreload(this.relationshipName)
|
|
410
504
|
|
|
411
505
|
if (batched) return this._loadedValue
|
|
412
506
|
|
|
413
|
-
|
|
507
|
+
await this.model.loadRelationship(this.relationshipName)
|
|
508
|
+
|
|
509
|
+
return this.loaded()
|
|
414
510
|
}
|
|
415
511
|
|
|
416
512
|
/**
|
|
417
513
|
* Runs to array.
|
|
418
|
-
* @returns {Promise<Array<
|
|
514
|
+
* @returns {Promise<Array<T>>} - Loaded relationship models.
|
|
419
515
|
*/
|
|
420
516
|
async toArray() {
|
|
421
517
|
if (this.getPreloaded() || this._loadedValue.length > 0) {
|
|
@@ -426,6 +522,22 @@ export class FrontendModelHasManyRelationship {
|
|
|
426
522
|
}
|
|
427
523
|
}
|
|
428
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Frontend model relationship helper type.
|
|
527
|
+
* @typedef {FrontendModelHasManyRelationship<FrontendModelBase, FrontendModelBase, Record<string, FrontendModelAttributeValue>> | FrontendModelSingularRelationship<FrontendModelBase, FrontendModelBase, Record<string, FrontendModelAttributeValue>>} FrontendModelRelationship
|
|
528
|
+
*/
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Copies loaded relationship state between helpers of the same relationship shape.
|
|
532
|
+
* @param {object} args - Arguments.
|
|
533
|
+
* @param {FrontendModelRelationship} args.sourceRelationship - Source relationship helper.
|
|
534
|
+
* @param {FrontendModelRelationship} args.targetRelationship - Target relationship helper.
|
|
535
|
+
* @returns {void}
|
|
536
|
+
*/
|
|
537
|
+
function copyLoadedRelationshipValue({sourceRelationship, targetRelationship}) {
|
|
538
|
+
targetRelationship.copyLoadedFrom(sourceRelationship)
|
|
539
|
+
}
|
|
540
|
+
|
|
429
541
|
/**
|
|
430
542
|
* Runs relationship type is collection.
|
|
431
543
|
* @param {string} relationshipType - Relationship type.
|
|
@@ -1694,7 +1806,12 @@ function assertDefinedFindByConditionValue(value, keyPath) {
|
|
|
1694
1806
|
}
|
|
1695
1807
|
}
|
|
1696
1808
|
|
|
1697
|
-
/**
|
|
1809
|
+
/**
|
|
1810
|
+
* Base frontend model.
|
|
1811
|
+
* @template {Record<string, FrontendModelAttributeValue>} [Attributes=Record<string, FrontendModelAttributeValue>]
|
|
1812
|
+
* @template {Record<string, FrontendModelAttributeValue>} [CreateAttributes=Record<string, FrontendModelAttributeValue>]
|
|
1813
|
+
* @template {Record<string, FrontendModelAttributeValue>} [UpdateAttributes=Record<string, FrontendModelAttributeValue>]
|
|
1814
|
+
*/
|
|
1698
1815
|
export default class FrontendModelBase {
|
|
1699
1816
|
/**
|
|
1700
1817
|
* Narrows the runtime value to the documented type.
|
|
@@ -1722,11 +1839,11 @@ export default class FrontendModelBase {
|
|
|
1722
1839
|
|
|
1723
1840
|
/**
|
|
1724
1841
|
* Narrows the runtime value to the documented type.
|
|
1725
|
-
@type {Record<string,
|
|
1842
|
+
@type {Record<string, FrontendModelAttributeValue>} */
|
|
1726
1843
|
_attributes
|
|
1727
1844
|
/**
|
|
1728
1845
|
* Narrows the runtime value to the documented type.
|
|
1729
|
-
@type {Record<string, FrontendModelHasManyRelationship<
|
|
1846
|
+
@type {Record<string, FrontendModelHasManyRelationship<FrontendModelBase, FrontendModelBase, Record<string, FrontendModelAttributeValue>> | FrontendModelSingularRelationship<FrontendModelBase, FrontendModelBase, Record<string, FrontendModelAttributeValue>>>} */
|
|
1730
1847
|
_relationships
|
|
1731
1848
|
/**
|
|
1732
1849
|
* Narrows the runtime value to the documented type.
|
|
@@ -1751,7 +1868,7 @@ export default class FrontendModelBase {
|
|
|
1751
1868
|
_markedForDestruction
|
|
1752
1869
|
/**
|
|
1753
1870
|
* Narrows the runtime value to the documented type.
|
|
1754
|
-
@type {Record<string,
|
|
1871
|
+
@type {Record<string, FrontendModelAttributeValue>} */
|
|
1755
1872
|
_persistedAttributes
|
|
1756
1873
|
/**
|
|
1757
1874
|
* Narrows the runtime value to the documented type.
|
|
@@ -1761,9 +1878,9 @@ export default class FrontendModelBase {
|
|
|
1761
1878
|
|
|
1762
1879
|
/**
|
|
1763
1880
|
* Runs constructor.
|
|
1764
|
-
* @param {
|
|
1881
|
+
* @param {Attributes | CreateAttributes} [attributes] - Initial attributes.
|
|
1765
1882
|
*/
|
|
1766
|
-
constructor(attributes
|
|
1883
|
+
constructor(attributes) {
|
|
1767
1884
|
const ModelClass = frontendModelClassFor(this)
|
|
1768
1885
|
|
|
1769
1886
|
ModelClass.ensureGeneratedAttachmentMethods()
|
|
@@ -1775,7 +1892,7 @@ export default class FrontendModelBase {
|
|
|
1775
1892
|
this._isNewRecord = true
|
|
1776
1893
|
this._markedForDestruction = false
|
|
1777
1894
|
this._persistedAttributes = {}
|
|
1778
|
-
this.assignAttributes(attributes)
|
|
1895
|
+
if (attributes) this.assignAttributes(attributes)
|
|
1779
1896
|
}
|
|
1780
1897
|
|
|
1781
1898
|
/**
|
|
@@ -1924,10 +2041,10 @@ export default class FrontendModelBase {
|
|
|
1924
2041
|
|
|
1925
2042
|
/**
|
|
1926
2043
|
* Runs attributes.
|
|
1927
|
-
* @returns {
|
|
2044
|
+
* @returns {Attributes} - Attributes hash.
|
|
1928
2045
|
*/
|
|
1929
2046
|
attributes() {
|
|
1930
|
-
return this._attributes
|
|
2047
|
+
return /** @type {Attributes} */ (this._attributes)
|
|
1931
2048
|
}
|
|
1932
2049
|
|
|
1933
2050
|
/**
|
|
@@ -2010,7 +2127,7 @@ export default class FrontendModelBase {
|
|
|
2010
2127
|
/**
|
|
2011
2128
|
* Runs get relationship by name.
|
|
2012
2129
|
* @param {string} relationshipName - Relationship name.
|
|
2013
|
-
* @returns {
|
|
2130
|
+
* @returns {FrontendModelRelationship} - Relationship state object.
|
|
2014
2131
|
*/
|
|
2015
2132
|
getRelationshipByName(relationshipName) {
|
|
2016
2133
|
if (!this._relationships[relationshipName]) {
|
|
@@ -2054,7 +2171,7 @@ export default class FrontendModelBase {
|
|
|
2054
2171
|
/**
|
|
2055
2172
|
* Runs load relationship.
|
|
2056
2173
|
* @param {string} relationshipName - Relationship name.
|
|
2057
|
-
* @returns {Promise
|
|
2174
|
+
* @returns {Promise<FrontendModelBase | Array<FrontendModelBase> | null>} - Loaded relationship value.
|
|
2058
2175
|
*/
|
|
2059
2176
|
async loadRelationship(relationshipName) {
|
|
2060
2177
|
const ModelClass = frontendModelClassFor(this)
|
|
@@ -2062,11 +2179,12 @@ export default class FrontendModelBase {
|
|
|
2062
2179
|
const reloadedModel = await ModelClass
|
|
2063
2180
|
.preload([relationshipName])
|
|
2064
2181
|
.find(id)
|
|
2065
|
-
const
|
|
2182
|
+
const sourceRelationship = reloadedModel.getRelationshipByName(relationshipName)
|
|
2183
|
+
const targetRelationship = this.getRelationshipByName(relationshipName)
|
|
2066
2184
|
|
|
2067
|
-
|
|
2185
|
+
copyLoadedRelationshipValue({sourceRelationship, targetRelationship})
|
|
2068
2186
|
|
|
2069
|
-
return
|
|
2187
|
+
return targetRelationship.loaded()
|
|
2070
2188
|
}
|
|
2071
2189
|
|
|
2072
2190
|
/**
|
|
@@ -2087,7 +2205,7 @@ export default class FrontendModelBase {
|
|
|
2087
2205
|
/**
|
|
2088
2206
|
* Runs relationship or load.
|
|
2089
2207
|
* @param {string} relationshipName - Relationship name.
|
|
2090
|
-
* @returns {Promise
|
|
2208
|
+
* @returns {Promise<FrontendModelBase | Array<FrontendModelBase> | null>} - Loaded relationship value.
|
|
2091
2209
|
*/
|
|
2092
2210
|
async relationshipOrLoad(relationshipName) {
|
|
2093
2211
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
@@ -2171,9 +2289,10 @@ export default class FrontendModelBase {
|
|
|
2171
2289
|
|
|
2172
2290
|
if (!reloaded) continue
|
|
2173
2291
|
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2292
|
+
copyLoadedRelationshipValue({
|
|
2293
|
+
sourceRelationship: reloaded.getRelationshipByName(relationshipName),
|
|
2294
|
+
targetRelationship: sibling.getRelationshipByName(relationshipName)
|
|
2295
|
+
})
|
|
2177
2296
|
}
|
|
2178
2297
|
|
|
2179
2298
|
// If the caller itself was not populated (record deleted/filtered between
|
|
@@ -2188,8 +2307,8 @@ export default class FrontendModelBase {
|
|
|
2188
2307
|
/**
|
|
2189
2308
|
* Runs set relationship.
|
|
2190
2309
|
* @param {string} relationshipName - Relationship name.
|
|
2191
|
-
* @param {
|
|
2192
|
-
* @returns {
|
|
2310
|
+
* @param {FrontendModelBase | null | undefined} relationshipValue - Relationship value.
|
|
2311
|
+
* @returns {FrontendModelBase | null | undefined} - Assigned relationship value.
|
|
2193
2312
|
*/
|
|
2194
2313
|
setRelationship(relationshipName, relationshipValue) {
|
|
2195
2314
|
const ModelClass = frontendModelClassFor(this)
|
|
@@ -2199,23 +2318,27 @@ export default class FrontendModelBase {
|
|
|
2199
2318
|
throw new Error(`Unknown relationship: ${ModelClass.name}#${relationshipName}`)
|
|
2200
2319
|
}
|
|
2201
2320
|
|
|
2202
|
-
|
|
2321
|
+
const relationship = this.getRelationshipByName(relationshipName)
|
|
2322
|
+
|
|
2323
|
+
if (relationship instanceof FrontendModelHasManyRelationship) {
|
|
2203
2324
|
throw new Error(`Cannot set has-many relationship with setRelationship(): ${ModelClass.name}#${relationshipName}`)
|
|
2204
2325
|
}
|
|
2205
2326
|
|
|
2206
|
-
|
|
2327
|
+
relationship.setLoaded(relationshipValue)
|
|
2207
2328
|
|
|
2208
2329
|
return relationshipValue
|
|
2209
2330
|
}
|
|
2210
2331
|
|
|
2211
2332
|
/**
|
|
2212
2333
|
* Runs assign attributes.
|
|
2213
|
-
* @param {Record<string,
|
|
2334
|
+
* @param {Attributes | CreateAttributes | UpdateAttributes | Record<string, FrontendModelAttributeValue>} attributes - Attributes to assign.
|
|
2214
2335
|
* @returns {void} - No return value.
|
|
2215
2336
|
*/
|
|
2216
2337
|
assignAttributes(attributes) {
|
|
2217
|
-
|
|
2218
|
-
|
|
2338
|
+
const attributeValues = /** @type {Record<string, FrontendModelAttributeValue>} */ (attributes)
|
|
2339
|
+
|
|
2340
|
+
for (const key in attributeValues) {
|
|
2341
|
+
this.setAttribute(key, attributeValues[key])
|
|
2219
2342
|
}
|
|
2220
2343
|
}
|
|
2221
2344
|
|
|
@@ -2812,7 +2935,7 @@ export default class FrontendModelBase {
|
|
|
2812
2935
|
* Runs attributes from response.
|
|
2813
2936
|
* @this {FrontendModelClass}
|
|
2814
2937
|
* @param {object} response - Response payload.
|
|
2815
|
-
* @returns {Record<string,
|
|
2938
|
+
* @returns {Record<string, FrontendModelAttributeValue>} - Attributes from payload.
|
|
2816
2939
|
*/
|
|
2817
2940
|
static attributesFromResponse(response) {
|
|
2818
2941
|
const modelData = this.modelDataFromResponse(response)
|
|
@@ -2824,39 +2947,34 @@ export default class FrontendModelBase {
|
|
|
2824
2947
|
* Runs model data from response.
|
|
2825
2948
|
* @this {FrontendModelClass}
|
|
2826
2949
|
* @param {object} response - Response payload.
|
|
2827
|
-
* @returns {{abilities: Record<string, boolean>, attributes: Record<string,
|
|
2950
|
+
* @returns {{abilities: Record<string, boolean>, attributes: Record<string, FrontendModelAttributeValue>, associationCounts: Record<string, number>, queryData: Record<string, FrontendModelAttributeValue>, preloadedRelationships: Record<string, FrontendModelAttributeValue>, selectedAttributes: Set<string>}} - Attributes, preloaded relationships, association counts, queryData, abilities, and the selected-attributes set.
|
|
2828
2951
|
*/
|
|
2829
2952
|
static modelDataFromResponse(response) {
|
|
2830
2953
|
if (!response || typeof response !== "object") {
|
|
2831
2954
|
throw new Error(`Expected object response but got: ${response}`)
|
|
2832
2955
|
}
|
|
2833
2956
|
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
@type {Record<string, ?>} */ (response)
|
|
2957
|
+
// Narrows the response object to the frontend-model transport value map.
|
|
2958
|
+
const responseObject = /** @type {Record<string, FrontendModelAttributeValue>} */ (response)
|
|
2837
2959
|
|
|
2838
2960
|
/**
|
|
2839
2961
|
* Defines modelData.
|
|
2840
|
-
@type {Record<string,
|
|
2962
|
+
@type {Record<string, FrontendModelAttributeValue>} */
|
|
2841
2963
|
let modelData
|
|
2842
2964
|
|
|
2843
2965
|
if (responseObject.model && typeof responseObject.model === "object") {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
@type {Record<string, ?>} */ (responseObject.model)
|
|
2966
|
+
// Narrows the nested model payload to the frontend-model value map.
|
|
2967
|
+
modelData = /** @type {Record<string, FrontendModelAttributeValue>} */ (responseObject.model)
|
|
2847
2968
|
} else if (responseObject.attributes && typeof responseObject.attributes === "object") {
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
@type {Record<string, ?>} */ (responseObject.attributes)
|
|
2969
|
+
// Narrows the nested attributes payload to the frontend-model value map.
|
|
2970
|
+
modelData = /** @type {Record<string, FrontendModelAttributeValue>} */ (responseObject.attributes)
|
|
2851
2971
|
} else {
|
|
2852
2972
|
modelData = responseObject
|
|
2853
2973
|
}
|
|
2854
2974
|
|
|
2855
|
-
const attributes = {...modelData}
|
|
2975
|
+
const attributes = /** @type {Record<string, FrontendModelAttributeValue>} */ ({...modelData})
|
|
2856
2976
|
const preloadedRelationships = isPlainObject(attributes[PRELOADED_RELATIONSHIPS_KEY])
|
|
2857
|
-
? /**
|
|
2858
|
-
* Narrows the runtime value to the documented type.
|
|
2859
|
-
@type {Record<string, ?>} */ (attributes[PRELOADED_RELATIONSHIPS_KEY])
|
|
2977
|
+
? /** @type {Record<string, FrontendModelAttributeValue>} */ (attributes[PRELOADED_RELATIONSHIPS_KEY])
|
|
2860
2978
|
: {}
|
|
2861
2979
|
const associationCounts = isPlainObject(attributes[ASSOCIATION_COUNTS_KEY])
|
|
2862
2980
|
? /**
|
|
@@ -2864,9 +2982,7 @@ export default class FrontendModelBase {
|
|
|
2864
2982
|
@type {Record<string, number>} */ (attributes[ASSOCIATION_COUNTS_KEY])
|
|
2865
2983
|
: {}
|
|
2866
2984
|
const queryData = isPlainObject(attributes[QUERY_DATA_KEY])
|
|
2867
|
-
? /**
|
|
2868
|
-
* Narrows the runtime value to the documented type.
|
|
2869
|
-
@type {Record<string, ?>} */ (attributes[QUERY_DATA_KEY])
|
|
2985
|
+
? /** @type {Record<string, FrontendModelAttributeValue>} */ (attributes[QUERY_DATA_KEY])
|
|
2870
2986
|
: {}
|
|
2871
2987
|
const abilities = isPlainObject(attributes[ABILITIES_KEY])
|
|
2872
2988
|
? /**
|
|
@@ -2902,12 +3018,39 @@ export default class FrontendModelBase {
|
|
|
2902
3018
|
const relationship = model.getRelationshipByName(relationshipName)
|
|
2903
3019
|
const targetModelClass = this.relationshipModelClass(relationshipName)
|
|
2904
3020
|
|
|
2905
|
-
if (
|
|
2906
|
-
|
|
3021
|
+
if (relationship instanceof FrontendModelHasManyRelationship) {
|
|
3022
|
+
if (!Array.isArray(relationshipPayload)) {
|
|
3023
|
+
throw new Error(`Expected ${this.name}#${relationshipName} payload to be an array`)
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
/** @type {Array<FrontendModelBase>} */
|
|
3027
|
+
const relatedModels = []
|
|
3028
|
+
|
|
3029
|
+
for (const entry of relationshipPayload) {
|
|
3030
|
+
const relatedModel = this.instantiateRelationshipValue(entry, targetModelClass)
|
|
3031
|
+
|
|
3032
|
+
if (!(relatedModel instanceof FrontendModelBase)) {
|
|
3033
|
+
throw new Error(`Expected ${this.name}#${relationshipName} payload entry to instantiate a frontend model`)
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
relatedModels.push(relatedModel)
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
relationship.setLoaded(relatedModels)
|
|
2907
3040
|
continue
|
|
2908
3041
|
}
|
|
2909
3042
|
|
|
2910
|
-
|
|
3043
|
+
if (Array.isArray(relationshipPayload)) {
|
|
3044
|
+
throw new Error(`Expected ${this.name}#${relationshipName} payload to be singular`)
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
const relatedModel = this.instantiateRelationshipValue(relationshipPayload, targetModelClass)
|
|
3048
|
+
|
|
3049
|
+
if (relatedModel != undefined && !(relatedModel instanceof FrontendModelBase)) {
|
|
3050
|
+
throw new Error(`Expected ${this.name}#${relationshipName} payload to instantiate a frontend model`)
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
relationship.setLoaded(relatedModel)
|
|
2911
3054
|
}
|
|
2912
3055
|
}
|
|
2913
3056
|
|
|
@@ -3422,15 +3565,16 @@ export default class FrontendModelBase {
|
|
|
3422
3565
|
|
|
3423
3566
|
/**
|
|
3424
3567
|
* Runs create.
|
|
3425
|
-
* @template {
|
|
3426
|
-
* @
|
|
3427
|
-
* @
|
|
3428
|
-
* @
|
|
3568
|
+
* @template {FrontendModelBase} Model
|
|
3569
|
+
* @template {Record<string, FrontendModelAttributeValue>} ModelAttributes
|
|
3570
|
+
* @template {Record<string, FrontendModelAttributeValue>} ModelCreateAttributes
|
|
3571
|
+
* @this {FrontendModelClass<Model, ModelAttributes, ModelCreateAttributes>}
|
|
3572
|
+
* @param {ModelCreateAttributes} [attributes] - Initial attributes.
|
|
3573
|
+
* @returns {Promise<Model>} - Persisted model.
|
|
3429
3574
|
*/
|
|
3430
|
-
static async create(attributes
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
@type {InstanceType<T>} */ (new this(attributes))
|
|
3575
|
+
static async create(attributes) {
|
|
3576
|
+
// Narrows the constructed instance to the receiver's documented model type.
|
|
3577
|
+
const model = /** @type {Model} */ (new this(attributes))
|
|
3434
3578
|
|
|
3435
3579
|
await model.save()
|
|
3436
3580
|
|
|
@@ -3601,13 +3745,13 @@ export default class FrontendModelBase {
|
|
|
3601
3745
|
|
|
3602
3746
|
/**
|
|
3603
3747
|
* Runs update.
|
|
3604
|
-
* @param {
|
|
3748
|
+
* @param {UpdateAttributes} [newAttributes] - New values to assign before update.
|
|
3605
3749
|
* @returns {Promise<this>} - Updated model.
|
|
3606
3750
|
*/
|
|
3607
|
-
async update(newAttributes
|
|
3608
|
-
this.assignAttributes(newAttributes)
|
|
3751
|
+
async update(newAttributes) {
|
|
3752
|
+
if (newAttributes) this.assignAttributes(newAttributes)
|
|
3609
3753
|
|
|
3610
|
-
return await this.save()
|
|
3754
|
+
return /** @type {this} */ (await this.save())
|
|
3611
3755
|
}
|
|
3612
3756
|
|
|
3613
3757
|
/**
|
|
@@ -3693,12 +3837,12 @@ export default class FrontendModelBase {
|
|
|
3693
3837
|
* fields the caller actually changed — avoiding strict permit rejections on
|
|
3694
3838
|
* framework-managed fields like `id`, `createdAt`, `updatedAt`, or owner
|
|
3695
3839
|
* foreign keys that the resource never lists in `permittedParams`.
|
|
3696
|
-
* @returns {Record<string,
|
|
3840
|
+
* @returns {Record<string, FrontendModelAttributeValue>} - Changed attributes hash.
|
|
3697
3841
|
*/
|
|
3698
3842
|
_changedAttributesForSave() {
|
|
3699
3843
|
/**
|
|
3700
3844
|
* Changed attributes.
|
|
3701
|
-
@type {Record<string,
|
|
3845
|
+
@type {Record<string, FrontendModelAttributeValue>} */
|
|
3702
3846
|
const changedAttributes = {}
|
|
3703
3847
|
|
|
3704
3848
|
for (const [attributeName, [previousValue, currentValue]] of Object.entries(this.changes())) {
|
|
@@ -75,9 +75,10 @@ export default class FrontendModelPreloader {
|
|
|
75
75
|
if (!reloadedModel) continue
|
|
76
76
|
|
|
77
77
|
for (const relationshipName of topLevelRelationships) {
|
|
78
|
-
const
|
|
78
|
+
const sourceRelationship = reloadedModel.getRelationshipByName(relationshipName)
|
|
79
|
+
const targetRelationship = model.getRelationshipByName(relationshipName)
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
targetRelationship.copyLoadedFrom(sourceRelationship)
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
}
|