velocious 1.0.642 → 1.0.644
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/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/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/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-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/src/testing/test-runner.d.ts +8 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +49 -548
- package/build/src/testing/velocious-attempt-executor.d.ts +69 -0
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-attempt-executor.js +402 -0
- package/build/src/testing/velocious-runner-reporter.d.ts +72 -0
- package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
- package/build/src/testing/velocious-runner-reporter.js +152 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts +37 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-suite-hook-executor.js +66 -0
- package/build/src/testing/velocious-test-arguments.d.ts +34 -0
- package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
- package/build/src/testing/velocious-test-arguments.js +47 -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-controller.js +5 -5
- package/build/sync/sync-envelope-replay-service.js +2 -1
- package/build/sync/sync-resource-base.js +3 -0
- package/build/testing/test-runner.js +46 -584
- package/build/testing/velocious-attempt-executor.js +431 -0
- package/build/testing/velocious-runner-reporter.js +166 -0
- package/build/testing/velocious-suite-hook-executor.js +71 -0
- package/build/testing/velocious-test-arguments.js +55 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/sha256-bytes-hex.js +132 -0
- package/build/utils/sha256-hex.js +7 -135
- package/package.json +1 -1
- package/src/authorization/base-resource.js +18 -5
- 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/assets/cache.js +998 -0
- package/src/sync/assets/types.js +62 -0
- 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
- package/src/testing/test-runner.js +46 -584
- package/src/testing/velocious-attempt-executor.js +431 -0
- package/src/testing/velocious-runner-reporter.js +166 -0
- package/src/testing/velocious-suite-hook-executor.js +71 -0
- package/src/testing/velocious-test-arguments.js +55 -0
- package/src/utils/sha256-bytes-hex.js +132 -0
- package/src/utils/sha256-hex.js +7 -135
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* AbortSignal-driven MySQL/MariaDB query cancellation for raw, model, and cross-tenant aggregate queries (see [docs/database-query-cancellation.md](docs/database-query-cancellation.md))
|
|
48
48
|
* Optional built-in debug endpoint for inspecting server and database connection state (see [docs/debug-endpoint.md](docs/debug-endpoint.md))
|
|
49
49
|
* Optional built-in API manifest endpoint describing every registered frontend-model resource as human- and machine-readable JSON (see [docs/api-manifest-endpoint.md](docs/api-manifest-endpoint.md))
|
|
50
|
-
* Backend record attachments with filesystem, S3, native callback,
|
|
50
|
+
* Backend record attachments with filesystem, S3, native callback, bounded Node path-input persistence, and model-declared client sync policy (see [docs/attachments.md](docs/attachments.md))
|
|
51
51
|
|
|
52
52
|
# Setup
|
|
53
53
|
|
|
@@ -701,11 +701,21 @@ For backend models, you can declare attachment helpers directly:
|
|
|
701
701
|
Task.hasManyAttachments("files")
|
|
702
702
|
Task.hasOneAttachment("descriptionFile")
|
|
703
703
|
Task.hasOneAttachment("archivedPdf", {driver: "s3"})
|
|
704
|
+
User.hasOneAttachment("profilePicture", {
|
|
705
|
+
sync: {
|
|
706
|
+
fetch: "eager",
|
|
707
|
+
offlineRequirement: "optional",
|
|
708
|
+
retention: "evictable"
|
|
709
|
+
}
|
|
710
|
+
})
|
|
704
711
|
```
|
|
705
712
|
|
|
706
713
|
`db:migrate` provisions the framework-owned attachment table before runtime
|
|
707
714
|
attachment work begins. See [Backend record attachments](docs/attachments.md)
|
|
708
715
|
for the complete input, storage-driver, lifecycle, and path-security contracts.
|
|
716
|
+
Offline-capable clients can apply synchronized attachment descriptors through
|
|
717
|
+
the platform-neutral [Synchronized asset cache](docs/synchronized-assets.md),
|
|
718
|
+
while Expo and web packages own their respective byte-storage adapters.
|
|
709
719
|
|
|
710
720
|
You can also pass a driver class or instance directly on the attachment:
|
|
711
721
|
|
|
@@ -815,7 +825,9 @@ behavior. See
|
|
|
815
825
|
[docs/attachments.md](docs/attachments.md#normalized-storage-driver-input) for
|
|
816
826
|
the normalized input passed to custom drivers.
|
|
817
827
|
|
|
818
|
-
For
|
|
828
|
+
For a resource with a backing model, the model attachment declaration
|
|
829
|
+
automatically generates `resourceConfig().attachments`; do not repeat it on the
|
|
830
|
+
resource. Use the generated attachment handles normally:
|
|
819
831
|
|
|
820
832
|
```js
|
|
821
833
|
await frontendTask.update({descriptionFile: file})
|
|
@@ -828,6 +840,17 @@ await frontendTask.attach(file)
|
|
|
828
840
|
|
|
829
841
|
Frontend model attachment input does not support `{path: ...}`.
|
|
830
842
|
Use `File`/`Blob`/bytes/`contentBase64` payloads instead.
|
|
843
|
+
The optional model-level `sync` block is client-safe policy metadata for asset
|
|
844
|
+
cache adapters. It distinguishes eager/on-demand fetching,
|
|
845
|
+
durable/evictable retention, and optional/required offline availability.
|
|
846
|
+
Required offline assets must be durable. Backend driver configuration never
|
|
847
|
+
appears in generated frontend models or API manifests. A descriptor ID keeps
|
|
848
|
+
its digest, byte size, and content type immutably. Cache descriptors that share
|
|
849
|
+
a digest must agree on byte size and content type, and eager synchronization
|
|
850
|
+
attempts each shared digest only once per reconciliation. On-demand resolution
|
|
851
|
+
rechecks the backing blob after cleanup and returns `null` instead of a stale
|
|
852
|
+
local URI when concurrent eviction removed it. Cleanup deferred by an active
|
|
853
|
+
cached resolution runs again after that digest's final guard releases.
|
|
831
854
|
Attachment metadata is exposed through the built-in `VelociousAttachment` frontend model with safe fields only: `id`, `recordType`, `recordId`, `name`, `position`, `filename`, `contentType`, `byteSize`, `createdAt`, and `updatedAt`. Storage internals such as `driver`, `storageKey`, and `contentBase64` remain hidden and non-queryable. Direct metadata queries require owner filters: `recordType`, `recordId`, and `name`.
|
|
832
855
|
|
|
833
856
|
When your frontend app calls a backend on another host/port (or under a path prefix), configure transport once:
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Model class supported by authorization and shared frontend-model resources.
|
|
5
|
+
* @typedef {{new (): import("../database/record/index.js").default | import("../frontend-models/base.js").default, getModelName: () => string}} AuthorizationResourceModelClass
|
|
6
|
+
*/
|
|
7
|
+
|
|
3
8
|
/** Base class for authorization resources defining abilities for a model. */
|
|
4
9
|
export default class AuthorizationBaseResource {
|
|
5
10
|
/**
|
|
6
11
|
* Model class.
|
|
7
|
-
* @type {
|
|
12
|
+
* @type {AuthorizationResourceModelClass | undefined} */
|
|
8
13
|
static ModelClass = undefined
|
|
9
14
|
|
|
10
15
|
/**
|
|
@@ -22,7 +27,9 @@ export default class AuthorizationBaseResource {
|
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Runs model class.
|
|
25
|
-
* @
|
|
30
|
+
* @template {AuthorizationResourceModelClass} TModelClass
|
|
31
|
+
* @this {{ModelClass: TModelClass | undefined, name: string}}
|
|
32
|
+
* @returns {TModelClass} - Model class handled by this resource.
|
|
26
33
|
*/
|
|
27
34
|
static modelClass() {
|
|
28
35
|
if (!this.ModelClass) {
|
|
@@ -41,7 +48,10 @@ export default class AuthorizationBaseResource {
|
|
|
41
48
|
*/
|
|
42
49
|
can(actions, conditions) {
|
|
43
50
|
this.assertResourceConditionsSignature({conditions, methodName: "can"})
|
|
44
|
-
|
|
51
|
+
// Authorization query rules are backend-only even when a shared resource is bound to a frontend model.
|
|
52
|
+
const modelClass = /** @type {typeof import("../database/record/index.js").default} */ (this.requiredModelClass())
|
|
53
|
+
|
|
54
|
+
this.requiredAbility().can(actions, modelClass, /** @type {import("./ability.js").AbilityConditionsType<typeof import("../database/record/index.js").default> | undefined} */ (conditions))
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
/**
|
|
@@ -53,7 +63,10 @@ export default class AuthorizationBaseResource {
|
|
|
53
63
|
*/
|
|
54
64
|
cannot(actions, conditions) {
|
|
55
65
|
this.assertResourceConditionsSignature({conditions, methodName: "cannot"})
|
|
56
|
-
|
|
66
|
+
// Authorization query rules are backend-only even when a shared resource is bound to a frontend model.
|
|
67
|
+
const modelClass = /** @type {typeof import("../database/record/index.js").default} */ (this.requiredModelClass())
|
|
68
|
+
|
|
69
|
+
this.requiredAbility().cannot(actions, modelClass, /** @type {import("./ability.js").AbilityConditionsType<typeof import("../database/record/index.js").default> | undefined} */ (conditions))
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
/**
|
|
@@ -70,7 +83,7 @@ export default class AuthorizationBaseResource {
|
|
|
70
83
|
|
|
71
84
|
/**
|
|
72
85
|
* Runs required model class.
|
|
73
|
-
* @returns {
|
|
86
|
+
* @returns {AuthorizationResourceModelClass} - Model class handled by this resource.
|
|
74
87
|
*/
|
|
75
88
|
requiredModelClass() {
|
|
76
89
|
const ResourceClass = /** @type {typeof AuthorizationBaseResource} */ (this.constructor)
|
|
@@ -375,6 +375,22 @@
|
|
|
375
375
|
* @property {Record<string, ReturnType<typeof JSON.parse>>} [instance] - Optional custom attachment driver instance.
|
|
376
376
|
*/
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Client-safe synchronization policy declared with a model attachment.
|
|
380
|
+
* @typedef {object} AttachmentSyncConfiguration
|
|
381
|
+
* @property {"eager" | "on-demand"} fetch - Whether clients prefetch the attachment or wait until it is requested.
|
|
382
|
+
* @property {"optional" | "required"} offlineRequirement - Whether an offline-ready scope requires the attachment bytes.
|
|
383
|
+
* @property {"durable" | "evictable"} retention - Whether clients may evict the attachment under storage pressure.
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Model attachment declaration retained by the record class.
|
|
388
|
+
* @typedef {object} RecordAttachmentConfiguration
|
|
389
|
+
* @property {string | AttachmentDriverConstructor | Record<string, ReturnType<typeof JSON.parse>>} [driver] - Attachment driver name, class, or instance.
|
|
390
|
+
* @property {AttachmentSyncConfiguration} [sync] - Client-safe synchronized asset policy.
|
|
391
|
+
* @property {"hasOne" | "hasMany"} type - Attachment cardinality.
|
|
392
|
+
*/
|
|
393
|
+
|
|
378
394
|
/**
|
|
379
395
|
* @typedef {object} AttachmentsConfiguration
|
|
380
396
|
* @property {string} [defaultDriver] - Default attachment storage driver name.
|
|
@@ -418,6 +434,7 @@
|
|
|
418
434
|
|
|
419
435
|
/**
|
|
420
436
|
* @typedef {object} FrontendModelAttachmentConfiguration
|
|
437
|
+
* @property {AttachmentSyncConfiguration} [sync] - Client-side synchronized asset policy.
|
|
421
438
|
* @property {"hasOne" | "hasMany"} type - Attachment cardinality.
|
|
422
439
|
*/
|
|
423
440
|
|
|
@@ -574,11 +591,26 @@
|
|
|
574
591
|
*/
|
|
575
592
|
|
|
576
593
|
/**
|
|
577
|
-
*
|
|
594
|
+
* Unbound resource class used by model-agnostic registries.
|
|
595
|
+
* @typedef {Omit<typeof import("./frontend-model-resource/base-resource.js").default, "modelClass"> & {modelClass: () => typeof import("./database/record/index.js").default, new (args: never): import("./frontend-model-resource/base-resource.js").default<typeof import("./database/record/index.js").default>}} UnboundFrontendModelResourceClassType
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Resource class bound to a specific model class.
|
|
600
|
+
* @template {import("./frontend-model-resource/base-resource.js").FrontendModelResourceModelClass} TModelClass
|
|
601
|
+
* @template {typeof import("./database/record/index.js").default} TDatabaseModelClass
|
|
602
|
+
* @typedef {Omit<typeof import("./frontend-model-resource/base-resource.js").default, "ModelClass" | "modelClass"> & {ModelClass: TModelClass | undefined, modelClass: () => TModelClass, new (args: import("./frontend-model-resource/base-resource.js").FrontendModelResourceAbilityArgs<TModelClass> | import("./frontend-model-resource/base-resource.js").FrontendModelResourceControllerArgs<TDatabaseModelClass>): import("./frontend-model-resource/base-resource.js").default<TModelClass, TDatabaseModelClass>}} BoundFrontendModelResourceClassType
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @template {import("./frontend-model-resource/base-resource.js").FrontendModelResourceModelClass} [TModelClass=never]
|
|
607
|
+
* @template {typeof import("./database/record/index.js").default} [TDatabaseModelClass=Extract<TModelClass, typeof import("./database/record/index.js").default>]
|
|
608
|
+
* @typedef {[TModelClass] extends [never] ? UnboundFrontendModelResourceClassType : BoundFrontendModelResourceClassType<TModelClass, TDatabaseModelClass>} FrontendModelResourceClassType
|
|
578
609
|
*/
|
|
579
610
|
|
|
580
611
|
/**
|
|
581
|
-
* @
|
|
612
|
+
* @template {import("./frontend-model-resource/base-resource.js").FrontendModelResourceModelClass} [TModelClass=never]
|
|
613
|
+
* @typedef {FrontendModelResourceClassType<TModelClass>} FrontendModelResourceDefinition
|
|
582
614
|
*/
|
|
583
615
|
|
|
584
616
|
/**
|
|
@@ -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: {
|