velocious 1.0.454 → 1.0.456
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 +2 -1
- package/build/authorization/ability.js +3 -6
- package/build/authorization/base-resource.js +7 -9
- package/build/configuration-types.js +3 -3
- package/build/configuration.js +12 -17
- package/build/database/drivers/base.js +3 -3
- package/build/database/pool/base.js +2 -1
- package/build/database/query/preloader/ensure-model-class-initialized.js +1 -6
- package/build/database/record/attachments/handle.js +32 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +44 -29
- package/build/frontend-model-controller.js +167 -88
- package/build/frontend-model-resource/base-resource.js +133 -31
- package/build/frontend-models/base.js +30 -6
- package/build/frontend-models/model-registry.js +1 -1
- package/build/frontend-models/query.js +3 -9
- package/build/frontend-models/resource-definition.js +1 -0
- package/build/frontend-models/transport-serialization.js +2 -3
- package/build/frontend-models/websocket-channel.js +7 -12
- package/build/frontend-models/websocket-publishers.js +20 -65
- package/build/routes/hooks/frontend-model-command-route-hook.js +1 -1
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +4 -8
- package/build/src/authorization/base-resource.d.ts +2 -2
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +7 -9
- package/build/src/configuration-types.d.ts +6 -6
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +4 -4
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +13 -18
- package/build/src/database/drivers/base.js +4 -4
- package/build/src/database/pool/base.d.ts +2 -1
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +3 -2
- package/build/src/database/query/preloader/ensure-model-class-initialized.d.ts.map +1 -1
- package/build/src/database/query/preloader/ensure-model-class-initialized.js +2 -6
- package/build/src/database/record/attachments/handle.d.ts +13 -0
- package/build/src/database/record/attachments/handle.d.ts.map +1 -1
- package/build/src/database/record/attachments/handle.js +29 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +6 -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 +44 -27
- package/build/src/frontend-model-controller.d.ts +70 -26
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +144 -87
- package/build/src/frontend-model-resource/base-resource.d.ts +192 -16
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +124 -33
- package/build/src/frontend-models/base.d.ts +14 -1
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +28 -7
- package/build/src/frontend-models/model-registry.js +2 -2
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +4 -10
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +2 -1
- package/build/src/frontend-models/transport-serialization.d.ts +2 -4
- package/build/src/frontend-models/transport-serialization.d.ts.map +1 -1
- package/build/src/frontend-models/transport-serialization.js +3 -4
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +8 -13
- package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-publishers.js +22 -59
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +2 -2
- package/package.json +1 -1
- package/scripts/test-browser.js +2 -2
- package/src/authorization/ability.js +3 -6
- package/src/authorization/base-resource.js +7 -9
- package/src/configuration-types.js +3 -3
- package/src/configuration.js +12 -17
- package/src/database/drivers/base.js +3 -3
- package/src/database/pool/base.js +2 -1
- package/src/database/query/preloader/ensure-model-class-initialized.js +1 -6
- package/src/database/record/attachments/handle.js +32 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +44 -29
- package/src/frontend-model-controller.js +167 -88
- package/src/frontend-model-resource/base-resource.js +133 -31
- package/src/frontend-models/base.js +30 -6
- package/src/frontend-models/model-registry.js +1 -1
- package/src/frontend-models/query.js +3 -9
- package/src/frontend-models/resource-definition.js +1 -0
- package/src/frontend-models/transport-serialization.js +2 -3
- package/src/frontend-models/websocket-channel.js +7 -12
- package/src/frontend-models/websocket-publishers.js +20 -65
- package/src/routes/hooks/frontend-model-command-route-hook.js +1 -1
package/README.md
CHANGED
|
@@ -376,6 +376,7 @@ export default new Configuration({
|
|
|
376
376
|
```
|
|
377
377
|
|
|
378
378
|
`frontendModels` entries must be `FrontendModelBaseResource` subclasses. Built-in CRUD/find/index/serialize behavior lives in the base class, and app resources override only the pieces they actually need.
|
|
379
|
+
Resource-level index customization should prefer `indexQuery()` or the pagination/search/sort hooks over replacing `records()`, so built-in pluck and aggregate count support can keep using the same query. See [`docs/frontend-model-resources.md`](docs/frontend-model-resources.md) for the resource extension points.
|
|
379
380
|
|
|
380
381
|
Resources expose the full CRUD ability set (`create`, `destroy`, `read`, `update`) by default. To restrict the API surface — for example to a read-only resource — declare an explicit subset:
|
|
381
382
|
|
|
@@ -446,7 +447,7 @@ Frontend-model `group(...)` is attribute/path based and does not accept raw SQL
|
|
|
446
447
|
Frontend-model `where(...)` supports nested relationship descriptors (for example `Task.where({project: {creatingUser: {reference: "owner-b"}}})`) and does not accept raw SQL fragments.
|
|
447
448
|
Frontend-model `joins(...)` supports relationship-object descriptors only (for example `Task.joins({project: {creatingUser: true}})`) and rejects raw SQL join strings.
|
|
448
449
|
Frontend-model `distinct(...)` only accepts booleans (`true` by default) and is applied server-side through the backend query API.
|
|
449
|
-
Frontend-model `pluck(...)` validates attribute/path descriptors against configured model metadata and does not accept SQL fragments.
|
|
450
|
+
Frontend-model `pluck(...)` validates attribute/path descriptors against configured resource/model metadata and does not accept SQL fragments or hidden raw model columns when the resource declares an explicit attribute list.
|
|
450
451
|
Frontend-model query fields are limited to attributes exposed by the backend resource. Use `{name: "attributeName", selectedByDefault: false}` for fields that may be selected or filtered explicitly but should stay out of default payloads.
|
|
451
452
|
|
|
452
453
|
When backend payloads include `__preloadedRelationships`, nested frontend-model relationships are hydrated recursively. Relationship methods can use `getRelationshipByName("relationship").loaded()` and will throw when a relationship was not preloaded.
|
|
@@ -72,7 +72,7 @@ export default class VelociousAuthorizationAbility {
|
|
|
72
72
|
_resolveResourcesFromConfiguration() {
|
|
73
73
|
const configuration = this.context?.configuration
|
|
74
74
|
|
|
75
|
-
if (!configuration
|
|
75
|
+
if (!configuration) {
|
|
76
76
|
return []
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -85,12 +85,10 @@ export default class VelociousAuthorizationAbility {
|
|
|
85
85
|
for (const backendProject of backendProjects) {
|
|
86
86
|
const frontendModels = backendProject.frontendModels
|
|
87
87
|
|
|
88
|
-
if (!frontendModels
|
|
88
|
+
if (!frontendModels) continue
|
|
89
89
|
|
|
90
90
|
for (const resourceDefinition of Object.values(frontendModels)) {
|
|
91
|
-
|
|
92
|
-
resolved.push(resourceDefinition)
|
|
93
|
-
}
|
|
91
|
+
resolved.push(resourceDefinition)
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
|
|
@@ -173,7 +171,6 @@ export default class VelociousAuthorizationAbility {
|
|
|
173
171
|
for (const ResourceClass of this.resources) {
|
|
174
172
|
const resourceModelClass = ResourceClass.modelClass()
|
|
175
173
|
|
|
176
|
-
if (!resourceModelClass) continue
|
|
177
174
|
if (resourceModelClass !== modelClass) continue
|
|
178
175
|
|
|
179
176
|
const resourceInstance = new ResourceClass({
|
|
@@ -22,9 +22,13 @@ export default class AuthorizationBaseResource {
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Runs model class.
|
|
25
|
-
* @returns {typeof import("../database/record/index.js").default
|
|
25
|
+
* @returns {typeof import("../database/record/index.js").default} - Model class handled by this resource.
|
|
26
26
|
*/
|
|
27
27
|
static modelClass() {
|
|
28
|
+
if (!this.ModelClass) {
|
|
29
|
+
throw new Error(`${this.name} must define static ModelClass before calling ability helpers.`)
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
return this.ModelClass
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -69,15 +73,9 @@ export default class AuthorizationBaseResource {
|
|
|
69
73
|
* @returns {typeof import("../database/record/index.js").default} - Model class handled by this resource.
|
|
70
74
|
*/
|
|
71
75
|
requiredModelClass() {
|
|
72
|
-
const
|
|
73
|
-
* Narrows the runtime value to the documented type.
|
|
74
|
-
* @type {typeof AuthorizationBaseResource} */ (this.constructor).modelClass()
|
|
75
|
-
|
|
76
|
-
if (!modelClass) {
|
|
77
|
-
throw new Error(`${this.constructor.name} must define static ModelClass before calling ability helpers.`)
|
|
78
|
-
}
|
|
76
|
+
const ResourceClass = /** @type {typeof AuthorizationBaseResource} */ (this.constructor)
|
|
79
77
|
|
|
80
|
-
return modelClass
|
|
78
|
+
return ResourceClass.modelClass()
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
/**
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
* @typedef {object} ClientErrorPayloadContext
|
|
217
217
|
* @property {string} controller - Controller class name.
|
|
218
218
|
* @property {string} [action] - Controller action or endpoint label.
|
|
219
|
-
* @property {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url" | "custom-command"} [commandType] - Frontend-model command type.
|
|
219
|
+
* @property {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url" | "custom-command"} [commandType] - Frontend-model command type.
|
|
220
220
|
* @property {boolean} [expectedError] - Whether the error is an expected user-flow failure.
|
|
221
221
|
* @property {boolean} [frontendModelEndpoint] - Whether the error came from the frontend-model endpoint.
|
|
222
222
|
* @property {string} [model] - Frontend-model name from the failed request.
|
|
@@ -342,10 +342,10 @@
|
|
|
342
342
|
|
|
343
343
|
/**
|
|
344
344
|
* @typedef {object} FrontendModelResourceServerConfiguration
|
|
345
|
-
* @property {function({action: "index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default}) : (boolean | void | Promise<boolean | void>)} [beforeAction] - Optional callback run before built-in frontend actions.
|
|
345
|
+
* @property {function({action: "index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default}) : (boolean | void | Promise<boolean | void>)} [beforeAction] - Optional callback run before built-in frontend actions.
|
|
346
346
|
* @property {function({action: "index", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default}) : Promise<import("./database/record/index.js").default[]>} [records] - Records loader for frontendIndex.
|
|
347
347
|
* @property {function({action: "index" | "find" | "create" | "update", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, model: import("./database/record/index.js").default}) : Record<string, ?> | Promise<Record<string, ?>>} [serialize] - Record serializer for response payloads.
|
|
348
|
-
* @property {function({action: "find" | "update" | "destroy" | "attach" | "download" | "url", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, id: string | number}) : Promise<import("./database/record/index.js").default | null>} [find] - Record loader for find/update/destroy/attach/download/url actions.
|
|
348
|
+
* @property {function({action: "find" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, id: string | number}) : Promise<import("./database/record/index.js").default | null>} [find] - Record loader for find/update/destroy/attach/download/url actions.
|
|
349
349
|
* @property {function({action: "create", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, attributes: Record<string, ?>}) : Promise<import("./database/record/index.js").default>} [create] - Custom create callback.
|
|
350
350
|
* @property {function({action: "update", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, model: import("./database/record/index.js").default, attributes: Record<string, ?>}) : Promise<import("./database/record/index.js").default | void>} [update] - Custom update callback.
|
|
351
351
|
* @property {function({action: "destroy", controller: import("./controller.js").default, params: Record<string, ?>, modelClass: typeof import("./database/record/index.js").default, model: import("./database/record/index.js").default}) : Promise<void>} [destroy] - Custom destroy callback.
|
package/build/configuration.js
CHANGED
|
@@ -18,7 +18,7 @@ import Ability from "./authorization/ability.js"
|
|
|
18
18
|
import EventEmitter from "./utils/event-emitter.js"
|
|
19
19
|
import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channel-subscribers.js"
|
|
20
20
|
import {CurrentConfigurationNotSetError, currentConfiguration, setCurrentConfiguration} from "./current-configuration.js"
|
|
21
|
-
import {frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
|
|
21
|
+
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
|
|
22
22
|
import PluginRoutes from "./routes/plugin-routes.js"
|
|
23
23
|
import restArgsError from "./utils/rest-args-error.js"
|
|
24
24
|
import {withTrackedStack} from "./utils/with-tracked-stack.js"
|
|
@@ -1673,12 +1673,13 @@ export default class VelociousConfiguration {
|
|
|
1673
1673
|
throw new Error(`Resource for ${modelName} defines relationships as an object. Use an array instead: static relationships = ${JSON.stringify(Object.keys(resourceConfig.relationships))}`)
|
|
1674
1674
|
}
|
|
1675
1675
|
|
|
1676
|
-
const
|
|
1677
|
-
* Types the following value.
|
|
1678
|
-
* @type {typeof import("./database/record/index.js").default | undefined} */ (this.modelClasses[modelName])
|
|
1676
|
+
const resourceClass = frontendModelResourceClassFromDefinition(resourceDefinition)
|
|
1679
1677
|
|
|
1680
|
-
if (!
|
|
1678
|
+
if (!resourceClass) {
|
|
1679
|
+
throw new Error(`Frontend model resource for ${modelName} must be a FrontendModelBaseResource subclass.`)
|
|
1680
|
+
}
|
|
1681
1681
|
|
|
1682
|
+
const modelClass = resourceClass.modelClass()
|
|
1682
1683
|
const existingRelationships = modelClass.getRelationshipsMap()
|
|
1683
1684
|
|
|
1684
1685
|
for (const relationshipName of resourceConfig.relationships) {
|
|
@@ -2567,27 +2568,21 @@ export default class VelociousConfiguration {
|
|
|
2567
2568
|
return
|
|
2568
2569
|
}
|
|
2569
2570
|
|
|
2571
|
+
/** @type {Set<typeof import("./database/pool/base.js").default>} */
|
|
2570
2572
|
const constructors = new Set()
|
|
2571
2573
|
|
|
2572
2574
|
this._closeDatabaseConnectionsPromise = (async () => {
|
|
2573
2575
|
for (const pool of Object.values(this.databasePools)) {
|
|
2574
2576
|
if (!pool) continue
|
|
2575
2577
|
|
|
2576
|
-
|
|
2577
|
-
await pool.closeAll()
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
const poolConstructor = /**
|
|
2581
|
-
* Types the following value.
|
|
2582
|
-
* @type {{clearGlobalConnections?: (configuration: VelociousConfiguration) => void}} */ (pool.constructor)
|
|
2578
|
+
await pool.closeAll()
|
|
2583
2579
|
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
}
|
|
2580
|
+
const PoolClass = /** @type {typeof import("./database/pool/base.js").default} */ (pool.constructor)
|
|
2581
|
+
constructors.add(PoolClass)
|
|
2587
2582
|
}
|
|
2588
2583
|
|
|
2589
|
-
for (const
|
|
2590
|
-
|
|
2584
|
+
for (const PoolClass of constructors) {
|
|
2585
|
+
PoolClass.clearGlobalConnections(this)
|
|
2591
2586
|
}
|
|
2592
2587
|
|
|
2593
2588
|
// Allow models to be re-initialized after connections are closed.
|
|
@@ -1178,7 +1178,7 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1178
1178
|
* @returns {boolean} - Whether query logging is enabled for this driver.
|
|
1179
1179
|
*/
|
|
1180
1180
|
_queryLoggingEnabled() {
|
|
1181
|
-
if (
|
|
1181
|
+
if (!this.configuration) return true
|
|
1182
1182
|
if (!this.configuration.getQueryLoggingEnabled()) return false
|
|
1183
1183
|
|
|
1184
1184
|
const logger = new Logger("SQL", {configuration: this.configuration})
|
|
@@ -1213,9 +1213,9 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1213
1213
|
_querySourceLine(sourceStack) {
|
|
1214
1214
|
if (!sourceStack) return undefined
|
|
1215
1215
|
|
|
1216
|
-
const applicationDirectory =
|
|
1216
|
+
const applicationDirectory = this.configuration
|
|
1217
1217
|
? this.configuration.getDirectoryIfAvailable()
|
|
1218
|
-
:
|
|
1218
|
+
: undefined
|
|
1219
1219
|
|
|
1220
1220
|
if (!applicationDirectory) return undefined
|
|
1221
1221
|
|
|
@@ -90,9 +90,10 @@ class VelociousDatabasePoolBase {
|
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Clears any global connections for the given configuration.
|
|
93
|
+
* @param {import("../../configuration.js").default} configuration - Configuration owning the pool.
|
|
93
94
|
* @returns {void} - No return value.
|
|
94
95
|
*/
|
|
95
|
-
static clearGlobalConnections() {}
|
|
96
|
+
static clearGlobalConnections(configuration) { void configuration }
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
99
|
* Runs constructor.
|
|
@@ -9,10 +9,5 @@
|
|
|
9
9
|
export default async function ensureModelClassInitialized(modelClass, configuration) {
|
|
10
10
|
if (modelClass.isInitialized()) return
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
await modelClass.ensureInitialized({configuration})
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
await modelClass.initializeRecord({configuration})
|
|
12
|
+
await modelClass.ensureInitialized({configuration})
|
|
18
13
|
}
|
|
@@ -170,6 +170,38 @@ export default class RecordAttachmentHandle {
|
|
|
170
170
|
return downloads
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Runs list metadata. Returns metadata (no content bytes) for every attachment
|
|
175
|
+
* under this (record, name), so callers can enumerate has-many attachments
|
|
176
|
+
* without downloading their content.
|
|
177
|
+
* @returns {Promise<Array<{byteSize: number, contentType: string | null, filename: string, id: string, url: string | null}>>} - Attachment metadata entries.
|
|
178
|
+
*/
|
|
179
|
+
async listMetadata() {
|
|
180
|
+
if (!this.model.isPersisted()) return []
|
|
181
|
+
|
|
182
|
+
const store = recordAttachmentsStoreForModel(this.model)
|
|
183
|
+
const rows = await store.findMany({model: this.model, name: this.name})
|
|
184
|
+
/**
|
|
185
|
+
* Metadata entries.
|
|
186
|
+
* @type {Array<{byteSize: number, contentType: string | null, filename: string, id: string, url: string | null}>} */
|
|
187
|
+
const entries = []
|
|
188
|
+
|
|
189
|
+
for (const row of rows) {
|
|
190
|
+
const url = await store.attachmentRowUrl({model: this.model, name: this.name, row})
|
|
191
|
+
const byteSize = Number(row.byte_size)
|
|
192
|
+
|
|
193
|
+
entries.push({
|
|
194
|
+
byteSize: Number.isFinite(byteSize) ? byteSize : 0,
|
|
195
|
+
contentType: row.content_type || null,
|
|
196
|
+
filename: row.filename || "attachment.bin",
|
|
197
|
+
id: row.id,
|
|
198
|
+
url
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return entries
|
|
203
|
+
}
|
|
204
|
+
|
|
173
205
|
/**
|
|
174
206
|
* Runs url.
|
|
175
207
|
* @param {string} [id] - Optional attachment id for has-many attachments.
|
|
@@ -106,7 +106,13 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
106
106
|
throw new Error(`Invalid frontend model resource definition for '${className}'`)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const
|
|
109
|
+
const resolvedResourceClass = frontendModelResourceClassFromDefinition(resources[modelClassName])
|
|
110
|
+
// An abstract base resource (no static ModelClass — e.g. an app's shared
|
|
111
|
+
// `BaseResource` that other resources extend) can't back a generated
|
|
112
|
+
// frontend model. Treat it as resource-less so the generator falls back
|
|
113
|
+
// to by-name model lookup + empty write params instead of throwing when
|
|
114
|
+
// it eagerly calls `modelClass()` / `permittedParams()` on it.
|
|
115
|
+
const resourceClass = resolvedResourceClass && resolvedResourceClass.ModelClass ? resolvedResourceClass : null
|
|
110
116
|
|
|
111
117
|
this.validateModelConfig({availableFrontendModelClassNames, className, modelConfig, resourceClass})
|
|
112
118
|
|
|
@@ -123,7 +129,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
123
129
|
const fileContent = await this.buildModelFileContent({
|
|
124
130
|
className,
|
|
125
131
|
importPath,
|
|
126
|
-
modelClass: resourceClass
|
|
132
|
+
modelClass: resourceClass ? resourceClass.modelClass() : configuration.getModelClasses()[className],
|
|
127
133
|
modelConfig,
|
|
128
134
|
resourceClass
|
|
129
135
|
})
|
|
@@ -817,22 +823,17 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
817
823
|
* @returns {FrontendModelGeneratorPermitSpec} - Permitted params spec.
|
|
818
824
|
*/
|
|
819
825
|
permittedParamsForGenerator(resourceClass, action) {
|
|
820
|
-
if (!resourceClass
|
|
821
|
-
|
|
822
|
-
const prototypeWithMethod = /**
|
|
823
|
-
* Resource prototype.
|
|
824
|
-
* @type {{permittedParams?: (arg?: object) => FrontendModelGeneratorPermitSpec}}
|
|
825
|
-
*/ (resourceClass.prototype)
|
|
826
|
-
|
|
827
|
-
if (typeof prototypeWithMethod?.permittedParams !== "function") return []
|
|
826
|
+
if (!resourceClass) return []
|
|
828
827
|
|
|
829
828
|
try {
|
|
829
|
+
const modelClass = resourceClass.modelClass()
|
|
830
|
+
|
|
830
831
|
const instance = new resourceClass({
|
|
831
832
|
ability: undefined,
|
|
832
833
|
context: {},
|
|
833
834
|
locals: {},
|
|
834
|
-
modelClass
|
|
835
|
-
modelName:
|
|
835
|
+
modelClass,
|
|
836
|
+
modelName: modelClass.getModelName(),
|
|
836
837
|
params: {},
|
|
837
838
|
resourceConfiguration: /**
|
|
838
839
|
* Resource configuration.
|
|
@@ -860,24 +861,19 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
860
861
|
* @returns {string[]} - Relationship names that accept nested writes (empty when none).
|
|
861
862
|
*/
|
|
862
863
|
nestedRelationshipNamesForGenerator(resourceClass) {
|
|
863
|
-
if (!resourceClass
|
|
864
|
-
|
|
865
|
-
const prototypeWithMethod = /**
|
|
866
|
-
* Resource prototype.
|
|
867
|
-
* @type {{permittedParams?: (arg?: object) => FrontendModelGeneratorPermitSpec}}
|
|
868
|
-
*/ (resourceClass.prototype)
|
|
869
|
-
|
|
870
|
-
if (typeof prototypeWithMethod?.permittedParams !== "function") return []
|
|
864
|
+
if (!resourceClass) return []
|
|
871
865
|
|
|
872
866
|
let spec
|
|
873
867
|
|
|
874
868
|
try {
|
|
869
|
+
const modelClass = resourceClass.modelClass()
|
|
870
|
+
|
|
875
871
|
const instance = new resourceClass({
|
|
876
872
|
ability: undefined,
|
|
877
873
|
context: {},
|
|
878
874
|
locals: {},
|
|
879
|
-
modelClass
|
|
880
|
-
modelName:
|
|
875
|
+
modelClass,
|
|
876
|
+
modelName: modelClass.getModelName(),
|
|
881
877
|
params: {},
|
|
882
878
|
resourceConfiguration: /**
|
|
883
879
|
* Resource configuration.
|
|
@@ -1073,6 +1069,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1073
1069
|
*/
|
|
1074
1070
|
frontendAttributeConfigForGeneratedAttribute({attributeConfig, attributeName, modelClass}) {
|
|
1075
1071
|
if (!this.frontendAttributeIsModelPrimaryKey({attributeName, modelClass})) return attributeConfig
|
|
1072
|
+
if (this.frontendAttributeConfigHasNullability(attributeConfig)) return attributeConfig
|
|
1076
1073
|
|
|
1077
1074
|
return {...attributeConfig, null: false}
|
|
1078
1075
|
}
|
|
@@ -1141,6 +1138,18 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1141
1138
|
|| typeof attributeConfig?.jsDocType == "string"
|
|
1142
1139
|
}
|
|
1143
1140
|
|
|
1141
|
+
/**
|
|
1142
|
+
* Runs frontend attribute config has nullability.
|
|
1143
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute config.
|
|
1144
|
+
* @returns {boolean} - Whether the config declares nullability.
|
|
1145
|
+
*/
|
|
1146
|
+
frontendAttributeConfigHasNullability(attributeConfig) {
|
|
1147
|
+
if (!attributeConfig || typeof attributeConfig !== "object") return false
|
|
1148
|
+
if (Object.prototype.hasOwnProperty.call(attributeConfig, "null")) return true
|
|
1149
|
+
|
|
1150
|
+
return typeof attributeConfig.getNull == "function"
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1144
1153
|
/**
|
|
1145
1154
|
* Runs js doc type for frontend attribute.
|
|
1146
1155
|
* @param {object} args - Arguments.
|
|
@@ -1513,7 +1522,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1513
1522
|
const classBodyEnd = this.matchingBraceIndex({openIndex: classBodyStart - 1, sourceText})
|
|
1514
1523
|
|
|
1515
1524
|
if (classBodyEnd == null) {
|
|
1516
|
-
|
|
1525
|
+
// The brace matcher can't tokenize every construct (e.g. a regex literal
|
|
1526
|
+
// whose quotes look like string delimiters), so it can fail to locate a
|
|
1527
|
+
// class body. Skip metadata extraction for that class rather than
|
|
1528
|
+
// aborting the whole frontend-model generation; resources that parse
|
|
1529
|
+
// cleanly still get their JSDoc-derived return/param types.
|
|
1530
|
+
continue
|
|
1517
1531
|
}
|
|
1518
1532
|
|
|
1519
1533
|
const classBody = sourceText.slice(classBodyStart, classBodyEnd)
|
|
@@ -1554,7 +1568,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1554
1568
|
const classBodyEnd = this.matchingBraceIndex({openIndex: classBodyStart - 1, sourceText})
|
|
1555
1569
|
|
|
1556
1570
|
if (classBodyEnd == null) {
|
|
1557
|
-
|
|
1571
|
+
// The brace matcher can't tokenize every construct (e.g. a regex literal
|
|
1572
|
+
// whose quotes look like string delimiters), so it can fail to locate a
|
|
1573
|
+
// class body. Skip metadata extraction for that class rather than
|
|
1574
|
+
// aborting the whole frontend-model generation; resources that parse
|
|
1575
|
+
// cleanly still get their JSDoc-derived return/param types.
|
|
1576
|
+
continue
|
|
1558
1577
|
}
|
|
1559
1578
|
|
|
1560
1579
|
const classBody = sourceText.slice(classBodyStart, classBodyEnd)
|
|
@@ -1832,11 +1851,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1832
1851
|
* @returns {{autoload: boolean, relationshipName: string, targetClassName: string, targetFileName: string, type: "belongsTo" | "hasOne" | "hasMany"}} Inferred relationship definition.
|
|
1833
1852
|
*/
|
|
1834
1853
|
inferredRelationshipDefinition({className, relationshipName, resourceClass}) {
|
|
1835
|
-
const modelClass = resourceClass
|
|
1836
|
-
|
|
1837
|
-
if (!modelClass) {
|
|
1838
|
-
throw new Error(`Could not find backend model class '${className}' for relationship '${relationshipName}'`)
|
|
1839
|
-
}
|
|
1854
|
+
const modelClass = resourceClass ? resourceClass.modelClass() : this.getConfiguration().getModelClass(className)
|
|
1840
1855
|
|
|
1841
1856
|
const relationship = modelClass.getRelationshipByName(relationshipName)
|
|
1842
1857
|
const relationshipType = relationship.getType()
|