velocious 1.0.473 → 1.0.475
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 +7 -2
- package/build/authorization/base-resource.js +99 -0
- package/build/configuration-types.js +41 -3
- package/build/configuration.js +41 -1
- package/build/database/drivers/base.js +51 -3
- package/build/database/drivers/mysql/index.js +74 -7
- package/build/database/drivers/sqlite/connection-sql-js.js +16 -5
- package/build/database/drivers/sqlite/index.web.js +29 -15
- package/build/database/drivers/sqlite/web-persistence.js +521 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +64 -1
- package/build/frontend-model-controller.js +139 -16
- package/build/frontend-model-resource/base-resource.js +258 -46
- package/build/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/build/frontend-models/base.js +146 -2
- package/build/frontend-models/query.js +3 -3
- package/build/frontend-models/resource-definition.js +400 -23
- package/build/routes/hooks/frontend-model-command-route-hook.js +8 -0
- package/build/src/authorization/base-resource.d.ts +41 -0
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +88 -1
- package/build/src/configuration-types.d.ts +128 -5
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +38 -4
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +37 -2
- package/build/src/database/drivers/base.d.ts +30 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +47 -4
- package/build/src/database/drivers/mysql/index.d.ts +32 -3
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +65 -7
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +8 -1
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/connection-sql-js.js +15 -6
- package/build/src/database/drivers/sqlite/index.web.d.ts +5 -5
- package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.web.js +27 -13
- package/build/src/database/drivers/sqlite/web-persistence.d.ts +77 -0
- package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -0
- package/build/src/database/drivers/sqlite/web-persistence.js +464 -0
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +30 -0
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +54 -2
- package/build/src/frontend-model-controller.d.ts +34 -0
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +125 -17
- package/build/src/frontend-model-resource/base-resource.d.ts +69 -0
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +247 -54
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +0 -2
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/velocious-attachment-resource.js +1 -3
- package/build/src/frontend-models/base.d.ts +29 -0
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +131 -3
- package/build/src/frontend-models/query.js +4 -4
- package/build/src/frontend-models/resource-definition.d.ts +6 -0
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +355 -28
- package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +8 -1
- package/build/src/sync/device-identity.d.ts +240 -0
- package/build/src/sync/device-identity.d.ts.map +1 -0
- package/build/src/sync/device-identity.js +454 -0
- package/build/src/sync/local-mutation-log.d.ts +212 -0
- package/build/src/sync/local-mutation-log.d.ts.map +1 -0
- package/build/src/sync/local-mutation-log.js +404 -0
- package/build/src/sync/offline-grant.d.ts +157 -0
- package/build/src/sync/offline-grant.d.ts.map +1 -0
- package/build/src/sync/offline-grant.js +292 -0
- package/build/sync/device-identity.js +503 -0
- package/build/sync/local-mutation-log.js +448 -0
- package/build/sync/offline-grant.js +327 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/scripts/tensorbuzz-retry +21 -0
- package/src/authorization/base-resource.js +99 -0
- package/src/configuration-types.js +41 -3
- package/src/configuration.js +41 -1
- package/src/database/drivers/base.js +51 -3
- package/src/database/drivers/mysql/index.js +74 -7
- package/src/database/drivers/sqlite/connection-sql-js.js +16 -5
- package/src/database/drivers/sqlite/index.web.js +29 -15
- package/src/database/drivers/sqlite/web-persistence.js +521 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +64 -1
- package/src/frontend-model-controller.js +139 -16
- package/src/frontend-model-resource/base-resource.js +258 -46
- package/src/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/src/frontend-models/base.js +146 -2
- package/src/frontend-models/query.js +3 -3
- package/src/frontend-models/resource-definition.js +400 -23
- package/src/routes/hooks/frontend-model-command-route-hook.js +8 -0
- package/src/sync/device-identity.js +503 -0
- package/src/sync/local-mutation-log.js +448 -0
- package/src/sync/offline-grant.js +327 -0
|
@@ -5,7 +5,8 @@ import Controller from "./controller.js"
|
|
|
5
5
|
import FrontendModelBaseResource from "./frontend-model-resource/base-resource.js"
|
|
6
6
|
import Response from "./http-server/client/response.js"
|
|
7
7
|
import {frontendModelResourcesWithBuiltInsForBackendProject} from "./frontend-models/built-in-resources.js"
|
|
8
|
-
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
|
|
8
|
+
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath, frontendModelResourcesForBackendProject, frontendModelSyncManifestForBackendProjects} from "./frontend-models/resource-definition.js"
|
|
9
|
+
import {createOfflineGrantFromBootstrap} from "./sync/offline-grant.js"
|
|
9
10
|
import {FrontendModelQueryError, normalizeGroup as normalizeQueryGroup, normalizeJoins as normalizeQueryJoins, normalizePluck as normalizeQueryPluck, normalizePreload as normalizeQueryPreload, normalizeSearchOperator as normalizeQuerySearchOperator, normalizeSort as normalizeQuerySort} from "./frontend-models/query.js"
|
|
10
11
|
import {assignSafeProperty, deserializeFrontendModelTransportValue, isBackendModelInstance, serializeFrontendModelTransportValue} from "./frontend-models/transport-serialization.js"
|
|
11
12
|
import {requestDetails} from "./error-reporting/request-details.js"
|
|
@@ -25,7 +26,11 @@ import {RansackQueryError, normalizeRansackGroup, parseRansackSort} from "./util
|
|
|
25
26
|
function normalizeFrontendModelPreload(preload) {
|
|
26
27
|
if (!preload) return null
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
try {
|
|
30
|
+
return normalizeQueryPreload(preload)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throwFrontendModelQueryErrorForParserError(error)
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -863,13 +868,31 @@ export default class FrontendModelController extends Controller {
|
|
|
863
868
|
for (const [relationshipName, relationshipPreload] of Object.entries(preload)) {
|
|
864
869
|
if (relationshipPreload === false) continue
|
|
865
870
|
|
|
866
|
-
const relationship = modelClass.
|
|
871
|
+
const relationship = modelClass.getRelationshipsMap()[relationshipName]
|
|
872
|
+
if (!relationship) {
|
|
873
|
+
throw frontendModelQueryError(`Unknown preload relationship "${relationshipName}" for ${modelClass.name}`)
|
|
874
|
+
}
|
|
875
|
+
|
|
867
876
|
const targetModelClass = await this.ensureFrontendModelRelationshipTargetClassInitialized({
|
|
868
877
|
backendProject,
|
|
869
878
|
relationship
|
|
870
879
|
})
|
|
871
880
|
|
|
872
|
-
if (!targetModelClass
|
|
881
|
+
if (!targetModelClass) {
|
|
882
|
+
if (isPlainObject(relationshipPreload) && Object.keys(relationshipPreload).length > 0) {
|
|
883
|
+
let message = `Cannot preload nested relationships through relationship "${relationshipName}" for ${modelClass.name} because its target model class could not be resolved`
|
|
884
|
+
|
|
885
|
+
if (relationship.getPolymorphic() && relationship.getType() === "belongsTo") {
|
|
886
|
+
message = `Cannot preload nested relationships through polymorphic relationship "${relationshipName}" for ${modelClass.name}`
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
throw frontendModelQueryError(message)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
continue
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (!isPlainObject(relationshipPreload)) continue
|
|
873
896
|
|
|
874
897
|
await this.ensureFrontendModelPreloadClassesInitialized({
|
|
875
898
|
backendProject,
|
|
@@ -2430,7 +2453,7 @@ export default class FrontendModelController extends Controller {
|
|
|
2430
2453
|
|
|
2431
2454
|
const resource = this.frontendModelResourceInstance()
|
|
2432
2455
|
const resourceClass = /** @type {typeof import("./frontend-model-resource/base-resource.js").default} */ (resource.constructor)
|
|
2433
|
-
const translatedSet = new Set(resourceClass.
|
|
2456
|
+
const translatedSet = new Set(resourceClass.translatedAttributesConfig() || [])
|
|
2434
2457
|
let needsTranslations = false
|
|
2435
2458
|
|
|
2436
2459
|
for (const attributeName of selectedAttributes) {
|
|
@@ -2667,10 +2690,10 @@ export default class FrontendModelController extends Controller {
|
|
|
2667
2690
|
* Resource has attribute.
|
|
2668
2691
|
* @param {string} attributeName - Attribute name.
|
|
2669
2692
|
*/
|
|
2670
|
-
const
|
|
2693
|
+
const resourceAttributeMethod = (attributeName) => {
|
|
2671
2694
|
const methodName = resourceAttributeMethodName(attributeName)
|
|
2672
2695
|
|
|
2673
|
-
return resourceInstance
|
|
2696
|
+
return resourceInstance?.resourceMethod(methodName) || null
|
|
2674
2697
|
}
|
|
2675
2698
|
|
|
2676
2699
|
/**
|
|
@@ -2700,10 +2723,10 @@ export default class FrontendModelController extends Controller {
|
|
|
2700
2723
|
*/
|
|
2701
2724
|
const serializedAttributeValue = async (attributeName) => {
|
|
2702
2725
|
// Check resource instance first (virtual/computed attributes via ${name}Attribute convention)
|
|
2703
|
-
|
|
2704
|
-
const methodName = resourceAttributeMethodName(attributeName)
|
|
2726
|
+
const resourceAttribute = resourceAttributeMethod(attributeName)
|
|
2705
2727
|
|
|
2706
|
-
|
|
2728
|
+
if (resourceAttribute) {
|
|
2729
|
+
return await resourceAttribute.method.call(resourceAttribute.resource, model)
|
|
2707
2730
|
}
|
|
2708
2731
|
|
|
2709
2732
|
// Fall back to model method
|
|
@@ -2722,7 +2745,7 @@ export default class FrontendModelController extends Controller {
|
|
|
2722
2745
|
* @param {string} attributeName - Attribute name.
|
|
2723
2746
|
*/
|
|
2724
2747
|
const attributeExists = (attributeName) => {
|
|
2725
|
-
return (attributeName in modelAttributes) || (attributeName in /** @type {Record<string, ?>} */ (model)) ||
|
|
2748
|
+
return (attributeName in modelAttributes) || (attributeName in /** @type {Record<string, ?>} */ (model)) || Boolean(resourceAttributeMethod(attributeName))
|
|
2726
2749
|
}
|
|
2727
2750
|
|
|
2728
2751
|
if (!selectedAttributes) {
|
|
@@ -3438,6 +3461,106 @@ export default class FrontendModelController extends Controller {
|
|
|
3438
3461
|
return {status: "success"}
|
|
3439
3462
|
}
|
|
3440
3463
|
|
|
3464
|
+
/**
|
|
3465
|
+
* Runs frontend sync bootstrap.
|
|
3466
|
+
* @returns {Promise<void>} - Sync bootstrap response with manifest and signed offline grant.
|
|
3467
|
+
*/
|
|
3468
|
+
async frontendSyncBootstrap() {
|
|
3469
|
+
if (this.request().httpMethod() === "OPTIONS") {
|
|
3470
|
+
await this.render({status: 204, json: {}})
|
|
3471
|
+
return
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
const params = /** @type {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue | undefined>} */ (deserializeFrontendModelTransportValue(this.params()))
|
|
3475
|
+
const configuration = this.getConfiguration()
|
|
3476
|
+
const syncManifest = frontendModelSyncManifestForBackendProjects(configuration.getBackendProjects())
|
|
3477
|
+
const offlineGrant = await createOfflineGrantFromBootstrap({
|
|
3478
|
+
deviceId: this.frontendSyncBootstrapDeviceId(params),
|
|
3479
|
+
grantId: this.frontendSyncBootstrapGrantId(params),
|
|
3480
|
+
grantTtlMs: configuration.getSyncConfiguration().offlineGrantTtlMs,
|
|
3481
|
+
now: this.frontendSyncBootstrapNow(params),
|
|
3482
|
+
resources: syncManifest,
|
|
3483
|
+
scopes: this.frontendSyncBootstrapScopes(params),
|
|
3484
|
+
signingKey: configuration.currentOfflineGrantSigningKey(),
|
|
3485
|
+
userId: this.frontendSyncBootstrapUserId()
|
|
3486
|
+
})
|
|
3487
|
+
|
|
3488
|
+
await this.render({
|
|
3489
|
+
json: /** @type {Record<string, ?>} */ (serializeFrontendModelTransportValue({
|
|
3490
|
+
offlineGrant,
|
|
3491
|
+
status: "success",
|
|
3492
|
+
syncManifest
|
|
3493
|
+
}, this.transportSerializationOptions()))
|
|
3494
|
+
})
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
/**
|
|
3498
|
+
* Resolves device id for sync bootstrap.
|
|
3499
|
+
* @param {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue | undefined>} params - Request params.
|
|
3500
|
+
* @returns {string} - Device id.
|
|
3501
|
+
*/
|
|
3502
|
+
frontendSyncBootstrapDeviceId(params) {
|
|
3503
|
+
if (typeof params.deviceId === "string" && params.deviceId.length > 0) return params.deviceId
|
|
3504
|
+
|
|
3505
|
+
throw new Error("Expected sync bootstrap deviceId")
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
/**
|
|
3509
|
+
* Resolves grant id for sync bootstrap.
|
|
3510
|
+
* @param {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue | undefined>} params - Request params.
|
|
3511
|
+
* @returns {string | undefined} - Deterministic grant id for tests, generated id otherwise.
|
|
3512
|
+
*/
|
|
3513
|
+
frontendSyncBootstrapGrantId(params) {
|
|
3514
|
+
if (this.getConfiguration().getEnvironment() === "test" && typeof params.grantId === "string") return params.grantId
|
|
3515
|
+
|
|
3516
|
+
return undefined
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
/**
|
|
3520
|
+
* Resolves bootstrap issue time.
|
|
3521
|
+
* @param {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue | undefined>} params - Request params.
|
|
3522
|
+
* @returns {Date} - Issue time.
|
|
3523
|
+
*/
|
|
3524
|
+
frontendSyncBootstrapNow(params) {
|
|
3525
|
+
if (this.getConfiguration().getEnvironment() === "test" && typeof params.now === "string") return new Date(params.now)
|
|
3526
|
+
|
|
3527
|
+
return new Date()
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
/**
|
|
3531
|
+
* Resolves sync bootstrap scopes.
|
|
3532
|
+
* @param {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue | undefined>} params - Request params.
|
|
3533
|
+
* @returns {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue>} - Grant scopes.
|
|
3534
|
+
*/
|
|
3535
|
+
frontendSyncBootstrapScopes(params) {
|
|
3536
|
+
const scopes = params.scopes
|
|
3537
|
+
|
|
3538
|
+
if (scopes && typeof scopes === "object" && !Array.isArray(scopes)) {
|
|
3539
|
+
return /** @type {Record<string, import("./configuration-types.js").FrontendModelSyncJsonValue>} */ (scopes)
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
return {}
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
/**
|
|
3546
|
+
* Resolves current user id for sync bootstrap.
|
|
3547
|
+
* @returns {string} - User id.
|
|
3548
|
+
*/
|
|
3549
|
+
frontendSyncBootstrapUserId() {
|
|
3550
|
+
const ability = this.currentAbility()
|
|
3551
|
+
const currentUser = ability?.currentUser()
|
|
3552
|
+
|
|
3553
|
+
if (typeof currentUser === "string" || typeof currentUser === "number") return String(currentUser)
|
|
3554
|
+
if (currentUser && typeof currentUser === "object") {
|
|
3555
|
+
const userRecord = /** @type {{id?: string | number | (() => string | number)}} */ (currentUser)
|
|
3556
|
+
const idValue = typeof userRecord.id === "function" ? userRecord.id() : userRecord.id
|
|
3557
|
+
|
|
3558
|
+
if (typeof idValue === "string" || typeof idValue === "number") return String(idValue)
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
throw new Error("Expected sync bootstrap current user")
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3441
3564
|
/**
|
|
3442
3565
|
* Runs frontend api.
|
|
3443
3566
|
* @returns {Promise<void>} - Shared frontend model API action with batch support.
|
|
@@ -3770,10 +3893,10 @@ export default class FrontendModelController extends Controller {
|
|
|
3770
3893
|
return this.frontendModelErrorPayload("Expected frontend-model custom command scope.")
|
|
3771
3894
|
}
|
|
3772
3895
|
|
|
3773
|
-
const resource =
|
|
3774
|
-
const
|
|
3896
|
+
const resource = this.frontendModelResourceInstance()
|
|
3897
|
+
const command = resource.resourceMethod(methodName)
|
|
3775
3898
|
|
|
3776
|
-
if (
|
|
3899
|
+
if (!command) {
|
|
3777
3900
|
return this.frontendModelErrorPayload(`Missing frontend-model custom command '${methodName}'.`)
|
|
3778
3901
|
}
|
|
3779
3902
|
|
|
@@ -3783,7 +3906,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3783
3906
|
// unchanged, so existing parameterless methods keep working. The args are untrusted
|
|
3784
3907
|
// client input typed only by the declared contract, so methods must still validate.
|
|
3785
3908
|
const commandArguments = this.frontendModelCustomCommandArguments(params)
|
|
3786
|
-
const responsePayload = await
|
|
3909
|
+
const responsePayload = await command.method.call(command.resource, commandArguments)
|
|
3787
3910
|
|
|
3788
3911
|
if (!responsePayload || typeof responsePayload !== "object") {
|
|
3789
3912
|
return {status: "success"}
|
|
@@ -3792,7 +3915,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3792
3915
|
return /** @type {Record<string, ?>} */ (
|
|
3793
3916
|
await this.autoSerializeFrontendModelsInPayload(
|
|
3794
3917
|
responsePayload,
|
|
3795
|
-
/** @type {{serialize: (model: ?, action: string) => Promise<Record<string, ?>>}} */ (resource),
|
|
3918
|
+
/** @type {{serialize: (model: ?, action: string) => Promise<Record<string, ?>>}} */ (command.resource),
|
|
3796
3919
|
methodName
|
|
3797
3920
|
)
|
|
3798
3921
|
)
|