velocious 1.0.641 → 1.0.643
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 +25 -2
- package/build/authorization/base-resource.js +18 -5
- package/build/background-jobs/main.js +21 -6
- package/build/background-jobs/store.js +116 -7
- package/build/configuration-types.js +34 -2
- package/build/database/record/index.js +42 -11
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +17 -3
- package/build/frontend-model-controller.js +10 -5
- package/build/frontend-model-resource/base-resource.js +95 -41
- package/build/frontend-models/base.js +1 -1
- package/build/src/authorization/base-resource.d.ts +19 -6
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +16 -6
- package/build/src/background-jobs/main.d.ts +3 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +26 -7
- package/build/src/background-jobs/store.d.ts +16 -0
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +103 -7
- package/build/src/configuration-types.d.ts +72 -5
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +31 -3
- package/build/src/database/record/index.d.ts +44 -46
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +39 -12
- 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 +19 -4
- package/build/src/frontend-model-controller.d.ts +1 -1
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +9 -6
- package/build/src/frontend-model-resource/base-resource.d.ts +66 -32
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +89 -42
- package/build/src/frontend-models/base.d.ts +1 -0
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +2 -2
- package/build/src/sync/sync-api-controller.d.ts.map +1 -1
- package/build/src/sync/sync-api-controller.js +6 -5
- package/build/src/sync/sync-envelope-replay-service.d.ts +1 -1
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +3 -2
- package/build/src/sync/sync-resource-base.d.ts +2 -0
- package/build/src/sync/sync-resource-base.d.ts.map +1 -1
- package/build/src/sync/sync-resource-base.js +3 -1
- package/build/sync/sync-api-controller.js +5 -5
- package/build/sync/sync-envelope-replay-service.js +2 -1
- package/build/sync/sync-resource-base.js +3 -0
- package/package.json +1 -1
- package/src/authorization/base-resource.js +18 -5
- package/src/background-jobs/main.js +21 -6
- package/src/background-jobs/store.js +116 -7
- package/src/configuration-types.js +34 -2
- package/src/database/record/index.js +42 -11
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +17 -3
- package/src/frontend-model-controller.js +10 -5
- package/src/frontend-model-resource/base-resource.js +95 -41
- package/src/frontend-models/base.js +1 -1
- package/src/sync/sync-api-controller.js +5 -5
- package/src/sync/sync-envelope-replay-service.js +2 -1
- package/src/sync/sync-resource-base.js +3 -0
|
@@ -71,6 +71,8 @@ import UUID from "pure-uuid"
|
|
|
71
71
|
* AttachmentDriverConstructor type.
|
|
72
72
|
* @typedef {import("../../configuration-types.js").AttachmentDriverConstructor} AttachmentDriverConstructor
|
|
73
73
|
*/
|
|
74
|
+
/** @typedef {import("../../configuration-types.js").AttachmentSyncConfiguration} AttachmentSyncConfiguration */
|
|
75
|
+
/** @typedef {import("../../configuration-types.js").RecordAttachmentConfiguration} RecordAttachmentConfiguration */
|
|
74
76
|
|
|
75
77
|
/** Stored values that a declared `"boolean"` cast reads back as `true`. */
|
|
76
78
|
const declaredBooleanTruthyValues = new Set([1, true, "1"])
|
|
@@ -238,7 +240,7 @@ class VelociousDatabaseRecord {
|
|
|
238
240
|
static _lifecycleCallbacks = undefined
|
|
239
241
|
/** @type {Record<string, typeof import("./validators/base.js").default> | undefined} */
|
|
240
242
|
static _validatorTypes = undefined
|
|
241
|
-
/** @type {Record<string,
|
|
243
|
+
/** @type {Record<string, RecordAttachmentConfiguration> | undefined} */
|
|
242
244
|
static _attachmentsMap = undefined
|
|
243
245
|
/** @type {Record<string, import("./relationships/base.js").default> | undefined} */
|
|
244
246
|
static _relationships = undefined
|
|
@@ -494,13 +496,13 @@ class VelociousDatabaseRecord {
|
|
|
494
496
|
|
|
495
497
|
/**
|
|
496
498
|
* Runs get attachments map.
|
|
497
|
-
* @returns {Record<string,
|
|
499
|
+
* @returns {Record<string, RecordAttachmentConfiguration>} - Attachment definitions keyed by name.
|
|
498
500
|
*/
|
|
499
501
|
static getAttachmentsMap() {
|
|
500
502
|
if (!this._attachmentsMap) {
|
|
501
503
|
/**
|
|
502
504
|
* Narrows the runtime value to the documented type.
|
|
503
|
-
* @type {Record<string,
|
|
505
|
+
* @type {Record<string, RecordAttachmentConfiguration>} */
|
|
504
506
|
this._attachmentsMap = {}
|
|
505
507
|
}
|
|
506
508
|
|
|
@@ -1165,16 +1167,25 @@ class VelociousDatabaseRecord {
|
|
|
1165
1167
|
|
|
1166
1168
|
/**
|
|
1167
1169
|
* Runs get attachments.
|
|
1168
|
-
* @returns {Record<string,
|
|
1170
|
+
* @returns {Record<string, RecordAttachmentConfiguration>} - Attachment definitions.
|
|
1169
1171
|
*/
|
|
1170
1172
|
static getAttachments() {
|
|
1171
1173
|
return this.getAttachmentsMap()
|
|
1172
1174
|
}
|
|
1173
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* Returns attachment definitions through the model contract shared with
|
|
1178
|
+
* frontend model classes.
|
|
1179
|
+
* @returns {Record<string, RecordAttachmentConfiguration>} - Attachment definitions.
|
|
1180
|
+
*/
|
|
1181
|
+
static attachmentDefinitions() {
|
|
1182
|
+
return this.getAttachmentsMap()
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1174
1185
|
/**
|
|
1175
1186
|
* Runs get attachment by name.
|
|
1176
1187
|
* @param {string} attachmentName - Attachment name.
|
|
1177
|
-
* @returns {
|
|
1188
|
+
* @returns {RecordAttachmentConfiguration} - Attachment definition.
|
|
1178
1189
|
*/
|
|
1179
1190
|
static getAttachmentByName(attachmentName) {
|
|
1180
1191
|
const definition = this.getAttachmentsMap()[attachmentName]
|
|
@@ -1443,14 +1454,34 @@ class VelociousDatabaseRecord {
|
|
|
1443
1454
|
* @param {string} attachmentName - Attachment name.
|
|
1444
1455
|
* @param {object} args - Attachment args.
|
|
1445
1456
|
* @param {string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse>>} [args.driver] - Attachment driver name, class, or instance.
|
|
1457
|
+
* @param {AttachmentSyncConfiguration} [args.sync] - Client-safe synchronized asset policy.
|
|
1446
1458
|
* @param {"hasOne" | "hasMany"} args.type - Attachment type.
|
|
1447
1459
|
* @returns {void} - No return value.
|
|
1448
1460
|
*/
|
|
1449
|
-
static _defineAttachment(attachmentName, {driver, type}) {
|
|
1461
|
+
static _defineAttachment(attachmentName, {driver, sync, type}) {
|
|
1450
1462
|
if (!attachmentName || typeof attachmentName !== "string") throw new Error(`Invalid attachment name: ${attachmentName}`)
|
|
1451
1463
|
if (attachmentName in this.getAttachmentsMap()) throw new Error(`Attachment ${attachmentName} already exists`)
|
|
1452
1464
|
|
|
1453
|
-
|
|
1465
|
+
if (sync) {
|
|
1466
|
+
const {fetch, offlineRequirement, retention, ...restSync} = sync
|
|
1467
|
+
|
|
1468
|
+
restArgsError(restSync)
|
|
1469
|
+
|
|
1470
|
+
if (fetch !== "eager" && fetch !== "on-demand") {
|
|
1471
|
+
throw new Error(`Attachment ${attachmentName} sync fetch must be eager or on-demand`)
|
|
1472
|
+
}
|
|
1473
|
+
if (offlineRequirement !== "optional" && offlineRequirement !== "required") {
|
|
1474
|
+
throw new Error(`Attachment ${attachmentName} offline requirement must be optional or required`)
|
|
1475
|
+
}
|
|
1476
|
+
if (retention !== "durable" && retention !== "evictable") {
|
|
1477
|
+
throw new Error(`Attachment ${attachmentName} sync retention must be durable or evictable`)
|
|
1478
|
+
}
|
|
1479
|
+
if (offlineRequirement === "required" && retention !== "durable") {
|
|
1480
|
+
throw new Error(`Attachment ${attachmentName} required offline assets must use durable retention`)
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
this.getAttachmentsMap()[attachmentName] = {driver, sync, type}
|
|
1454
1485
|
|
|
1455
1486
|
const prototype = /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (/** @type {ReturnType<typeof JSON.parse>} */ (this.prototype))
|
|
1456
1487
|
|
|
@@ -1467,21 +1498,21 @@ class VelociousDatabaseRecord {
|
|
|
1467
1498
|
/**
|
|
1468
1499
|
* Adds a single attachment helper to the model.
|
|
1469
1500
|
* @param {string} attachmentName - Attachment name.
|
|
1470
|
-
* @param {{driver?: string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse
|
|
1501
|
+
* @param {{driver?: string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse>>, sync?: AttachmentSyncConfiguration}} [args] - Attachment options.
|
|
1471
1502
|
* @returns {void} - No return value.
|
|
1472
1503
|
*/
|
|
1473
1504
|
static hasOneAttachment(attachmentName, args = {}) {
|
|
1474
|
-
this._defineAttachment(attachmentName, {driver: args.driver, type: "hasOne"})
|
|
1505
|
+
this._defineAttachment(attachmentName, {driver: args.driver, sync: args.sync, type: "hasOne"})
|
|
1475
1506
|
}
|
|
1476
1507
|
|
|
1477
1508
|
/**
|
|
1478
1509
|
* Adds a collection attachment helper to the model.
|
|
1479
1510
|
* @param {string} attachmentName - Attachment name.
|
|
1480
|
-
* @param {{driver?: string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse
|
|
1511
|
+
* @param {{driver?: string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse>>, sync?: AttachmentSyncConfiguration}} [args] - Attachment options.
|
|
1481
1512
|
* @returns {void} - No return value.
|
|
1482
1513
|
*/
|
|
1483
1514
|
static hasManyAttachments(attachmentName, args = {}) {
|
|
1484
|
-
this._defineAttachment(attachmentName, {driver: args.driver, type: "hasMany"})
|
|
1515
|
+
this._defineAttachment(attachmentName, {driver: args.driver, sync: args.sync, type: "hasMany"})
|
|
1485
1516
|
}
|
|
1486
1517
|
|
|
1487
1518
|
/**
|
|
@@ -5,6 +5,7 @@ import path from "node:path"
|
|
|
5
5
|
import * as inflection from "inflection"
|
|
6
6
|
import {frontendModelResourceIsBuiltIn, frontendModelResourcesWithBuiltInsForBackendProject} from "../../../../../frontend-models/built-in-resources.js"
|
|
7
7
|
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition} from "../../../../../frontend-models/resource-definition.js"
|
|
8
|
+
import {frontendModelResourceInternalConstructor} from "../../../../../frontend-model-resource/base-resource.js"
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Attribute metadata used for generated frontend-model JSDoc.
|
|
@@ -381,7 +382,18 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
381
382
|
? "hasMany"
|
|
382
383
|
: "hasOne"
|
|
383
384
|
|
|
384
|
-
|
|
385
|
+
if (attachmentConfig.sync) {
|
|
386
|
+
fileContent += ` ${attachmentName}: {\n`
|
|
387
|
+
fileContent += " sync: {\n"
|
|
388
|
+
fileContent += ` fetch: ${JSON.stringify(attachmentConfig.sync.fetch)},\n`
|
|
389
|
+
fileContent += ` offlineRequirement: ${JSON.stringify(attachmentConfig.sync.offlineRequirement)},\n`
|
|
390
|
+
fileContent += ` retention: ${JSON.stringify(attachmentConfig.sync.retention)},\n`
|
|
391
|
+
fileContent += " },\n"
|
|
392
|
+
fileContent += ` type: ${JSON.stringify(attachmentType)}\n`
|
|
393
|
+
fileContent += " },\n"
|
|
394
|
+
} else {
|
|
395
|
+
fileContent += ` ${attachmentName}: {type: ${JSON.stringify(attachmentType)}},\n`
|
|
396
|
+
}
|
|
385
397
|
}
|
|
386
398
|
fileContent += " },\n"
|
|
387
399
|
}
|
|
@@ -857,7 +869,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
857
869
|
try {
|
|
858
870
|
const modelClass = resourceClass.modelClass()
|
|
859
871
|
|
|
860
|
-
const
|
|
872
|
+
const ResourceClass = frontendModelResourceInternalConstructor(resourceClass)
|
|
873
|
+
const instance = new ResourceClass({
|
|
861
874
|
ability: undefined,
|
|
862
875
|
context: {},
|
|
863
876
|
locals: {},
|
|
@@ -894,7 +907,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
894
907
|
try {
|
|
895
908
|
const modelClass = resourceClass.modelClass()
|
|
896
909
|
|
|
897
|
-
const
|
|
910
|
+
const ResourceClass = frontendModelResourceInternalConstructor(resourceClass)
|
|
911
|
+
const instance = new ResourceClass({
|
|
898
912
|
ability: undefined,
|
|
899
913
|
context: {},
|
|
900
914
|
locals: {},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {randomUUID} from "node:crypto"
|
|
4
4
|
import * as inflection from "inflection"
|
|
5
5
|
import Controller from "./controller.js"
|
|
6
|
-
import FrontendModelBaseResource from "./frontend-model-resource/base-resource.js"
|
|
6
|
+
import FrontendModelBaseResource, {frontendModelResourceInternalConstructor} from "./frontend-model-resource/base-resource.js"
|
|
7
7
|
import Response from "./http-server/client/response.js"
|
|
8
8
|
import {frontendModelResourcesWithBuiltInsForBackendProject} from "./frontend-models/built-in-resources.js"
|
|
9
9
|
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath, frontendModelResourcesForBackendProject, frontendModelSyncManifestForBackendProjects} from "./frontend-models/resource-definition.js"
|
|
@@ -70,7 +70,7 @@ import {RansackQueryError, normalizeRansackGroup, parseRansackSort} from "./util
|
|
|
70
70
|
* @typedef {object} FrontendModelIndexQueryOptions
|
|
71
71
|
* @property {boolean} [includePagination] - Whether frontend-model pagination params should be applied.
|
|
72
72
|
* @property {boolean} [includeSort] - Whether frontend-model sort params should be applied.
|
|
73
|
-
* @property {import("./frontend-model-resource/base-resource.js").default} [resource] - Resource providing query hooks.
|
|
73
|
+
* @property {Pick<import("./frontend-model-resource/base-resource.js").default<import("./frontend-model-resource/base-resource.js").FrontendModelResourceModelClass>, "applyFrontendModelIndexPagination" | "applyFrontendModelIndexSearch" | "applyFrontendModelIndexSort">} [resource] - Resource providing query hooks.
|
|
74
74
|
*/
|
|
75
75
|
/** @typedef {import("./database/query/model-class-query.js").default & Record<symbol, Set<string> | undefined>} FrontendModelQueryMetadata */
|
|
76
76
|
/**
|
|
@@ -1040,7 +1040,9 @@ export default class FrontendModelController extends Controller {
|
|
|
1040
1040
|
resourceConfiguration: frontendModelResource.resourceConfiguration
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
-
|
|
1043
|
+
const ResourceClass = frontendModelResourceInternalConstructor(frontendModelResource.resourceClass)
|
|
1044
|
+
|
|
1045
|
+
return new ResourceClass(resourceArgs)
|
|
1044
1046
|
}
|
|
1045
1047
|
|
|
1046
1048
|
/**
|
|
@@ -2908,7 +2910,9 @@ export default class FrontendModelController extends Controller {
|
|
|
2908
2910
|
const resourceClass = resourceDefinition ? frontendModelResourceClassFromDefinition(resourceDefinition) : null
|
|
2909
2911
|
|
|
2910
2912
|
if (resourceClass) {
|
|
2911
|
-
|
|
2913
|
+
const ResourceClass = frontendModelResourceInternalConstructor(resourceClass)
|
|
2914
|
+
|
|
2915
|
+
resource = new ResourceClass({
|
|
2912
2916
|
ability: this.currentAbility(),
|
|
2913
2917
|
// Propagate the controller so a related/preloaded model's serialization
|
|
2914
2918
|
// resource can use request context (e.g. `requestBaseUrl()` for signed
|
|
@@ -3985,7 +3989,8 @@ export default class FrontendModelController extends Controller {
|
|
|
3985
3989
|
|
|
3986
3990
|
if (!frontendModelResource) throw frontendSyncReplaySafeError(`Sync replay model is not enabled: ${mutation.model}`)
|
|
3987
3991
|
|
|
3988
|
-
const
|
|
3992
|
+
const ResourceClass = frontendModelResourceInternalConstructor(frontendModelResource.resourceClass)
|
|
3993
|
+
const resource = new ResourceClass({
|
|
3989
3994
|
ability: this.currentAbility(),
|
|
3990
3995
|
controller: this,
|
|
3991
3996
|
context: {
|
|
@@ -5,6 +5,11 @@ import * as inflection from "inflection"
|
|
|
5
5
|
import isPlainObject from "../utils/plain-object.js"
|
|
6
6
|
import VelociousError from "../velocious-error.js"
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 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: () => string}} FrontendModelResourceModelClass
|
|
11
|
+
*/
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* Built-in frontend-model resource action.
|
|
10
15
|
* @typedef {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} FrontendModelResourceAction
|
|
@@ -20,7 +25,7 @@ import VelociousError from "../velocious-error.js"
|
|
|
20
25
|
* frontendModelAbilityAction: (action: FrontendModelResourceAction) => string,
|
|
21
26
|
* frontendModelAbilityAuthorizedQuery: (action: FrontendModelResourceAction) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
22
27
|
* frontendModelAuthorizedQuery: (action: FrontendModelResourceAction) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
23
|
-
* frontendModelIndexQuery: (options?: FrontendModelResourceIndexQueryOptions & {resource?: FrontendModelBaseResource}) => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
28
|
+
* 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>,
|
|
24
29
|
* frontendModelParams: () => import("../configuration-types.js").VelociousParams,
|
|
25
30
|
* frontendModelPreload: () => import("../database/query/index.js").NestedPreloadRecord | null,
|
|
26
31
|
* frontendModelResourceConfigurationForModelClass: (modelClass: typeof import("../database/record/index.js").default) => FrontendModelResolvedResourceConfiguration | null,
|
|
@@ -68,9 +73,10 @@ import VelociousError from "../velocious-error.js"
|
|
|
68
73
|
|
|
69
74
|
/**
|
|
70
75
|
* FrontendModelResourceControllerArgs type.
|
|
76
|
+
* @template {typeof import("../database/record/index.js").default} [TDatabaseModelClass=typeof import("../database/record/index.js").default]
|
|
71
77
|
* @typedef {object} FrontendModelResourceControllerArgs
|
|
72
78
|
* @property {FrontendModelResourceController} controller - Frontend-model controller instance.
|
|
73
|
-
* @property {
|
|
79
|
+
* @property {TDatabaseModelClass} modelClass - Backing model class.
|
|
74
80
|
* @property {string} modelName - Model name.
|
|
75
81
|
* @property {import("../configuration-types.js").VelociousParams} params - Request params.
|
|
76
82
|
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} resourceConfiguration - Normalized resource configuration (or raw input shape during early bootstrap).
|
|
@@ -78,17 +84,26 @@ import VelociousError from "../velocious-error.js"
|
|
|
78
84
|
|
|
79
85
|
/**
|
|
80
86
|
* FrontendModelResourceAbilityArgs type.
|
|
87
|
+
* @template {FrontendModelResourceModelClass} [TModelClass=FrontendModelResourceModelClass]
|
|
81
88
|
* @typedef {object} FrontendModelResourceAbilityArgs
|
|
82
89
|
* @property {import("../authorization/ability.js").default} [ability] - Ability instance when the resource is used directly for authorization.
|
|
83
90
|
* @property {import("../configuration.js").default} [configuration] - Velocious configuration for controller-less construction (for example the sync websocket channel); the controller path derives it from the controller instead.
|
|
84
91
|
* @property {import("../configuration-types.js").VelociousLooseObject} [context] - Ability context.
|
|
85
92
|
* @property {import("../configuration-types.js").VelociousLooseObject} [locals] - Ability locals.
|
|
86
|
-
* @property {
|
|
93
|
+
* @property {TModelClass} [modelClass] - Optional backing model class override.
|
|
87
94
|
* @property {string} [modelName] - Optional model name override.
|
|
88
95
|
* @property {import("../configuration-types.js").VelociousParams} [params] - Optional params override.
|
|
89
96
|
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} [resourceConfiguration] - Optional normalized resource configuration.
|
|
90
97
|
*/
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Internal constructor contract used when a resource instantiates its shared
|
|
101
|
+
* counterpart across the frontend/backend model boundary.
|
|
102
|
+
* @template {FrontendModelResourceModelClass} TModelClass
|
|
103
|
+
* @template {typeof import("../database/record/index.js").default} TDatabaseModelClass
|
|
104
|
+
* @typedef {{new (args: FrontendModelResourceAbilityArgs<FrontendModelResourceModelClass> | FrontendModelResourceControllerArgs): FrontendModelBaseResource<TModelClass, TDatabaseModelClass>}} FrontendModelResourceInternalConstructor
|
|
105
|
+
*/
|
|
106
|
+
|
|
92
107
|
/**
|
|
93
108
|
* Normalized sync replay mutation passed to the resource sync hooks.
|
|
94
109
|
* @typedef {import("../sync/sync-envelope-replay-service.js").SyncReplayMutation} FrontendModelSyncMutation
|
|
@@ -163,19 +178,30 @@ import VelociousError from "../velocious-error.js"
|
|
|
163
178
|
* @typedef {FrontendModelResourceAttributePayload & {id?: string | number, _destroy?: boolean, attributes?: FrontendModelResourceAttributePayload, attachments?: FrontendModelResourceAttributePayload, nestedAttributes?: FrontendModelResourceAttributePayload}} FrontendModelResourceNestedEntry
|
|
164
179
|
*/
|
|
165
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Narrows an unbound resource registry entry at framework-owned construction
|
|
183
|
+
* sites where the backing database model has already been resolved.
|
|
184
|
+
* @param {import("../configuration-types.js").FrontendModelResourceClassType} ResourceClass - Unbound resource class.
|
|
185
|
+
* @returns {FrontendModelResourceInternalConstructor<typeof import("../database/record/index.js").default, typeof import("../database/record/index.js").default>} Runtime constructor.
|
|
186
|
+
*/
|
|
187
|
+
export function frontendModelResourceInternalConstructor(ResourceClass) {
|
|
188
|
+
return /** @type {FrontendModelResourceInternalConstructor<typeof import("../database/record/index.js").default, typeof import("../database/record/index.js").default>} */ (/** @type {unknown} */ (ResourceClass))
|
|
189
|
+
}
|
|
190
|
+
|
|
166
191
|
/**
|
|
167
192
|
* Base class for backend frontend-model resources.
|
|
168
|
-
* @template {
|
|
193
|
+
* @template {FrontendModelResourceModelClass} [TModelClass=typeof import("../database/record/index.js").default]
|
|
194
|
+
* @template {typeof import("../database/record/index.js").default} [TDatabaseModelClass=Extract<TModelClass, typeof import("../database/record/index.js").default>]
|
|
169
195
|
*/
|
|
170
196
|
export default class FrontendModelBaseResource extends AuthorizationBaseResource {
|
|
171
|
-
/** @type {
|
|
197
|
+
/** @type {FrontendModelResourceModelClass | undefined} */
|
|
172
198
|
static ModelClass = undefined
|
|
173
199
|
|
|
174
200
|
/** @type {Record<string, ReturnType<typeof JSON.parse>> | string[] | undefined} */
|
|
175
201
|
static attributes = undefined
|
|
176
202
|
/** @type {string[] | undefined} */
|
|
177
203
|
static abilities = undefined
|
|
178
|
-
/** @type {Record<string,
|
|
204
|
+
/** @type {Record<string, import("../configuration-types.js").FrontendModelAttachmentConfiguration> | undefined} */
|
|
179
205
|
static attachments = undefined
|
|
180
206
|
/** @type {string[] | undefined} */
|
|
181
207
|
static commands = undefined
|
|
@@ -214,7 +240,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
214
240
|
|
|
215
241
|
/**
|
|
216
242
|
* Runs constructor.
|
|
217
|
-
* @param {FrontendModelResourceAbilityArgs | FrontendModelResourceControllerArgs} args - Resource args.
|
|
243
|
+
* @param {FrontendModelResourceAbilityArgs<TModelClass> | FrontendModelResourceControllerArgs<TDatabaseModelClass>} args - Resource args.
|
|
218
244
|
*/
|
|
219
245
|
constructor(args) {
|
|
220
246
|
super({
|
|
@@ -223,16 +249,18 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
223
249
|
locals: "locals" in args ? args.locals || {} : {}
|
|
224
250
|
})
|
|
225
251
|
|
|
226
|
-
|
|
252
|
+
// Narrows the subclass static side to the model class carried by this resource generic.
|
|
253
|
+
const ResourceClass = /** @type {typeof FrontendModelBaseResource & {ModelClass: TModelClass | undefined, modelClass: () => TModelClass}} */ (this.constructor)
|
|
227
254
|
const defaultResourceConfiguration = /** @type {import("../configuration-types.js").FrontendModelResourceConfiguration} */ ({attributes: []})
|
|
228
255
|
|
|
229
256
|
this.controller = "controller" in args ? args.controller : undefined
|
|
230
257
|
this.configurationValue = "configuration" in args ? args.configuration : undefined
|
|
231
|
-
|
|
258
|
+
// Narrows the internal controller/shared-resource construction path to the resource's declared model generic.
|
|
259
|
+
this.modelClassValue = /** @type {TModelClass} */ ("modelClass" in args ? args.modelClass : ResourceClass.modelClass())
|
|
232
260
|
this.modelNameValue = "modelName" in args ? args.modelName : this.modelClass().getModelName()
|
|
233
261
|
this.paramsValue = "params" in args ? args.params : undefined
|
|
234
262
|
this.resourceConfigurationValue = "resourceConfiguration" in args ? args.resourceConfiguration : defaultResourceConfiguration
|
|
235
|
-
/** @type {FrontendModelBaseResource | null | undefined} */
|
|
263
|
+
/** @type {FrontendModelBaseResource<TModelClass, TDatabaseModelClass> | null | undefined} */
|
|
236
264
|
this.sharedResourceInstanceValue = undefined
|
|
237
265
|
}
|
|
238
266
|
|
|
@@ -269,15 +297,37 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
269
297
|
return /** @type {string[] | undefined} */ (this.sharedResourceStaticValue("translatedAttributes"))
|
|
270
298
|
}
|
|
271
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Resolves frontend-safe attachment declarations from the backing model.
|
|
302
|
+
* Resource-level declarations remain as a fallback for frontend-only resources.
|
|
303
|
+
* @returns {Record<string, import("../configuration-types.js").FrontendModelAttachmentConfiguration>} - Client attachment configuration keyed by name.
|
|
304
|
+
*/
|
|
305
|
+
static attachmentConfigurations() {
|
|
306
|
+
const configuredAttachments = /** @type {Record<string, import("../configuration-types.js").FrontendModelAttachmentConfiguration> | undefined} */ (this.sharedResourceStaticValue("attachments"))
|
|
307
|
+
const attachments = configuredAttachments ? {...configuredAttachments} : {}
|
|
308
|
+
|
|
309
|
+
if (!this.ModelClass) return attachments
|
|
310
|
+
|
|
311
|
+
for (const [attachmentName, definition] of Object.entries(this.ModelClass.attachmentDefinitions())) {
|
|
312
|
+
const attachmentConfig = /** @type {import("../configuration-types.js").FrontendModelAttachmentConfiguration} */ ({type: definition.type})
|
|
313
|
+
|
|
314
|
+
if (definition.sync) attachmentConfig.sync = {...definition.sync}
|
|
315
|
+
|
|
316
|
+
attachments[attachmentName] = attachmentConfig
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return attachments
|
|
320
|
+
}
|
|
321
|
+
|
|
272
322
|
/**
|
|
273
323
|
* Builds a resource instance for shared-resource fallback calls.
|
|
274
|
-
* @returns {FrontendModelBaseResource | null} - Shared resource instance when configured.
|
|
324
|
+
* @returns {FrontendModelBaseResource<TModelClass, TDatabaseModelClass> | null} - Shared resource instance when configured.
|
|
275
325
|
*/
|
|
276
326
|
sharedResourceInstance() {
|
|
277
327
|
if (this.sharedResourceInstanceValue !== undefined) return this.sharedResourceInstanceValue
|
|
278
328
|
|
|
279
|
-
const ResourceClass = /** @type {
|
|
280
|
-
const SharedResource = /** @type {
|
|
329
|
+
const ResourceClass = /** @type {import("../configuration-types.js").FrontendModelResourceClassType<TModelClass, TDatabaseModelClass>} */ (this.constructor)
|
|
330
|
+
const SharedResource = /** @type {import("../configuration-types.js").FrontendModelResourceClassType<TModelClass, TDatabaseModelClass> | undefined} */ (ResourceClass.sharedResourceClass())
|
|
281
331
|
|
|
282
332
|
if (!SharedResource) {
|
|
283
333
|
this.sharedResourceInstanceValue = null
|
|
@@ -288,7 +338,8 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
288
338
|
throw new Error(`${ResourceClass.name}.SharedResource cannot point to itself.`)
|
|
289
339
|
}
|
|
290
340
|
|
|
291
|
-
|
|
341
|
+
const SharedResourceConstructor = /** @type {FrontendModelResourceInternalConstructor<TModelClass, TDatabaseModelClass>} */ (/** @type {unknown} */ (SharedResource))
|
|
342
|
+
const sharedResource = new SharedResourceConstructor({
|
|
292
343
|
ability: this.ability,
|
|
293
344
|
controller: this.controller,
|
|
294
345
|
context: this.context,
|
|
@@ -298,8 +349,9 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
298
349
|
params: this.params(),
|
|
299
350
|
resourceConfiguration: this.resourceConfiguration()
|
|
300
351
|
})
|
|
352
|
+
this.sharedResourceInstanceValue = sharedResource
|
|
301
353
|
|
|
302
|
-
return
|
|
354
|
+
return sharedResource
|
|
303
355
|
}
|
|
304
356
|
|
|
305
357
|
/**
|
|
@@ -343,7 +395,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
343
395
|
/**
|
|
344
396
|
* Resolves a method on this resource or its shared fallback.
|
|
345
397
|
* @param {string} methodName - Method name.
|
|
346
|
-
* @returns {{method: (...methodArgs: unknown[]) => unknown, resource: FrontendModelBaseResource} | null} - Resolved method and receiver.
|
|
398
|
+
* @returns {{method: (...methodArgs: unknown[]) => unknown, resource: FrontendModelBaseResource<TModelClass, TDatabaseModelClass>} | null} - Resolved method and receiver.
|
|
347
399
|
*/
|
|
348
400
|
resourceMethod(methodName) {
|
|
349
401
|
const ownMethod = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (this))[methodName]
|
|
@@ -392,7 +444,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
392
444
|
static resourceConfig() {
|
|
393
445
|
const attributes = this.sharedResourceStaticValue("attributes")
|
|
394
446
|
const abilities = this.sharedResourceStaticValue("abilities")
|
|
395
|
-
const attachments = this.
|
|
447
|
+
const attachments = this.attachmentConfigurations()
|
|
396
448
|
const commands = this.sharedResourceStaticValue("commands")
|
|
397
449
|
const builtInCollectionCommands = this.sharedResourceStaticValue("builtInCollectionCommands")
|
|
398
450
|
const builtInMemberCommands = this.sharedResourceStaticValue("builtInMemberCommands")
|
|
@@ -409,7 +461,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
409
461
|
}
|
|
410
462
|
|
|
411
463
|
if (abilities) config.abilities = /** @type {string[]} */ (abilities)
|
|
412
|
-
if (attachments) config.attachments =
|
|
464
|
+
if (Object.keys(attachments).length > 0) config.attachments = attachments
|
|
413
465
|
if (commands) config.commands = /** @type {string[]} */ (commands)
|
|
414
466
|
if (builtInCollectionCommands) config.builtInCollectionCommands = /** @type {string[]} */ (builtInCollectionCommands)
|
|
415
467
|
if (builtInMemberCommands) config.builtInMemberCommands = /** @type {string[]} */ (builtInMemberCommands)
|
|
@@ -424,16 +476,6 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
424
476
|
return config
|
|
425
477
|
}
|
|
426
478
|
|
|
427
|
-
/**
|
|
428
|
-
* Runs static model class.
|
|
429
|
-
* @returns {typeof import("../database/record/index.js").default} - Backing model class.
|
|
430
|
-
*/
|
|
431
|
-
static modelClass() {
|
|
432
|
-
if (!this.ModelClass) throw new Error(`${this.name} requires a static ModelClass.`)
|
|
433
|
-
|
|
434
|
-
return this.ModelClass
|
|
435
|
-
}
|
|
436
|
-
|
|
437
479
|
/**
|
|
438
480
|
* Runs controller instance.
|
|
439
481
|
* @returns {import("../controller.js").default} - Controller instance.
|
|
@@ -466,7 +508,16 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
466
508
|
throw new Error(`${this.constructor.name} requires a model class.`)
|
|
467
509
|
}
|
|
468
510
|
|
|
469
|
-
return
|
|
511
|
+
return this.modelClassValue
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Returns the database model class used by server-only resource operations.
|
|
516
|
+
* @returns {TDatabaseModelClass} - Database model class.
|
|
517
|
+
*/
|
|
518
|
+
databaseModelClass() {
|
|
519
|
+
// Narrows the portable resource generic at the explicit backend-operation boundary.
|
|
520
|
+
return /** @type {TDatabaseModelClass} */ (/** @type {unknown} */ (this.modelClass()))
|
|
470
521
|
}
|
|
471
522
|
|
|
472
523
|
/**
|
|
@@ -619,7 +670,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
619
670
|
* @returns {Promise<import("../database/record/index.js").default | null>} Existing record or null.
|
|
620
671
|
*/
|
|
621
672
|
async findSyncRecord({ability = this.ability, forDelete = false, mutation}) {
|
|
622
|
-
const ModelClass = this.
|
|
673
|
+
const ModelClass = this.databaseModelClass()
|
|
623
674
|
const primaryKey = ModelClass.primaryKey()
|
|
624
675
|
const query = ability
|
|
625
676
|
? ModelClass.accessibleFor(this.syncAbilityAction(forDelete ? "destroy" : "update"), ability)
|
|
@@ -819,20 +870,20 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
819
870
|
/**
|
|
820
871
|
* Runs authorized query.
|
|
821
872
|
* @param {FrontendModelResourceAction} action - Ability action.
|
|
822
|
-
* @returns {import("../database/query/model-class-query.js").default<
|
|
873
|
+
* @returns {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} - Authorized query.
|
|
823
874
|
*/
|
|
824
875
|
authorizedQuery(action) {
|
|
825
876
|
// Narrows the controller query to this resource's model class.
|
|
826
|
-
return /** @type {import("../database/query/model-class-query.js").default<
|
|
877
|
+
return /** @type {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} */ (this.typedControllerInstance().frontendModelAbilityAuthorizedQuery(action))
|
|
827
878
|
}
|
|
828
879
|
|
|
829
880
|
/**
|
|
830
881
|
* Runs index query.
|
|
831
882
|
* @param {FrontendModelResourceIndexQueryOptions} [options] - Query options.
|
|
832
|
-
* @returns {import("../database/query/model-class-query.js").default<
|
|
883
|
+
* @returns {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} - Frontend-model index query.
|
|
833
884
|
*/
|
|
834
885
|
indexQuery(options = {}) {
|
|
835
|
-
return /** @type {import("../database/query/model-class-query.js").default<
|
|
886
|
+
return /** @type {import("../database/query/model-class-query.js").default<TDatabaseModelClass>} */ (this.typedControllerInstance().frontendModelIndexQuery({
|
|
836
887
|
...options,
|
|
837
888
|
resource: this
|
|
838
889
|
}))
|
|
@@ -961,8 +1012,8 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
961
1012
|
const normalizedAttributes = await this.normalizeCreateAttributes(attributes, options)
|
|
962
1013
|
const attachmentSplit = this._extractAttachmentAttributes(normalizedAttributes, options.attachments ?? null)
|
|
963
1014
|
const permit = parsePermittedParams(this.permittedParams({action: "create", ability: this.ability, locals: this.locals, params: normalizedAttributes}))
|
|
964
|
-
const ModelClass = this.
|
|
965
|
-
const filtered = filterWritableFrontendModelAttributes(
|
|
1015
|
+
const ModelClass = this.databaseModelClass()
|
|
1016
|
+
const filtered = filterWritableFrontendModelAttributes(ModelClass.prototype, ModelClass, attachmentSplit.attributes, this, permit.attributes)
|
|
966
1017
|
const model = new ModelClass()
|
|
967
1018
|
|
|
968
1019
|
return await this.runMutationTransaction({
|
|
@@ -1021,7 +1072,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1021
1072
|
* @returns {Promise<import("../database/record/index.js").default>} - Saved model.
|
|
1022
1073
|
*/
|
|
1023
1074
|
async _saveWithNestedAttributes({filtered, model, options, permit}) {
|
|
1024
|
-
await this.
|
|
1075
|
+
await this.databaseModelClass().transaction(async () => {
|
|
1025
1076
|
await this._assignWithVirtualSetters(model, filtered)
|
|
1026
1077
|
this._assignAttachments(model, options.attachments ?? null, permit.attributes)
|
|
1027
1078
|
|
|
@@ -1079,7 +1130,7 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1079
1130
|
* @returns {{attributes: Record<string, ReturnType<typeof JSON.parse>>, attachments: Record<string, ReturnType<typeof JSON.parse>> | null}} Attributes with attachment keys removed and merged attachment payload.
|
|
1080
1131
|
*/
|
|
1081
1132
|
_extractAttachmentAttributes(attributes, attachments) {
|
|
1082
|
-
const attachmentDefinitions = this.modelClass().
|
|
1133
|
+
const attachmentDefinitions = this.modelClass().attachmentDefinitions()
|
|
1083
1134
|
const attachmentNames = new Set(Object.keys(attachmentDefinitions))
|
|
1084
1135
|
|
|
1085
1136
|
if (attachmentNames.size === 0) return {attributes, attachments}
|
|
@@ -1272,7 +1323,8 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
1272
1323
|
throw new Error(`No frontend-model resource registered for child model '${targetModelClass.getModelName()}' under relationship '${relationshipName}'.`)
|
|
1273
1324
|
}
|
|
1274
1325
|
|
|
1275
|
-
const
|
|
1326
|
+
const ChildResource = frontendModelResourceInternalConstructor(childResourceConfig.resourceClass)
|
|
1327
|
+
const childResource = new ChildResource({
|
|
1276
1328
|
ability: this.ability,
|
|
1277
1329
|
controller,
|
|
1278
1330
|
context: this.context || {},
|
|
@@ -1934,10 +1986,12 @@ function prototypeOwnerForMethod(instance, methodName) {
|
|
|
1934
1986
|
|
|
1935
1987
|
/**
|
|
1936
1988
|
* Runs filter writable frontend model attributes.
|
|
1989
|
+
* @template {FrontendModelResourceModelClass} ResourceModelClass
|
|
1990
|
+
* @template {typeof import("../database/record/index.js").default} ResourceDatabaseModelClass
|
|
1937
1991
|
* @param {Record<string, ReturnType<typeof JSON.parse>>} receiver - Model instance or prototype.
|
|
1938
1992
|
* @param {WritableAttributeReceiverClass} receiverClass - Static helper owner for the receiver.
|
|
1939
1993
|
* @param {Record<string, ReturnType<typeof JSON.parse>>} attributes - Incoming frontend-model attributes.
|
|
1940
|
-
* @param {FrontendModelBaseResource | null} [resource] - Resource instance for virtual-setter detection.
|
|
1994
|
+
* @param {FrontendModelBaseResource<ResourceModelClass, ResourceDatabaseModelClass> | null} [resource] - Resource instance for virtual-setter detection.
|
|
1941
1995
|
* @param {string[] | null} [permittedAttributeNames] - Optional explicit permit list. `null` falls back to setter-existence checks only.
|
|
1942
1996
|
* @returns {Record<string, ReturnType<typeof JSON.parse>>} - Writable attributes only.
|
|
1943
1997
|
*/
|
|
@@ -1945,7 +1999,7 @@ function filterWritableFrontendModelAttributes(
|
|
|
1945
1999
|
receiver,
|
|
1946
2000
|
receiverClass,
|
|
1947
2001
|
attributes,
|
|
1948
|
-
resource = /** @type {FrontendModelBaseResource | null} */ (null),
|
|
2002
|
+
resource = /** @type {FrontendModelBaseResource<ResourceModelClass, ResourceDatabaseModelClass> | null} */ (null),
|
|
1949
2003
|
permittedAttributeNames = null
|
|
1950
2004
|
) {
|
|
1951
2005
|
// Frontend-model writes should fail fast when callers submit read-only or unknown attrs.
|
|
@@ -66,7 +66,7 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
|
|
|
66
66
|
*/
|
|
67
67
|
/**
|
|
68
68
|
* Defines this typedef.
|
|
69
|
-
* @typedef {{type: "hasOne" | "hasMany"}} FrontendModelAttachmentDefinition
|
|
69
|
+
* @typedef {{sync?: import("../configuration-types.js").AttachmentSyncConfiguration, type: "hasOne" | "hasMany"}} FrontendModelAttachmentDefinition
|
|
70
70
|
*/
|
|
71
71
|
/**
|
|
72
72
|
* Defines frontend-model attribute metadata.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import Controller from "../controller.js"
|
|
4
|
-
import FrontendModelBaseResource from "../frontend-model-resource/base-resource.js"
|
|
4
|
+
import FrontendModelBaseResource, {frontendModelResourceInternalConstructor} from "../frontend-model-resource/base-resource.js"
|
|
5
5
|
|
|
6
6
|
/** Configurations whose sync.api routes have already been mounted. */
|
|
7
7
|
const mountedConfigurations = new WeakSet()
|
|
@@ -41,9 +41,10 @@ export default class SyncApiController extends Controller {
|
|
|
41
41
|
*/
|
|
42
42
|
syncResource(params) {
|
|
43
43
|
const ResourceClass = this.syncResourceClass()
|
|
44
|
+
const ResourceConstructor = frontendModelResourceInternalConstructor(ResourceClass)
|
|
44
45
|
const ability = this.currentAbility()
|
|
45
46
|
|
|
46
|
-
return new
|
|
47
|
+
return new ResourceConstructor({
|
|
47
48
|
ability,
|
|
48
49
|
controller: /** @type {import("../frontend-model-resource/base-resource.js").FrontendModelResourceController} */ (/** @type {unknown} */ (this)),
|
|
49
50
|
context: {
|
|
@@ -137,7 +138,7 @@ export default class SyncApiController extends Controller {
|
|
|
137
138
|
* @returns {import("../configuration-types.js").FrontendModelResourceClassType} Sync resource class.
|
|
138
139
|
*/
|
|
139
140
|
missingSyncResourceClass() {
|
|
140
|
-
return /** @type {
|
|
141
|
+
return /** @type {import("../configuration-types.js").FrontendModelResourceClassType} */ (/** @type {unknown} */ (this.raiseMissingSyncResourceClass()))
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
/** Raises a configuration error for subclasses that do not provide a resource. */
|
|
@@ -155,7 +156,7 @@ export default class SyncApiController extends Controller {
|
|
|
155
156
|
|
|
156
157
|
if (!modelClass) throw new Error("Sync resource class must define static ModelClass")
|
|
157
158
|
|
|
158
|
-
return modelClass
|
|
159
|
+
return /** @type {typeof import("../database/record/index.js").default} */ (/** @type {unknown} */ (modelClass))
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
/**
|
|
@@ -178,4 +179,3 @@ export default class SyncApiController extends Controller {
|
|
|
178
179
|
})
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import {deliverDeclaredBroadcasts, upsertSyncRow} from "./sync-change-fanout.js"
|
|
4
|
+
import {frontendModelResourceInternalConstructor} from "../frontend-model-resource/base-resource.js"
|
|
4
5
|
import {markServerApply} from "./sync-publish-suppression.js"
|
|
5
6
|
import {resolveFrontendModelResourceClass} from "../frontend-models/resource-definition.js"
|
|
6
7
|
import {resolveSyncConflict} from "./conflict-strategy.js"
|
|
@@ -567,7 +568,7 @@ export default class SyncEnvelopeReplayService {
|
|
|
567
568
|
* @returns {Promise<import("../frontend-model-resource/base-resource.js").default>} Routed resource instance.
|
|
568
569
|
*/
|
|
569
570
|
async buildReplayResource({actor, context, mutation, registration}) {
|
|
570
|
-
const ResourceClass = registration.resourceClass
|
|
571
|
+
const ResourceClass = frontendModelResourceInternalConstructor(registration.resourceClass)
|
|
571
572
|
const {ability, abilityContext} = await this.replayAbilityFor({actor, context})
|
|
572
573
|
|
|
573
574
|
return new ResourceClass({
|