velocious 1.0.472 → 1.0.474

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 (42) hide show
  1. package/build/authorization/base-resource.js +99 -0
  2. package/build/configuration-types.js +3 -2
  3. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -1
  4. package/build/frontend-model-controller.js +21 -17
  5. package/build/frontend-model-resource/base-resource.js +254 -46
  6. package/build/frontend-model-resource/velocious-attachment-resource.js +0 -3
  7. package/build/frontend-models/resource-definition.js +96 -21
  8. package/build/frontend-models/websocket-channel.js +24 -20
  9. package/build/routes/resolver.js +7 -5
  10. package/build/src/authorization/base-resource.d.ts +41 -0
  11. package/build/src/authorization/base-resource.d.ts.map +1 -1
  12. package/build/src/authorization/base-resource.js +88 -1
  13. package/build/src/configuration-types.d.ts +15 -3
  14. package/build/src/configuration-types.d.ts.map +1 -1
  15. package/build/src/configuration-types.js +4 -3
  16. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
  17. package/build/src/frontend-model-controller.d.ts.map +1 -1
  18. package/build/src/frontend-model-controller.js +22 -18
  19. package/build/src/frontend-model-resource/base-resource.d.ts +67 -0
  20. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  21. package/build/src/frontend-model-resource/base-resource.js +242 -54
  22. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +0 -2
  23. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
  24. package/build/src/frontend-model-resource/velocious-attachment-resource.js +1 -3
  25. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  26. package/build/src/frontend-models/resource-definition.js +83 -25
  27. package/build/src/frontend-models/websocket-channel.d.ts +6 -0
  28. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  29. package/build/src/frontend-models/websocket-channel.js +23 -19
  30. package/build/src/routes/resolver.d.ts.map +1 -1
  31. package/build/src/routes/resolver.js +8 -6
  32. package/package.json +1 -1
  33. package/scripts/tensorbuzz-retry +21 -0
  34. package/src/authorization/base-resource.js +99 -0
  35. package/src/configuration-types.js +3 -2
  36. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -1
  37. package/src/frontend-model-controller.js +21 -17
  38. package/src/frontend-model-resource/base-resource.js +254 -46
  39. package/src/frontend-model-resource/velocious-attachment-resource.js +0 -3
  40. package/src/frontend-models/resource-definition.js +96 -21
  41. package/src/frontend-models/websocket-channel.js +24 -20
  42. package/src/routes/resolver.js +7 -5
@@ -115,6 +115,105 @@ export default class AuthorizationBaseResource {
115
115
  return this.context.currentUser
116
116
  }
117
117
 
118
+ /**
119
+ * Runs current device.
120
+ * @returns {unknown} - Current device from context.
121
+ */
122
+ currentDevice() {
123
+ return this.context.currentDevice
124
+ }
125
+
126
+ /**
127
+ * Runs offline grant.
128
+ * @returns {unknown} - Offline grant from context.
129
+ */
130
+ offlineGrant() {
131
+ return this.context.offlineGrant
132
+ }
133
+
134
+ /**
135
+ * Runs now.
136
+ * @returns {Date} - Current time from context or the system clock.
137
+ */
138
+ now() {
139
+ if (typeof this.context.now === "function") return this.context.now()
140
+ if (this.context.now instanceof Date) return this.context.now
141
+
142
+ return new Date()
143
+ }
144
+
145
+ /**
146
+ * Runs resource runtime.
147
+ * @returns {"backend" | "frontend" | "offline"} - Resource runtime context.
148
+ */
149
+ resourceRuntime() {
150
+ if (this.context.resourceRuntime === "frontend") return "frontend"
151
+ if (this.context.resourceRuntime === "offline") return "offline"
152
+
153
+ return "backend"
154
+ }
155
+
156
+ /**
157
+ * Runs is backend.
158
+ * @returns {boolean} - Whether the resource is running in the backend runtime.
159
+ */
160
+ isBackend() {
161
+ return this.resourceRuntime() === "backend"
162
+ }
163
+
164
+ /**
165
+ * Runs is frontend.
166
+ * @returns {boolean} - Whether the resource is running in the frontend runtime.
167
+ */
168
+ isFrontend() {
169
+ return this.resourceRuntime() === "frontend"
170
+ }
171
+
172
+ /**
173
+ * Runs is offline.
174
+ * @returns {boolean} - Whether the resource is running with offline context.
175
+ */
176
+ isOffline() {
177
+ return this.resourceRuntime() === "offline" || this.context.offlineGrant !== undefined
178
+ }
179
+
180
+ /**
181
+ * Resolves a model class from the portable resource context.
182
+ * @param {string} name - Model name.
183
+ * @returns {unknown} - Model class from registry.
184
+ */
185
+ model(name) {
186
+ const registry = this.context.modelRegistry
187
+
188
+ if (registry && typeof registry === "object") {
189
+ if ("model" in registry && typeof registry.model === "function") {
190
+ const modelClass = registry.model(name)
191
+
192
+ if (modelClass) return modelClass
193
+ }
194
+
195
+ if (name in registry) return /** @type {Record<string, unknown>} */ (registry)[name]
196
+ }
197
+
198
+ const contextModel = this.context.model
199
+
200
+ if (typeof contextModel === "function") {
201
+ const modelClass = contextModel(name)
202
+
203
+ if (modelClass) return modelClass
204
+ }
205
+
206
+ const configuration = this.context.configuration
207
+
208
+ if (configuration) {
209
+ const modelClasses = configuration.getModelClasses()
210
+
211
+ if (name in modelClasses) return modelClasses[name]
212
+ }
213
+
214
+ throw new Error(`${this.constructor.name} could not resolve model '${name}' from the resource context model registry.`)
215
+ }
216
+
118
217
  /**
119
218
  * Runs request.
120
219
  * @returns {import("../http-server/client/request.js").default | import("../http-server/client/websocket-request.js").default | undefined} - Request from context.
@@ -240,7 +240,7 @@
240
240
  */
