velocious 1.0.445 → 1.0.447
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/README.md +1 -1
- package/build/configuration-types.js +2 -2
- package/build/database/pool/async-tracked-multi-connection.js +3 -1
- package/build/database/record/index.js +38 -38
- package/build/environment-handlers/node/cli/commands/generate/base-models.js +67 -1
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +169 -0
- package/build/frontend-model-controller.js +44 -12
- package/build/frontend-model-resource/base-resource.js +519 -129
- package/build/frontend-models/base.js +417 -203
- package/build/frontend-models/preloader.js +7 -7
- package/build/frontend-models/query.js +18 -18
- package/build/frontend-models/use-created-event.js +1 -1
- package/build/frontend-models/use-destroyed-event.js +1 -1
- package/build/frontend-models/use-model-class-event.js +1 -1
- package/build/frontend-models/use-updated-event.js +1 -1
- package/build/frontend-models/websocket-channel.js +39 -3
- package/build/routes/resolver.js +17 -14
- package/build/src/configuration-types.d.ts +6 -6
- package/build/src/configuration-types.js +3 -3
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +5 -2
- package/build/src/database/record/index.d.ts +38 -38
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +39 -39
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +13 -0
- 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 +59 -2
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +74 -0
- 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 +155 -1
- package/build/src/frontend-model-controller.d.ts +2 -1
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +38 -14
- package/build/src/frontend-model-resource/base-resource.d.ts +196 -21
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +467 -112
- package/build/src/frontend-models/base.d.ts +232 -149
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +371 -201
- package/build/src/frontend-models/preloader.d.ts +10 -10
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +8 -8
- package/build/src/frontend-models/query.d.ts +8 -8
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +19 -19
- package/build/src/frontend-models/use-created-event.d.ts +2 -2
- package/build/src/frontend-models/use-created-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-created-event.js +2 -2
- package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
- package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-destroyed-event.js +2 -2
- package/build/src/frontend-models/use-model-class-event.d.ts +1 -1
- package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-model-class-event.js +2 -2
- package/build/src/frontend-models/use-updated-event.d.ts +1 -1
- package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-updated-event.js +2 -2
- package/build/src/frontend-models/websocket-channel.d.ts +8 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +35 -4
- package/build/src/routes/resolver.d.ts.map +1 -1
- package/build/src/routes/resolver.js +7 -4
- package/build/src/utils/model-scope.d.ts +4 -4
- package/build/src/utils/model-scope.d.ts.map +1 -1
- package/build/src/utils/model-scope.js +3 -3
- package/build/src/utils/ransack.d.ts +1 -1
- package/build/src/utils/ransack.d.ts.map +1 -1
- package/build/src/utils/ransack.js +2 -2
- package/build/utils/model-scope.js +2 -2
- package/build/utils/ransack.js +1 -1
- package/package.json +1 -1
- package/src/configuration-types.js +2 -2
- package/src/database/pool/async-tracked-multi-connection.js +3 -1
- package/src/database/record/index.js +38 -38
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +67 -1
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +169 -0
- package/src/frontend-model-controller.js +44 -12
- package/src/frontend-model-resource/base-resource.js +519 -129
- package/src/frontend-models/base.js +417 -203
- package/src/frontend-models/preloader.js +7 -7
- package/src/frontend-models/query.js +18 -18
- package/src/frontend-models/use-created-event.js +1 -1
- package/src/frontend-models/use-destroyed-event.js +1 -1
- package/src/frontend-models/use-model-class-event.js +1 -1
- package/src/frontend-models/use-updated-event.js +1 -1
- package/src/frontend-models/websocket-channel.js +39 -3
- package/src/routes/resolver.js +17 -14
- package/src/utils/model-scope.js +2 -2
- package/src/utils/ransack.js +1 -1
|
@@ -24,10 +24,22 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
24
24
|
* Defines this typedef.
|
|
25
25
|
* @typedef {{type: "hasOne" | "hasMany"}} FrontendModelAttachmentDefinition
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Attachment input accepted by frontend-model attachment helpers before normalization.
|
|
29
|
+
* @typedef {Record<string, ?> | {arrayBuffer: () => Promise<ArrayBuffer>, type?: string, name?: string} | null | undefined} FrontendModelAttachmentInput
|
|
30
|
+
*/
|
|
27
31
|
/**
|
|
28
32
|
* Defines this typedef.
|
|
29
33
|
* @typedef {{attributes?: string[], builtInCollectionCommands?: string[], builtInMemberCommands?: string[], collectionCommands?: string[], commands?: string[], memberCommands?: string[], attachments?: Record<string, FrontendModelAttachmentDefinition>, modelName?: string, nestedAttributes?: Record<string, {allowDestroy?: boolean, limit?: number}>, primaryKey?: string, relationships?: string[]}} FrontendModelResourceConfig
|
|
30
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Frontend model constructor type.
|
|
37
|
+
* @typedef {{new (attributes?: Record<string, ?>): FrontendModelBase}} FrontendModelConstructor
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Frontend model static side without generated per-model create overloads.
|
|
41
|
+
* @typedef {FrontendModelConstructor & Omit<typeof FrontendModelBase, "create">} FrontendModelClass
|
|
42
|
+
*/
|
|
31
43
|
/**
|
|
32
44
|
* FrontendModelTransportConfig type.
|
|
33
45
|
* @typedef {object} FrontendModelTransportConfig
|
|
@@ -57,7 +69,7 @@ const QUERY_DATA_KEY = "__queryData"
|
|
|
57
69
|
const ABILITIES_KEY = "__abilities"
|
|
58
70
|
/**
|
|
59
71
|
* Pending shared frontend model requests.
|
|
60
|
-
@type {Array<{commandName?: string, commandType: FrontendModelRequestCommandType, customPath?: string, modelClass:
|
|
72
|
+
@type {Array<{commandName?: string, commandType: FrontendModelRequestCommandType, customPath?: string, modelClass: FrontendModelClass, payload: Record<string, ?>, requestId: string, resolve: (response: Record<string, ?>) => void, reject: (error: ?) => void, resourcePath?: string | null}>} */
|
|
61
73
|
let pendingSharedFrontendModelRequests = []
|
|
62
74
|
let sharedFrontendModelRequestId = 0
|
|
63
75
|
let sharedFrontendModelFlushScheduled = false
|
|
@@ -204,7 +216,7 @@ async function flushBufferedOutgoingEventsAfterReconnect() {
|
|
|
204
216
|
|
|
205
217
|
/**
|
|
206
218
|
* Runs default frontend model resource path.
|
|
207
|
-
* @param {
|
|
219
|
+
* @param {FrontendModelClass} modelClass - Frontend model class.
|
|
208
220
|
* @returns {string} - Default resource path for the model class.
|
|
209
221
|
*/
|
|
210
222
|
function defaultFrontendModelResourcePath(modelClass) {
|
|
@@ -226,8 +238,8 @@ export class AttributeNotSelectedError extends Error {
|
|
|
226
238
|
|
|
227
239
|
/**
|
|
228
240
|
* Lightweight singular relationship state holder for frontend model instances.
|
|
229
|
-
* @template {
|
|
230
|
-
* @template {
|
|
241
|
+
* @template {FrontendModelClass} S
|
|
242
|
+
* @template {FrontendModelClass} T
|
|
231
243
|
*/
|
|
232
244
|
export class FrontendModelSingularRelationship {
|
|
233
245
|
/**
|
|
@@ -296,8 +308,8 @@ export class FrontendModelSingularRelationship {
|
|
|
296
308
|
|
|
297
309
|
/**
|
|
298
310
|
* Lightweight has-many relationship state holder for frontend model instances.
|
|
299
|
-
* @template {
|
|
300
|
-
* @template {
|
|
311
|
+
* @template {FrontendModelClass} S
|
|
312
|
+
* @template {FrontendModelClass} T
|
|
301
313
|
*/
|
|
302
314
|
export class FrontendModelHasManyRelationship {
|
|
303
315
|
/**
|
|
@@ -614,6 +626,17 @@ function frontendModelPayloadContainsAttachmentUpload(value) {
|
|
|
614
626
|
return Object.values(value).some((entry) => frontendModelPayloadContainsAttachmentUpload(entry))
|
|
615
627
|
}
|
|
616
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Returns the concrete frontend-model class for an instance.
|
|
631
|
+
* @param {FrontendModelBase} model - Frontend model instance.
|
|
632
|
+
* @returns {FrontendModelClass} Concrete frontend-model class.
|
|
633
|
+
*/
|
|
634
|
+
function frontendModelClassFor(model) {
|
|
635
|
+
const constructorValue = model.constructor
|
|
636
|
+
|
|
637
|
+
return /** @type {FrontendModelClass} */ (constructorValue)
|
|
638
|
+
}
|
|
639
|
+
|
|
617
640
|
/**
|
|
618
641
|
* Runs normalize frontend attachment input.
|
|
619
642
|
* @param {?} input - Attachment input.
|
|
@@ -691,6 +714,12 @@ async function normalizeFrontendAttachmentInput(input) {
|
|
|
691
714
|
* Frontend-model attachment helper for one attachment name.
|
|
692
715
|
*/
|
|
693
716
|
export class FrontendModelAttachmentHandle {
|
|
717
|
+
/**
|
|
718
|
+
* Pending attachment inputs queued for the next model save.
|
|
719
|
+
* @type {FrontendModelAttachmentInput[]}
|
|
720
|
+
*/
|
|
721
|
+
pendingInputs = []
|
|
722
|
+
|
|
694
723
|
/**
|
|
695
724
|
* Runs constructor.
|
|
696
725
|
* @param {object} args - Options.
|
|
@@ -702,15 +731,70 @@ export class FrontendModelAttachmentHandle {
|
|
|
702
731
|
this.attachmentName = attachmentName
|
|
703
732
|
}
|
|
704
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Queue attachment input for the parent model's next save.
|
|
736
|
+
* @param {FrontendModelAttachmentInput | FrontendModelAttachmentInput[]} input - Attachment input.
|
|
737
|
+
* @returns {void}
|
|
738
|
+
*/
|
|
739
|
+
queueAttach(input) {
|
|
740
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
741
|
+
const attachmentDefinition = ModelClass.attachmentDefinition(this.attachmentName)
|
|
742
|
+
|
|
743
|
+
if (attachmentDefinition?.type === "hasOne") {
|
|
744
|
+
if (Array.isArray(input)) {
|
|
745
|
+
const lastInput = input[input.length - 1]
|
|
746
|
+
|
|
747
|
+
this.pendingInputs = typeof lastInput === "undefined" ? [] : [lastInput]
|
|
748
|
+
} else {
|
|
749
|
+
this.pendingInputs = [input]
|
|
750
|
+
}
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (Array.isArray(input)) {
|
|
755
|
+
this.pendingInputs.push(...input)
|
|
756
|
+
} else {
|
|
757
|
+
this.pendingInputs.push(input)
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Whether this attachment has queued inputs for the next model save.
|
|
763
|
+
* @returns {boolean} Whether any pending inputs exist.
|
|
764
|
+
*/
|
|
765
|
+
hasPendingAttachments() {
|
|
766
|
+
return this.pendingInputs.length > 0
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Builds the save payload for queued attachment inputs.
|
|
771
|
+
* @returns {Promise<Record<string, ?> | Record<string, ?>[] | undefined>} Normalized attachment payload.
|
|
772
|
+
*/
|
|
773
|
+
async pendingAttachmentsPayload() {
|
|
774
|
+
if (this.pendingInputs.length === 0) return undefined
|
|
775
|
+
|
|
776
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
777
|
+
const attachmentDefinition = ModelClass.attachmentDefinition(this.attachmentName)
|
|
778
|
+
|
|
779
|
+
if (attachmentDefinition?.type === "hasMany") {
|
|
780
|
+
return await Promise.all(this.pendingInputs.map(async (input) => await normalizeFrontendAttachmentInput(input)))
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return await normalizeFrontendAttachmentInput(this.pendingInputs[this.pendingInputs.length - 1])
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** Clears queued attachment inputs after a successful model save. */
|
|
787
|
+
clearPendingAttachments() {
|
|
788
|
+
this.pendingInputs = []
|
|
789
|
+
}
|
|
790
|
+
|
|
705
791
|
/**
|
|
706
792
|
* Runs attach.
|
|
707
793
|
* @param {?} input - Attachment input.
|
|
708
794
|
* @returns {Promise<void>} - Resolves when attached.
|
|
709
795
|
*/
|
|
710
796
|
async attach(input) {
|
|
711
|
-
const ModelClass =
|
|
712
|
-
* Narrows the runtime value to the documented type.
|
|
713
|
-
@type {typeof FrontendModelBase} */ (this.model.constructor)
|
|
797
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
714
798
|
const normalizedInput = await normalizeFrontendAttachmentInput(input)
|
|
715
799
|
const response = await ModelClass.executeCommand("attach", {
|
|
716
800
|
attachment: normalizedInput,
|
|
@@ -727,9 +811,7 @@ export class FrontendModelAttachmentHandle {
|
|
|
727
811
|
* @returns {Promise<FrontendModelAttachmentDownload | null>} - Downloaded attachment payload.
|
|
728
812
|
*/
|
|
729
813
|
async download(attachmentId) {
|
|
730
|
-
const ModelClass =
|
|
731
|
-
* Narrows the runtime value to the documented type.
|
|
732
|
-
@type {typeof FrontendModelBase} */ (this.model.constructor)
|
|
814
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
733
815
|
const response = await ModelClass.executeCommand("download", frontendModelAttachmentCommandPayload(this, attachmentId))
|
|
734
816
|
const attachmentPayload = response.attachment
|
|
735
817
|
|
|
@@ -755,9 +837,7 @@ export class FrontendModelAttachmentHandle {
|
|
|
755
837
|
* @returns {Promise<string | null>} - Resolvable attachment URL.
|
|
756
838
|
*/
|
|
757
839
|
async url(attachmentId) {
|
|
758
|
-
const ModelClass =
|
|
759
|
-
* Narrows the runtime value to the documented type.
|
|
760
|
-
@type {typeof FrontendModelBase} */ (this.model.constructor)
|
|
840
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
761
841
|
const response = await ModelClass.executeCommand("url", frontendModelAttachmentCommandPayload(this, attachmentId))
|
|
762
842
|
|
|
763
843
|
if (typeof response.url === "string" && response.url.length > 0) {
|
|
@@ -772,9 +852,7 @@ export class FrontendModelAttachmentHandle {
|
|
|
772
852
|
* @returns {string} - Download URL for this attachment on the configured backend.
|
|
773
853
|
*/
|
|
774
854
|
downloadUrl() {
|
|
775
|
-
const ModelClass =
|
|
776
|
-
* Narrows the runtime value to the documented type.
|
|
777
|
-
@type {typeof FrontendModelBase} */ (this.model.constructor)
|
|
855
|
+
const ModelClass = frontendModelClassFor(this.model)
|
|
778
856
|
const commandName = ModelClass.commandName("download")
|
|
779
857
|
const resourcePath = ModelClass.resourcePath()
|
|
780
858
|
const commandUrl = frontendModelCommandUrl(resourcePath, commandName)
|
|
@@ -831,7 +909,7 @@ const FRONTEND_MODELS_CHANNEL_NAME = "frontend-models"
|
|
|
831
909
|
|
|
832
910
|
/**
|
|
833
911
|
* Defines this typedef.
|
|
834
|
-
* @typedef {{callback: (payload: {id: string, model:
|
|
912
|
+
* @typedef {{callback: (payload: {id: string, model: FrontendModelBase}) => void, eventFilterKey: string | null, eventFilterPayload: import("./query.js").FrontendModelEventFilterPayload | null, projectionPayload: import("./query.js").FrontendModelProjectionPayload}} FrontendModelModelEventCallbackEntry
|
|
835
913
|
*/
|
|
836
914
|
/**
|
|
837
915
|
* Defines this typedef.
|
|
@@ -981,7 +1059,7 @@ function frontendModelEventEntryMatches(entry, matchedEventFilterKeys) {
|
|
|
981
1059
|
|
|
982
1060
|
/**
|
|
983
1061
|
* Runs assert no destroy event filter.
|
|
984
|
-
* @param {
|
|
1062
|
+
* @param {FrontendModelClass} ModelClass - Event model class.
|
|
985
1063
|
* @param {import("./query.js").FrontendModelEventOptions} options - Event options.
|
|
986
1064
|
* @returns {void}
|
|
987
1065
|
*/
|
|
@@ -1007,7 +1085,7 @@ function assertNoDestroyEventFilter(ModelClass, options) {
|
|
|
1007
1085
|
class FrontendModelEventSubscription {
|
|
1008
1086
|
/**
|
|
1009
1087
|
* Runs constructor.
|
|
1010
|
-
* @param {
|
|
1088
|
+
* @param {FrontendModelClass} ModelClass - Frontend model class for this subscription bucket.
|
|
1011
1089
|
*/
|
|
1012
1090
|
constructor(ModelClass) {
|
|
1013
1091
|
this.ModelClass = ModelClass
|
|
@@ -1025,7 +1103,7 @@ class FrontendModelEventSubscription {
|
|
|
1025
1103
|
this.classDestroyCallbacks = new Set()
|
|
1026
1104
|
/**
|
|
1027
1105
|
* Narrows the runtime value to the documented type.
|
|
1028
|
-
@type {Map<string, {instance:
|
|
1106
|
+
@type {Map<string, {instance: FrontendModelBase, updateCallbacks: Set<FrontendModelModelEventCallbackEntry>, destroyCallbacks: Set<FrontendModelDestroyEventCallbackEntry>}>} */
|
|
1029
1107
|
this.instanceListeners = new Map()
|
|
1030
1108
|
/**
|
|
1031
1109
|
* Narrows the runtime value to the documented type.
|
|
@@ -1263,12 +1341,12 @@ class FrontendModelEventSubscription {
|
|
|
1263
1341
|
|
|
1264
1342
|
/**
|
|
1265
1343
|
* Frontend model event subscriptions.
|
|
1266
|
-
@type {WeakMap<
|
|
1344
|
+
@type {WeakMap<FrontendModelClass, FrontendModelEventSubscription>} */
|
|
1267
1345
|
const frontendModelEventSubscriptions = new WeakMap()
|
|
1268
1346
|
|
|
1269
1347
|
/**
|
|
1270
1348
|
* Runs ensure frontend model event subscription.
|
|
1271
|
-
* @param {
|
|
1349
|
+
* @param {FrontendModelClass} ModelClass - Model class.
|
|
1272
1350
|
* @returns {FrontendModelEventSubscription} - Per-class subscription helper.
|
|
1273
1351
|
*/
|
|
1274
1352
|
function ensureFrontendModelEventSubscription(ModelClass) {
|
|
@@ -1286,8 +1364,8 @@ function ensureFrontendModelEventSubscription(ModelClass) {
|
|
|
1286
1364
|
* Runs ensure frontend model instance listener.
|
|
1287
1365
|
* @param {FrontendModelEventSubscription} sub - Event subscription bucket.
|
|
1288
1366
|
* @param {string} id - Model id.
|
|
1289
|
-
* @param {
|
|
1290
|
-
* @returns {{instance:
|
|
1367
|
+
* @param {FrontendModelBase} instance - Listener instance.
|
|
1368
|
+
* @returns {{instance: FrontendModelBase, updateCallbacks: Set<FrontendModelModelEventCallbackEntry>, destroyCallbacks: Set<FrontendModelDestroyEventCallbackEntry>}} - Instance listener bucket.
|
|
1291
1369
|
*/
|
|
1292
1370
|
function ensureFrontendModelInstanceListener(sub, id, instance) {
|
|
1293
1371
|
let listener = sub.instanceListeners.get(id)
|
|
@@ -1648,12 +1726,17 @@ export default class FrontendModelBase {
|
|
|
1648
1726
|
_attributes
|
|
1649
1727
|
/**
|
|
1650
1728
|
* Narrows the runtime value to the documented type.
|
|
1651
|
-
@type {Record<string, FrontendModelHasManyRelationship<
|
|
1729
|
+
@type {Record<string, FrontendModelHasManyRelationship<FrontendModelClass, FrontendModelClass> | FrontendModelSingularRelationship<FrontendModelClass, FrontendModelClass>>} */
|
|
1652
1730
|
_relationships
|
|
1653
1731
|
/**
|
|
1654
1732
|
* Narrows the runtime value to the documented type.
|
|
1655
1733
|
@type {Record<string, FrontendModelAttachmentHandle>} */
|
|
1656
1734
|
_attachments
|
|
1735
|
+
/**
|
|
1736
|
+
* Rails-style nested attribute payloads queued for the next save.
|
|
1737
|
+
* @type {Record<string, ?>}
|
|
1738
|
+
*/
|
|
1739
|
+
_pendingNestedAttributes
|
|
1657
1740
|
/**
|
|
1658
1741
|
* Narrows the runtime value to the documented type.
|
|
1659
1742
|
@type {Set<string> | null} */
|
|
@@ -1681,14 +1764,13 @@ export default class FrontendModelBase {
|
|
|
1681
1764
|
* @param {Record<string, ?>} [attributes] - Initial attributes.
|
|
1682
1765
|
*/
|
|
1683
1766
|
constructor(attributes = {}) {
|
|
1684
|
-
const ModelClass =
|
|
1685
|
-
* Narrows the runtime value to the documented type.
|
|
1686
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
1767
|
+
const ModelClass = frontendModelClassFor(this)
|
|
1687
1768
|
|
|
1688
1769
|
ModelClass.ensureGeneratedAttachmentMethods()
|
|
1689
1770
|
this._attributes = {}
|
|
1690
1771
|
this._relationships = {}
|
|
1691
1772
|
this._attachments = {}
|
|
1773
|
+
this._pendingNestedAttributes = {}
|
|
1692
1774
|
this._selectedAttributes = null
|
|
1693
1775
|
this._isNewRecord = true
|
|
1694
1776
|
this._markedForDestruction = false
|
|
@@ -1698,7 +1780,7 @@ export default class FrontendModelBase {
|
|
|
1698
1780
|
|
|
1699
1781
|
/**
|
|
1700
1782
|
* Runs ensure generated attachment methods.
|
|
1701
|
-
* @this {
|
|
1783
|
+
* @this {FrontendModelClass}
|
|
1702
1784
|
* @returns {void} - Ensures attachment helper methods exist on the prototype.
|
|
1703
1785
|
*/
|
|
1704
1786
|
static ensureGeneratedAttachmentMethods() {
|
|
@@ -1732,8 +1814,8 @@ export default class FrontendModelBase {
|
|
|
1732
1814
|
|
|
1733
1815
|
/**
|
|
1734
1816
|
* Runs relationship model classes.
|
|
1735
|
-
* @this {
|
|
1736
|
-
* @returns {Record<string,
|
|
1817
|
+
* @this {FrontendModelClass}
|
|
1818
|
+
* @returns {Record<string, FrontendModelClass | string>} - Relationship model classes (or class name strings) keyed by relationship name.
|
|
1737
1819
|
*/
|
|
1738
1820
|
static relationshipModelClasses() {
|
|
1739
1821
|
return {}
|
|
@@ -1741,7 +1823,7 @@ export default class FrontendModelBase {
|
|
|
1741
1823
|
|
|
1742
1824
|
/**
|
|
1743
1825
|
* Register a frontend model class so it can be resolved by name in relationship lookups.
|
|
1744
|
-
* @param {
|
|
1826
|
+
* @param {FrontendModelClass} modelClass - Model class to register.
|
|
1745
1827
|
* @returns {void}
|
|
1746
1828
|
*/
|
|
1747
1829
|
static registerModel(modelClass) {
|
|
@@ -1751,7 +1833,7 @@ export default class FrontendModelBase {
|
|
|
1751
1833
|
/**
|
|
1752
1834
|
* Runs define scope.
|
|
1753
1835
|
* @param {(...args: Array<?>) => ?} callback - Scope callback.
|
|
1754
|
-
* @returns {((...args: Array<?>) => import("./query.js").default<
|
|
1836
|
+
* @returns {((...args: Array<?>) => import("./query.js").default<FrontendModelClass>) & {scope: (...args: Array<?>) => import("../utils/model-scope.js").ModelScopeDescriptor}} - Scope helper.
|
|
1755
1837
|
*/
|
|
1756
1838
|
static defineScope(callback) {
|
|
1757
1839
|
return defineModelScope({
|
|
@@ -1763,8 +1845,8 @@ export default class FrontendModelBase {
|
|
|
1763
1845
|
|
|
1764
1846
|
/**
|
|
1765
1847
|
* Resolve a relationship model class value that may be a class reference or a string name.
|
|
1766
|
-
* @param {
|
|
1767
|
-
* @returns {
|
|
1848
|
+
* @param {FrontendModelClass | string | null | undefined} value - Class or class name.
|
|
1849
|
+
* @returns {FrontendModelClass | null} - Resolved model class.
|
|
1768
1850
|
*/
|
|
1769
1851
|
static resolveModelClass(value) {
|
|
1770
1852
|
return resolveFrontendModelClass(value)
|
|
@@ -1772,7 +1854,7 @@ export default class FrontendModelBase {
|
|
|
1772
1854
|
|
|
1773
1855
|
/**
|
|
1774
1856
|
* Runs relationship definitions.
|
|
1775
|
-
* @this {
|
|
1857
|
+
* @this {FrontendModelClass}
|
|
1776
1858
|
* @returns {Record<string, {type: "belongsTo" | "hasOne" | "hasMany", autoload?: boolean}>} - Relationship definitions keyed by relationship name.
|
|
1777
1859
|
*/
|
|
1778
1860
|
static relationshipDefinitions() {
|
|
@@ -1781,7 +1863,7 @@ export default class FrontendModelBase {
|
|
|
1781
1863
|
|
|
1782
1864
|
/**
|
|
1783
1865
|
* Runs attachment definitions.
|
|
1784
|
-
* @this {
|
|
1866
|
+
* @this {FrontendModelClass}
|
|
1785
1867
|
* @returns {Record<string, FrontendModelAttachmentDefinition>} - Attachment definitions keyed by attachment name.
|
|
1786
1868
|
*/
|
|
1787
1869
|
static attachmentDefinitions() {
|
|
@@ -1790,7 +1872,7 @@ export default class FrontendModelBase {
|
|
|
1790
1872
|
|
|
1791
1873
|
/**
|
|
1792
1874
|
* Runs attachment definition.
|
|
1793
|
-
* @this {
|
|
1875
|
+
* @this {FrontendModelClass}
|
|
1794
1876
|
* @param {string} attachmentName - Attachment name.
|
|
1795
1877
|
* @returns {FrontendModelAttachmentDefinition | null} - Attachment definition.
|
|
1796
1878
|
*/
|
|
@@ -1800,7 +1882,7 @@ export default class FrontendModelBase {
|
|
|
1800
1882
|
|
|
1801
1883
|
/**
|
|
1802
1884
|
* Runs relationship definition.
|
|
1803
|
-
* @this {
|
|
1885
|
+
* @this {FrontendModelClass}
|
|
1804
1886
|
* @param {string} relationshipName - Relationship name.
|
|
1805
1887
|
* @returns {{type: "belongsTo" | "hasOne" | "hasMany", autoload?: boolean} | null} - Relationship definition.
|
|
1806
1888
|
*/
|
|
@@ -1810,11 +1892,28 @@ export default class FrontendModelBase {
|
|
|
1810
1892
|
return definitions[relationshipName] || null
|
|
1811
1893
|
}
|
|
1812
1894
|
|
|
1895
|
+
/**
|
|
1896
|
+
* Resolves a Rails-style nested attributes key to a configured relationship.
|
|
1897
|
+
* @this {FrontendModelClass}
|
|
1898
|
+
* @param {string} attributeName - Candidate attribute name, such as `tasksAttributes`.
|
|
1899
|
+
* @returns {string | null} Relationship name when nested attributes are configured.
|
|
1900
|
+
*/
|
|
1901
|
+
static nestedAttributesRelationshipName(attributeName) {
|
|
1902
|
+
if (!attributeName.endsWith("Attributes")) return null
|
|
1903
|
+
|
|
1904
|
+
const relationshipName = attributeName.slice(0, -"Attributes".length)
|
|
1905
|
+
const nestedAttributesConfig = this.resourceConfig().nestedAttributes || {}
|
|
1906
|
+
|
|
1907
|
+
return Object.prototype.hasOwnProperty.call(nestedAttributesConfig, relationshipName)
|
|
1908
|
+
? relationshipName
|
|
1909
|
+
: null
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1813
1912
|
/**
|
|
1814
1913
|
* Runs relationship model class.
|
|
1815
|
-
* @this {
|
|
1914
|
+
* @this {FrontendModelClass}
|
|
1816
1915
|
* @param {string} relationshipName - Relationship name.
|
|
1817
|
-
* @returns {
|
|
1916
|
+
* @returns {FrontendModelClass | null} - Target relationship model class.
|
|
1818
1917
|
*/
|
|
1819
1918
|
static relationshipModelClass(relationshipName) {
|
|
1820
1919
|
const relationshipModelClasses = this.relationshipModelClasses()
|
|
@@ -1911,13 +2010,11 @@ export default class FrontendModelBase {
|
|
|
1911
2010
|
/**
|
|
1912
2011
|
* Runs get relationship by name.
|
|
1913
2012
|
* @param {string} relationshipName - Relationship name.
|
|
1914
|
-
* @returns {FrontendModelHasManyRelationship<
|
|
2013
|
+
* @returns {FrontendModelHasManyRelationship<FrontendModelClass, FrontendModelClass> | FrontendModelSingularRelationship<FrontendModelClass, FrontendModelClass>} - Relationship state object.
|
|
1915
2014
|
*/
|
|
1916
2015
|
getRelationshipByName(relationshipName) {
|
|
1917
2016
|
if (!this._relationships[relationshipName]) {
|
|
1918
|
-
const ModelClass =
|
|
1919
|
-
* Narrows the runtime value to the documented type.
|
|
1920
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2017
|
+
const ModelClass = frontendModelClassFor(this)
|
|
1921
2018
|
const relationshipDefinition = ModelClass.relationshipDefinition(relationshipName)
|
|
1922
2019
|
const targetModelClass = ModelClass.relationshipModelClass(relationshipName)
|
|
1923
2020
|
|
|
@@ -1937,9 +2034,7 @@ export default class FrontendModelBase {
|
|
|
1937
2034
|
* @returns {FrontendModelAttachmentHandle} - Attachment helper.
|
|
1938
2035
|
*/
|
|
1939
2036
|
getAttachmentByName(attachmentName) {
|
|
1940
|
-
const ModelClass =
|
|
1941
|
-
* Narrows the runtime value to the documented type.
|
|
1942
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2037
|
+
const ModelClass = frontendModelClassFor(this)
|
|
1943
2038
|
const attachmentDefinition = ModelClass.attachmentDefinition(attachmentName)
|
|
1944
2039
|
|
|
1945
2040
|
if (!attachmentDefinition) {
|
|
@@ -1962,9 +2057,7 @@ export default class FrontendModelBase {
|
|
|
1962
2057
|
* @returns {Promise<?>} - Loaded relationship value.
|
|
1963
2058
|
*/
|
|
1964
2059
|
async loadRelationship(relationshipName) {
|
|
1965
|
-
const ModelClass =
|
|
1966
|
-
* Narrows the runtime value to the documented type.
|
|
1967
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2060
|
+
const ModelClass = frontendModelClassFor(this)
|
|
1968
2061
|
const id = this.primaryKeyValue()
|
|
1969
2062
|
const reloadedModel = await ModelClass
|
|
1970
2063
|
.preload([relationshipName])
|
|
@@ -1983,7 +2076,7 @@ export default class FrontendModelBase {
|
|
|
1983
2076
|
* required columns present are left untouched unless `force` is set. Carries
|
|
1984
2077
|
* the query's preload graph, select, selectsExtra, withCount, abilities, and
|
|
1985
2078
|
* queryData when re-fetching.
|
|
1986
|
-
* @param {import("./query.js").default<
|
|
2079
|
+
* @param {import("./query.js").default<FrontendModelClass> | import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} queryOrSpec - Preload source.
|
|
1987
2080
|
* @param {{force?: boolean}} [options] - Options.
|
|
1988
2081
|
* @returns {Promise<void>} - Resolves when preloading completes.
|
|
1989
2082
|
*/
|
|
@@ -2024,9 +2117,7 @@ export default class FrontendModelBase {
|
|
|
2024
2117
|
async _tryCohortPreload(relationshipName) {
|
|
2025
2118
|
if (!FrontendModelBase.getAutoload()) return false
|
|
2026
2119
|
|
|
2027
|
-
const ModelClass =
|
|
2028
|
-
* Narrows the runtime value to the documented type.
|
|
2029
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2120
|
+
const ModelClass = frontendModelClassFor(this)
|
|
2030
2121
|
const cohort = this._loadCohort
|
|
2031
2122
|
|
|
2032
2123
|
if (!cohort || cohort.length <= 1) return false
|
|
@@ -2101,9 +2192,7 @@ export default class FrontendModelBase {
|
|
|
2101
2192
|
* @returns {?} - Assigned relationship value.
|
|
2102
2193
|
*/
|
|
2103
2194
|
setRelationship(relationshipName, relationshipValue) {
|
|
2104
|
-
const ModelClass =
|
|
2105
|
-
* Narrows the runtime value to the documented type.
|
|
2106
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2195
|
+
const ModelClass = frontendModelClassFor(this)
|
|
2107
2196
|
const relationshipDefinition = ModelClass.relationshipDefinition(relationshipName)
|
|
2108
2197
|
|
|
2109
2198
|
if (!relationshipDefinition) {
|
|
@@ -2140,7 +2229,7 @@ export default class FrontendModelBase {
|
|
|
2140
2229
|
|
|
2141
2230
|
/**
|
|
2142
2231
|
* Runs primary key.
|
|
2143
|
-
* @this {
|
|
2232
|
+
* @this {FrontendModelClass}
|
|
2144
2233
|
* @returns {string} - Primary key name.
|
|
2145
2234
|
*/
|
|
2146
2235
|
static primaryKey() {
|
|
@@ -2152,9 +2241,7 @@ export default class FrontendModelBase {
|
|
|
2152
2241
|
* @returns {number | string} - Primary key value.
|
|
2153
2242
|
*/
|
|
2154
2243
|
primaryKeyValue() {
|
|
2155
|
-
const ModelClass =
|
|
2156
|
-
* Narrows the runtime value to the documented type.
|
|
2157
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2244
|
+
const ModelClass = frontendModelClassFor(this)
|
|
2158
2245
|
const value = this.readAttribute(ModelClass.primaryKey())
|
|
2159
2246
|
|
|
2160
2247
|
if (value === undefined || value === null) {
|
|
@@ -2296,6 +2383,19 @@ export default class FrontendModelBase {
|
|
|
2296
2383
|
* @returns {?} - Assigned value.
|
|
2297
2384
|
*/
|
|
2298
2385
|
setAttribute(attributeName, newValue) {
|
|
2386
|
+
const ModelClass = frontendModelClassFor(this)
|
|
2387
|
+
const nestedAttributesRelationshipName = ModelClass.nestedAttributesRelationshipName(attributeName)
|
|
2388
|
+
|
|
2389
|
+
if (nestedAttributesRelationshipName) {
|
|
2390
|
+
this._pendingNestedAttributes[nestedAttributesRelationshipName] = newValue
|
|
2391
|
+
return newValue
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
if (ModelClass.attachmentDefinition(attributeName)) {
|
|
2395
|
+
this.getAttachmentByName(attributeName).queueAttach(newValue)
|
|
2396
|
+
return newValue
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2299
2399
|
const previousValue = this._attributes[attributeName]
|
|
2300
2400
|
|
|
2301
2401
|
this._attributes[attributeName] = newValue
|
|
@@ -2330,9 +2430,7 @@ export default class FrontendModelBase {
|
|
|
2330
2430
|
_invalidateRelationshipsForAttribute(attributeName) {
|
|
2331
2431
|
if (!this._relationships || Object.keys(this._relationships).length === 0) return
|
|
2332
2432
|
|
|
2333
|
-
const ModelClass =
|
|
2334
|
-
* Narrows the runtime value to the documented type.
|
|
2335
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
2433
|
+
const ModelClass = frontendModelClassFor(this)
|
|
2336
2434
|
const definitions = typeof ModelClass.relationshipDefinitions === "function" ? ModelClass.relationshipDefinitions() : {}
|
|
2337
2435
|
|
|
2338
2436
|
for (const relationshipName of Object.keys(this._relationships)) {
|
|
@@ -2352,7 +2450,7 @@ export default class FrontendModelBase {
|
|
|
2352
2450
|
|
|
2353
2451
|
/**
|
|
2354
2452
|
* Runs resource path.
|
|
2355
|
-
* @this {
|
|
2453
|
+
* @this {FrontendModelClass}
|
|
2356
2454
|
* @returns {string} - Derived resource path.
|
|
2357
2455
|
*/
|
|
2358
2456
|
static resourcePath() {
|
|
@@ -2364,7 +2462,7 @@ export default class FrontendModelBase {
|
|
|
2364
2462
|
|
|
2365
2463
|
/**
|
|
2366
2464
|
* Runs command name.
|
|
2367
|
-
* @this {
|
|
2465
|
+
* @this {FrontendModelClass}
|
|
2368
2466
|
* @param {FrontendModelCommandType} commandType - Command type.
|
|
2369
2467
|
* @returns {string} - Resolved command name.
|
|
2370
2468
|
*/
|
|
@@ -2385,7 +2483,7 @@ export default class FrontendModelBase {
|
|
|
2385
2483
|
|
|
2386
2484
|
/**
|
|
2387
2485
|
* Runs normalize custom command payload arguments.
|
|
2388
|
-
* @this {
|
|
2486
|
+
* @this {FrontendModelClass}
|
|
2389
2487
|
* @param {Array<?>} args - Command arguments.
|
|
2390
2488
|
* @returns {Record<string, ?>} - Command payload.
|
|
2391
2489
|
*/
|
|
@@ -2420,7 +2518,7 @@ export default class FrontendModelBase {
|
|
|
2420
2518
|
* Returns the model name, preferring an explicit `static modelName` declaration
|
|
2421
2519
|
* over the JavaScript class `.name` property. This allows minified builds to
|
|
2422
2520
|
* preserve correct model names without relying on `keep_classnames`.
|
|
2423
|
-
* @this {
|
|
2521
|
+
* @this {FrontendModelClass}
|
|
2424
2522
|
* @returns {string} - The model name.
|
|
2425
2523
|
*/
|
|
2426
2524
|
static getModelName() {
|
|
@@ -2712,7 +2810,7 @@ export default class FrontendModelBase {
|
|
|
2712
2810
|
|
|
2713
2811
|
/**
|
|
2714
2812
|
* Runs attributes from response.
|
|
2715
|
-
* @this {
|
|
2813
|
+
* @this {FrontendModelClass}
|
|
2716
2814
|
* @param {object} response - Response payload.
|
|
2717
2815
|
* @returns {Record<string, ?>} - Attributes from payload.
|
|
2718
2816
|
*/
|
|
@@ -2724,7 +2822,7 @@ export default class FrontendModelBase {
|
|
|
2724
2822
|
|
|
2725
2823
|
/**
|
|
2726
2824
|
* Runs model data from response.
|
|
2727
|
-
* @this {
|
|
2825
|
+
* @this {FrontendModelClass}
|
|
2728
2826
|
* @param {object} response - Response payload.
|
|
2729
2827
|
* @returns {{abilities: Record<string, boolean>, attributes: Record<string, ?>, associationCounts: Record<string, number>, queryData: Record<string, ?>, preloadedRelationships: Record<string, ?>, selectedAttributes: Set<string>}} - Attributes, preloaded relationships, association counts, queryData, abilities, and the selected-attributes set.
|
|
2730
2828
|
*/
|
|
@@ -2794,7 +2892,7 @@ export default class FrontendModelBase {
|
|
|
2794
2892
|
|
|
2795
2893
|
/**
|
|
2796
2894
|
* Runs apply preloaded relationships.
|
|
2797
|
-
* @this {
|
|
2895
|
+
* @this {FrontendModelClass}
|
|
2798
2896
|
* @param {FrontendModelBase} model - Model instance.
|
|
2799
2897
|
* @param {Record<string, ?>} preloadedRelationships - Preloaded relationship payload.
|
|
2800
2898
|
* @returns {void}
|
|
@@ -2815,9 +2913,9 @@ export default class FrontendModelBase {
|
|
|
2815
2913
|
|
|
2816
2914
|
/**
|
|
2817
2915
|
* Runs instantiate relationship value.
|
|
2818
|
-
* @this {
|
|
2916
|
+
* @this {FrontendModelClass}
|
|
2819
2917
|
* @param {?} relationshipPayload - Relationship payload value.
|
|
2820
|
-
* @param {
|
|
2918
|
+
* @param {FrontendModelClass | null} targetModelClass - Target model class.
|
|
2821
2919
|
* @returns {?} - Instantiated relationship value.
|
|
2822
2920
|
*/
|
|
2823
2921
|
static instantiateRelationshipValue(relationshipPayload, targetModelClass) {
|
|
@@ -2830,7 +2928,7 @@ export default class FrontendModelBase {
|
|
|
2830
2928
|
|
|
2831
2929
|
/**
|
|
2832
2930
|
* Runs instantiate from response.
|
|
2833
|
-
* @template {
|
|
2931
|
+
* @template {FrontendModelClass} T
|
|
2834
2932
|
* @this {T}
|
|
2835
2933
|
* @param {Record<string, ?> | InstanceType<T>} response - Response payload, or an already-hydrated instance of this class.
|
|
2836
2934
|
* @returns {InstanceType<T>} - New model instance, or the same instance unchanged if it was already hydrated.
|
|
@@ -2883,7 +2981,7 @@ export default class FrontendModelBase {
|
|
|
2883
2981
|
|
|
2884
2982
|
/**
|
|
2885
2983
|
* Runs find.
|
|
2886
|
-
* @template {
|
|
2984
|
+
* @template {FrontendModelClass} T
|
|
2887
2985
|
* @this {T}
|
|
2888
2986
|
* @param {number | string} id - Record identifier.
|
|
2889
2987
|
* @returns {Promise<InstanceType<T>>} - Resolved model.
|
|
@@ -2894,7 +2992,7 @@ export default class FrontendModelBase {
|
|
|
2894
2992
|
|
|
2895
2993
|
/**
|
|
2896
2994
|
* Runs find by.
|
|
2897
|
-
* @template {
|
|
2995
|
+
* @template {FrontendModelClass} T
|
|
2898
2996
|
* @this {T}
|
|
2899
2997
|
* @param {Record<string, ?>} conditions - Attribute match conditions.
|
|
2900
2998
|
* @returns {Promise<InstanceType<T> | null>} - Found model or null.
|
|
@@ -2905,7 +3003,7 @@ export default class FrontendModelBase {
|
|
|
2905
3003
|
|
|
2906
3004
|
/**
|
|
2907
3005
|
* Runs find by or fail.
|
|
2908
|
-
* @template {
|
|
3006
|
+
* @template {FrontendModelClass} T
|
|
2909
3007
|
* @this {T}
|
|
2910
3008
|
* @param {Record<string, ?>} conditions - Attribute match conditions.
|
|
2911
3009
|
* @returns {Promise<InstanceType<T>>} - Found model.
|
|
@@ -2916,7 +3014,7 @@ export default class FrontendModelBase {
|
|
|
2916
3014
|
|
|
2917
3015
|
/**
|
|
2918
3016
|
* Runs to array.
|
|
2919
|
-
* @template {
|
|
3017
|
+
* @template {FrontendModelClass} T
|
|
2920
3018
|
* @this {T}
|
|
2921
3019
|
* @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
|
|
2922
3020
|
*/
|
|
@@ -2926,7 +3024,7 @@ export default class FrontendModelBase {
|
|
|
2926
3024
|
|
|
2927
3025
|
/**
|
|
2928
3026
|
* Runs load.
|
|
2929
|
-
* @template {
|
|
3027
|
+
* @template {FrontendModelClass} T
|
|
2930
3028
|
* @this {T}
|
|
2931
3029
|
* @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
|
|
2932
3030
|
*/
|
|
@@ -2936,7 +3034,7 @@ export default class FrontendModelBase {
|
|
|
2936
3034
|
|
|
2937
3035
|
/**
|
|
2938
3036
|
* Runs all.
|
|
2939
|
-
* @template {
|
|
3037
|
+
* @template {FrontendModelClass} T
|
|
2940
3038
|
* @this {T}
|
|
2941
3039
|
* @returns {FrontendModelQuery<T>} - Query builder.
|
|
2942
3040
|
*/
|
|
@@ -2946,7 +3044,7 @@ export default class FrontendModelBase {
|
|
|
2946
3044
|
|
|
2947
3045
|
/**
|
|
2948
3046
|
* Runs where.
|
|
2949
|
-
* @template {
|
|
3047
|
+
* @template {FrontendModelClass} T
|
|
2950
3048
|
* @this {T}
|
|
2951
3049
|
* @param {Record<string, ?>} conditions - Root-model where conditions.
|
|
2952
3050
|
* @returns {import("./query.js").default<T>} - Query with where conditions.
|
|
@@ -2957,7 +3055,7 @@ export default class FrontendModelBase {
|
|
|
2957
3055
|
|
|
2958
3056
|
/**
|
|
2959
3057
|
* Runs joins.
|
|
2960
|
-
* @template {
|
|
3058
|
+
* @template {FrontendModelClass} T
|
|
2961
3059
|
* @this {T}
|
|
2962
3060
|
* @param {Record<string, ?> | Array<Record<string, ?>>} joins - Relationship descriptor joins.
|
|
2963
3061
|
* @returns {import("./query.js").default<T>} - Query with joins.
|
|
@@ -2968,7 +3066,7 @@ export default class FrontendModelBase {
|
|
|
2968
3066
|
|
|
2969
3067
|
/**
|
|
2970
3068
|
* Runs limit.
|
|
2971
|
-
* @template {
|
|
3069
|
+
* @template {FrontendModelClass} T
|
|
2972
3070
|
* @this {T}
|
|
2973
3071
|
* @param {number} value - Maximum number of records.
|
|
2974
3072
|
* @returns {import("./query.js").default<T>} - Query with limit.
|
|
@@ -2979,7 +3077,7 @@ export default class FrontendModelBase {
|
|
|
2979
3077
|
|
|
2980
3078
|
/**
|
|
2981
3079
|
* Runs offset.
|
|
2982
|
-
* @template {
|
|
3080
|
+
* @template {FrontendModelClass} T
|
|
2983
3081
|
* @this {T}
|
|
2984
3082
|
* @param {number} value - Number of records to skip.
|
|
2985
3083
|
* @returns {import("./query.js").default<T>} - Query with offset.
|
|
@@ -2990,7 +3088,7 @@ export default class FrontendModelBase {
|
|
|
2990
3088
|
|
|
2991
3089
|
/**
|
|
2992
3090
|
* Runs page.
|
|
2993
|
-
* @template {
|
|
3091
|
+
* @template {FrontendModelClass} T
|
|
2994
3092
|
* @this {T}
|
|
2995
3093
|
* @param {number} pageNumber - 1-based page number.
|
|
2996
3094
|
* @returns {import("./query.js").default<T>} - Query with page applied.
|
|
@@ -3001,7 +3099,7 @@ export default class FrontendModelBase {
|
|
|
3001
3099
|
|
|
3002
3100
|
/**
|
|
3003
3101
|
* Runs per page.
|
|
3004
|
-
* @template {
|
|
3102
|
+
* @template {FrontendModelClass} T
|
|
3005
3103
|
* @this {T}
|
|
3006
3104
|
* @param {number} value - Number of records per page.
|
|
3007
3105
|
* @returns {import("./query.js").default<T>} - Query with page size.
|
|
@@ -3012,7 +3110,7 @@ export default class FrontendModelBase {
|
|
|
3012
3110
|
|
|
3013
3111
|
/**
|
|
3014
3112
|
* Runs count.
|
|
3015
|
-
* @template {
|
|
3113
|
+
* @template {FrontendModelClass} T
|
|
3016
3114
|
* @this {T}
|
|
3017
3115
|
* @returns {Promise<number>} - Number of loaded model instances.
|
|
3018
3116
|
*/
|
|
@@ -3026,8 +3124,8 @@ export default class FrontendModelBase {
|
|
|
3026
3124
|
* accepted, future `create` events for this model are delivered
|
|
3027
3125
|
* without re-checking per-record visibility. Query options can still
|
|
3028
3126
|
* narrow which events reach this callback.
|
|
3029
|
-
* @this {
|
|
3030
|
-
* @param {(payload: {id: string, model:
|
|
3127
|
+
* @this {FrontendModelClass}
|
|
3128
|
+
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3031
3129
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3032
3130
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3033
3131
|
*/
|
|
@@ -3046,8 +3144,8 @@ export default class FrontendModelBase {
|
|
|
3046
3144
|
|
|
3047
3145
|
/**
|
|
3048
3146
|
* Class-level hook fired when any record of this model is updated.
|
|
3049
|
-
* @this {
|
|
3050
|
-
* @param {(payload: {id: string, model:
|
|
3147
|
+
* @this {FrontendModelClass}
|
|
3148
|
+
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3051
3149
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3052
3150
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3053
3151
|
*/
|
|
@@ -3066,7 +3164,7 @@ export default class FrontendModelBase {
|
|
|
3066
3164
|
|
|
3067
3165
|
/**
|
|
3068
3166
|
* Class-level hook fired when any record of this model is destroyed.
|
|
3069
|
-
* @this {
|
|
3167
|
+
* @this {FrontendModelClass}
|
|
3070
3168
|
* @param {(payload: {id: string}) => void} callback - Event callback.
|
|
3071
3169
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Accepted for API symmetry; destroy events carry ids only.
|
|
3072
3170
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
@@ -3091,7 +3189,7 @@ export default class FrontendModelBase {
|
|
|
3091
3189
|
* instance's attributes are auto-merged with the broadcast payload
|
|
3092
3190
|
* before the callback runs, so callers can read fresh values via
|
|
3093
3191
|
* `this.someAttr()` without re-fetching.
|
|
3094
|
-
* @param {(payload: {id: string, model:
|
|
3192
|
+
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3095
3193
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3096
3194
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3097
3195
|
*/
|
|
@@ -3099,9 +3197,7 @@ export default class FrontendModelBase {
|
|
|
3099
3197
|
const self = /**
|
|
3100
3198
|
* Narrows the runtime value to the documented type.
|
|
3101
3199
|
@type {?} */ (this)
|
|
3102
|
-
const ModelClass =
|
|
3103
|
-
* Narrows the runtime value to the documented type.
|
|
3104
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3200
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3105
3201
|
const sub = ensureFrontendModelEventSubscription(ModelClass)
|
|
3106
3202
|
const id = String(self.id())
|
|
3107
3203
|
const entry = {callback, ...frontendModelEventOptionsPayload(ModelClass, options)}
|
|
@@ -3133,9 +3229,7 @@ export default class FrontendModelBase {
|
|
|
3133
3229
|
const self = /**
|
|
3134
3230
|
* Narrows the runtime value to the documented type.
|
|
3135
3231
|
@type {?} */ (this)
|
|
3136
|
-
const ModelClass =
|
|
3137
|
-
* Narrows the runtime value to the documented type.
|
|
3138
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3232
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3139
3233
|
|
|
3140
3234
|
assertNoDestroyEventFilter(ModelClass, options)
|
|
3141
3235
|
|
|
@@ -3162,7 +3256,7 @@ export default class FrontendModelBase {
|
|
|
3162
3256
|
|
|
3163
3257
|
/**
|
|
3164
3258
|
* Runs pluck.
|
|
3165
|
-
* @template {
|
|
3259
|
+
* @template {FrontendModelClass} T
|
|
3166
3260
|
* @this {T}
|
|
3167
3261
|
* @param {...(string | string[] | Record<string, ?> | Array<Record<string, ?>>)} columns - Pluck definition(s).
|
|
3168
3262
|
* @returns {Promise<Array<?>>} - Plucked values.
|
|
@@ -3173,7 +3267,7 @@ export default class FrontendModelBase {
|
|
|
3173
3267
|
|
|
3174
3268
|
/**
|
|
3175
3269
|
* Runs search.
|
|
3176
|
-
* @template {
|
|
3270
|
+
* @template {FrontendModelClass} T
|
|
3177
3271
|
* @this {T}
|
|
3178
3272
|
* @param {string[]} path - Relationship path.
|
|
3179
3273
|
* @param {string} column - Column or attribute name.
|
|
@@ -3187,7 +3281,7 @@ export default class FrontendModelBase {
|
|
|
3187
3281
|
|
|
3188
3282
|
/**
|
|
3189
3283
|
* Runs ransack.
|
|
3190
|
-
* @template {
|
|
3284
|
+
* @template {FrontendModelClass} T
|
|
3191
3285
|
* @this {T}
|
|
3192
3286
|
* @param {Record<string, ?>} params - Ransack-style params hash.
|
|
3193
3287
|
* @returns {FrontendModelQuery<T>} - Query builder with Ransack filters applied.
|
|
@@ -3198,7 +3292,7 @@ export default class FrontendModelBase {
|
|
|
3198
3292
|
|
|
3199
3293
|
/**
|
|
3200
3294
|
* Runs sort.
|
|
3201
|
-
* @template {
|
|
3295
|
+
* @template {FrontendModelClass} T
|
|
3202
3296
|
* @this {T}
|
|
3203
3297
|
* @param {string | string[] | string[][] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} sort - Sort definition(s).
|
|
3204
3298
|
* @returns {FrontendModelQuery<T>} - Query builder with sort definitions.
|
|
@@ -3209,7 +3303,7 @@ export default class FrontendModelBase {
|
|
|
3209
3303
|
|
|
3210
3304
|
/**
|
|
3211
3305
|
* Runs order.
|
|
3212
|
-
* @template {
|
|
3306
|
+
* @template {FrontendModelClass} T
|
|
3213
3307
|
* @this {T}
|
|
3214
3308
|
* @param {string | string[] | string[][] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} sort - Sort definition(s).
|
|
3215
3309
|
* @returns {FrontendModelQuery<T>} - Query builder with sort definitions.
|
|
@@ -3220,7 +3314,7 @@ export default class FrontendModelBase {
|
|
|
3220
3314
|
|
|
3221
3315
|
/**
|
|
3222
3316
|
* Runs group.
|
|
3223
|
-
* @template {
|
|
3317
|
+
* @template {FrontendModelClass} T
|
|
3224
3318
|
* @this {T}
|
|
3225
3319
|
* @param {string | string[] | Record<string, ?> | Array<Record<string, ?>>} group - Group definition(s).
|
|
3226
3320
|
* @returns {FrontendModelQuery<T>} - Query builder with group definitions.
|
|
@@ -3231,7 +3325,7 @@ export default class FrontendModelBase {
|
|
|
3231
3325
|
|
|
3232
3326
|
/**
|
|
3233
3327
|
* Runs distinct.
|
|
3234
|
-
* @template {
|
|
3328
|
+
* @template {FrontendModelClass} T
|
|
3235
3329
|
* @this {T}
|
|
3236
3330
|
* @param {boolean} [value] - Whether to request distinct rows.
|
|
3237
3331
|
* @returns {FrontendModelQuery<T>} - Query builder with distinct flag.
|
|
@@ -3242,7 +3336,7 @@ export default class FrontendModelBase {
|
|
|
3242
3336
|
|
|
3243
3337
|
/**
|
|
3244
3338
|
* Runs query.
|
|
3245
|
-
* @template {
|
|
3339
|
+
* @template {FrontendModelClass} T
|
|
3246
3340
|
* @this {T}
|
|
3247
3341
|
* @returns {FrontendModelQuery<T>} - Query builder.
|
|
3248
3342
|
*/
|
|
@@ -3252,7 +3346,7 @@ export default class FrontendModelBase {
|
|
|
3252
3346
|
|
|
3253
3347
|
/**
|
|
3254
3348
|
* Runs preload.
|
|
3255
|
-
* @template {
|
|
3349
|
+
* @template {FrontendModelClass} T
|
|
3256
3350
|
* @this {T}
|
|
3257
3351
|
* @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} preload - Preload graph.
|
|
3258
3352
|
* @returns {FrontendModelQuery<T>} - Query with preload.
|
|
@@ -3263,7 +3357,7 @@ export default class FrontendModelBase {
|
|
|
3263
3357
|
|
|
3264
3358
|
/**
|
|
3265
3359
|
* Runs select.
|
|
3266
|
-
* @template {
|
|
3360
|
+
* @template {FrontendModelClass} T
|
|
3267
3361
|
* @this {T}
|
|
3268
3362
|
* @param {Record<string, string[] | string> | string | string[]} select - Model-aware attribute select map or root-model shorthand.
|
|
3269
3363
|
* @returns {FrontendModelQuery<T>} - Query with selected attributes.
|
|
@@ -3274,7 +3368,7 @@ export default class FrontendModelBase {
|
|
|
3274
3368
|
|
|
3275
3369
|
/**
|
|
3276
3370
|
* Runs selects extra.
|
|
3277
|
-
* @template {
|
|
3371
|
+
* @template {FrontendModelClass} T
|
|
3278
3372
|
* @this {T}
|
|
3279
3373
|
* @param {Record<string, string[] | string> | string | string[]} select - Extra attributes to load in addition to the defaults, keyed by model name or root-model shorthand.
|
|
3280
3374
|
* @returns {FrontendModelQuery<T>} - Query with extra selected attributes.
|
|
@@ -3285,7 +3379,7 @@ export default class FrontendModelBase {
|
|
|
3285
3379
|
|
|
3286
3380
|
/**
|
|
3287
3381
|
* Runs first.
|
|
3288
|
-
* @template {
|
|
3382
|
+
* @template {FrontendModelClass} T
|
|
3289
3383
|
* @this {T}
|
|
3290
3384
|
* @returns {Promise<InstanceType<T> | null>} - First model or null.
|
|
3291
3385
|
*/
|
|
@@ -3295,7 +3389,7 @@ export default class FrontendModelBase {
|
|
|
3295
3389
|
|
|
3296
3390
|
/**
|
|
3297
3391
|
* Runs last.
|
|
3298
|
-
* @template {
|
|
3392
|
+
* @template {FrontendModelClass} T
|
|
3299
3393
|
* @this {T}
|
|
3300
3394
|
* @returns {Promise<InstanceType<T> | null>} - Last model or null.
|
|
3301
3395
|
*/
|
|
@@ -3305,7 +3399,7 @@ export default class FrontendModelBase {
|
|
|
3305
3399
|
|
|
3306
3400
|
/**
|
|
3307
3401
|
* Runs find or initialize by.
|
|
3308
|
-
* @template {
|
|
3402
|
+
* @template {FrontendModelClass} T
|
|
3309
3403
|
* @this {T}
|
|
3310
3404
|
* @param {Record<string, ?>} conditions - Attribute match conditions.
|
|
3311
3405
|
* @returns {Promise<InstanceType<T>>} - Existing or initialized model.
|
|
@@ -3316,7 +3410,7 @@ export default class FrontendModelBase {
|
|
|
3316
3410
|
|
|
3317
3411
|
/**
|
|
3318
3412
|
* Runs find or create by.
|
|
3319
|
-
* @template {
|
|
3413
|
+
* @template {FrontendModelClass} T
|
|
3320
3414
|
* @this {T}
|
|
3321
3415
|
* @param {Record<string, ?>} conditions - Attribute match conditions.
|
|
3322
3416
|
* @param {(model: InstanceType<T>) => Promise<void> | void} [callback] - Optional callback before save when created.
|
|
@@ -3328,7 +3422,7 @@ export default class FrontendModelBase {
|
|
|
3328
3422
|
|
|
3329
3423
|
/**
|
|
3330
3424
|
* Runs create.
|
|
3331
|
-
* @template {
|
|
3425
|
+
* @template {FrontendModelClass} T
|
|
3332
3426
|
* @this {T}
|
|
3333
3427
|
* @param {Record<string, ?>} [attributes] - Initial attributes.
|
|
3334
3428
|
* @returns {Promise<InstanceType<T>>} - Persisted model.
|
|
@@ -3345,7 +3439,7 @@ export default class FrontendModelBase {
|
|
|
3345
3439
|
|
|
3346
3440
|
/**
|
|
3347
3441
|
* Runs assert find by conditions.
|
|
3348
|
-
* @this {
|
|
3442
|
+
* @this {FrontendModelClass}
|
|
3349
3443
|
* @param {Record<string, ?>} conditions - findBy conditions.
|
|
3350
3444
|
* @returns {void}
|
|
3351
3445
|
*/
|
|
@@ -3359,7 +3453,7 @@ export default class FrontendModelBase {
|
|
|
3359
3453
|
|
|
3360
3454
|
/**
|
|
3361
3455
|
* Runs matches find by conditions.
|
|
3362
|
-
* @this {
|
|
3456
|
+
* @this {FrontendModelClass}
|
|
3363
3457
|
* @param {FrontendModelBase} model - Candidate model.
|
|
3364
3458
|
* @param {Record<string, ?>} conditions - Match conditions.
|
|
3365
3459
|
* @returns {boolean} - Whether the model matches all conditions.
|
|
@@ -3389,7 +3483,7 @@ export default class FrontendModelBase {
|
|
|
3389
3483
|
|
|
3390
3484
|
/**
|
|
3391
3485
|
* Runs find by condition value matches.
|
|
3392
|
-
* @this {
|
|
3486
|
+
* @this {FrontendModelClass}
|
|
3393
3487
|
* @param {?} actualValue - Actual model value.
|
|
3394
3488
|
* @param {?} expectedValue - Expected find condition value.
|
|
3395
3489
|
* @returns {boolean} - Whether values match.
|
|
@@ -3457,7 +3551,7 @@ export default class FrontendModelBase {
|
|
|
3457
3551
|
|
|
3458
3552
|
/**
|
|
3459
3553
|
* Runs find by primitive values match.
|
|
3460
|
-
* @this {
|
|
3554
|
+
* @this {FrontendModelClass}
|
|
3461
3555
|
* @param {?} actualValue - Actual model value.
|
|
3462
3556
|
* @param {?} expectedValue - Expected find condition value.
|
|
3463
3557
|
* @returns {boolean} - Whether primitive values match after safe coercion.
|
|
@@ -3488,7 +3582,7 @@ export default class FrontendModelBase {
|
|
|
3488
3582
|
|
|
3489
3583
|
/**
|
|
3490
3584
|
* Runs find by numeric string matches number.
|
|
3491
|
-
* @this {
|
|
3585
|
+
* @this {FrontendModelClass}
|
|
3492
3586
|
* @param {string} numericString - Numeric string value.
|
|
3493
3587
|
* @param {number} expectedNumber - Number value.
|
|
3494
3588
|
* @returns {boolean} - Whether values represent the same number.
|
|
@@ -3511,50 +3605,9 @@ export default class FrontendModelBase {
|
|
|
3511
3605
|
* @returns {Promise<this>} - Updated model.
|
|
3512
3606
|
*/
|
|
3513
3607
|
async update(newAttributes = {}) {
|
|
3514
|
-
|
|
3515
|
-
* Narrows the runtime value to the documented type.
|
|
3516
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3517
|
-
const attachmentDefinitions = ModelClass.attachmentDefinitions()
|
|
3518
|
-
/**
|
|
3519
|
-
* Regular attributes.
|
|
3520
|
-
@type {Record<string, ?>} */
|
|
3521
|
-
const regularAttributes = {}
|
|
3522
|
-
/**
|
|
3523
|
-
* Pending attachments.
|
|
3524
|
-
@type {Array<{attachmentName: string, value: ?}>} */
|
|
3525
|
-
const pendingAttachments = []
|
|
3526
|
-
|
|
3527
|
-
for (const [attributeName, attributeValue] of Object.entries(newAttributes)) {
|
|
3528
|
-
if (attachmentDefinitions[attributeName]) {
|
|
3529
|
-
if (attributeValue !== undefined && attributeValue !== null) {
|
|
3530
|
-
pendingAttachments.push({attachmentName: attributeName, value: attributeValue})
|
|
3531
|
-
}
|
|
3532
|
-
} else {
|
|
3533
|
-
regularAttributes[attributeName] = attributeValue
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
|
-
|
|
3537
|
-
if (Object.keys(regularAttributes).length > 0) {
|
|
3538
|
-
this.assignAttributes(regularAttributes)
|
|
3539
|
-
const changedAttributes = Object.fromEntries(
|
|
3540
|
-
Object.entries(this.changes()).map(([attributeName, [, currentValue]]) => [attributeName, currentValue])
|
|
3541
|
-
)
|
|
3542
|
-
|
|
3543
|
-
const response = await ModelClass.executeCommand("update", {
|
|
3544
|
-
attributes: changedAttributes,
|
|
3545
|
-
id: this.primaryKeyValue()
|
|
3546
|
-
})
|
|
3547
|
-
|
|
3548
|
-
this.assignAttributes(ModelClass.attributesFromResponse(response))
|
|
3549
|
-
this.setIsNewRecord(false)
|
|
3550
|
-
this._persistedAttributes = cloneFrontendModelAttributes(this.attributes())
|
|
3551
|
-
}
|
|
3608
|
+
this.assignAttributes(newAttributes)
|
|
3552
3609
|
|
|
3553
|
-
|
|
3554
|
-
await this.getAttachmentByName(pendingAttachment.attachmentName).attach(pendingAttachment.value)
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
return this
|
|
3610
|
+
return await this.save()
|
|
3558
3611
|
}
|
|
3559
3612
|
|
|
3560
3613
|
/**
|
|
@@ -3563,9 +3616,7 @@ export default class FrontendModelBase {
|
|
|
3563
3616
|
* @returns {Promise<void>} - Resolves when attached.
|
|
3564
3617
|
*/
|
|
3565
3618
|
async attach(attachmentInput) {
|
|
3566
|
-
const ModelClass =
|
|
3567
|
-
* Narrows the runtime value to the documented type.
|
|
3568
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3619
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3569
3620
|
const attachmentDefinitions = ModelClass.attachmentDefinitions()
|
|
3570
3621
|
const attachmentNames = Object.keys(attachmentDefinitions)
|
|
3571
3622
|
let attachmentName = attachmentNames[0]
|
|
@@ -3597,9 +3648,7 @@ export default class FrontendModelBase {
|
|
|
3597
3648
|
* @returns {Promise<this>} - Saved model.
|
|
3598
3649
|
*/
|
|
3599
3650
|
async save() {
|
|
3600
|
-
const ModelClass =
|
|
3601
|
-
* Narrows the runtime value to the documented type.
|
|
3602
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3651
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3603
3652
|
const isNew = this.isNewRecord()
|
|
3604
3653
|
const commandType = isNew ? "create" : "update"
|
|
3605
3654
|
/**
|
|
@@ -3613,17 +3662,25 @@ export default class FrontendModelBase {
|
|
|
3613
3662
|
payload.id = this.primaryKeyValue()
|
|
3614
3663
|
}
|
|
3615
3664
|
|
|
3616
|
-
const nestedAttributes = this._buildNestedAttributesPayload()
|
|
3665
|
+
const nestedAttributes = await this._buildNestedAttributesPayload()
|
|
3617
3666
|
|
|
3618
3667
|
if (nestedAttributes && Object.keys(nestedAttributes).length > 0) {
|
|
3619
3668
|
payload.nestedAttributes = nestedAttributes
|
|
3620
3669
|
}
|
|
3621
3670
|
|
|
3671
|
+
const attachments = await this._buildAttachmentsPayload()
|
|
3672
|
+
|
|
3673
|
+
if (Object.keys(attachments).length > 0) {
|
|
3674
|
+
payload.attachments = attachments
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3622
3677
|
const response = await ModelClass.executeCommand(commandType, payload)
|
|
3623
3678
|
|
|
3624
3679
|
this.assignAttributes(ModelClass.attributesFromResponse(response))
|
|
3625
3680
|
this.setIsNewRecord(false)
|
|
3626
3681
|
this._persistedAttributes = cloneFrontendModelAttributes(this.attributes())
|
|
3682
|
+
this._pendingNestedAttributes = {}
|
|
3683
|
+
this._clearPendingAttachments()
|
|
3627
3684
|
|
|
3628
3685
|
this._reconcileNestedAttributesFromResponse(response)
|
|
3629
3686
|
|
|
@@ -3668,15 +3725,39 @@ export default class FrontendModelBase {
|
|
|
3668
3725
|
* @returns {Promise<void>} - Resolves when destroyed on backend.
|
|
3669
3726
|
*/
|
|
3670
3727
|
async destroy() {
|
|
3671
|
-
const ModelClass =
|
|
3672
|
-
* Narrows the runtime value to the documented type.
|
|
3673
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3728
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3674
3729
|
|
|
3675
3730
|
await ModelClass.executeCommand("destroy", {
|
|
3676
3731
|
id: this.primaryKeyValue()
|
|
3677
3732
|
})
|
|
3678
3733
|
}
|
|
3679
3734
|
|
|
3735
|
+
/**
|
|
3736
|
+
* Builds the attachment payload queued on this model for the next save.
|
|
3737
|
+
* @returns {Promise<Record<string, ?>>} Attachment payload keyed by attachment name.
|
|
3738
|
+
*/
|
|
3739
|
+
async _buildAttachmentsPayload() {
|
|
3740
|
+
/** @type {Record<string, ?>} */
|
|
3741
|
+
const payload = {}
|
|
3742
|
+
|
|
3743
|
+
for (const attachmentName of Object.keys(this._attachments)) {
|
|
3744
|
+
const attachmentPayload = await this._attachments[attachmentName].pendingAttachmentsPayload()
|
|
3745
|
+
|
|
3746
|
+
if (attachmentPayload !== undefined) {
|
|
3747
|
+
payload[attachmentName] = attachmentPayload
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
return payload
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
/** Clears queued attachment inputs after a successful save. */
|
|
3755
|
+
_clearPendingAttachments() {
|
|
3756
|
+
for (const attachmentName of Object.keys(this._attachments)) {
|
|
3757
|
+
this._attachments[attachmentName].clearPendingAttachments()
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3680
3761
|
/**
|
|
3681
3762
|
* Walks relationships declared in this resource's `nestedAttributes` config
|
|
3682
3763
|
* and builds the per-relationship payload of dirty children for a parent save.
|
|
@@ -3689,12 +3770,10 @@ export default class FrontendModelBase {
|
|
|
3689
3770
|
*
|
|
3690
3771
|
* Loaded but untouched records are omitted so nested save preserves Rails-style
|
|
3691
3772
|
* "children not referenced in payload are left alone" semantics.
|
|
3692
|
-
* @returns {Record<string, Array<Record<string,
|
|
3773
|
+
* @returns {Promise<Record<string, Array<Record<string, ?>>>>} - Per-relationship list of nested-attribute entries.
|
|
3693
3774
|
*/
|
|
3694
|
-
_buildNestedAttributesPayload() {
|
|
3695
|
-
const ModelClass =
|
|
3696
|
-
* Narrows the runtime value to the documented type.
|
|
3697
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3775
|
+
async _buildNestedAttributesPayload() {
|
|
3776
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3698
3777
|
const resourceConfig = typeof ModelClass.resourceConfig === "function" ? ModelClass.resourceConfig() : null
|
|
3699
3778
|
const nestedAttributesConfig = resourceConfig?.nestedAttributes
|
|
3700
3779
|
|
|
@@ -3706,20 +3785,34 @@ export default class FrontendModelBase {
|
|
|
3706
3785
|
const payload = {}
|
|
3707
3786
|
|
|
3708
3787
|
for (const relationshipName of Object.keys(nestedAttributesConfig)) {
|
|
3788
|
+
/** @type {Array<Record<string, ?>>} */
|
|
3789
|
+
const entries = []
|
|
3709
3790
|
const relationship = this._relationships[relationshipName]
|
|
3710
3791
|
|
|
3711
|
-
if (
|
|
3712
|
-
|
|
3792
|
+
if (relationship instanceof FrontendModelHasManyRelationship && Array.isArray(relationship._loadedValue)) {
|
|
3793
|
+
for (const child of relationship._loadedValue) {
|
|
3794
|
+
const childEntry = await child._nestedAttributesEntryForParentSave()
|
|
3713
3795
|
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3796
|
+
if (childEntry) entries.push(childEntry)
|
|
3797
|
+
}
|
|
3798
|
+
} else if (relationship instanceof FrontendModelSingularRelationship && relationship.getPreloaded()) {
|
|
3799
|
+
const child = relationship.loaded()
|
|
3718
3800
|
|
|
3719
|
-
|
|
3720
|
-
|
|
3801
|
+
if (child instanceof FrontendModelBase) {
|
|
3802
|
+
const childEntry = await child._nestedAttributesEntryForParentSave()
|
|
3721
3803
|
|
|
3722
|
-
|
|
3804
|
+
if (childEntry) entries.push(childEntry)
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
if (Object.prototype.hasOwnProperty.call(this._pendingNestedAttributes, relationshipName)) {
|
|
3809
|
+
entries.push(
|
|
3810
|
+
...await this._nestedAttributesPayloadForSubmittedValue(
|
|
3811
|
+
ModelClass,
|
|
3812
|
+
relationshipName,
|
|
3813
|
+
this._pendingNestedAttributes[relationshipName]
|
|
3814
|
+
)
|
|
3815
|
+
)
|
|
3723
3816
|
}
|
|
3724
3817
|
|
|
3725
3818
|
if (entries.length > 0) {
|
|
@@ -3734,41 +3827,164 @@ export default class FrontendModelBase {
|
|
|
3734
3827
|
* Builds the payload entry for this child when walked by a parent's
|
|
3735
3828
|
* `_buildNestedAttributesPayload`. Returns `null` when the child has no
|
|
3736
3829
|
* dirty state and no dirty descendants, so the parent can omit it.
|
|
3737
|
-
* @returns {Record<string, ?> | null} - Nested-attribute entry or null if clean.
|
|
3830
|
+
* @returns {Promise<Record<string, ?> | null>} - Nested-attribute entry or null if clean.
|
|
3738
3831
|
*/
|
|
3739
|
-
_nestedAttributesEntryForParentSave() {
|
|
3832
|
+
async _nestedAttributesEntryForParentSave() {
|
|
3740
3833
|
if (this.markedForDestruction()) {
|
|
3741
3834
|
if (this.isNewRecord()) return null
|
|
3742
3835
|
return {id: this.primaryKeyValue(), _destroy: true}
|
|
3743
3836
|
}
|
|
3744
3837
|
|
|
3745
|
-
const nestedAttributes = this._buildNestedAttributesPayload()
|
|
3838
|
+
const nestedAttributes = await this._buildNestedAttributesPayload()
|
|
3746
3839
|
const hasNestedDirty = Object.keys(nestedAttributes).length > 0
|
|
3840
|
+
const attachments = await this._buildAttachmentsPayload()
|
|
3841
|
+
const hasAttachments = Object.keys(attachments).length > 0
|
|
3747
3842
|
|
|
3748
3843
|
if (this.isNewRecord()) {
|
|
3749
3844
|
/**
|
|
3750
3845
|
* Entry.
|
|
3751
3846
|
@type {Record<string, ?>} */
|
|
3752
|
-
const entry = {
|
|
3847
|
+
const entry = {}
|
|
3848
|
+
const attributes = this._changedAttributesForSave()
|
|
3753
3849
|
|
|
3850
|
+
if (Object.keys(attributes).length > 0) entry.attributes = attributes
|
|
3851
|
+
if (hasAttachments) entry.attachments = attachments
|
|
3754
3852
|
if (hasNestedDirty) entry.nestedAttributes = nestedAttributes
|
|
3755
3853
|
|
|
3756
3854
|
return entry
|
|
3757
3855
|
}
|
|
3758
3856
|
|
|
3759
|
-
if (!this.isChanged() && !hasNestedDirty) return null
|
|
3857
|
+
if (!this.isChanged() && !hasNestedDirty && !hasAttachments) return null
|
|
3760
3858
|
|
|
3761
3859
|
/**
|
|
3762
3860
|
* Entry.
|
|
3763
3861
|
@type {Record<string, ?>} */
|
|
3764
3862
|
const entry = {id: this.primaryKeyValue()}
|
|
3765
3863
|
|
|
3766
|
-
if (this.isChanged()) entry.attributes = this.
|
|
3864
|
+
if (this.isChanged()) entry.attributes = this._changedAttributesForSave()
|
|
3865
|
+
if (hasAttachments) entry.attachments = attachments
|
|
3767
3866
|
if (hasNestedDirty) entry.nestedAttributes = nestedAttributes
|
|
3768
3867
|
|
|
3769
3868
|
return entry
|
|
3770
3869
|
}
|
|
3771
3870
|
|
|
3871
|
+
/**
|
|
3872
|
+
* Builds nested entries from a Rails-style submitted `*Attributes` value.
|
|
3873
|
+
* @param {FrontendModelClass} ModelClass - Parent model class.
|
|
3874
|
+
* @param {string} relationshipName - Nested relationship name.
|
|
3875
|
+
* @param {?} value - Submitted nested attributes value.
|
|
3876
|
+
* @returns {Promise<Array<Record<string, ?>>>} Nested entries for the transport payload.
|
|
3877
|
+
*/
|
|
3878
|
+
async _nestedAttributesPayloadForSubmittedValue(ModelClass, relationshipName, value) {
|
|
3879
|
+
const relationshipDefinition = ModelClass.relationshipDefinition(relationshipName)
|
|
3880
|
+
const TargetModelClass = ModelClass.relationshipModelClass(relationshipName)
|
|
3881
|
+
|
|
3882
|
+
if (!relationshipDefinition) {
|
|
3883
|
+
throw new Error(`Unknown nested relationship: ${ModelClass.name}#${relationshipName}`)
|
|
3884
|
+
}
|
|
3885
|
+
if (!TargetModelClass) {
|
|
3886
|
+
throw new Error(`No target model class configured for ${ModelClass.name}#${relationshipName}`)
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
if (relationshipTypeIsCollection(relationshipDefinition.type)) {
|
|
3890
|
+
if (!Array.isArray(value)) {
|
|
3891
|
+
throw new Error(`${ModelClass.name}#${relationshipName}Attributes must be an array`)
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
return await Promise.all(
|
|
3895
|
+
value.map(async (entry) => await this._nestedAttributesEntryPayloadForSubmittedValue(TargetModelClass, entry))
|
|
3896
|
+
)
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
if (value == null) return []
|
|
3900
|
+
if (Array.isArray(value)) {
|
|
3901
|
+
throw new Error(`${ModelClass.name}#${relationshipName}Attributes must be an object`)
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
return [await this._nestedAttributesEntryPayloadForSubmittedValue(TargetModelClass, value)]
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
/**
|
|
3908
|
+
* Converts one submitted Rails-style nested attributes object into transport payload shape.
|
|
3909
|
+
* @param {FrontendModelClass} ModelClass - Nested child model class.
|
|
3910
|
+
* @param {?} submittedEntry - Submitted nested attributes entry.
|
|
3911
|
+
* @returns {Promise<Record<string, ?>>} Transport nested-attributes entry.
|
|
3912
|
+
*/
|
|
3913
|
+
async _nestedAttributesEntryPayloadForSubmittedValue(ModelClass, submittedEntry) {
|
|
3914
|
+
if (!frontendAttachmentValueIsPlainObject(submittedEntry)) {
|
|
3915
|
+
throw new Error(`${ModelClass.name} nested attributes entries must be objects`)
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
/** @type {Record<string, ?>} */
|
|
3919
|
+
const entry = {}
|
|
3920
|
+
/** @type {Record<string, ?>} */
|
|
3921
|
+
const attributes = {}
|
|
3922
|
+
/** @type {Record<string, ?>} */
|
|
3923
|
+
const attachments = {}
|
|
3924
|
+
/** @type {Record<string, Array<Record<string, ?>>>} */
|
|
3925
|
+
const nestedAttributes = {}
|
|
3926
|
+
|
|
3927
|
+
for (const [attributeName, value] of Object.entries(submittedEntry)) {
|
|
3928
|
+
if (attributeName === "id" || attributeName === "_destroy") {
|
|
3929
|
+
entry[attributeName] = value
|
|
3930
|
+
continue
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
const nestedRelationshipName = ModelClass.nestedAttributesRelationshipName(attributeName)
|
|
3934
|
+
|
|
3935
|
+
if (nestedRelationshipName) {
|
|
3936
|
+
nestedAttributes[nestedRelationshipName] = await this._nestedAttributesPayloadForSubmittedValue(
|
|
3937
|
+
ModelClass,
|
|
3938
|
+
nestedRelationshipName,
|
|
3939
|
+
value
|
|
3940
|
+
)
|
|
3941
|
+
continue
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
if (ModelClass.attachmentDefinition(attributeName)) {
|
|
3945
|
+
attachments[attributeName] = await this._attachmentPayloadForSubmittedValue(ModelClass, attributeName, value)
|
|
3946
|
+
continue
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
attributes[attributeName] = value
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
if (Object.keys(attributes).length > 0) entry.attributes = attributes
|
|
3953
|
+
if (Object.keys(attachments).length > 0) entry.attachments = attachments
|
|
3954
|
+
if (Object.keys(nestedAttributes).length > 0) entry.nestedAttributes = nestedAttributes
|
|
3955
|
+
|
|
3956
|
+
return entry
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
/**
|
|
3960
|
+
* Normalizes a submitted attachment value for transport.
|
|
3961
|
+
* @param {FrontendModelClass} ModelClass - Model class owning the attachment.
|
|
3962
|
+
* @param {string} attachmentName - Attachment name.
|
|
3963
|
+
* @param {?} value - Submitted attachment value.
|
|
3964
|
+
* @returns {Promise<Record<string, ?> | Record<string, ?>[]>} Normalized attachment payload.
|
|
3965
|
+
*/
|
|
3966
|
+
async _attachmentPayloadForSubmittedValue(ModelClass, attachmentName, value) {
|
|
3967
|
+
const attachmentDefinition = ModelClass.attachmentDefinition(attachmentName)
|
|
3968
|
+
|
|
3969
|
+
if (attachmentDefinition?.type === "hasMany") {
|
|
3970
|
+
const values = Array.isArray(value) ? value : [value]
|
|
3971
|
+
|
|
3972
|
+
return await Promise.all(values.map(async (entry) => await normalizeFrontendAttachmentInput(entry)))
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (Array.isArray(value)) {
|
|
3976
|
+
const lastValue = value[value.length - 1]
|
|
3977
|
+
|
|
3978
|
+
if (lastValue === undefined) {
|
|
3979
|
+
throw new Error(`${ModelClass.name}#${attachmentName} attachment array cannot be empty`)
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
return await normalizeFrontendAttachmentInput(lastValue)
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
return await normalizeFrontendAttachmentInput(value)
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3772
3988
|
/**
|
|
3773
3989
|
* After a parent save with `nestedAttributes`, the server response includes
|
|
3774
3990
|
* preloaded versions of the affected relationships. This replaces the local
|
|
@@ -3779,9 +3995,7 @@ export default class FrontendModelBase {
|
|
|
3779
3995
|
* @returns {void}
|
|
3780
3996
|
*/
|
|
3781
3997
|
_reconcileNestedAttributesFromResponse(response) {
|
|
3782
|
-
const ModelClass =
|
|
3783
|
-
* Narrows the runtime value to the documented type.
|
|
3784
|
-
@type {typeof FrontendModelBase} */ (this.constructor)
|
|
3998
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3785
3999
|
const resourceConfig = typeof ModelClass.resourceConfig === "function" ? ModelClass.resourceConfig() : null
|
|
3786
4000
|
const nestedAttributesConfig = resourceConfig?.nestedAttributes
|
|
3787
4001
|
|
|
@@ -3808,7 +4022,7 @@ export default class FrontendModelBase {
|
|
|
3808
4022
|
|
|
3809
4023
|
/**
|
|
3810
4024
|
* Runs execute command.
|
|
3811
|
-
* @this {
|
|
4025
|
+
* @this {FrontendModelClass}
|
|
3812
4026
|
* @param {FrontendModelCommandType} commandType - Command type.
|
|
3813
4027
|
* @param {Record<string, ?>} payload - Command payload.
|
|
3814
4028
|
* @returns {Promise<Record<string, ?>>} - Parsed JSON response.
|
|
@@ -3885,7 +4099,7 @@ export default class FrontendModelBase {
|
|
|
3885
4099
|
|
|
3886
4100
|
/**
|
|
3887
4101
|
* Runs execute custom command.
|
|
3888
|
-
* @this {
|
|
4102
|
+
* @this {FrontendModelClass}
|
|
3889
4103
|
* @param {object} args - Command arguments.
|
|
3890
4104
|
* @param {string} args.commandName - Raw command path segment.
|
|
3891
4105
|
* @param {FrontendModelRequestCommandType} args.commandType - Logical command type for error handling.
|
|
@@ -3933,7 +4147,7 @@ export default class FrontendModelBase {
|
|
|
3933
4147
|
|
|
3934
4148
|
/**
|
|
3935
4149
|
* Runs throw on error frontend model response.
|
|
3936
|
-
* @this {
|
|
4150
|
+
* @this {FrontendModelClass}
|
|
3937
4151
|
* @param {object} args - Arguments.
|
|
3938
4152
|
* @param {FrontendModelRequestCommandType} args.commandType - Command type.
|
|
3939
4153
|
* @param {Record<string, ?>} args.response - Decoded response.
|
|
@@ -3992,7 +4206,7 @@ export default class FrontendModelBase {
|
|
|
3992
4206
|
|
|
3993
4207
|
/**
|
|
3994
4208
|
* Runs configured frontend model attribute names.
|
|
3995
|
-
* @this {
|
|
4209
|
+
* @this {FrontendModelClass}
|
|
3996
4210
|
* @returns {Set<string>} - Configured frontend model attribute names.
|
|
3997
4211
|
*/
|
|
3998
4212
|
static configuredFrontendModelAttributeNames() {
|