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
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import AuthorizationBaseResource from "../authorization/base-resource.js"
|
|
4
4
|
import * as inflection from "inflection"
|
|
5
5
|
import isPlainObject from "../utils/plain-object.js"
|
|
6
|
+
import {modelPrimaryKeyConditions, readModelPrimaryKeyValue, scalarModelPrimaryKeyValue} from "../utils/model-primary-key.js"
|
|
6
7
|
import VelociousError from "../velocious-error.js"
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Backend or frontend model class bound to a frontend-model resource.
|
|
10
|
-
* @typedef {import("../authorization/base-resource.js").AuthorizationResourceModelClass & {attachmentDefinitions: () => Record<string, import("../configuration-types.js").FrontendModelAttachmentConfiguration>, primaryKey: () =>
|
|
11
|
+
* @typedef {import("../authorization/base-resource.js").AuthorizationResourceModelClass & {attachmentDefinitions: () => Record<string, import("../configuration-types.js").FrontendModelAttachmentConfiguration>, primaryKey: () => import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition}} FrontendModelResourceModelClass
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -15,6 +16,12 @@ import VelociousError from "../velocious-error.js"
|
|
|
15
16
|
* @typedef {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} FrontendModelResourceAction
|
|
16
17
|
*/
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Options for building an authorized frontend-model resource query.
|
|
21
|
+
* @typedef {object} FrontendModelResourceAuthorizedQueryOptions
|
|
22
|
+
* @property {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [ruleQueryFactory] - Query factory used when authorization must target a captured record snapshot.
|
|
23
|
+
*/
|
|
24
|
+
|
|
18
25
|
/**
|
|
19
26
|
* Frontend-model controller methods used by resources.
|
|
20
27
|
* @typedef {import("../controller.js").default & {
|
|
@@ -23,8 +30,8 @@ import VelociousError from "../velocious-error.js"
|
|
|
23
30
|
* applyFrontendModelSearch: (args: {query: import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>, search: FrontendModelResourceSearch}) => void,
|
|
24
31
|
* applyFrontendModelSort: (args: {query: import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>, sort: FrontendModelResourceSort}) => void,
|
|
25
32
|
* frontendModelAbilityAction: (action: FrontendModelResourceAction) => string,
|
|
26
|
-
* frontendModelAbilityAuthorizedQuery: (action: FrontendModelResourceAction) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
27
|
-
* frontendModelAuthorizedQuery: (action: FrontendModelResourceAction) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
33
|
+
* frontendModelAbilityAuthorizedQuery: (action: FrontendModelResourceAction, options?: FrontendModelResourceAuthorizedQueryOptions) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
34
|
+
* frontendModelAuthorizedQuery: (action: FrontendModelResourceAction, options?: FrontendModelResourceAuthorizedQueryOptions) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
28
35
|
* frontendModelIndexQuery: (options?: FrontendModelResourceIndexQueryOptions & {resource?: Pick<FrontendModelBaseResource<FrontendModelResourceModelClass>, "applyFrontendModelIndexPagination" | "applyFrontendModelIndexSearch" | "applyFrontendModelIndexSort">}) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
29
36
|
* frontendModelParams: () => import("../configuration-types.js").VelociousParams,
|
|
30
37
|
* frontendModelPreload: () => import("../database/query/index.js").NestedPreloadRecord | null,
|
|
@@ -175,7 +182,7 @@ import VelociousError from "../velocious-error.js"
|
|
|
175
182
|
|
|
176
183
|
/**
|
|
177
184
|
* Normalized nested attributes entry.
|
|
178
|
-
* @typedef {FrontendModelResourceAttributePayload & {id?:
|
|
185
|
+
* @typedef {FrontendModelResourceAttributePayload & {id?: import("../utils/model-primary-key.js").ModelPrimaryKeyValue, _destroy?: boolean, attributes?: FrontendModelResourceAttributePayload, attachments?: FrontendModelResourceAttributePayload, nestedAttributes?: FrontendModelResourceAttributePayload}} FrontendModelResourceNestedEntry
|
|
179
186
|
*/
|
|
180
187
|
|
|
181
188
|
/**
|
|
@@ -217,7 +224,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
217
224
|
static relationships = undefined
|
|
218
225
|
/** @type {string | undefined} */
|
|
219
226
|
static modelName = undefined
|
|
220
|
-
/** @type {string | undefined} */
|
|
227
|
+
/** @type {string | string[] | undefined} */
|
|
221
228
|
static primaryKey = undefined
|
|
222
229
|
/** @type {import("../configuration-types.js").FrontendModelResourceServerConfiguration | undefined} */
|
|
223
230
|
static server = undefined
|
|
@@ -468,7 +475,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
468
475
|
if (collectionCommands) config.collectionCommands = /** @type {string[]} */ (collectionCommands)
|
|
469
476
|
if (memberCommands) config.memberCommands = /** @type {string[]} */ (memberCommands)
|
|
470
477
|
if (modelName) config.modelName = /** @type {string} */ (modelName)
|
|
471
|
-
if (primaryKey) config.primaryKey = /** @type {string} */ (primaryKey)
|
|
478
|
+
if (primaryKey) config.primaryKey = /** @type {string | string[]} */ (primaryKey)
|
|
472
479
|
if (relationships) config.relationships = /** @type {string[]} */ (relationships)
|
|
473
480
|
if (server) config.server = /** @type {import("../configuration-types.js").FrontendModelResourceServerConfiguration} */ (server)
|
|
474
481
|
if (sync !== undefined) config.sync = /** @type {import("../configuration-types.js").FrontendModelResourceSyncConfiguration | boolean} */ (sync)
|
|
@@ -476,6 +483,23 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
476
483
|
return config
|
|
477
484
|
}
|
|
478
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Resolves the client-facing resource primary key from explicit resource config or the backing model.
|
|
488
|
+
* @param {import("../configuration-types.js").FrontendModelResourceConfiguration | import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} resourceConfiguration - Resource configuration.
|
|
489
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} - Client-facing primary key.
|
|
490
|
+
*/
|
|
491
|
+
static resolvedPrimaryKey(resourceConfiguration) {
|
|
492
|
+
if (resourceConfiguration.primaryKey) return resourceConfiguration.primaryKey
|
|
493
|
+
if (!this.ModelClass) return "id"
|
|
494
|
+
|
|
495
|
+
const modelClass = /** @type {typeof import("../database/record/index.js").default} */ (this.modelClass())
|
|
496
|
+
const modelPrimaryKey = modelClass.primaryKey()
|
|
497
|
+
|
|
498
|
+
return Array.isArray(modelPrimaryKey)
|
|
499
|
+
? modelPrimaryKey.map((columnName) => modelClass.resolveAttributeName(columnName) || columnName)
|
|
500
|
+
: modelClass.resolveAttributeName(modelPrimaryKey) || modelPrimaryKey
|
|
501
|
+
}
|
|
502
|
+
|
|
479
503
|
/**
|
|
480
504
|
* Runs controller instance.
|
|
481
505
|
* @returns {import("../controller.js").default} - Controller instance.
|
|
@@ -671,12 +695,11 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
671
695
|
*/
|
|
672
696
|
async findSyncRecord({ability = this.ability, forDelete = false, mutation}) {
|
|
673
697
|
const ModelClass = this.databaseModelClass()
|
|
674
|
-
const primaryKey = ModelClass.primaryKey()
|
|
675
698
|
const query = ability
|
|
676
699
|
? ModelClass.accessibleFor(this.syncAbilityAction(forDelete ? "destroy" : "update"), ability)
|
|
677
700
|
: ModelClass.where({})
|
|
678
701
|
|
|
679
|
-
return await query.findBy(
|
|
702
|
+
return await query.findBy(modelPrimaryKeyConditions(this.primaryKey(), mutation.resourceId))
|
|
680
703
|
}
|
|
681
704
|
|
|
682
705
|
/**
|
|
@@ -863,18 +886,23 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
863
886
|
|
|
864
887
|
/**
|
|
865
888
|
* Runs primary key.
|
|
866
|
-
* @returns {
|
|
889
|
+
* @returns {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} - Primary key.
|
|
867
890
|
*/
|
|
868
|
-
primaryKey() {
|
|
891
|
+
primaryKey() {
|
|
892
|
+
const ResourceClass = /** @type {typeof FrontendModelBaseResource} */ (this.constructor)
|
|
893
|
+
|
|
894
|
+
return ResourceClass.resolvedPrimaryKey(this.resourceConfiguration())
|
|
895
|
+
}
|
|
869
896
|
|
|
870
897
|
/**
|
|
871
898
|
* Runs authorized query.
|
|
872
899
|
* @param {FrontendModelResourceAction} action - Ability action.
|
|
900
|
+
* @param {FrontendModelResourceAuthorizedQueryOptions} [options] - Authorization query options.
|
|
873
901
|
* @returns {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} - Authorized query.
|
|
874
902
|
*/
|
|
875
|
-
authorizedQuery(action) {
|
|
903
|
+
authorizedQuery(action, options) {
|
|
876
904
|
// Narrows the controller query to this resource's model class.
|
|
877
|
-
return /** @type {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} */ (this.typedControllerInstance().frontendModelAbilityAuthorizedQuery(action))
|
|
905
|
+
return /** @type {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} */ (this.typedControllerInstance().frontendModelAbilityAuthorizedQuery(action, options))
|
|
878
906
|
}
|
|
879
907
|
|
|
880
908
|
/**
|
|
@@ -988,7 +1016,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
988
1016
|
/**
|
|
989
1017
|
* Runs find.
|
|
990
1018
|
* @param {"find" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Action.
|
|
991
|
-
* @param {
|
|
1019
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} id - Record id.
|
|
992
1020
|
* @returns {Promise<import("../database/record/index.js").default | null>} - Located model.
|
|
993
1021
|
*/
|
|
994
1022
|
async find(action, id) {
|
|
@@ -999,7 +1027,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
999
1027
|
query = query.preload(preload)
|
|
1000
1028
|
}
|
|
1001
1029
|
|
|
1002
|
-
return await query.findBy(
|
|
1030
|
+
return await query.findBy(modelPrimaryKeyConditions(this.primaryKey(), id))
|
|
1003
1031
|
}
|
|
1004
1032
|
|
|
1005
1033
|
/**
|
|
@@ -1282,7 +1310,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1282
1310
|
* @param {FrontendModelResourcePayloadValue} args.rawEntries - Raw nested entries from the request payload.
|
|
1283
1311
|
* @param {{attributes: string[], nested: Record<string, ReturnType<typeof JSON.parse>>}} args.childPermit - Parsed child permit.
|
|
1284
1312
|
* @param {FrontendModelResourceController | null | undefined} args.controller - Controller instance for child resource lookup.
|
|
1285
|
-
* @returns {{ability: import("../authorization/ability.js").default | undefined, childResource: FrontendModelBaseResource, childResourceConfig: FrontendModelResolvedResourceConfiguration, childWritableAttributes: string[], destroyPermitted: boolean, entries: Array<FrontendModelResourceNestedEntry>, relationship: import("../database/record/relationships/base.js").default, targetModelClass: typeof import("../database/record/index.js").default}} Nested relationship context.
|
|
1313
|
+
* @returns {{ability: import("../authorization/ability.js").default | undefined, childPrimaryKey: import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition, childResource: FrontendModelBaseResource, childResourceConfig: FrontendModelResolvedResourceConfiguration, childWritableAttributes: string[], destroyPermitted: boolean, entries: Array<FrontendModelResourceNestedEntry>, relationship: import("../database/record/relationships/base.js").default, targetModelClass: typeof import("../database/record/index.js").default}} Nested relationship context.
|
|
1286
1314
|
*/
|
|
1287
1315
|
_nestedRelationshipContext({parent, relationshipName, rawEntries, childPermit, controller}) {
|
|
1288
1316
|
if (!controller) {
|
|
@@ -1334,9 +1362,10 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1334
1362
|
params: controller.frontendModelParams(),
|
|
1335
1363
|
resourceConfiguration: childResourceConfig.resourceConfiguration
|
|
1336
1364
|
})
|
|
1365
|
+
const childPrimaryKey = childResource.primaryKey()
|
|
1337
1366
|
const childWritableAttributes = childPermit.attributes.filter((name) => name !== "_destroy")
|
|
1338
1367
|
const entries = rawNormalizedEntries
|
|
1339
|
-
.map((entry) => this._normalizeNestedRelationshipEntry({childPermit, entry, relationshipName, targetModelClass}))
|
|
1368
|
+
.map((entry) => this._normalizeNestedRelationshipEntry({childPermit, childPrimaryKey, entry, relationshipName, targetModelClass}))
|
|
1340
1369
|
.filter((entry) => {
|
|
1341
1370
|
if (typeof modelAcceptance.rejectIf !== "function") return true
|
|
1342
1371
|
|
|
@@ -1345,6 +1374,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1345
1374
|
|
|
1346
1375
|
return {
|
|
1347
1376
|
ability: controller.currentAbility() || this.ability,
|
|
1377
|
+
childPrimaryKey,
|
|
1348
1378
|
childResource,
|
|
1349
1379
|
childResourceConfig,
|
|
1350
1380
|
childWritableAttributes,
|
|
@@ -1400,12 +1430,13 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1400
1430
|
* fields (`{name, file, commentsAttributes}`).
|
|
1401
1431
|
* @param {object} args - Normalization inputs.
|
|
1402
1432
|
* @param {{attributes: string[], nested: Record<string, ReturnType<typeof JSON.parse>>}} args.childPermit - Parsed child permit spec.
|
|
1433
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} args.childPrimaryKey - Resolved child resource primary key.
|
|
1403
1434
|
* @param {FrontendModelResourceNestedEntry} args.entry - Raw nested entry.
|
|
1404
1435
|
* @param {string} args.relationshipName - Relationship name for error messages.
|
|
1405
1436
|
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
1406
1437
|
* @returns {FrontendModelResourceNestedEntry} Normalized nested entry.
|
|
1407
1438
|
*/
|
|
1408
|
-
_normalizeNestedRelationshipEntry({childPermit, entry, relationshipName, targetModelClass}) {
|
|
1439
|
+
_normalizeNestedRelationshipEntry({childPermit, childPrimaryKey, entry, relationshipName, targetModelClass}) {
|
|
1409
1440
|
/** @type {FrontendModelResourceAttributePayload} */
|
|
1410
1441
|
const attributes = {}
|
|
1411
1442
|
/** @type {FrontendModelResourceAttributePayload} */
|
|
@@ -1418,11 +1449,16 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1418
1449
|
|
|
1419
1450
|
for (const [attributeName, value] of Object.entries(entry)) {
|
|
1420
1451
|
if (attributeName === "id") {
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1423
|
-
|
|
1452
|
+
if (Array.isArray(childPrimaryKey)) {
|
|
1453
|
+
modelPrimaryKeyConditions(childPrimaryKey, value)
|
|
1454
|
+
normalized.id = /** @type {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} */ (value)
|
|
1455
|
+
} else {
|
|
1456
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
1457
|
+
throw new Error(`nestedAttributes['${relationshipName}'] entry id must be a string or number.`)
|
|
1458
|
+
}
|
|
1424
1459
|
|
|
1425
|
-
|
|
1460
|
+
normalized.id = value
|
|
1461
|
+
}
|
|
1426
1462
|
continue
|
|
1427
1463
|
}
|
|
1428
1464
|
|
|
@@ -1520,6 +1556,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1520
1556
|
const existing = await this._findNestedRecord({
|
|
1521
1557
|
ability: context.ability,
|
|
1522
1558
|
action: "destroy",
|
|
1559
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1523
1560
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1524
1561
|
id,
|
|
1525
1562
|
relationshipName,
|
|
@@ -1536,6 +1573,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1536
1573
|
? await this._findNestedRecord({
|
|
1537
1574
|
ability: context.ability,
|
|
1538
1575
|
action: "update",
|
|
1576
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1539
1577
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1540
1578
|
id,
|
|
1541
1579
|
relationshipName,
|
|
@@ -1555,6 +1593,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1555
1593
|
await this._authorizeCreatedChild({
|
|
1556
1594
|
ability: context.ability,
|
|
1557
1595
|
child,
|
|
1596
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1558
1597
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1559
1598
|
relationshipName,
|
|
1560
1599
|
targetModelClass: context.targetModelClass
|
|
@@ -1565,7 +1604,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1565
1604
|
await context.childResource._applyNestedAttributes(child, entry.nestedAttributes, controller, childPermit)
|
|
1566
1605
|
}
|
|
1567
1606
|
|
|
1568
|
-
parent.setAttribute(foreignKey, child.id())
|
|
1607
|
+
parent.setAttribute(foreignKey, scalarModelPrimaryKeyValue(child.id(), `Nested belongs-to write for ${child.getModelClass().name}`))
|
|
1569
1608
|
}
|
|
1570
1609
|
}
|
|
1571
1610
|
}
|
|
@@ -1645,6 +1684,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1645
1684
|
const existing = await this._findScopedChild({
|
|
1646
1685
|
ability: context.ability,
|
|
1647
1686
|
action: "destroy",
|
|
1687
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1648
1688
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1649
1689
|
id,
|
|
1650
1690
|
parent,
|
|
@@ -1666,6 +1706,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1666
1706
|
const existing = await this._findScopedChild({
|
|
1667
1707
|
ability: context.ability,
|
|
1668
1708
|
action: "update",
|
|
1709
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1669
1710
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1670
1711
|
id,
|
|
1671
1712
|
parent,
|
|
@@ -1702,6 +1743,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1702
1743
|
await this._authorizeCreatedChild({
|
|
1703
1744
|
ability: context.ability,
|
|
1704
1745
|
child,
|
|
1746
|
+
childPrimaryKey: context.childPrimaryKey,
|
|
1705
1747
|
childResourceConfiguration: context.childResourceConfig.resourceConfiguration,
|
|
1706
1748
|
relationshipName,
|
|
1707
1749
|
targetModelClass: context.targetModelClass
|
|
@@ -1760,7 +1802,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1760
1802
|
_parentLinkAttributesForNestedChild({parent, relationship, targetModelClass}) {
|
|
1761
1803
|
const foreignKey = this._foreignKeyAttributeForModel(relationship, targetModelClass)
|
|
1762
1804
|
/** @type {Record<string, string | number>} */
|
|
1763
|
-
const attributes = {[foreignKey]:
|
|
1805
|
+
const attributes = {[foreignKey]: scalarModelPrimaryKeyValue(parent.id(), `Nested child write for ${parent.getModelClass().name}`)}
|
|
1764
1806
|
|
|
1765
1807
|
if (relationship.getPolymorphic()) {
|
|
1766
1808
|
const typeAttribute = this._polymorphicTypeAttributeForModel(relationship, targetModelClass)
|
|
@@ -1788,18 +1830,18 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1788
1830
|
* @param {object} args - Lookup inputs.
|
|
1789
1831
|
* @param {import("../authorization/ability.js").default | undefined} args.ability - Current ability.
|
|
1790
1832
|
* @param {"update" | "destroy"} args.action - Frontend action.
|
|
1833
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} args.childPrimaryKey - Resolved child resource primary key.
|
|
1791
1834
|
* @param {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.childResourceConfiguration - Child resource configuration.
|
|
1792
|
-
* @param {
|
|
1835
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} args.id - Child id from the payload.
|
|
1793
1836
|
* @param {string} args.relationshipName - Parent's relationship name for error messages.
|
|
1794
1837
|
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
1795
1838
|
* @returns {Promise<import("../database/record/index.js").default>} Authorized child model.
|
|
1796
1839
|
*/
|
|
1797
|
-
async _findNestedRecord({ability, action, childResourceConfiguration, id, relationshipName, targetModelClass}) {
|
|
1798
|
-
const primaryKey = targetModelClass.primaryKey()
|
|
1840
|
+
async _findNestedRecord({ability, action, childPrimaryKey, childResourceConfiguration, id, relationshipName, targetModelClass}) {
|
|
1799
1841
|
const query = ability
|
|
1800
1842
|
? targetModelClass.accessibleFor(this._resolveChildAbilityAction(childResourceConfiguration, action), ability)
|
|
1801
1843
|
: targetModelClass.where({})
|
|
1802
|
-
const existing = await query.findBy(
|
|
1844
|
+
const existing = await query.findBy(modelPrimaryKeyConditions(childPrimaryKey, id))
|
|
1803
1845
|
|
|
1804
1846
|
if (!existing) {
|
|
1805
1847
|
throw new Error(`Cannot ${action} nested ${relationshipName}[id=${id}]: record not found or not authorized.`)
|
|
@@ -1842,17 +1884,25 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1842
1884
|
* @param {object} args - Arguments.
|
|
1843
1885
|
* @param {import("../authorization/ability.js").default | undefined} args.ability - Current ability.
|
|
1844
1886
|
* @param {"update" | "destroy"} args.action - Frontend action.
|
|
1887
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} args.childPrimaryKey - Resolved child resource primary key.
|
|
1845
1888
|
* @param {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.childResourceConfiguration - Child resource configuration.
|
|
1846
|
-
* @param {
|
|
1889
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyValue} args.id - Child id from the payload.
|
|
1847
1890
|
* @param {import("../database/record/index.js").default} args.parent - Parent model instance.
|
|
1848
1891
|
* @param {Record<string, string | number>} args.parentLinkAttributes - Attributes that scope the child to the parent.
|
|
1849
1892
|
* @param {string} args.relationshipName - Parent's relationship name (for error messages).
|
|
1850
1893
|
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
1851
1894
|
* @returns {Promise<import("../database/record/index.js").default>} - Authorized, parent-linked child model.
|
|
1852
1895
|
*/
|
|
1853
|
-
async _findScopedChild({ability, action, childResourceConfiguration, id, parent, parentLinkAttributes, relationshipName, targetModelClass}) {
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1896
|
+
async _findScopedChild({ability, action, childPrimaryKey, childResourceConfiguration, id, parent, parentLinkAttributes, relationshipName, targetModelClass}) {
|
|
1897
|
+
const identityConditions = modelPrimaryKeyConditions(childPrimaryKey, id)
|
|
1898
|
+
|
|
1899
|
+
for (const [attributeName, parentValue] of Object.entries(parentLinkAttributes)) {
|
|
1900
|
+
if (Object.prototype.hasOwnProperty.call(identityConditions, attributeName) && identityConditions[attributeName] !== parentValue) {
|
|
1901
|
+
throw new Error(`Cannot ${action} nested ${relationshipName}[id=${id}]: identity field '${attributeName}' does not match parent ${parent.getModelClass().name}[id=${parent.id()}].`)
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const lookup = {...identityConditions, ...parentLinkAttributes}
|
|
1856
1906
|
const query = ability
|
|
1857
1907
|
? targetModelClass.accessibleFor(this._resolveChildAbilityAction(childResourceConfiguration, action), ability)
|
|
1858
1908
|
: targetModelClass.where({})
|
|
@@ -1873,22 +1923,22 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1873
1923
|
* @param {object} args - Arguments.
|
|
1874
1924
|
* @param {import("../authorization/ability.js").default | undefined} args.ability - Current ability.
|
|
1875
1925
|
* @param {import("../database/record/index.js").default} args.child - Child model instance just created.
|
|
1926
|
+
* @param {import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition} args.childPrimaryKey - Resolved child resource primary key.
|
|
1876
1927
|
* @param {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.childResourceConfiguration - Child resource configuration.
|
|
1877
1928
|
* @param {string} args.relationshipName - Parent's relationship name (for error messages).
|
|
1878
1929
|
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
1879
1930
|
* @returns {Promise<void>}
|
|
1880
1931
|
*/
|
|
1881
|
-
async _authorizeCreatedChild({ability, child, childResourceConfiguration, relationshipName, targetModelClass}) {
|
|
1932
|
+
async _authorizeCreatedChild({ability, child, childPrimaryKey, childResourceConfiguration, relationshipName, targetModelClass}) {
|
|
1882
1933
|
if (!ability) return
|
|
1883
1934
|
|
|
1884
1935
|
const abilityAction = this._resolveChildAbilityAction(childResourceConfiguration, "create")
|
|
1885
|
-
const
|
|
1886
|
-
const
|
|
1936
|
+
const identity = readModelPrimaryKeyValue(childPrimaryKey, (attributeName) => child.readAttribute(attributeName))
|
|
1937
|
+
const authorizedChild = await targetModelClass
|
|
1887
1938
|
.accessibleFor(abilityAction, ability)
|
|
1888
|
-
.
|
|
1889
|
-
.pluck(primaryKey)
|
|
1939
|
+
.findBy(modelPrimaryKeyConditions(childPrimaryKey, identity))
|
|
1890
1940
|
|
|
1891
|
-
if (
|
|
1941
|
+
if (!authorizedChild) {
|
|
1892
1942
|
throw new Error(`Nested create on ${relationshipName}[${targetModelClass.name}] not authorized.`)
|
|
1893
1943
|
}
|
|
1894
1944
|
}
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import FrontendModelBaseResource from "./base-resource.js"
|
|
4
4
|
import VelociousAttachment from "../database/record/attachments/attachment-record.js"
|
|
5
|
+
import {frontendModelResourcesForBackendProject} from "../frontend-models/resource-definition.js"
|
|
5
6
|
import isPlainObject from "../utils/plain-object.js"
|
|
7
|
+
import {modelPrimaryKeyConditions, modelPrimaryKeyValueFromCacheKey} from "../utils/model-primary-key.js"
|
|
8
|
+
|
|
9
|
+
/** @typedef {{name: string, recordId: string, recordType: string, resourceName: string | null}} AttachmentOwnerScope */
|
|
10
|
+
/** @typedef {{backendProject: import("../configuration-types.js").BackendProjectConfiguration, modelName: string, resourceClass: import("../configuration-types.js").FrontendModelResourceClassType, resourceConfiguration: import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration}} AttachmentOwnerResource */
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Framework-owned frontend resource exposing safe attachment metadata while
|
|
@@ -59,6 +64,20 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Loads attachment metadata after removing the authorization-only resource name from database filters.
|
|
69
|
+
* @returns {Promise<VelociousAttachment[]>} - Attachment metadata rows.
|
|
70
|
+
*/
|
|
71
|
+
async records() {
|
|
72
|
+
const controller = /** @type {import("../frontend-model-controller.js").default} */ (this.controllerInstance())
|
|
73
|
+
const params = controller.frontendModelParams()
|
|
74
|
+
const where = {...this.requiredWhereFromParams()}
|
|
75
|
+
|
|
76
|
+
delete where.resourceName
|
|
77
|
+
|
|
78
|
+
return await controller.withFrontendModelParams({...params, where}, async () => await this.indexQuery().toArray())
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
/**
|
|
63
82
|
* Runs find.
|
|
64
83
|
* @param {"find" | "update" | "destroy" | "attach" | "download" | "url"} action - Action.
|
|
@@ -96,20 +115,32 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
96
115
|
|
|
97
116
|
/**
|
|
98
117
|
* Returns a validated owner scope from frontend-model where params.
|
|
99
|
-
* @returns {
|
|
118
|
+
* @returns {AttachmentOwnerScope} - Attachment owner scope.
|
|
100
119
|
*/
|
|
101
120
|
requiredOwnerScopeFromParams() {
|
|
102
|
-
const where = this.
|
|
103
|
-
|
|
104
|
-
if (!isPlainObject(where)) {
|
|
105
|
-
throw new Error("VelociousAttachment index requires recordType, recordId, and name where filters")
|
|
106
|
-
}
|
|
121
|
+
const where = this.requiredWhereFromParams()
|
|
122
|
+
const recordType = this.requiredSingleWhereValue({attributeName: "recordType", where})
|
|
107
123
|
|
|
108
124
|
return {
|
|
109
125
|
name: this.requiredSingleWhereValue({attributeName: "name", where}),
|
|
110
126
|
recordId: this.requiredSingleWhereValue({attributeName: "recordId", where}),
|
|
111
|
-
recordType
|
|
127
|
+
recordType,
|
|
128
|
+
resourceName: this.requiredSingleWhereValue({attributeName: "resourceName", where})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns the required attachment metadata where object.
|
|
134
|
+
* @returns {Record<string, ReturnType<typeof JSON.parse>>} - Attachment where filters.
|
|
135
|
+
*/
|
|
136
|
+
requiredWhereFromParams() {
|
|
137
|
+
const where = this.params().where
|
|
138
|
+
|
|
139
|
+
if (!isPlainObject(where)) {
|
|
140
|
+
throw new Error("VelociousAttachment index requires resourceName, recordType, recordId, and name where filters")
|
|
112
141
|
}
|
|
142
|
+
|
|
143
|
+
return where
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
/**
|
|
@@ -130,57 +161,91 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
130
161
|
/**
|
|
131
162
|
* Builds owner scope from a stored attachment row.
|
|
132
163
|
* @param {VelociousAttachment} attachment - Attachment row.
|
|
133
|
-
* @returns {
|
|
164
|
+
* @returns {AttachmentOwnerScope} - Owner scope.
|
|
134
165
|
*/
|
|
135
166
|
ownerScopeFromAttachment(attachment) {
|
|
136
167
|
return {
|
|
137
168
|
name: attachment.name(),
|
|
138
169
|
recordId: attachment.recordId(),
|
|
139
|
-
recordType: attachment.recordType()
|
|
170
|
+
recordType: attachment.recordType(),
|
|
171
|
+
resourceName: null
|
|
140
172
|
}
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
/**
|
|
144
176
|
* Checks whether the current ability can read the attachment owner.
|
|
145
|
-
* @param {
|
|
177
|
+
* @param {AttachmentOwnerScope} ownerScope - Owner scope.
|
|
146
178
|
* @returns {Promise<boolean>} - Whether owner is readable.
|
|
147
179
|
*/
|
|
148
180
|
async attachmentOwnerAuthorized(ownerScope) {
|
|
149
181
|
const controller = /** @type {import("../frontend-model-controller.js").default} */ (this.controllerInstance())
|
|
150
|
-
const
|
|
182
|
+
const ownerResources = this.attachmentOwnerResources({controller, ownerScope})
|
|
151
183
|
|
|
152
|
-
if (
|
|
153
|
-
throw new Error(`No frontend model resource configured for attachment owner ${ownerScope.recordType}`)
|
|
184
|
+
if (ownerResources.length < 1) {
|
|
185
|
+
throw new Error(`No frontend model resource configured for attachment owner ${ownerScope.resourceName ?? ownerScope.recordType}`)
|
|
154
186
|
}
|
|
155
187
|
|
|
156
|
-
const
|
|
188
|
+
for (const ownerResource of ownerResources) {
|
|
189
|
+
const ownerModelClass = controller.frontendModelResourceModelClass(ownerResource)
|
|
190
|
+
|
|
191
|
+
await controller.ensureFrontendModelRecordClassInitialized(ownerModelClass)
|
|
192
|
+
|
|
193
|
+
const abilityAction = ownerResource.resourceConfiguration.abilities.find || ownerResource.resourceConfiguration.abilities.index || "read"
|
|
194
|
+
const primaryKey = ownerModelClass.primaryKey()
|
|
195
|
+
const ownerIdentity = modelPrimaryKeyValueFromCacheKey(primaryKey, ownerScope.recordId)
|
|
196
|
+
const owner = await ownerModelClass
|
|
197
|
+
.accessibleFor(abilityAction, this.ability)
|
|
198
|
+
.findBy(modelPrimaryKeyConditions(primaryKey, ownerIdentity))
|
|
157
199
|
|
|
158
|
-
|
|
159
|
-
throw new Error(`No model class configured for attachment owner ${ownerScope.recordType}`)
|
|
200
|
+
if (owner) return true
|
|
160
201
|
}
|
|
161
202
|
|
|
162
|
-
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
163
205
|
|
|
164
|
-
|
|
165
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Resolves the explicit owner resource for scoped queries or every matching alias for member lookups.
|
|
208
|
+
* @param {object} args - Options object.
|
|
209
|
+
* @param {import("../frontend-model-controller.js").default} args.controller - Frontend-model controller.
|
|
210
|
+
* @param {AttachmentOwnerScope} args.ownerScope - Owner scope.
|
|
211
|
+
* @returns {AttachmentOwnerResource[]} - Matching owner resources.
|
|
212
|
+
*/
|
|
213
|
+
attachmentOwnerResources({controller, ownerScope}) {
|
|
214
|
+
if (ownerScope.resourceName) {
|
|
215
|
+
const ownerResource = this.attachmentOwnerResource({
|
|
216
|
+
controller,
|
|
217
|
+
ownerScope: {...ownerScope, resourceName: ownerScope.resourceName}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return ownerResource ? [ownerResource] : []
|
|
166
221
|
}
|
|
167
222
|
|
|
168
|
-
|
|
223
|
+
const ownerResources = []
|
|
224
|
+
|
|
225
|
+
for (const backendProject of controller.getConfiguration().getBackendProjects()) {
|
|
226
|
+
for (const resourceName of Object.keys(frontendModelResourcesForBackendProject(backendProject))) {
|
|
227
|
+
const ownerResource = controller.frontendModelResourceConfigurationForBackendProjectModelName({backendProject, modelName: resourceName})
|
|
228
|
+
|
|
229
|
+
if (!ownerResource) continue
|
|
230
|
+
|
|
231
|
+
const ownerModelClass = controller.frontendModelResourceModelClass(ownerResource)
|
|
169
232
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.accessibleFor(abilityAction, this.ability)
|
|
173
|
-
.findBy({[ownerModelClass.primaryKey()]: ownerScope.recordId})
|
|
233
|
+
if (ownerModelClass.getModelName() !== ownerScope.recordType) continue
|
|
234
|
+
if (!ownerResource.resourceConfiguration.attachments?.[ownerScope.name]) continue
|
|
174
235
|
|
|
175
|
-
|
|
236
|
+
ownerResources.push(ownerResource)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return ownerResources
|
|
176
241
|
}
|
|
177
242
|
|
|
178
243
|
/**
|
|
179
244
|
* Finds the frontend-model resource that owns an attachment scope.
|
|
180
245
|
* @param {object} args - Options object.
|
|
181
246
|
* @param {import("../frontend-model-controller.js").default} args.controller - Frontend-model controller.
|
|
182
|
-
* @param {{name: string, recordId: string, recordType: string}} args.ownerScope - Owner scope.
|
|
183
|
-
* @returns {
|
|
247
|
+
* @param {{name: string, recordId: string, recordType: string, resourceName: string}} args.ownerScope - Owner scope.
|
|
248
|
+
* @returns {AttachmentOwnerResource | null} - Owner resource configuration.
|
|
184
249
|
*/
|
|
185
250
|
attachmentOwnerResource({controller, ownerScope}) {
|
|
186
251
|
const backendProjects = controller.getConfiguration().getBackendProjects()
|
|
@@ -191,7 +256,7 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
191
256
|
for (const backendProject of backendProjects) {
|
|
192
257
|
const ownerResource = controller.frontendModelResourceConfigurationForBackendProjectModelName({
|
|
193
258
|
backendProject,
|
|
194
|
-
modelName: ownerScope.
|
|
259
|
+
modelName: ownerScope.resourceName
|
|
195
260
|
})
|
|
196
261
|
|
|
197
262
|
if (!ownerResource) continue
|
|
@@ -199,10 +264,12 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
199
264
|
const ownerModelClass = controller.frontendModelResourceModelClass(ownerResource)
|
|
200
265
|
|
|
201
266
|
if (!ownerModelClass) {
|
|
202
|
-
throw new Error(`No model class configured for attachment owner ${ownerScope.
|
|
267
|
+
throw new Error(`No model class configured for attachment owner ${ownerScope.resourceName}`)
|
|
203
268
|
}
|
|
204
269
|
|
|
205
|
-
|
|
270
|
+
if (ownerModelClass.getModelName() !== ownerScope.recordType) continue
|
|
271
|
+
|
|
272
|
+
const attachmentDefinitions = ownerResource.resourceConfiguration.attachments || {}
|
|
206
273
|
|
|
207
274
|
if (attachmentDefinitions[ownerScope.name]) return ownerResource
|
|
208
275
|
|
|
@@ -210,7 +277,7 @@ export default class VelociousAttachmentResource extends FrontendModelBaseResour
|
|
|
210
277
|
}
|
|
211
278
|
|
|
212
279
|
if (resourceWithoutAttachment) {
|
|
213
|
-
throw new Error(`No attachment '${ownerScope.name}' configured for ${ownerScope.
|
|
280
|
+
throw new Error(`No attachment '${ownerScope.name}' configured for ${ownerScope.resourceName}`)
|
|
214
281
|
}
|
|
215
282
|
|
|
216
283
|
return null
|