241
241
 
242
242
  /**
243
- * @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentUser?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default}} VelociousLooseObject
243
+ * @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentDevice?: unknown, currentUser?: unknown, modelRegistry?: Record<string, unknown> | {model: (name: string) => unknown}, now?: Date | (() => Date), offlineGrant?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, resourceRuntime?: "backend" | "frontend" | "offline"}} VelociousLooseObject
244
244
  */
245
245
 
246
246
  /**
@@ -309,13 +309,14 @@
309
309
  /**
310
310
  * @typedef {object} FrontendModelResourceConfiguration
311
311
  * @property {Array<string | FrontendModelAttributeConfiguration> | Record<string, FrontendModelAttributeConfiguration | import("./database/drivers/base-column.js").default | boolean>} attributes - Attributes to expose on the frontend model.
312
- * @property {string[]} [abilities] - Ability action list (camelCase action names). Defaults to `["read"]` for `find` and `index` when omitted.
312
+ * @property {string[]} [abilities] - Additional camelCase ability action names to expose for per-record `record.can(action)` reads. Base CRUD actions (`read`, `create`, `update`, `destroy`) are always included and must not be listed here.
313
313
  * @property {Record<string, FrontendModelAttachmentConfiguration>} [attachments] - Attachment helpers keyed by attachment name.
314
314
  * @property {string[]} [commands] - Legacy built-in command names (`index`, `find`, `create`, `update`, `destroy`, `attach`, `download`, `url`).
315
315
  * @property {Array<FrontendModelResourceCustomCommand>} [collectionCommands] - Custom collection commands. Each entry is a camelCase method name, or a `{name, args?, returnType?}` object declaring typed arguments and/or a response type. The runtime derives the kebab-case command slug from the name.
316
316
  * @property {Array<FrontendModelResourceCustomCommand>} [memberCommands] - Custom member commands. Each entry is a camelCase method name, or a `{name, args?, returnType?}` object declaring typed arguments and/or a response type. The runtime derives the kebab-case command slug from the name.
317
317
  * @property {string[]} [builtInCollectionCommands] - Built-in collection command names (`index`, `create`).
318
318
  * @property {string[]} [builtInMemberCommands] - Built-in member command names (`find`, `update`, `destroy`, `attach`, `download`, `url`).
319
+ * @property {string} [modelName] - Frontend model name override.
319
320
  * @property {string[]} [relationships] - Relationship names to expose in frontend models. Type and target model are inferred from the backend model's registered relationships.
320
321
  * @property {string} [primaryKey] - Primary key attribute name.
321
322
  * @property {FrontendModelResourceServerConfiguration} [server] - Optional legacy backend behavior overrides for built-in frontend actions.
@@ -1439,7 +1439,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
1439
1439
  */
