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.
- package/build/authorization/base-resource.js +99 -0
- package/build/configuration-types.js +3 -2
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -1
- package/build/frontend-model-controller.js +21 -17
- package/build/frontend-model-resource/base-resource.js +254 -46
- package/build/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/build/frontend-models/resource-definition.js +96 -21
- package/build/frontend-models/websocket-channel.js +24 -20
- package/build/routes/resolver.js +7 -5
- 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 +15 -3
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +4 -3
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +22 -18
- package/build/src/frontend-model-resource/base-resource.d.ts +67 -0
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +242 -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/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +83 -25
- package/build/src/frontend-models/websocket-channel.d.ts +6 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +23 -19
- package/build/src/routes/resolver.d.ts.map +1 -1
- package/build/src/routes/resolver.js +8 -6
- package/package.json +1 -1
- package/scripts/tensorbuzz-retry +21 -0
- package/src/authorization/base-resource.js +99 -0
- package/src/configuration-types.js +3 -2
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -1
- package/src/frontend-model-controller.js +21 -17
- package/src/frontend-model-resource/base-resource.js +254 -46
- package/src/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/src/frontend-models/resource-definition.js +96 -21
- package/src/frontend-models/websocket-channel.js +24 -20
- 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] -
|
|
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.
|
|
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 =
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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.
|
|
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
|
|
2670
|
+
const resourceAttributeMethod = (attributeName) => {
|
|
2667
2671
|
const methodName = resourceAttributeMethodName(attributeName)
|
|
2668
2672
|
|
|
2669
|
-
return resourceInstance
|
|
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
|
-
|
|
2700
|
-
const methodName = resourceAttributeMethodName(attributeName)
|
|
2703
|
+
const resourceAttribute = resourceAttributeMethod(attributeName)
|
|
2701
2704
|
|
|
2702
|
-
|
|
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)) ||
|
|
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 =
|
|
3770
|
-
const
|
|
3773
|
+
const resource = this.frontendModelResourceInstance()
|
|
3774
|
+
const command = resource.resourceMethod(methodName)
|
|
3771
3775
|
|
|
3772
|
-
if (
|
|
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
|
|
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
|
)
|