velocious 1.0.643 → 1.0.645
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 +21 -10
- package/build/authorization/ability.js +36 -13
- package/build/background-jobs/adapter.js +9 -0
- package/build/background-jobs/main.js +35 -10
- package/build/background-jobs/store.js +217 -38
- package/build/background-jobs/types.js +15 -0
- package/build/configuration-types.js +1 -1
- package/build/configuration.js +45 -9
- package/build/database/drivers/base-column.js +8 -0
- package/build/database/drivers/mssql/sql/update.js +1 -3
- package/build/database/drivers/mysql/sql/update.js +1 -3
- package/build/database/drivers/pgsql/column.js +22 -0
- package/build/database/drivers/pgsql/sql/update.js +1 -3
- package/build/database/drivers/sqlite/sql/alter-table.js +11 -1
- package/build/database/drivers/sqlite/sql/update.js +1 -3
- package/build/database/query/index.js +2 -2
- package/build/database/query/model-class-query.js +26 -10
- package/build/database/query/preloader/has-many.js +4 -1
- package/build/database/query/query-data.js +2 -1
- package/build/database/query/update-base.js +14 -0
- package/build/database/query/with-count.js +3 -1
- package/build/database/record/acts-as-list.js +37 -13
- package/build/database/record/attachments/store.js +336 -26
- package/build/database/record/auditing.js +6 -3
- package/build/database/record/index.js +197 -42
- package/build/database/record/instance-relationships/belongs-to.js +2 -1
- package/build/database/record/validators/uniqueness.js +8 -5
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
- package/build/frontend-model-controller.js +136 -53
- package/build/frontend-model-resource/base-resource.js +87 -37
- package/build/frontend-model-resource/velocious-attachment-resource.js +98 -31
- package/build/frontend-models/base.js +307 -59
- package/build/frontend-models/preloader.js +6 -3
- package/build/frontend-models/query.js +15 -6
- package/build/frontend-models/resource-definition.js +21 -2
- package/build/frontend-models/use-destroyed-event.js +1 -1
- package/build/frontend-models/use-model-class-event.js +2 -2
- package/build/frontend-models/use-updated-event.js +1 -1
- package/build/frontend-models/websocket-channel.js +256 -93
- package/build/frontend-models/websocket-publishers.js +218 -18
- package/build/http-server/client/websocket-session.js +13 -1
- package/build/http-server/websocket-channel.js +15 -2
- package/build/src/authorization/ability.d.ts +9 -3
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +33 -14
- package/build/src/background-jobs/adapter.d.ts +6 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +9 -1
- package/build/src/background-jobs/main.d.ts +6 -0
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +34 -11
- package/build/src/background-jobs/store.d.ts +66 -12
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +191 -35
- package/build/src/background-jobs/types.d.ts +55 -0
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +16 -1
- package/build/src/configuration-types.d.ts +3 -3
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -2
- package/build/src/configuration.d.ts +18 -8
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +38 -7
- package/build/src/database/drivers/base-column.d.ts +5 -0
- package/build/src/database/drivers/base-column.d.ts.map +1 -1
- package/build/src/database/drivers/base-column.js +8 -1
- package/build/src/database/drivers/mssql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/sql/update.js +2 -4
- package/build/src/database/drivers/mysql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/sql/update.js +2 -4
- package/build/src/database/drivers/pgsql/column.d.ts +5 -0
- package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/column.js +19 -1
- package/build/src/database/drivers/pgsql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/sql/update.js +2 -4
- package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/sql/alter-table.js +6 -2
- package/build/src/database/drivers/sqlite/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/sql/update.js +2 -4
- package/build/src/database/query/index.d.ts +4 -4
- package/build/src/database/query/index.d.ts.map +1 -1
- package/build/src/database/query/index.js +3 -3
- package/build/src/database/query/model-class-query.d.ts +8 -2
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +25 -11
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.js +4 -2
- package/build/src/database/query/query-data.d.ts.map +1 -1
- package/build/src/database/query/query-data.js +3 -2
- package/build/src/database/query/update-base.d.ts +7 -0
- package/build/src/database/query/update-base.d.ts.map +1 -1
- package/build/src/database/query/update-base.js +13 -1
- package/build/src/database/query/with-count.d.ts.map +1 -1
- package/build/src/database/query/with-count.js +3 -2
- package/build/src/database/record/acts-as-list.d.ts.map +1 -1
- package/build/src/database/record/acts-as-list.js +35 -14
- package/build/src/database/record/attachments/store.d.ts +67 -0
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +301 -25
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +7 -4
- package/build/src/database/record/index.d.ts +78 -26
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +171 -42
- package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/belongs-to.js +3 -2
- package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
- package/build/src/database/record/validators/uniqueness.js +8 -6
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +4 -4
- 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 +24 -12
- package/build/src/frontend-model-controller.d.ts +29 -8
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +122 -51
- package/build/src/frontend-model-resource/base-resource.d.ts +51 -24
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +83 -37
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +45 -26
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/velocious-attachment-resource.js +89 -31
- package/build/src/frontend-models/base.d.ts +53 -20
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +275 -59
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +6 -4
- package/build/src/frontend-models/query.d.ts +2 -2
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +14 -7
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +20 -3
- 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 +2 -2
- package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-model-class-event.js +3 -3
- 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 +60 -41
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +225 -85
- package/build/src/frontend-models/websocket-publishers.d.ts +9 -0
- package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-publishers.js +190 -19
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +11 -2
- package/build/src/http-server/websocket-channel.d.ts +20 -8
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +15 -3
- package/build/src/sync/assets/cache.d.ts +288 -0
- package/build/src/sync/assets/cache.d.ts.map +1 -0
- package/build/src/sync/assets/cache.js +883 -0
- package/build/src/sync/assets/types.d.ts +199 -0
- package/build/src/sync/assets/types.d.ts.map +1 -0
- package/build/src/sync/assets/types.js +56 -0
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +4 -3
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +3 -2
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +6 -5
- package/build/src/sync/sync-publisher.d.ts.map +1 -1
- package/build/src/sync/sync-publisher.js +4 -3
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts +2 -2
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +3 -3
- package/build/src/testing/test-profiler.d.ts +4 -4
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +3 -3
- package/build/src/testing/test-runner.d.ts +279 -117
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +503 -933
- package/build/src/testing/test.d.ts +11 -72
- package/build/src/testing/test.d.ts.map +1 -1
- package/build/src/testing/test.js +131 -284
- package/build/src/testing/velocious-attempt-executor.d.ts +54 -0
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-attempt-executor.js +408 -0
- package/build/src/testing/velocious-runner-reporter.d.ts +67 -0
- package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
- package/build/src/testing/velocious-runner-reporter.js +240 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts +19 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-suite-hook-executor.js +41 -0
- package/build/src/testing/velocious-test-arguments.d.ts +39 -0
- package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
- package/build/src/testing/velocious-test-arguments.js +55 -0
- package/build/src/utils/model-primary-key.d.ts +73 -0
- package/build/src/utils/model-primary-key.d.ts.map +1 -0
- package/build/src/utils/model-primary-key.js +144 -0
- package/build/src/utils/sha256-bytes-hex.d.ts +7 -0
- package/build/src/utils/sha256-bytes-hex.d.ts.map +1 -0
- package/build/src/utils/sha256-bytes-hex.js +117 -0
- package/build/src/utils/sha256-hex.d.ts +2 -4
- package/build/src/utils/sha256-hex.d.ts.map +1 -1
- package/build/src/utils/sha256-hex.js +6 -120
- package/build/sync/assets/cache.js +998 -0
- package/build/sync/assets/types.js +62 -0
- package/build/sync/sync-api-client.js +3 -2
- package/build/sync/sync-client.js +2 -1
- package/build/sync/sync-envelope-replay-service.js +5 -4
- package/build/sync/sync-publisher.js +3 -2
- package/build/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/build/testing/test-profiler.js +2 -2
- package/build/testing/test-runner.js +496 -990
- package/build/testing/test.js +164 -306
- package/build/testing/velocious-attempt-executor.js +441 -0
- package/build/testing/velocious-runner-reporter.js +264 -0
- package/build/testing/velocious-suite-hook-executor.js +43 -0
- package/build/testing/velocious-test-arguments.js +64 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/model-primary-key.js +171 -0
- package/build/utils/sha256-bytes-hex.js +132 -0
- package/build/utils/sha256-hex.js +7 -135
- package/package.json +5 -4
- package/src/authorization/ability.js +36 -13
- package/src/background-jobs/adapter.js +9 -0
- package/src/background-jobs/main.js +35 -10
- package/src/background-jobs/store.js +217 -38
- package/src/background-jobs/types.js +15 -0
- package/src/configuration-types.js +1 -1
- package/src/configuration.js +45 -9
- package/src/database/drivers/base-column.js +8 -0
- package/src/database/drivers/mssql/sql/update.js +1 -3
- package/src/database/drivers/mysql/sql/update.js +1 -3
- package/src/database/drivers/pgsql/column.js +22 -0
- package/src/database/drivers/pgsql/sql/update.js +1 -3
- package/src/database/drivers/sqlite/sql/alter-table.js +11 -1
- package/src/database/drivers/sqlite/sql/update.js +1 -3
- package/src/database/query/index.js +2 -2
- package/src/database/query/model-class-query.js +26 -10
- package/src/database/query/preloader/has-many.js +4 -1
- package/src/database/query/query-data.js +2 -1
- package/src/database/query/update-base.js +14 -0
- package/src/database/query/with-count.js +3 -1
- package/src/database/record/acts-as-list.js +37 -13
- package/src/database/record/attachments/store.js +336 -26
- package/src/database/record/auditing.js +6 -3
- package/src/database/record/index.js +197 -42
- package/src/database/record/instance-relationships/belongs-to.js +2 -1
- package/src/database/record/validators/uniqueness.js +8 -5
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
- package/src/frontend-model-controller.js +136 -53
- package/src/frontend-model-resource/base-resource.js +87 -37
- package/src/frontend-model-resource/velocious-attachment-resource.js +98 -31
- package/src/frontend-models/base.js +307 -59
- package/src/frontend-models/preloader.js +6 -3
- package/src/frontend-models/query.js +15 -6
- package/src/frontend-models/resource-definition.js +21 -2
- package/src/frontend-models/use-destroyed-event.js +1 -1
- package/src/frontend-models/use-model-class-event.js +2 -2
- package/src/frontend-models/use-updated-event.js +1 -1
- package/src/frontend-models/websocket-channel.js +256 -93
- package/src/frontend-models/websocket-publishers.js +218 -18
- package/src/http-server/client/websocket-session.js +13 -1
- package/src/http-server/websocket-channel.js +15 -2
- package/src/sync/assets/cache.js +998 -0
- package/src/sync/assets/types.js +62 -0
- package/src/sync/sync-api-client.js +3 -2
- package/src/sync/sync-client.js +2 -1
- package/src/sync/sync-envelope-replay-service.js +5 -4
- package/src/sync/sync-publisher.js +3 -2
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/src/testing/test-profiler.js +2 -2
- package/src/testing/test-runner.js +496 -990
- package/src/testing/test.js +164 -306
- package/src/testing/velocious-attempt-executor.js +441 -0
- package/src/testing/velocious-runner-reporter.js +264 -0
- package/src/testing/velocious-suite-hook-executor.js +43 -0
- package/src/testing/velocious-test-arguments.js +64 -0
- package/src/utils/model-primary-key.js +171 -0
- package/src/utils/sha256-bytes-hex.js +132 -0
- package/src/utils/sha256-hex.js +7 -135
- package/build/src/testing/testing-package-adapter.d.ts +0 -9
- package/build/src/testing/testing-package-adapter.d.ts.map +0 -1
- package/build/src/testing/testing-package-adapter.js +0 -69
- package/build/testing/testing-package-adapter.js +0 -76
- package/src/testing/testing-package-adapter.js +0 -76
|
@@ -17,6 +17,8 @@ import {captureFrontendModelRemoteRequestContext, mergeFrontendModelRemoteReques
|
|
|
17
17
|
import {bufferOutgoingEvent, clearBufferedOutgoingEvents, drainBufferedOutgoingEvents} from "./outgoing-event-buffer.js"
|
|
18
18
|
import {defineModelScope} from "../utils/model-scope.js"
|
|
19
19
|
import isPlainObject from "../utils/plain-object.js"
|
|
20
|
+
import {forcedNonBlankString} from "typanic"
|
|
21
|
+
import {modelPrimaryKeyCacheKey, modelPrimaryKeyConditions, readModelPrimaryKeyValue, scalarModelPrimaryKey, scalarModelPrimaryKeyValue} from "../utils/model-primary-key.js"
|
|
20
22
|
import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQueryData, setPayloadAssociationCount, setPayloadComputedAbility, setPayloadQueryData} from "../record-payload-values.js"
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -30,11 +32,11 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
30
32
|
*/
|
|
31
33
|
/**
|
|
32
34
|
* Defines this typedef.
|
|
33
|
-
* @typedef {{callback: (payload: {id: string, model: FrontendModelBase}) => void, eventFilterKey: string | null, eventFilterPayload: import("./query.js").FrontendModelEventFilterPayload | null, projectionPayload: import("./query.js").FrontendModelProjectionPayload}} FrontendModelModelEventCallbackEntry
|
|
35
|
+
* @typedef {{callback: (payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue, model: FrontendModelBase}) => void, eventFilterKey: string | null, eventFilterPayload: import("./query.js").FrontendModelEventFilterPayload | null, projectionPayload: import("./query.js").FrontendModelProjectionPayload}} FrontendModelModelEventCallbackEntry
|
|
34
36
|
*/
|
|
35
37
|
/**
|
|
36
38
|
* Defines this typedef.
|
|
37
|
-
* @typedef {{callback: (payload: {id: string}) => void}} FrontendModelDestroyEventCallbackEntry
|
|
39
|
+
* @typedef {{callback: (payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue}) => void}} FrontendModelDestroyEventCallbackEntry
|
|
38
40
|
*/
|
|
39
41
|
/**
|
|
40
42
|
* FrontendModelCommandType type.
|
|
@@ -90,7 +92,7 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
90
92
|
*/
|
|
91
93
|
/**
|
|
92
94
|
* Defines this typedef.
|
|
93
|
-
* @typedef {{attributes?: Array<string | FrontendModelAttributeDefinition> | Record<string, FrontendModelAttributeDefinition>, 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[], sync?: FrontendModelSyncConfig}} FrontendModelResourceConfig
|
|
95
|
+
* @typedef {{attributes?: Array<string | FrontendModelAttributeDefinition> | Record<string, FrontendModelAttributeDefinition>, 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 | string[], relationships?: string[], sync?: FrontendModelSyncConfig}} FrontendModelResourceConfig
|
|
94
96
|
*/
|
|
95
97
|
/**
|
|
96
98
|
* Frontend model constructor type.
|
|
@@ -158,6 +160,7 @@ const SELECTED_ATTRIBUTES_KEY = "__selectedAttributes"
|
|
|
158
160
|
const ASSOCIATION_COUNTS_KEY = "__associationCounts"
|
|
159
161
|
const QUERY_DATA_KEY = "__queryData"
|
|
160
162
|
const ABILITIES_KEY = "__abilities"
|
|
163
|
+
const ATTACHMENT_OWNER_KEY = "__attachmentOwner"
|
|
161
164
|
/**
|
|
162
165
|
* Pending shared frontend model requests.
|
|
163
166
|
* @type {Array<{commandName?: string, commandType: FrontendModelRequestCommandType, customPath?: string, modelClass: FrontendModelClass, payload: Record<string, ReturnType<typeof JSON.parse>>, requestContext: import("../remote-request-context.js").RemoteRequestContext, requestId: string, resolve: (response: Record<string, ReturnType<typeof JSON.parse>>) => void, reject: (error: ReturnType<typeof JSON.parse>) => void, resourcePath?: string | null}>} */
|
|
@@ -774,6 +777,19 @@ function frontendModelAttachmentCommandPayload(attachment, attachmentId) {
|
|
|
774
777
|
return payload
|
|
775
778
|
}
|
|
776
779
|
|
|
780
|
+
/**
|
|
781
|
+
* Returns the canonical backing owner used by attachment metadata storage.
|
|
782
|
+
* @param {FrontendModelBase} model - Frontend attachment owner.
|
|
783
|
+
* @returns {{recordId: string, recordType: string, resourceName: string}} - Canonical attachment owner and originating resource.
|
|
784
|
+
*/
|
|
785
|
+
function frontendModelAttachmentOwner(model) {
|
|
786
|
+
if (!model._attachmentOwner) {
|
|
787
|
+
throw new Error(`Missing attachment owner metadata on ${frontendModelClassFor(model).name}`)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return model._attachmentOwner
|
|
791
|
+
}
|
|
792
|
+
|
|
777
793
|
/**
|
|
778
794
|
* Runs frontend attachment value is bytes.
|
|
779
795
|
* @param {ReturnType<typeof JSON.parse>} value - Candidate value.
|
|
@@ -1179,13 +1195,14 @@ export class FrontendModelAttachmentHandle {
|
|
|
1179
1195
|
* @returns {import("./query.js").default<typeof VelociousAttachment>} - Attachment metadata query.
|
|
1180
1196
|
*/
|
|
1181
1197
|
query() {
|
|
1182
|
-
const
|
|
1198
|
+
const attachmentOwner = frontendModelAttachmentOwner(this.model)
|
|
1183
1199
|
|
|
1184
1200
|
return VelociousAttachment
|
|
1185
1201
|
.where({
|
|
1186
1202
|
name: this.attachmentName,
|
|
1187
|
-
recordId:
|
|
1188
|
-
recordType:
|
|
1203
|
+
recordId: attachmentOwner.recordId,
|
|
1204
|
+
recordType: attachmentOwner.recordType,
|
|
1205
|
+
resourceName: attachmentOwner.resourceName
|
|
1189
1206
|
})
|
|
1190
1207
|
.order([["position", "asc"]])
|
|
1191
1208
|
}
|
|
@@ -1241,7 +1258,7 @@ export class FrontendModelAttachmentHandle {
|
|
|
1241
1258
|
const commandUrl = frontendModelCommandUrl(resourcePath, commandName)
|
|
1242
1259
|
const params = new URLSearchParams({
|
|
1243
1260
|
attachmentName: this.attachmentName,
|
|
1244
|
-
id:
|
|
1261
|
+
id: modelPrimaryKeyCacheKey(ModelClass.primaryKey(), this.model.primaryKeyValue())
|
|
1245
1262
|
})
|
|
1246
1263
|
|
|
1247
1264
|
return `${commandUrl}?${params.toString()}`
|
|
@@ -1644,7 +1661,20 @@ class FrontendModelEventSubscription {
|
|
|
1644
1661
|
if (action !== "create" && action !== "update" && action !== "destroy") return
|
|
1645
1662
|
if (rawId === undefined || rawId === null) return
|
|
1646
1663
|
|
|
1647
|
-
const
|
|
1664
|
+
const primaryKey = this.ModelClass.primaryKey()
|
|
1665
|
+
const identity = Array.isArray(primaryKey)
|
|
1666
|
+
? modelPrimaryKeyConditions(primaryKey, rawId)
|
|
1667
|
+
: String(rawId)
|
|
1668
|
+
const id = modelPrimaryKeyCacheKey(primaryKey, identity)
|
|
1669
|
+
const rawPreviousId = body.previousId
|
|
1670
|
+
const previousIdentity = rawPreviousId === undefined || rawPreviousId === null
|
|
1671
|
+
? null
|
|
1672
|
+
: Array.isArray(primaryKey)
|
|
1673
|
+
? modelPrimaryKeyConditions(primaryKey, rawPreviousId)
|
|
1674
|
+
: String(rawPreviousId)
|
|
1675
|
+
const previousId = previousIdentity === null
|
|
1676
|
+
? null
|
|
1677
|
+
: modelPrimaryKeyCacheKey(primaryKey, previousIdentity)
|
|
1648
1678
|
const matchedEventFilterKeys = frontendModelMatchedEventFilterKeys(body)
|
|
1649
1679
|
|
|
1650
1680
|
if (action === "destroy") {
|
|
@@ -1652,23 +1682,33 @@ class FrontendModelEventSubscription {
|
|
|
1652
1682
|
|
|
1653
1683
|
if (listener) {
|
|
1654
1684
|
for (const entry of listener.destroyCallbacks) {
|
|
1655
|
-
try { entry.callback({id}) } catch (error) { console.error(error) }
|
|
1685
|
+
try { entry.callback({id: identity}) } catch (error) { console.error(error) }
|
|
1656
1686
|
}
|
|
1657
|
-
this
|
|
1687
|
+
deleteFrontendModelInstanceListener(this, listener)
|
|
1658
1688
|
}
|
|
1659
1689
|
for (const entry of this.classDestroyCallbacks) {
|
|
1660
|
-
try { entry.callback({id}) } catch (error) { console.error(error) }
|
|
1690
|
+
try { entry.callback({id: identity}) } catch (error) { console.error(error) }
|
|
1661
1691
|
}
|
|
1662
1692
|
return
|
|
1663
1693
|
}
|
|
1664
1694
|
|
|
1695
|
+
const listener = this.instanceListeners.get(id) || (previousId === null ? undefined : this.instanceListeners.get(previousId))
|
|
1696
|
+
|
|
1697
|
+
if (action === "update" && listener && previousIdentity !== null) {
|
|
1698
|
+
applyFrontendModelPersistedIdentity(this.ModelClass, listener.instance, identity)
|
|
1699
|
+
rekeyFrontendModelInstanceListeners(this.ModelClass, listener.instance, previousIdentity, identity)
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1665
1702
|
if (!body.record || typeof body.record !== "object") return
|
|
1666
1703
|
|
|
1667
1704
|
const deserializedRecord = /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (deserializeFrontendModelTransportValue(body.record))
|
|
1668
1705
|
const freshModel = /** @type {ReturnType<typeof JSON.parse>} */ (this.ModelClass).instantiateFromResponse(deserializedRecord)
|
|
1669
|
-
const listener = this.instanceListeners.get(id)
|
|
1670
1706
|
|
|
1671
1707
|
if (action === "update" && listener) {
|
|
1708
|
+
const instanceAny = /** @type {ReturnType<typeof JSON.parse>} */ (listener.instance)
|
|
1709
|
+
|
|
1710
|
+
instanceAny._attachmentOwner = freshModel._attachmentOwner
|
|
1711
|
+
|
|
1672
1712
|
const matchingUpdateCallbacks = Array.from(listener.updateCallbacks).filter((entry) =>
|
|
1673
1713
|
frontendModelEventEntryMatches(entry, matchedEventFilterKeys)
|
|
1674
1714
|
)
|
|
@@ -1676,13 +1716,11 @@ class FrontendModelEventSubscription {
|
|
|
1676
1716
|
if (matchingUpdateCallbacks.length > 0) {
|
|
1677
1717
|
// Auto-merge into the registered instance so callers reading
|
|
1678
1718
|
// through the same handle see fresh attributes.
|
|
1679
|
-
const instanceAny = /** @type {ReturnType<typeof JSON.parse>} */ (listener.instance)
|
|
1680
|
-
|
|
1681
1719
|
instanceAny.assignAttributes(freshModel.attributes())
|
|
1682
1720
|
instanceAny._persistedAttributes = cloneFrontendModelAttributes(listener.instance.attributes())
|
|
1683
1721
|
|
|
1684
1722
|
for (const entry of matchingUpdateCallbacks) {
|
|
1685
|
-
try { entry.callback({id, model: listener.instance}) } catch (error) { console.error(error) }
|
|
1723
|
+
try { entry.callback({id: identity, model: listener.instance}) } catch (error) { console.error(error) }
|
|
1686
1724
|
}
|
|
1687
1725
|
}
|
|
1688
1726
|
}
|
|
@@ -1692,7 +1730,7 @@ class FrontendModelEventSubscription {
|
|
|
1692
1730
|
for (const entry of classCallbacks) {
|
|
1693
1731
|
if (!frontendModelEventEntryMatches(entry, matchedEventFilterKeys)) continue
|
|
1694
1732
|
|
|
1695
|
-
try { entry.callback({id, model: freshModel}) } catch (error) { console.error(error) }
|
|
1733
|
+
try { entry.callback({id: identity, model: freshModel}) } catch (error) { console.error(error) }
|
|
1696
1734
|
}
|
|
1697
1735
|
}
|
|
1698
1736
|
|
|
@@ -1810,25 +1848,129 @@ function ensureFrontendModelInstanceListener(sub, id, instance) {
|
|
|
1810
1848
|
return listener
|
|
1811
1849
|
}
|
|
1812
1850
|
|
|
1851
|
+
/**
|
|
1852
|
+
* Removes every identity key pointing at an instance listener.
|
|
1853
|
+
* @param {FrontendModelEventSubscription} sub - Event subscription bucket.
|
|
1854
|
+
* @param {ReturnType<typeof ensureFrontendModelInstanceListener>} listener - Instance listener bucket.
|
|
1855
|
+
* @returns {void}
|
|
1856
|
+
*/
|
|
1857
|
+
function deleteFrontendModelInstanceListener(sub, listener) {
|
|
1858
|
+
for (const [id, current] of sub.instanceListeners) {
|
|
1859
|
+
if (current === listener) sub.instanceListeners.delete(id)
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1813
1863
|
/**
|
|
1814
1864
|
* Removes one instance callback entry and tears down an empty listener/subscription bucket.
|
|
1815
1865
|
* @param {FrontendModelEventSubscription} sub - Event subscription bucket.
|
|
1816
|
-
* @param {
|
|
1817
|
-
* @param {(listener: {instance: FrontendModelBase, updateCallbacks: Set<FrontendModelModelEventCallbackEntry>, destroyCallbacks: Set<FrontendModelDestroyEventCallbackEntry>}) => void} removeEntry - Callback entry removal.
|
|
1866
|
+
* @param {(listener: ReturnType<typeof ensureFrontendModelInstanceListener>) => boolean} removeEntry - Callback entry removal.
|
|
1818
1867
|
* @returns {void}
|
|
1819
1868
|
*/
|
|
1820
|
-
function removeFrontendModelInstanceListenerEntry(sub,
|
|
1821
|
-
const current
|
|
1869
|
+
function removeFrontendModelInstanceListenerEntry(sub, removeEntry) {
|
|
1870
|
+
for (const current of sub.instanceListeners.values()) {
|
|
1871
|
+
if (!removeEntry(current)) continue
|
|
1872
|
+
|
|
1873
|
+
if (current.updateCallbacks.size === 0 && current.destroyCallbacks.size === 0) {
|
|
1874
|
+
deleteFrontendModelInstanceListener(sub, current)
|
|
1875
|
+
}
|
|
1876
|
+
break
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
sub.maybeTeardown()
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* Temporarily registers an instance listener under its pending identity while retaining its persisted identity.
|
|
1884
|
+
* @param {FrontendModelClass} ModelClass - Frontend model class.
|
|
1885
|
+
* @param {FrontendModelBase} instance - Instance being re-keyed.
|
|
1886
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} previousIdentity - Persisted identity.
|
|
1887
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} nextIdentity - Pending identity sent to the server.
|
|
1888
|
+
* @returns {() => void} - Callback that removes the temporary aliases.
|
|
1889
|
+
*/
|
|
1890
|
+
function aliasFrontendModelInstanceListeners(ModelClass, instance, previousIdentity, nextIdentity) {
|
|
1891
|
+
const primaryKey = ModelClass.primaryKey()
|
|
1892
|
+
const previousId = modelPrimaryKeyCacheKey(primaryKey, previousIdentity)
|
|
1893
|
+
const nextId = modelPrimaryKeyCacheKey(primaryKey, nextIdentity)
|
|
1894
|
+
/** @type {Array<{listener: ReturnType<typeof ensureFrontendModelInstanceListener>, sub: FrontendModelEventSubscription}>} */
|
|
1895
|
+
const aliases = []
|
|
1896
|
+
|
|
1897
|
+
if (previousId === nextId) return () => {}
|
|
1898
|
+
|
|
1899
|
+
const subscriptions = frontendModelEventSubscriptions.get(ModelClass)
|
|
1900
|
+
|
|
1901
|
+
if (!subscriptions) return () => {}
|
|
1822
1902
|
|
|
1823
|
-
|
|
1903
|
+
for (const sub of subscriptions.values()) {
|
|
1904
|
+
const listener = sub.instanceListeners.get(previousId)
|
|
1824
1905
|
|
|
1825
|
-
|
|
1906
|
+
if (!listener || listener.instance !== instance || sub.instanceListeners.has(nextId)) continue
|
|
1826
1907
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1908
|
+
sub.instanceListeners.set(nextId, listener)
|
|
1909
|
+
aliases.push({listener, sub})
|
|
1829
1910
|
}
|
|
1830
1911
|
|
|
1831
|
-
|
|
1912
|
+
return () => {
|
|
1913
|
+
for (const {listener, sub} of aliases) {
|
|
1914
|
+
if (sub.instanceListeners.get(previousId) === listener && sub.instanceListeners.get(nextId) === listener) {
|
|
1915
|
+
sub.instanceListeners.delete(nextId)
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* Applies a remotely persisted identity to a listener instance without merging an unavailable record payload.
|
|
1923
|
+
* @param {FrontendModelClass} ModelClass - Frontend model class.
|
|
1924
|
+
* @param {FrontendModelBase} instance - Listener instance receiving the identity.
|
|
1925
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} identity - Persisted identity.
|
|
1926
|
+
* @returns {void}
|
|
1927
|
+
*/
|
|
1928
|
+
function applyFrontendModelPersistedIdentity(ModelClass, instance, identity) {
|
|
1929
|
+
const identityAttributes = modelPrimaryKeyConditions(ModelClass.primaryKey(), identity)
|
|
1930
|
+
|
|
1931
|
+
instance.assignAttributes(identityAttributes)
|
|
1932
|
+
|
|
1933
|
+
for (const attributeName of Object.keys(identityAttributes)) {
|
|
1934
|
+
instance.markAttributeUnchanged(attributeName)
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Moves callbacks registered on an instance to its newly persisted identity.
|
|
1940
|
+
* @param {FrontendModelClass} ModelClass - Frontend model class.
|
|
1941
|
+
* @param {FrontendModelBase} instance - Re-keyed instance.
|
|
1942
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} previousIdentity - Previous persisted identity.
|
|
1943
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} nextIdentity - New persisted identity.
|
|
1944
|
+
* @returns {void}
|
|
1945
|
+
*/
|
|
1946
|
+
function rekeyFrontendModelInstanceListeners(ModelClass, instance, previousIdentity, nextIdentity) {
|
|
1947
|
+
const primaryKey = ModelClass.primaryKey()
|
|
1948
|
+
const previousId = modelPrimaryKeyCacheKey(primaryKey, previousIdentity)
|
|
1949
|
+
const nextId = modelPrimaryKeyCacheKey(primaryKey, nextIdentity)
|
|
1950
|
+
|
|
1951
|
+
if (previousId === nextId) return
|
|
1952
|
+
|
|
1953
|
+
const subscriptions = frontendModelEventSubscriptions.get(ModelClass)
|
|
1954
|
+
|
|
1955
|
+
if (!subscriptions) return
|
|
1956
|
+
|
|
1957
|
+
for (const sub of subscriptions.values()) {
|
|
1958
|
+
const listener = sub.instanceListeners.get(previousId)
|
|
1959
|
+
|
|
1960
|
+
if (!listener || listener.instance !== instance) continue
|
|
1961
|
+
|
|
1962
|
+
const nextListener = sub.instanceListeners.get(nextId)
|
|
1963
|
+
|
|
1964
|
+
sub.instanceListeners.delete(previousId)
|
|
1965
|
+
|
|
1966
|
+
if (nextListener) {
|
|
1967
|
+
nextListener.instance = instance
|
|
1968
|
+
for (const entry of listener.updateCallbacks) nextListener.updateCallbacks.add(entry)
|
|
1969
|
+
for (const entry of listener.destroyCallbacks) nextListener.destroyCallbacks.add(entry)
|
|
1970
|
+
} else {
|
|
1971
|
+
sub.instanceListeners.set(nextId, listener)
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1832
1974
|
}
|
|
1833
1975
|
|
|
1834
1976
|
/**
|
|
@@ -2333,6 +2475,11 @@ export default class FrontendModelBase {
|
|
|
2333
2475
|
* @type {Array<FrontendModelBase> | undefined} - Shared reference to sibling records loaded in the same batch. Used by auto-batch-preload.
|
|
2334
2476
|
*/
|
|
2335
2477
|
_loadCohort
|
|
2478
|
+
/**
|
|
2479
|
+
* Canonical backing-record attachment owner returned by the server.
|
|
2480
|
+
* @type {{recordId: string, recordType: string, resourceName: string} | null}
|
|
2481
|
+
*/
|
|
2482
|
+
_attachmentOwner
|
|
2336
2483
|
|
|
2337
2484
|
/**
|
|
2338
2485
|
* Runs constructor.
|
|
@@ -2350,6 +2497,7 @@ export default class FrontendModelBase {
|
|
|
2350
2497
|
this._isNewRecord = true
|
|
2351
2498
|
this._markedForDestruction = false
|
|
2352
2499
|
this._persistedAttributes = {}
|
|
2500
|
+
this._attachmentOwner = null
|
|
2353
2501
|
if (attributes) this.assignAttributes(attributes)
|
|
2354
2502
|
}
|
|
2355
2503
|
|
|
@@ -2724,6 +2872,9 @@ export default class FrontendModelBase {
|
|
|
2724
2872
|
if (batch.length === 0) return false
|
|
2725
2873
|
|
|
2726
2874
|
const primaryKey = ModelClass.primaryKey()
|
|
2875
|
+
|
|
2876
|
+
if (Array.isArray(primaryKey)) return false
|
|
2877
|
+
|
|
2727
2878
|
const batchIds = batch.map((sibling) => sibling.primaryKeyValue())
|
|
2728
2879
|
const reloadedBatch = await ModelClass
|
|
2729
2880
|
.preload([relationshipName])
|
|
@@ -2736,11 +2887,11 @@ export default class FrontendModelBase {
|
|
|
2736
2887
|
const reloadedById = new Map()
|
|
2737
2888
|
|
|
2738
2889
|
for (const reloaded of reloadedBatch) {
|
|
2739
|
-
reloadedById.set(
|
|
2890
|
+
reloadedById.set(modelPrimaryKeyCacheKey(primaryKey, reloaded.primaryKeyValue()), reloaded)
|
|
2740
2891
|
}
|
|
2741
2892
|
|
|
2742
2893
|
for (const sibling of batch) {
|
|
2743
|
-
const key =
|
|
2894
|
+
const key = modelPrimaryKeyCacheKey(primaryKey, sibling.primaryKeyValue())
|
|
2744
2895
|
const reloaded = reloadedById.get(key)
|
|
2745
2896
|
|
|
2746
2897
|
if (!reloaded) continue
|
|
@@ -2809,7 +2960,7 @@ export default class FrontendModelBase {
|
|
|
2809
2960
|
/**
|
|
2810
2961
|
* Runs primary key.
|
|
2811
2962
|
* @this {FrontendModelClass}
|
|
2812
|
-
* @returns {
|
|
2963
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} - Primary key name.
|
|
2813
2964
|
*/
|
|
2814
2965
|
static primaryKey() {
|
|
2815
2966
|
return this.resourceConfig().primaryKey || "id"
|
|
@@ -2817,17 +2968,49 @@ export default class FrontendModelBase {
|
|
|
2817
2968
|
|
|
2818
2969
|
/**
|
|
2819
2970
|
* Runs primary key value.
|
|
2820
|
-
* @returns {
|
|
2971
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} - Primary key value.
|
|
2821
2972
|
*/
|
|
2822
2973
|
primaryKeyValue() {
|
|
2823
2974
|
const ModelClass = frontendModelClassFor(this)
|
|
2824
|
-
const
|
|
2975
|
+
const primaryKey = ModelClass.primaryKey()
|
|
2825
2976
|
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2977
|
+
return readModelPrimaryKeyValue(primaryKey, (attributeName) => {
|
|
2978
|
+
const value = this.readAttribute(attributeName)
|
|
2979
|
+
|
|
2980
|
+
if (value === undefined || value === null) {
|
|
2981
|
+
throw new Error(`Missing primary key '${attributeName}' on ${ModelClass.name}`)
|
|
2982
|
+
}
|
|
2829
2983
|
|
|
2830
|
-
|
|
2984
|
+
return value
|
|
2985
|
+
})
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
/**
|
|
2989
|
+
* Returns the scalar identity required by scalar-only frontend features.
|
|
2990
|
+
* @param {string} operation - Operation requiring a scalar identity.
|
|
2991
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyScalar} - Scalar primary-key value.
|
|
2992
|
+
*/
|
|
2993
|
+
scalarPrimaryKeyValue(operation) {
|
|
2994
|
+
return scalarModelPrimaryKeyValue(this.primaryKeyValue(), operation)
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* Returns the identity represented by the last persisted frontend attributes.
|
|
2999
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} - Persisted primary-key value.
|
|
3000
|
+
*/
|
|
3001
|
+
persistedPrimaryKeyValue() {
|
|
3002
|
+
const ModelClass = frontendModelClassFor(this)
|
|
3003
|
+
const primaryKey = ModelClass.primaryKey()
|
|
3004
|
+
|
|
3005
|
+
return readModelPrimaryKeyValue(primaryKey, (attributeName) => {
|
|
3006
|
+
const value = this._persistedAttributes[attributeName]
|
|
3007
|
+
|
|
3008
|
+
if (value === undefined || value === null) {
|
|
3009
|
+
throw new Error(`Missing persisted primary key '${attributeName}' on ${ModelClass.name}`)
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
return value
|
|
3013
|
+
})
|
|
2831
3014
|
}
|
|
2832
3015
|
|
|
2833
3016
|
/**
|
|
@@ -3435,7 +3618,7 @@ export default class FrontendModelBase {
|
|
|
3435
3618
|
* Runs model data from response.
|
|
3436
3619
|
* @this {FrontendModelClass}
|
|
3437
3620
|
* @param {object} response - Response payload.
|
|
3438
|
-
* @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
|
|
3621
|
+
* @returns {{abilities: Record<string, boolean>, attachmentOwner: {recordId: string, recordType: string, resourceName: string} | null, attributes: Record<string, FrontendModelAttributeValue>, associationCounts: Record<string, number>, queryData: Record<string, FrontendModelAttributeValue>, preloadedRelationships: Record<string, FrontendModelAttributeValue>, selectedAttributes: Set<string>}} - Attributes, attachment owner, preloaded relationships, association counts, queryData, abilities, and selected attributes.
|
|
3439
3622
|
*/
|
|
3440
3623
|
static modelDataFromResponse(response) {
|
|
3441
3624
|
if (!response || typeof response !== "object") {
|
|
@@ -3476,7 +3659,24 @@ export default class FrontendModelBase {
|
|
|
3476
3659
|
const selectedAttributesFromPayload = Array.isArray(attributes[SELECTED_ATTRIBUTES_KEY])
|
|
3477
3660
|
? new Set(/** @type {string[]} */ (attributes[SELECTED_ATTRIBUTES_KEY]).filter((attributeName) => typeof attributeName === "string"))
|
|
3478
3661
|
: null
|
|
3662
|
+
const attachmentOwnerPayload = attributes[ATTACHMENT_OWNER_KEY]
|
|
3663
|
+
let attachmentOwner = null
|
|
3664
|
+
|
|
3665
|
+
if (attachmentOwnerPayload !== undefined) {
|
|
3666
|
+
if (!isPlainObject(attachmentOwnerPayload)) {
|
|
3667
|
+
throw new TypeError(`Expected ${ATTACHMENT_OWNER_KEY} to be an object`)
|
|
3668
|
+
}
|
|
3479
3669
|
|
|
3670
|
+
const attachmentOwnerObject = /** @type {{recordId?: unknown, recordType?: unknown, resourceName?: unknown}} */ (attachmentOwnerPayload)
|
|
3671
|
+
|
|
3672
|
+
attachmentOwner = {
|
|
3673
|
+
recordId: forcedNonBlankString(attachmentOwnerObject.recordId, `${ATTACHMENT_OWNER_KEY}.recordId`),
|
|
3674
|
+
recordType: forcedNonBlankString(attachmentOwnerObject.recordType, `${ATTACHMENT_OWNER_KEY}.recordType`),
|
|
3675
|
+
resourceName: forcedNonBlankString(attachmentOwnerObject.resourceName, `${ATTACHMENT_OWNER_KEY}.resourceName`)
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
delete attributes[ATTACHMENT_OWNER_KEY]
|
|
3480
3680
|
delete attributes[PRELOADED_RELATIONSHIPS_KEY]
|
|
3481
3681
|
delete attributes[SELECTED_ATTRIBUTES_KEY]
|
|
3482
3682
|
delete attributes[ASSOCIATION_COUNTS_KEY]
|
|
@@ -3485,7 +3685,7 @@ export default class FrontendModelBase {
|
|
|
3485
3685
|
|
|
3486
3686
|
const selectedAttributes = selectedAttributesFromPayload || new Set(Object.keys(attributes))
|
|
3487
3687
|
|
|
3488
|
-
return {abilities, attributes, associationCounts, queryData, preloadedRelationships, selectedAttributes}
|
|
3688
|
+
return {abilities, attachmentOwner, attributes, associationCounts, queryData, preloadedRelationships, selectedAttributes}
|
|
3489
3689
|
}
|
|
3490
3690
|
|
|
3491
3691
|
/**
|
|
@@ -3578,10 +3778,12 @@ export default class FrontendModelBase {
|
|
|
3578
3778
|
const associationCounts = modelData.associationCounts
|
|
3579
3779
|
const queryData = modelData.queryData
|
|
3580
3780
|
const abilities = modelData.abilities
|
|
3781
|
+
const attachmentOwner = modelData.attachmentOwner
|
|
3581
3782
|
const selectedAttributes = modelData.selectedAttributes
|
|
3582
3783
|
const receiver = /** @type {unknown} */ (this)
|
|
3583
3784
|
const ModelClass = /** @type {new (attributes?: Record<string, FrontendModelAttributeValue>) => InstanceType<T>} */ (receiver)
|
|
3584
3785
|
const model = new ModelClass(attributes)
|
|
3786
|
+
model._attachmentOwner = attachmentOwner
|
|
3585
3787
|
model._selectedAttributes = selectedAttributes ? new Set(selectedAttributes) : null
|
|
3586
3788
|
|
|
3587
3789
|
this.applyPreloadedRelationships(model, preloadedRelationships)
|
|
@@ -3608,7 +3810,7 @@ export default class FrontendModelBase {
|
|
|
3608
3810
|
* Runs find.
|
|
3609
3811
|
* @template {FrontendModelClass} T
|
|
3610
3812
|
* @this {T}
|
|
3611
|
-
* @param {
|
|
3813
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} id - Record identifier.
|
|
3612
3814
|
* @returns {Promise<InstanceType<T>>} - Resolved model.
|
|
3613
3815
|
*/
|
|
3614
3816
|
static async find(id) {
|
|
@@ -3750,7 +3952,7 @@ export default class FrontendModelBase {
|
|
|
3750
3952
|
* without re-checking per-record visibility. Query options can still
|
|
3751
3953
|
* narrow which events reach this callback.
|
|
3752
3954
|
* @this {FrontendModelClass}
|
|
3753
|
-
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3955
|
+
* @param {(payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3754
3956
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3755
3957
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3756
3958
|
*/
|
|
@@ -3765,7 +3967,7 @@ export default class FrontendModelBase {
|
|
|
3765
3967
|
/**
|
|
3766
3968
|
* Class-level hook fired when any record of this model is updated.
|
|
3767
3969
|
* @this {FrontendModelClass}
|
|
3768
|
-
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3970
|
+
* @param {(payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3769
3971
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3770
3972
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3771
3973
|
*/
|
|
@@ -3780,7 +3982,7 @@ export default class FrontendModelBase {
|
|
|
3780
3982
|
/**
|
|
3781
3983
|
* Class-level hook fired when any record of this model is destroyed.
|
|
3782
3984
|
* @this {FrontendModelClass}
|
|
3783
|
-
* @param {(payload: {id: string}) => void} callback - Event callback.
|
|
3985
|
+
* @param {(payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue}) => void} callback - Event callback.
|
|
3784
3986
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Accepted for API symmetry; destroy events carry ids only.
|
|
3785
3987
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3786
3988
|
*/
|
|
@@ -3799,16 +4001,16 @@ export default class FrontendModelBase {
|
|
|
3799
4001
|
* instance's attributes are auto-merged with the broadcast payload
|
|
3800
4002
|
* before the callback runs, so callers can read fresh values via
|
|
3801
4003
|
* `this.someAttr()` without re-fetching.
|
|
3802
|
-
* @param {(payload: {id: string, model: FrontendModelBase}) => void} callback - Event callback.
|
|
4004
|
+
* @param {(payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue, model: FrontendModelBase}) => void} callback - Event callback.
|
|
3803
4005
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Event query or record projection options.
|
|
3804
4006
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3805
4007
|
*/
|
|
3806
4008
|
async onUpdate(callback, options = {}) {
|
|
3807
|
-
const self = /** @type {ReturnType<typeof JSON.parse>} */ (this)
|
|
3808
4009
|
const ModelClass = frontendModelClassFor(this)
|
|
3809
4010
|
const {requestContext, ...eventOptionsPayload} = frontendModelEventOptionsPayload(ModelClass, options)
|
|
3810
4011
|
const sub = ensureFrontendModelEventSubscription(ModelClass, frontendModelEventRequestContext(requestContext))
|
|
3811
|
-
const
|
|
4012
|
+
const identity = this.isNewRecord() ? this.primaryKeyValue() : this.persistedPrimaryKeyValue()
|
|
4013
|
+
const id = modelPrimaryKeyCacheKey(ModelClass.primaryKey(), identity)
|
|
3812
4014
|
const entry = {callback, ...eventOptionsPayload}
|
|
3813
4015
|
const listener = ensureFrontendModelInstanceListener(sub, id, this)
|
|
3814
4016
|
|
|
@@ -3817,30 +4019,30 @@ export default class FrontendModelBase {
|
|
|
3817
4019
|
try {
|
|
3818
4020
|
await sub.ensureSubscribed()
|
|
3819
4021
|
} catch (error) {
|
|
3820
|
-
removeFrontendModelInstanceListenerEntry(sub,
|
|
4022
|
+
removeFrontendModelInstanceListenerEntry(sub, (current) => current.updateCallbacks.delete(entry))
|
|
3821
4023
|
throw error
|
|
3822
4024
|
}
|
|
3823
4025
|
|
|
3824
4026
|
return () => {
|
|
3825
|
-
removeFrontendModelInstanceListenerEntry(sub,
|
|
4027
|
+
removeFrontendModelInstanceListenerEntry(sub, (current) => current.updateCallbacks.delete(entry))
|
|
3826
4028
|
}
|
|
3827
4029
|
}
|
|
3828
4030
|
|
|
3829
4031
|
/**
|
|
3830
4032
|
* Instance-level hook fired when THIS record is destroyed.
|
|
3831
|
-
* @param {(payload: {id: string}) => void} callback - Event callback.
|
|
4033
|
+
* @param {(payload: {id: string | import("../utils/model-primary-key.js").CompositeModelPrimaryKeyValue}) => void} callback - Event callback.
|
|
3832
4034
|
* @param {import("./query.js").FrontendModelEventOptions} [options] - Accepted for API symmetry; destroy events carry ids only.
|
|
3833
4035
|
* @returns {Promise<() => void>} - Unsubscribe callback.
|
|
3834
4036
|
*/
|
|
3835
4037
|
async onDestroy(callback, options = {}) {
|
|
3836
|
-
const self = /** @type {ReturnType<typeof JSON.parse>} */ (this)
|
|
3837
4038
|
const ModelClass = frontendModelClassFor(this)
|
|
3838
4039
|
|
|
3839
4040
|
assertNoDestroyEventFilter(ModelClass, options)
|
|
3840
4041
|
|
|
3841
4042
|
const {requestContext} = frontendModelEventOptionsPayload(ModelClass, options)
|
|
3842
4043
|
const sub = ensureFrontendModelEventSubscription(ModelClass, frontendModelEventRequestContext(requestContext))
|
|
3843
|
-
const
|
|
4044
|
+
const identity = this.isNewRecord() ? this.primaryKeyValue() : this.persistedPrimaryKeyValue()
|
|
4045
|
+
const id = modelPrimaryKeyCacheKey(ModelClass.primaryKey(), identity)
|
|
3844
4046
|
const entry = {callback}
|
|
3845
4047
|
const listener = ensureFrontendModelInstanceListener(sub, id, this)
|
|
3846
4048
|
|
|
@@ -3849,12 +4051,12 @@ export default class FrontendModelBase {
|
|
|
3849
4051
|
try {
|
|
3850
4052
|
await sub.ensureSubscribed()
|
|
3851
4053
|
} catch (error) {
|
|
3852
|
-
removeFrontendModelInstanceListenerEntry(sub,
|
|
4054
|
+
removeFrontendModelInstanceListenerEntry(sub, (current) => current.destroyCallbacks.delete(entry))
|
|
3853
4055
|
throw error
|
|
3854
4056
|
}
|
|
3855
4057
|
|
|
3856
4058
|
return () => {
|
|
3857
|
-
removeFrontendModelInstanceListenerEntry(sub,
|
|
4059
|
+
removeFrontendModelInstanceListenerEntry(sub, (current) => current.destroyCallbacks.delete(entry))
|
|
3858
4060
|
}
|
|
3859
4061
|
}
|
|
3860
4062
|
|
|
@@ -4256,6 +4458,17 @@ export default class FrontendModelBase {
|
|
|
4256
4458
|
async save() {
|
|
4257
4459
|
const ModelClass = frontendModelClassFor(this)
|
|
4258
4460
|
const isNew = this.isNewRecord()
|
|
4461
|
+
const primaryKey = ModelClass.primaryKey()
|
|
4462
|
+
const previousIdentity = isNew ? null : this.persistedPrimaryKeyValue()
|
|
4463
|
+
const listenerIdentityBeforeSave = isNew
|
|
4464
|
+
&& Array.isArray(primaryKey)
|
|
4465
|
+
&& primaryKey.every((attributeName) => {
|
|
4466
|
+
const value = this._attributes[attributeName]
|
|
4467
|
+
|
|
4468
|
+
return typeof value === "string" || typeof value === "number"
|
|
4469
|
+
})
|
|
4470
|
+
? this.primaryKeyValue()
|
|
4471
|
+
: previousIdentity
|
|
4259
4472
|
const commandType = isNew ? "create" : "update"
|
|
4260
4473
|
/**
|
|
4261
4474
|
* Payload.
|
|
@@ -4265,7 +4478,7 @@ export default class FrontendModelBase {
|
|
|
4265
4478
|
}
|
|
4266
4479
|
|
|
4267
4480
|
if (!isNew) {
|
|
4268
|
-
payload.id = this.
|
|
4481
|
+
payload.id = this.persistedPrimaryKeyValue()
|
|
4269
4482
|
}
|
|
4270
4483
|
|
|
4271
4484
|
const nestedAttributes = await this._buildNestedAttributesPayload()
|
|
@@ -4285,7 +4498,7 @@ export default class FrontendModelBase {
|
|
|
4285
4498
|
let clientMutationId
|
|
4286
4499
|
|
|
4287
4500
|
if (isNew) {
|
|
4288
|
-
const primaryKey = ModelClass.primaryKey()
|
|
4501
|
+
const primaryKey = scalarModelPrimaryKey(ModelClass.primaryKey(), `Offline create for ${ModelClass.name}`)
|
|
4289
4502
|
const currentPrimaryKey = this.readAttribute(primaryKey)
|
|
4290
4503
|
|
|
4291
4504
|
if (currentPrimaryKey === undefined || currentPrimaryKey === null) {
|
|
@@ -4296,7 +4509,9 @@ export default class FrontendModelBase {
|
|
|
4296
4509
|
offlineAttributes[primaryKey] = clientMutationId
|
|
4297
4510
|
}
|
|
4298
4511
|
} else {
|
|
4299
|
-
|
|
4512
|
+
const primaryKey = scalarModelPrimaryKey(ModelClass.primaryKey(), `Offline update for ${ModelClass.name}`)
|
|
4513
|
+
|
|
4514
|
+
offlineAttributes[primaryKey] = payload.id
|
|
4300
4515
|
}
|
|
4301
4516
|
|
|
4302
4517
|
if (payload.nestedAttributes !== undefined || payload.attachments !== undefined) {
|
|
@@ -4317,10 +4532,30 @@ export default class FrontendModelBase {
|
|
|
4317
4532
|
return this
|
|
4318
4533
|
}
|
|
4319
4534
|
|
|
4320
|
-
const
|
|
4535
|
+
const removeTemporaryListenerAliases = previousIdentity === null
|
|
4536
|
+
? () => {}
|
|
4537
|
+
: aliasFrontendModelInstanceListeners(ModelClass, this, previousIdentity, this.primaryKeyValue())
|
|
4538
|
+
let response
|
|
4539
|
+
|
|
4540
|
+
try {
|
|
4541
|
+
response = await ModelClass.executeCommand(commandType, payload)
|
|
4542
|
+
} catch (error) {
|
|
4543
|
+
removeTemporaryListenerAliases()
|
|
4544
|
+
throw error
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
removeTemporaryListenerAliases()
|
|
4321
4548
|
|
|
4322
|
-
|
|
4549
|
+
const modelData = ModelClass.modelDataFromResponse(response)
|
|
4550
|
+
|
|
4551
|
+
this.assignAttributes(modelData.attributes)
|
|
4552
|
+
this._attachmentOwner = modelData.attachmentOwner
|
|
4323
4553
|
this.setIsNewRecord(false)
|
|
4554
|
+
|
|
4555
|
+
if (listenerIdentityBeforeSave !== null) {
|
|
4556
|
+
rekeyFrontendModelInstanceListeners(ModelClass, this, listenerIdentityBeforeSave, this.primaryKeyValue())
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4324
4559
|
this._persistedAttributes = cloneFrontendModelAttributes(this.attributes())
|
|
4325
4560
|
this._pendingNestedAttributes = {}
|
|
4326
4561
|
this._clearPendingAttachments()
|
|
@@ -4369,11 +4604,13 @@ export default class FrontendModelBase {
|
|
|
4369
4604
|
*/
|
|
4370
4605
|
async destroy() {
|
|
4371
4606
|
const ModelClass = frontendModelClassFor(this)
|
|
4372
|
-
const id = this.primaryKeyValue()
|
|
4607
|
+
const id = this.isNewRecord() ? this.primaryKeyValue() : this.persistedPrimaryKeyValue()
|
|
4373
4608
|
|
|
4374
4609
|
if (shouldQueueFrontendModelOperationOffline(ModelClass, "destroy")) {
|
|
4610
|
+
const primaryKey = scalarModelPrimaryKey(ModelClass.primaryKey(), `Offline destroy for ${ModelClass.name}`)
|
|
4611
|
+
|
|
4375
4612
|
await queueFrontendModelMutationOffline({
|
|
4376
|
-
attributes: {[
|
|
4613
|
+
attributes: {[primaryKey]: id},
|
|
4377
4614
|
ModelClass,
|
|
4378
4615
|
operation: "destroy"
|
|
4379
4616
|
})
|
|
@@ -4486,7 +4723,7 @@ export default class FrontendModelBase {
|
|
|
4486
4723
|
async _nestedAttributesEntryForParentSave() {
|
|
4487
4724
|
if (this.markedForDestruction()) {
|
|
4488
4725
|
if (this.isNewRecord()) return null
|
|
4489
|
-
return {id: this.
|
|
4726
|
+
return {id: this.persistedPrimaryKeyValue(), _destroy: true}
|
|
4490
4727
|
}
|
|
4491
4728
|
|
|
4492
4729
|
const nestedAttributes = await this._buildNestedAttributesPayload()
|
|
@@ -4513,7 +4750,7 @@ export default class FrontendModelBase {
|
|
|
4513
4750
|
/**
|
|
4514
4751
|
* Entry.
|
|
4515
4752
|
* @type {Record<string, ReturnType<typeof JSON.parse>>} */
|
|
4516
|
-
const entry = {id: this.
|
|
4753
|
+
const entry = {id: this.persistedPrimaryKeyValue()}
|
|
4517
4754
|
|
|
4518
4755
|
if (this.isChanged()) entry.attributes = this._changedAttributesForSave()
|
|
4519
4756
|
if (hasAttachments) entry.attachments = attachments
|
|
@@ -4915,6 +5152,17 @@ export class VelociousAttachment extends FrontendModelBase {
|
|
|
4915
5152
|
}
|
|
4916
5153
|
}
|
|
4917
5154
|
|
|
5155
|
+
/**
|
|
5156
|
+
* Finds attachment metadata by its public id through the member authorization path.
|
|
5157
|
+
* @template {FrontendModelClass} T
|
|
5158
|
+
* @this {T}
|
|
5159
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} id - Attachment id.
|
|
5160
|
+
* @returns {Promise<InstanceType<T>>} - Resolved attachment metadata.
|
|
5161
|
+
*/
|
|
5162
|
+
static async find(id) {
|
|
5163
|
+
return this.instantiateFromResponse(await this.executeCommand("find", {id}))
|
|
5164
|
+
}
|
|
5165
|
+
|
|
4918
5166
|
/**
|
|
4919
5167
|
* Returns the attachment id.
|
|
4920
5168
|
* @returns {string} - Attachment id.
|