1440
1440
  frontendAttributeIsTranslated({attributeName, modelClass, resourceClass}) {
1441
1441
  if (resourceClass) {
1442
- const translatedAttributes = resourceClass.translatedAttributes
1442
+ const translatedAttributes = resourceClass.translatedAttributesConfig()
1443
1443
 
1444
1444
  if (Array.isArray(translatedAttributes) && translatedAttributes.includes(attributeName)) return true
1445
1445
  }
@@ -638,11 +638,15 @@ export default class FrontendModelController extends Controller {
638
638
  */
639
639
  async withFrontendModelRequestContext(params, response, callback) {
640
640
  const configuration = this.getConfiguration()
641
- const tenant = await configuration.resolveTenant({
642
- params,
643
- request: this.request(),
644
- response
645
- })
641
+ const tenant = configuration.getTenantResolver()
642
+ ? await configuration.ensureConnections({name: "Frontend model request tenant resolution"}, async () => {
643
+ return await configuration.resolveTenant({
644
+ params,
645
+ request: this.request(),
646
+ response
647
+ })
648
+ })
649
+ : undefined
646
650
 
647
651
  return await configuration.runWithTenant(tenant, async () => {
648
652
  return await configuration.ensureConnections({name: "Frontend model request"}, async () => {
@@ -2426,7 +2430,7 @@ export default class FrontendModelController extends Controller {
2426
2430
 
2427
2431
  const resource = this.frontendModelResourceInstance()
2428
2432
  const resourceClass = /** @type {typeof import("./frontend-model-resource/base-resource.js").default} */ (resource.constructor)
2429
- const translatedSet = new Set(resourceClass.translatedAttributes || [])
2433
+ const translatedSet = new Set(resourceClass.translatedAttributesConfig() || [])
2430
2434
  let needsTranslations = false
2431
2435
 
2432
2436
  for (const attributeName of selectedAttributes) {
@@ -2663,10 +2667,10 @@ export default class FrontendModelController extends Controller {
2663
2667
  * Resource has attribute.
2664
2668
  * @param {string} attributeName - Attribute name.
2665
2669
  */
2666
- const resourceHasAttribute = (attributeName) => {
2670
+ const resourceAttributeMethod = (attributeName) => {
2667
2671
  const methodName = resourceAttributeMethodName(attributeName)
2668
2672
 
2669
- return resourceInstance && typeof /** @type {Record<string, ?>} */ (/** @type {?} */ (resourceInstance))[methodName] === "function"
2673
+ return resourceInstance?.resourceMethod(methodName) || null
2670
2674
  }
2671
2675
 
2672
2676
  /**
@@ -2696,10 +2700,10 @@ export default class FrontendModelController extends Controller {
2696
2700
  */
2697
2701
  const serializedAttributeValue = async (attributeName) => {
2698
2702
  // Check resource instance first (virtual/computed attributes via ${name}Attribute convention)
2699
- if (resourceHasAttribute(attributeName)) {
2700
- const methodName = resourceAttributeMethodName(attributeName)
2703
+ const resourceAttribute = resourceAttributeMethod(attributeName)
2701
2704
 
2702
- return await /** @type {Record<string, Function>} */ (/** @type {?} */ (resourceInstance))[methodName](model)
2705
+ if (resourceAttribute) {
2706
+ return await resourceAttribute.method.call(resourceAttribute.resource, model)
2703
2707
  }
2704
2708
 
2705
2709
  // Fall back to model method
@@ -2718,7 +2722,7 @@ export default class FrontendModelController extends Controller {
2718
2722
  * @param {string} attributeName - Attribute name.
2719
2723
  */
2720
2724
  const attributeExists = (attributeName) => {
2721
- return (attributeName in modelAttributes) || (attributeName in /** @type {Record<string, ?>} */ (model)) || resourceHasAttribute(attributeName)
2725
+ return (attributeName in modelAttributes) || (attributeName in /** @type {Record<string, ?>} */ (model)) || Boolean(resourceAttributeMethod(attributeName))
2722
2726
  }
2723
2727
 
2724
2728
  if (!selectedAttributes) {
@@ -3766,10 +3770,10 @@ export default class FrontendModelController extends Controller {
3766
3770
  return this.frontendModelErrorPayload("Expected frontend-model custom command scope.")
3767
3771
  }
3768
3772
 
3769
- const resource = /** @type {Record<string, ?>} */ (this.frontendModelResourceInstance())
3770
- const commandMethod = resource[methodName]
3773
+ const resource = this.frontendModelResourceInstance()
3774
+ const command = resource.resourceMethod(methodName)
3771
3775
 
3772
- if (typeof commandMethod !== "function") {
3776
+ if (!command) {
3773
3777
  return this.frontendModelErrorPayload(`Missing frontend-model custom command '${methodName}'.`)
3774
3778
  }
3775
3779
 
@@ -3779,7 +3783,7 @@ export default class FrontendModelController extends Controller {
3779
3783
  // unchanged, so existing parameterless methods keep working. The args are untrusted
3780
3784
  // client input typed only by the declared contract, so methods must still validate.
3781
3785
  const commandArguments = this.frontendModelCustomCommandArguments(params)
3782
- const responsePayload = await commandMethod.call(resource, commandArguments)
3786
+ const responsePayload = await command.method.call(command.resource, commandArguments)
3783
3787
 
3784
3788
  if (!responsePayload || typeof responsePayload !== "object") {
3785
3789
  return {status: "success"}
@@ -3788,7 +3792,7 @@ export default class FrontendModelController extends Controller {
3788
3792
  return /** @type {Record<string, ?>} */ (
3789
3793
  await this.autoSerializeFrontendModelsInPayload(
3790
3794
  responsePayload,
3791
- /** @type {{serialize: (model: ?, action: string) => Promise<Record<string, ?>>}} */ (resource),
3795
+ /** @type {{serialize: (model: ?, action: string) => Promise<Record<string, ?>>}} */ (command.resource),
3792
3796
  methodName
3793
3797
  )
3794
3798
  )