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.
Files changed (96) hide show
  1. package/README.md +7 -2
  2. package/build/authorization/base-resource.js +99 -0
  3. package/build/configuration-types.js +41 -3
  4. package/build/configuration.js +41 -1
  5. package/build/database/drivers/base.js +51 -3
  6. package/build/database/drivers/mysql/index.js +74 -7
  7. package/build/database/drivers/sqlite/connection-sql-js.js +16 -5
  8. package/build/database/drivers/sqlite/index.web.js +29 -15
  9. package/build/database/drivers/sqlite/web-persistence.js +521 -0
  10. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +64 -1
  11. package/build/frontend-model-controller.js +139 -16
  12. package/build/frontend-model-resource/base-resource.js +258 -46
  13. package/build/frontend-model-resource/velocious-attachment-resource.js +0 -3
  14. package/build/frontend-models/base.js +146 -2
  15. package/build/frontend-models/query.js +3 -3
  16. package/build/frontend-models/resource-definition.js +400 -23
  17. package/build/routes/hooks/frontend-model-command-route-hook.js +8 -0
  18. package/build/src/authorization/base-resource.d.ts +41 -0
  19. package/build/src/authorization/base-resource.d.ts.map +1 -1
  20. package/build/src/authorization/base-resource.js +88 -1
  21. package/build/src/configuration-types.d.ts +128 -5
  22. package/build/src/configuration-types.d.ts.map +1 -1
  23. package/build/src/configuration-types.js +38 -4
  24. package/build/src/configuration.d.ts +18 -1
  25. package/build/src/configuration.d.ts.map +1 -1
  26. package/build/src/configuration.js +37 -2
  27. package/build/src/database/drivers/base.d.ts +30 -0
  28. package/build/src/database/drivers/base.d.ts.map +1 -1
  29. package/build/src/database/drivers/base.js +47 -4
  30. package/build/src/database/drivers/mysql/index.d.ts +32 -3
  31. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  32. package/build/src/database/drivers/mysql/index.js +65 -7
  33. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +8 -1
  34. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
  35. package/build/src/database/drivers/sqlite/connection-sql-js.js +15 -6
  36. package/build/src/database/drivers/sqlite/index.web.d.ts +5 -5
  37. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  38. package/build/src/database/drivers/sqlite/index.web.js +27 -13
  39. package/build/src/database/drivers/sqlite/web-persistence.d.ts +77 -0
  40. package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -0
  41. package/build/src/database/drivers/sqlite/web-persistence.js +464 -0
  42. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +30 -0
  43. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  44. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +54 -2
  45. package/build/src/frontend-model-controller.d.ts +34 -0
  46. package/build/src/frontend-model-controller.d.ts.map +1 -1
  47. package/build/src/frontend-model-controller.js +125 -17
  48. package/build/src/frontend-model-resource/base-resource.d.ts +69 -0
  49. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  50. package/build/src/frontend-model-resource/base-resource.js +247 -54
  51. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +0 -2
  52. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
  53. package/build/src/frontend-model-resource/velocious-attachment-resource.js +1 -3
  54. package/build/src/frontend-models/base.d.ts +29 -0
  55. package/build/src/frontend-models/base.d.ts.map +1 -1
  56. package/build/src/frontend-models/base.js +131 -3
  57. package/build/src/frontend-models/query.js +4 -4
  58. package/build/src/frontend-models/resource-definition.d.ts +6 -0
  59. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  60. package/build/src/frontend-models/resource-definition.js +355 -28
  61. package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
  62. package/build/src/routes/hooks/frontend-model-command-route-hook.js +8 -1
  63. package/build/src/sync/device-identity.d.ts +240 -0
  64. package/build/src/sync/device-identity.d.ts.map +1 -0
  65. package/build/src/sync/device-identity.js +454 -0
  66. package/build/src/sync/local-mutation-log.d.ts +212 -0
  67. package/build/src/sync/local-mutation-log.d.ts.map +1 -0
  68. package/build/src/sync/local-mutation-log.js +404 -0
  69. package/build/src/sync/offline-grant.d.ts +157 -0
  70. package/build/src/sync/offline-grant.d.ts.map +1 -0
  71. package/build/src/sync/offline-grant.js +292 -0
  72. package/build/sync/device-identity.js +503 -0
  73. package/build/sync/local-mutation-log.js +448 -0
  74. package/build/sync/offline-grant.js +327 -0
  75. package/build/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +3 -3
  77. package/scripts/tensorbuzz-retry +21 -0
  78. package/src/authorization/base-resource.js +99 -0
  79. package/src/configuration-types.js +41 -3
  80. package/src/configuration.js +41 -1
  81. package/src/database/drivers/base.js +51 -3
  82. package/src/database/drivers/mysql/index.js +74 -7
  83. package/src/database/drivers/sqlite/connection-sql-js.js +16 -5
  84. package/src/database/drivers/sqlite/index.web.js +29 -15
  85. package/src/database/drivers/sqlite/web-persistence.js +521 -0
  86. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +64 -1
  87. package/src/frontend-model-controller.js +139 -16
  88. package/src/frontend-model-resource/base-resource.js +258 -46
  89. package/src/frontend-model-resource/velocious-attachment-resource.js +0 -3
  90. package/src/frontend-models/base.js +146 -2
  91. package/src/frontend-models/query.js +3 -3
  92. package/src/frontend-models/resource-definition.js +400 -23
  93. package/src/routes/hooks/frontend-model-command-route-hook.js +8 -0
  94. package/src/sync/device-identity.js +503 -0
  95. package/src/sync/local-mutation-log.js +448 -0
  96. 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
- return normalizeQueryPreload(preload)
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.getRelationshipByName(relationshipName)
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 || !isPlainObject(relationshipPreload)) continue
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.translatedAttributes || [])
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 resourceHasAttribute = (attributeName) => {
2693
+ const resourceAttributeMethod = (attributeName) => {
2671
2694
  const methodName = resourceAttributeMethodName(attributeName)
2672
2695
 
2673
- return resourceInstance && typeof /** @type {Record<string, ?>} */ (/** @type {?} */ (resourceInstance))[methodName] === "function"
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
- if (resourceHasAttribute(attributeName)) {
2704
- const methodName = resourceAttributeMethodName(attributeName)
2726
+ const resourceAttribute = resourceAttributeMethod(attributeName)
2705
2727
 
2706
- return await /** @type {Record<string, Function>} */ (/** @type {?} */ (resourceInstance))[methodName](model)
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)) || resourceHasAttribute(attributeName)
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 = /** @type {Record<string, ?>} */ (this.frontendModelResourceInstance())
3774
- const commandMethod = resource[methodName]
3896
+ const resource = this.frontendModelResourceInstance()
3897
+ const command = resource.resourceMethod(methodName)
3775
3898
 
3776
- if (typeof commandMethod !== "function") {
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 commandMethod.call(resource, commandArguments)
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
  )