velocious 1.0.447 → 1.0.449
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 +1 -1
- package/build/configuration-types.js +2 -0
- package/build/database/pool/async-tracked-multi-connection.js +123 -8
- package/build/database/pool/base.js +14 -1
- package/build/database/record/index.js +13 -8
- package/build/environment-handlers/node/cli/commands/generate/base-models.js +1 -16
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +125 -73
- package/build/frontend-model-controller.js +9 -0
- package/build/frontend-model-resource/base-resource.js +266 -53
- package/build/frontend-models/base.js +241 -97
- package/build/frontend-models/preloader.js +3 -2
- package/build/src/background-jobs/job-record.d.ts +2 -1
- package/build/src/background-jobs/job-record.d.ts.map +1 -1
- package/build/src/configuration-types.d.ts +10 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +3 -1
- package/build/src/database/pool/async-tracked-multi-connection.d.ts +60 -4
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +113 -9
- package/build/src/database/pool/base.d.ts +38 -1
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +14 -2
- package/build/src/database/query/preloader/belongs-to.d.ts +2 -2
- package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.d.ts +1 -1
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-one.d.ts +2 -2
- package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
- package/build/src/database/query/preloader.d.ts +1 -1
- package/build/src/database/query/preloader.d.ts.map +1 -1
- package/build/src/database/record/attachments/handle.d.ts +1 -1
- package/build/src/database/record/attachments/handle.d.ts.map +1 -1
- package/build/src/database/record/index.d.ts +23 -13
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +14 -9
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +2 -15
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +89 -32
- 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 +123 -72
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +8 -1
- package/build/src/frontend-model-resource/base-resource.d.ts +203 -64
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +237 -54
- package/build/src/frontend-models/base.d.ts +173 -110
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +218 -102
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +4 -3
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/build/src/testing/expect.d.ts +6 -0
- package/build/src/testing/expect.d.ts.map +1 -1
- package/build/src/testing/expect.js +9 -1
- package/build/testing/browser-frontend-model-event-hook-scenarios.js +1 -1
- package/build/testing/expect.js +9 -0
- package/package.json +1 -1
- package/src/configuration-types.js +2 -0
- package/src/database/pool/async-tracked-multi-connection.js +123 -8
- package/src/database/pool/base.js +14 -1
- package/src/database/record/index.js +13 -8
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +1 -16
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +125 -73
- package/src/frontend-model-controller.js +9 -0
- package/src/frontend-model-resource/base-resource.js +266 -53
- package/src/frontend-models/base.js +241 -97
- package/src/frontend-models/preloader.js +3 -2
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +1 -1
- package/src/testing/expect.js +9 -0
|
@@ -222,6 +222,10 @@ class TenantDatabaseScopeError extends Error {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Base database record.
|
|
227
|
+
* @template {Record<string, ?>} [WriteAttributes=Record<string, ?>]
|
|
228
|
+
*/
|
|
225
229
|
class VelociousDatabaseRecord {
|
|
226
230
|
/**
|
|
227
231
|
* Narrows the runtime value to the documented type.
|
|
@@ -1150,17 +1154,18 @@ class VelociousDatabaseRecord {
|
|
|
1150
1154
|
|
|
1151
1155
|
/**
|
|
1152
1156
|
* Runs create.
|
|
1153
|
-
* @template {
|
|
1154
|
-
* @
|
|
1155
|
-
* @
|
|
1156
|
-
* @
|
|
1157
|
+
* @template {Record<string, ?>} CreateAttributes
|
|
1158
|
+
* @template {VelociousDatabaseRecord<CreateAttributes>} Model
|
|
1159
|
+
* @this {{new (changes?: CreateAttributes): Model} & typeof VelociousDatabaseRecord}
|
|
1160
|
+
* @param {CreateAttributes} [attributes] - Attributes.
|
|
1161
|
+
* @returns {Promise<Model>} - Resolves with the create.
|
|
1157
1162
|
*/
|
|
1158
1163
|
static async create(attributes) {
|
|
1159
1164
|
await this.ensureInitialized()
|
|
1160
1165
|
|
|
1161
1166
|
const record = /**
|
|
1162
1167
|
* Narrows the runtime value to the documented type.
|
|
1163
|
-
@type {
|
|
1168
|
+
@type {Model} */ (new this(attributes))
|
|
1164
1169
|
|
|
1165
1170
|
await record.save()
|
|
1166
1171
|
|
|
@@ -3353,9 +3358,9 @@ class VelociousDatabaseRecord {
|
|
|
3353
3358
|
|
|
3354
3359
|
/**
|
|
3355
3360
|
* Runs constructor.
|
|
3356
|
-
* @param {
|
|
3361
|
+
* @param {WriteAttributes} changes - Changes.
|
|
3357
3362
|
*/
|
|
3358
|
-
constructor(changes = {}) {
|
|
3363
|
+
constructor(changes = /** @type {WriteAttributes} */ ({})) {
|
|
3359
3364
|
this.getModelClass()._assertHasBeenInitialized()
|
|
3360
3365
|
this._attributes = {}
|
|
3361
3366
|
this._changes = {}
|
|
@@ -4256,7 +4261,7 @@ class VelociousDatabaseRecord {
|
|
|
4256
4261
|
|
|
4257
4262
|
/**
|
|
4258
4263
|
* Assigns the attributes to the record and saves it.
|
|
4259
|
-
* @param {
|
|
4264
|
+
* @param {WriteAttributes} attributesToAssign - The attributes to assign to the record.
|
|
4260
4265
|
*/
|
|
4261
4266
|
async update(attributesToAssign) {
|
|
4262
4267
|
if (attributesToAssign) this.assign(attributesToAssign)
|
|
@@ -139,24 +139,9 @@ export default class DbGenerateModel extends BaseCommand {
|
|
|
139
139
|
|
|
140
140
|
const hasManyRelationFilePath = `${velociousPath}/database/record/instance-relationships/has-many.js`
|
|
141
141
|
|
|
142
|
+
fileContent += `/** @augments {DatabaseRecord<${writeAttributeTypeName}>} */\n`
|
|
142
143
|
fileContent += `export default class ${modelNameCamelized}Base extends DatabaseRecord {\n`
|
|
143
144
|
|
|
144
|
-
fileContent += " /**\n"
|
|
145
|
-
fileContent += ` * Creates a ${modelNameCamelized} record.\n`
|
|
146
|
-
fileContent += ` * @template {typeof ${modelNameCamelized}Base} T\n`
|
|
147
|
-
fileContent += " * @this {T}\n"
|
|
148
|
-
fileContent += ` * @param {${writeAttributeTypeName}} [attributes] - Attributes for the new record.\n`
|
|
149
|
-
fileContent += " * @returns {Promise<InstanceType<T>>} - Persisted record.\n"
|
|
150
|
-
fileContent += " */\n"
|
|
151
|
-
fileContent += " static async create(attributes) { return /** @type {Promise<InstanceType<T>>} */ (super.create(attributes)) }\n\n"
|
|
152
|
-
|
|
153
|
-
fileContent += " /**\n"
|
|
154
|
-
fileContent += ` * Updates this ${modelNameCamelized} record.\n`
|
|
155
|
-
fileContent += ` * @param {${writeAttributeTypeName}} attributes - Attributes to assign before saving.\n`
|
|
156
|
-
fileContent += " * @returns {Promise<void>} - Resolves when the record is saved.\n"
|
|
157
|
-
fileContent += " */\n"
|
|
158
|
-
fileContent += " async update(attributes) { return await super.update(attributes) }\n\n"
|
|
159
|
-
|
|
160
145
|
// --- getModelClass() override (fixes polymorphic typing in JS/JSDoc) ---
|
|
161
146
|
if (await fileExists(sourceModelFullFilePath)) {
|
|
162
147
|
// Model file exists (e.g. src/models/ticket.js) → return typeof Ticket
|
|
@@ -4,6 +4,22 @@ import path from "node:path"
|
|
|
4
4
|
import * as inflection from "inflection"
|
|
5
5
|
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "../../../../../frontend-models/resource-definition.js"
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Attribute metadata used for generated frontend-model JSDoc.
|
|
9
|
+
* @typedef {object} FrontendAttributeConfig
|
|
10
|
+
* @property {string} [type] - Column type.
|
|
11
|
+
* @property {string} [columnType] - Column type.
|
|
12
|
+
* @property {string} [sqlType] - SQL type.
|
|
13
|
+
* @property {string} [dataType] - Data type.
|
|
14
|
+
* @property {string} [name] - Attribute name when configured as an array entry.
|
|
15
|
+
* @property {boolean} [null] - Whether null is allowed.
|
|
16
|
+
* @property {() => string} [getType] - Returns column type.
|
|
17
|
+
* @property {() => boolean} [getNull] - Returns whether null is allowed.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Permit spec returned by frontend-model resources during generation.
|
|
21
|
+
* @typedef {Array<string | Record<string, object>>} FrontendModelGeneratorPermitSpec
|
|
22
|
+
*/
|
|
7
23
|
|
|
8
24
|
/** Node CLI command that generates frontend model classes from backend project resource config. */
|
|
9
25
|
export default class DbGenerateFrontendModels extends BaseCommand {
|
|
@@ -124,8 +140,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
124
140
|
* @param {object} args - Arguments.
|
|
125
141
|
* @param {Set<string>} args.availableFrontendModelClassNames - Available frontend model class names in backend project.
|
|
126
142
|
* @param {string} args.className - Model class name.
|
|
127
|
-
* @param {
|
|
128
|
-
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass]
|
|
143
|
+
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
144
|
+
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
129
145
|
* @returns {void} - No return value.
|
|
130
146
|
*/
|
|
131
147
|
validateModelConfig({availableFrontendModelClassNames, className, modelConfig, resourceClass}) {
|
|
@@ -135,11 +151,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
135
151
|
throw new Error(`Model '${className}' is missing required 'abilities' config`)
|
|
136
152
|
}
|
|
137
153
|
|
|
138
|
-
const readActions = [
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
const readActions = [
|
|
155
|
+
{action: "index", abilityAction: abilities.index},
|
|
156
|
+
{action: "find", abilityAction: abilities.find}
|
|
157
|
+
]
|
|
142
158
|
|
|
159
|
+
for (const {action, abilityAction} of readActions) {
|
|
143
160
|
if (typeof abilityAction !== "string" || abilityAction.length < 1) {
|
|
144
161
|
throw new Error(`Model '${className}' is missing required abilities.${action} config`)
|
|
145
162
|
}
|
|
@@ -169,7 +186,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
169
186
|
|
|
170
187
|
/**
|
|
171
188
|
* Runs available frontend model class names.
|
|
172
|
-
* @param {Record<string,
|
|
189
|
+
* @param {Record<string, import("../../../../../configuration-types.js").FrontendModelResourceDefinition>} resources - Resource configuration keyed by model name.
|
|
173
190
|
* @returns {Set<string>} - Available frontend model class names.
|
|
174
191
|
*/
|
|
175
192
|
availableFrontendModelClassNames(resources) {
|
|
@@ -217,8 +234,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
217
234
|
* @param {string} args.className - Model class name.
|
|
218
235
|
* @param {string} args.importPath - Base class import path.
|
|
219
236
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
220
|
-
* @param {
|
|
221
|
-
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass]
|
|
237
|
+
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
238
|
+
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
222
239
|
* @returns {string} - Generated file content.
|
|
223
240
|
*/
|
|
224
241
|
buildModelFileContent({className, importPath, modelClass, modelConfig, resourceClass}) {
|
|
@@ -234,6 +251,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
234
251
|
const permittedCreateParams = this.permittedParamsForGenerator(resourceClass || null, "create")
|
|
235
252
|
const permittedUpdateParams = this.permittedParamsForGenerator(resourceClass || null, "update")
|
|
236
253
|
const nestedWriteTypes = this.nestedWriteTypesForModel({className, permittedParams: permittedCreateParams.concat(permittedUpdateParams), relationships})
|
|
254
|
+
const usesTransportValue = attributes.some((attribute) => attribute.jsDocType.includes("FrontendModelTransportValue"))
|
|
255
|
+
|| nestedWriteTypes.some((nestedWriteType) => nestedWriteType.attributes.some((attribute) => attribute.type.includes("FrontendModelTransportValue")))
|
|
237
256
|
const builtInCollectionCommands = {
|
|
238
257
|
create: modelConfig.builtInCollectionCommands.create || "create",
|
|
239
258
|
index: modelConfig.builtInCollectionCommands.index || "index"
|
|
@@ -265,6 +284,16 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
265
284
|
fileContent += ` * Frontend model resource config.\n`
|
|
266
285
|
fileContent += ` * @typedef {import("${importPath}").FrontendModelResourceConfig} FrontendModelResourceConfig\n`
|
|
267
286
|
fileContent += " */\n"
|
|
287
|
+
fileContent += "/**\n"
|
|
288
|
+
fileContent += " * Fallback attribute value type for generated fields without narrower metadata.\n"
|
|
289
|
+
fileContent += ` * @typedef {import("${importPath}").FrontendModelAttributeValue} FrontendModelAttributeValue\n`
|
|
290
|
+
fileContent += " */\n"
|
|
291
|
+
if (usesTransportValue) {
|
|
292
|
+
fileContent += "/**\n"
|
|
293
|
+
fileContent += " * Value supported by frontend-model transport serialization and deserialization.\n"
|
|
294
|
+
fileContent += ` * @typedef {import("${importPath}").FrontendModelTransportValue} FrontendModelTransportValue\n`
|
|
295
|
+
fileContent += " */\n"
|
|
296
|
+
}
|
|
268
297
|
fileContent += "\n"
|
|
269
298
|
fileContent += "/**\n"
|
|
270
299
|
fileContent += ` * ${attributesTypeName} type.\n`
|
|
@@ -284,21 +313,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
284
313
|
}
|
|
285
314
|
fileContent += this.writeAttributesTypedef({attributes, attributesTypeName, nestedWriteTypes, permittedParams: permittedCreateParams, typeName: createAttributesTypeName})
|
|
286
315
|
fileContent += this.writeAttributesTypedef({attributes, attributesTypeName, nestedWriteTypes, permittedParams: permittedUpdateParams, typeName: updateAttributesTypeName})
|
|
287
|
-
fileContent +=
|
|
288
|
-
fileContent += `
|
|
289
|
-
fileContent +=
|
|
290
|
-
fileContent +=
|
|
291
|
-
fileContent += `
|
|
292
|
-
fileContent += ` * @returns {Promise<${className}>} - Persisted model.\n`
|
|
293
|
-
fileContent += " */\n"
|
|
294
|
-
fileContent += ` static async create(attributes = {}) { return /** @type {Promise<${className}>} */ (super.create(attributes)) }\n\n`
|
|
295
|
-
|
|
296
|
-
fileContent += " /**\n"
|
|
297
|
-
fileContent += ` * Updates this ${className}.\n`
|
|
298
|
-
fileContent += ` * @param {${updateAttributesTypeName}} [newAttributes] - Attributes to assign before saving.\n`
|
|
299
|
-
fileContent += ` * @returns {Promise<${className}>} - Updated model.\n`
|
|
300
|
-
fileContent += " */\n"
|
|
301
|
-
fileContent += ` async update(newAttributes = {}) { return /** @type {Promise<${className}>} */ (super.update(newAttributes)) }\n\n`
|
|
316
|
+
fileContent += "/**\n"
|
|
317
|
+
fileContent += ` * Frontend model for ${className}.\n`
|
|
318
|
+
fileContent += ` * @augments {FrontendModelBase<${attributesTypeName}, ${createAttributesTypeName}, ${updateAttributesTypeName}>}\n`
|
|
319
|
+
fileContent += " */\n"
|
|
320
|
+
fileContent += `class ${className} extends FrontendModelBase {\n`
|
|
302
321
|
fileContent += " /** @returns {FrontendModelResourceConfig} - Resource config. */\n"
|
|
303
322
|
fileContent += " static resourceConfig() {\n"
|
|
304
323
|
fileContent += " return {\n"
|
|
@@ -416,8 +435,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
416
435
|
fileContent += "\n"
|
|
417
436
|
fileContent += " /**\n"
|
|
418
437
|
fileContent += ` * Runs ${methodName}.\n`
|
|
419
|
-
fileContent += " * @param {
|
|
420
|
-
fileContent += " * @returns {Promise<Record<string,
|
|
438
|
+
fileContent += " * @param {...FrontendModelAttributeValue} commandArguments - Custom command arguments.\n"
|
|
439
|
+
fileContent += " * @returns {Promise<Record<string, FrontendModelAttributeValue>>} - Command response.\n"
|
|
421
440
|
fileContent += " */\n"
|
|
422
441
|
fileContent += ` static async ${methodName}(...commandArguments) {\n`
|
|
423
442
|
fileContent += " return await this.executeCustomCommand({\n"
|
|
@@ -433,8 +452,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
433
452
|
fileContent += "\n"
|
|
434
453
|
fileContent += " /**\n"
|
|
435
454
|
fileContent += ` * Runs ${methodName}.\n`
|
|
436
|
-
fileContent += " * @param {
|
|
437
|
-
fileContent += " * @returns {Promise<Record<string,
|
|
455
|
+
fileContent += " * @param {...FrontendModelAttributeValue} commandArguments - Custom command arguments.\n"
|
|
456
|
+
fileContent += " * @returns {Promise<Record<string, FrontendModelAttributeValue>>} - Command response.\n"
|
|
438
457
|
fileContent += " */\n"
|
|
439
458
|
fileContent += ` async ${methodName}(...commandArguments) {\n`
|
|
440
459
|
fileContent += ` return await ${className}.executeCustomCommand({\n`
|
|
@@ -450,71 +469,91 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
450
469
|
for (const relationship of relationships) {
|
|
451
470
|
const relationshipNameCamelized = inflection.camelize(relationship.relationshipName)
|
|
452
471
|
const targetImportPath = `./${relationship.targetFileName}.js`
|
|
472
|
+
const targetInstanceType = `import(${JSON.stringify(targetImportPath)}).${relationship.targetClassName}`
|
|
473
|
+
const targetCreateAttributesType = `import(${JSON.stringify(targetImportPath)}).${relationship.targetClassName}CreateAttributes`
|
|
453
474
|
|
|
454
475
|
if (relationship.type == "hasMany") {
|
|
476
|
+
fileContent += "\n"
|
|
477
|
+
fileContent += " /**\n"
|
|
478
|
+
fileContent += ` * Returns ${relationship.relationshipName} relationship helper.\n`
|
|
479
|
+
fileContent += ` * @returns {import(${JSON.stringify(importPath)}).FrontendModelHasManyRelationship<${className}, ${targetInstanceType}, ${targetCreateAttributesType}>} - Relationship helper.\n`
|
|
480
|
+
fileContent += " */\n"
|
|
481
|
+
fileContent += ` ${relationship.relationshipName}Relationship() { return /** @type {import(${JSON.stringify(importPath)}).FrontendModelHasManyRelationship<${className}, ${targetInstanceType}, ${targetCreateAttributesType}>} */ (this.getRelationshipByName(${JSON.stringify(relationship.relationshipName)})) }\n`
|
|
482
|
+
|
|
455
483
|
fileContent += "\n"
|
|
456
484
|
fileContent += " /**\n"
|
|
457
485
|
fileContent += ` * Returns ${relationship.relationshipName}.\n`
|
|
458
|
-
fileContent += ` * @returns {import(${JSON.stringify(importPath)}).FrontendModelHasManyRelationship
|
|
486
|
+
fileContent += ` * @returns {import(${JSON.stringify(importPath)}).FrontendModelHasManyRelationship<${className}, ${targetInstanceType}, ${targetCreateAttributesType}>} - Relationship helper.\n`
|
|
459
487
|
fileContent += " */\n"
|
|
460
|
-
fileContent += ` ${relationship.relationshipName}() { return
|
|
488
|
+
fileContent += ` ${relationship.relationshipName}() { return this.${relationship.relationshipName}Relationship() }\n`
|
|
461
489
|
|
|
462
490
|
fileContent += "\n"
|
|
463
491
|
fileContent += " /**\n"
|
|
464
492
|
fileContent += ` * Returns loaded ${relationship.relationshipName}.\n`
|
|
465
|
-
fileContent += ` * @returns {Array
|
|
493
|
+
fileContent += ` * @returns {Array<${targetInstanceType}>} - Loaded related models.\n`
|
|
466
494
|
fileContent += " */\n"
|
|
467
|
-
fileContent += ` ${relationship.relationshipName}Loaded() { return
|
|
495
|
+
fileContent += ` ${relationship.relationshipName}Loaded() { return this.${relationship.relationshipName}Relationship().loaded() }\n`
|
|
468
496
|
|
|
469
497
|
fileContent += "\n"
|
|
470
498
|
fileContent += " /**\n"
|
|
471
499
|
fileContent += ` * Loads ${relationship.relationshipName}.\n`
|
|
472
|
-
fileContent += ` * @returns {Promise<Array
|
|
500
|
+
fileContent += ` * @returns {Promise<Array<${targetInstanceType}>>} - Loaded related models.\n`
|
|
473
501
|
fileContent += " */\n"
|
|
474
|
-
fileContent += ` async load${relationshipNameCamelized}() { return
|
|
502
|
+
fileContent += ` async load${relationshipNameCamelized}() { return await this.${relationship.relationshipName}Relationship().load() }\n`
|
|
475
503
|
} else {
|
|
504
|
+
fileContent += "\n"
|
|
505
|
+
fileContent += " /**\n"
|
|
506
|
+
fileContent += ` * Returns ${relationship.relationshipName} relationship helper.\n`
|
|
507
|
+
fileContent += ` * @returns {import(${JSON.stringify(importPath)}).FrontendModelSingularRelationship<${className}, ${targetInstanceType}, ${targetCreateAttributesType}>} - Relationship helper.\n`
|
|
508
|
+
fileContent += " */\n"
|
|
509
|
+
fileContent += ` ${relationship.relationshipName}Relationship() { return /** @type {import(${JSON.stringify(importPath)}).FrontendModelSingularRelationship<${className}, ${targetInstanceType}, ${targetCreateAttributesType}>} */ (this.getRelationshipByName(${JSON.stringify(relationship.relationshipName)})) }\n`
|
|
510
|
+
|
|
476
511
|
fileContent += "\n"
|
|
477
512
|
fileContent += " /**\n"
|
|
478
513
|
fileContent += ` * Returns ${relationship.relationshipName}.\n`
|
|
479
|
-
fileContent += ` * @returns {
|
|
514
|
+
fileContent += ` * @returns {${targetInstanceType} | null} - Loaded related model.\n`
|
|
480
515
|
fileContent += " */\n"
|
|
481
|
-
fileContent += ` ${relationship.relationshipName}() { return
|
|
516
|
+
fileContent += ` ${relationship.relationshipName}() { return this.${relationship.relationshipName}Relationship().loaded() }\n`
|
|
482
517
|
|
|
483
518
|
fileContent += "\n"
|
|
484
519
|
fileContent += " /**\n"
|
|
485
520
|
fileContent += ` * Builds ${relationship.relationshipName}.\n`
|
|
486
|
-
fileContent += ` * @param {
|
|
487
|
-
fileContent += ` * @returns {
|
|
521
|
+
fileContent += ` * @param {${targetCreateAttributesType}} [attributes] - Attributes for the new related model.\n`
|
|
522
|
+
fileContent += ` * @returns {${targetInstanceType}} - Built related model.\n`
|
|
488
523
|
fileContent += " */\n"
|
|
489
|
-
fileContent += ` build${relationshipNameCamelized}(attributes = {}) { return
|
|
524
|
+
fileContent += ` build${relationshipNameCamelized}(attributes = {}) { return this.${relationship.relationshipName}Relationship().build(attributes) }\n`
|
|
490
525
|
|
|
491
526
|
fileContent += "\n"
|
|
492
527
|
fileContent += " /**\n"
|
|
493
528
|
fileContent += ` * Loads ${relationship.relationshipName}.\n`
|
|
494
|
-
fileContent += ` * @returns {Promise
|
|
529
|
+
fileContent += ` * @returns {Promise<${targetInstanceType} | null>} - Loaded related model.\n`
|
|
495
530
|
fileContent += " */\n"
|
|
496
|
-
fileContent += ` async load${relationshipNameCamelized}() { return
|
|
531
|
+
fileContent += ` async load${relationshipNameCamelized}() { return await this.${relationship.relationshipName}Relationship().load() }\n`
|
|
497
532
|
|
|
498
533
|
fileContent += "\n"
|
|
499
534
|
fileContent += " /**\n"
|
|
500
535
|
fileContent += ` * Returns or loads ${relationship.relationshipName}.\n`
|
|
501
|
-
fileContent += ` * @returns {Promise
|
|
536
|
+
fileContent += ` * @returns {Promise<${targetInstanceType} | null>} - Loaded related model.\n`
|
|
502
537
|
fileContent += " */\n"
|
|
503
|
-
fileContent += ` async ${relationship.relationshipName}OrLoad() { return
|
|
538
|
+
fileContent += ` async ${relationship.relationshipName}OrLoad() { return await this.${relationship.relationshipName}Relationship().orLoad() }\n`
|
|
504
539
|
|
|
505
540
|
fileContent += "\n"
|
|
506
541
|
fileContent += " /**\n"
|
|
507
542
|
fileContent += ` * Sets ${relationship.relationshipName}.\n`
|
|
508
|
-
fileContent += ` * @param {
|
|
509
|
-
fileContent +=
|
|
543
|
+
fileContent += ` * @param {${targetInstanceType} | null} model - Related model.\n`
|
|
544
|
+
fileContent += " * @returns {void}\n"
|
|
510
545
|
fileContent += " */\n"
|
|
511
|
-
fileContent += ` set${relationshipNameCamelized}(model) {
|
|
546
|
+
fileContent += ` set${relationshipNameCamelized}(model) { this.${relationship.relationshipName}Relationship().setLoaded(model) }\n`
|
|
512
547
|
}
|
|
513
548
|
}
|
|
514
549
|
|
|
515
550
|
fileContent += "}\n"
|
|
516
551
|
fileContent += "\n"
|
|
517
552
|
fileContent += `FrontendModelBase.registerModel(${className})\n`
|
|
553
|
+
fileContent += "\n"
|
|
554
|
+
fileContent += `export {${className}}\n`
|
|
555
|
+
fileContent += "\n"
|
|
556
|
+
fileContent += `export default /** @type {import(${JSON.stringify(importPath)}).FrontendModelClass<${className}, ${attributesTypeName}, ${createAttributesTypeName}>} */ (${className})\n`
|
|
518
557
|
|
|
519
558
|
return fileContent
|
|
520
559
|
}
|
|
@@ -557,7 +596,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
557
596
|
* @param {Array<{jsDocType: string, name: string}>} args.attributes - Generated read attributes.
|
|
558
597
|
* @param {string} args.attributesTypeName - Generated read attributes typedef name.
|
|
559
598
|
* @param {Array<{attributes: Array<{name: string, type: string}>, relationshipName: string, typeName: string}>} args.nestedWriteTypes - Nested write typedefs.
|
|
560
|
-
* @param {Array<string | Record<string,
|
|
599
|
+
* @param {Array<string | Record<string, object>>} args.permittedParams - Resource permitted params spec.
|
|
561
600
|
* @param {string} args.typeName - Typedef name.
|
|
562
601
|
* @returns {string} - Generated typedef source.
|
|
563
602
|
*/
|
|
@@ -573,13 +612,13 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
573
612
|
for (const entry of permittedParams) {
|
|
574
613
|
if (typeof entry == "string") {
|
|
575
614
|
const attribute = attributesByName.get(entry)
|
|
576
|
-
const type = attribute ? `${attributesTypeName}[${JSON.stringify(attribute.name)}]` : "
|
|
615
|
+
const type = attribute ? `${attributesTypeName}[${JSON.stringify(attribute.name)}]` : "FrontendModelAttributeValue"
|
|
577
616
|
|
|
578
617
|
output += ` * @property {${type}} [${entry}] - Permitted ${entry} value.\n`
|
|
579
618
|
} else if (entry && typeof entry == "object" && !Array.isArray(entry)) {
|
|
580
619
|
for (const key of Object.keys(entry)) {
|
|
581
620
|
const nestedWriteType = nestedWriteTypesByKey.get(key)
|
|
582
|
-
const type = nestedWriteType ? `Array<${nestedWriteType.typeName}>` : "Array<
|
|
621
|
+
const type = nestedWriteType ? `Array<${nestedWriteType.typeName}>` : "Array<object>"
|
|
583
622
|
|
|
584
623
|
output += ` * @property {${type}} [${key}] - Permitted nested ${key} values.\n`
|
|
585
624
|
}
|
|
@@ -595,7 +634,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
595
634
|
* Runs nested write types for model.
|
|
596
635
|
* @param {object} args - Arguments.
|
|
597
636
|
* @param {string} args.className - Frontend model class name.
|
|
598
|
-
* @param {
|
|
637
|
+
* @param {FrontendModelGeneratorPermitSpec} args.permittedParams - Combined permitted params specs.
|
|
599
638
|
* @param {Array<{autoload: boolean, relationshipName: string, targetClassName: string, targetFileName: string, type: "belongsTo" | "hasOne" | "hasMany"}>} args.relationships - Generated relationships.
|
|
600
639
|
* @returns {Array<{attributes: Array<{name: string, type: string}>, relationshipName: string, typeName: string}>} - Nested write typedefs.
|
|
601
640
|
*/
|
|
@@ -637,7 +676,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
637
676
|
/**
|
|
638
677
|
* Runs nested write attributes for spec.
|
|
639
678
|
* @param {object} args - Arguments.
|
|
640
|
-
* @param {
|
|
679
|
+
* @param {Array<string | Record<string, object>> | object | string | null | undefined} args.nestedSpec - Nested permit spec.
|
|
641
680
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.targetModelClass - Target backend model class.
|
|
642
681
|
* @returns {Array<{name: string, type: string}>} - Nested write attributes.
|
|
643
682
|
*/
|
|
@@ -649,7 +688,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
649
688
|
|
|
650
689
|
return {
|
|
651
690
|
name: attributeName,
|
|
652
|
-
type: attributeConfig ? this.jsDocTypeForFrontendAttribute({attributeConfig}) : "
|
|
691
|
+
type: attributeConfig ? this.jsDocTypeForFrontendAttribute({attributeConfig}) : "FrontendModelAttributeValue"
|
|
653
692
|
}
|
|
654
693
|
})
|
|
655
694
|
}
|
|
@@ -658,14 +697,14 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
658
697
|
* Runs permitted params for generator.
|
|
659
698
|
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} resourceClass - Resource class.
|
|
660
699
|
* @param {"create" | "update"} action - Write action.
|
|
661
|
-
* @returns {
|
|
700
|
+
* @returns {FrontendModelGeneratorPermitSpec} - Permitted params spec.
|
|
662
701
|
*/
|
|
663
702
|
permittedParamsForGenerator(resourceClass, action) {
|
|
664
703
|
if (!resourceClass || typeof resourceClass !== "function") return []
|
|
665
704
|
|
|
666
705
|
const prototypeWithMethod = /**
|
|
667
706
|
* Resource prototype.
|
|
668
|
-
* @type {{permittedParams?: (arg?: object) =>
|
|
707
|
+
* @type {{permittedParams?: (arg?: object) => FrontendModelGeneratorPermitSpec}}
|
|
669
708
|
*/ (resourceClass.prototype)
|
|
670
709
|
|
|
671
710
|
if (typeof prototypeWithMethod?.permittedParams !== "function") return []
|
|
@@ -708,7 +747,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
708
747
|
|
|
709
748
|
const prototypeWithMethod = /**
|
|
710
749
|
* Resource prototype.
|
|
711
|
-
* @type {{permittedParams?: (arg?: object) =>
|
|
750
|
+
* @type {{permittedParams?: (arg?: object) => FrontendModelGeneratorPermitSpec}}
|
|
712
751
|
*/ (resourceClass.prototype)
|
|
713
752
|
|
|
714
753
|
if (typeof prototypeWithMethod?.permittedParams !== "function") return []
|
|
@@ -818,7 +857,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
818
857
|
* Runs attribute definitions for model.
|
|
819
858
|
* @param {object} args - Arguments.
|
|
820
859
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
821
|
-
* @param {
|
|
860
|
+
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
822
861
|
* @returns {Array<{jsDocType: string, name: string}>} - Attribute definitions.
|
|
823
862
|
*/
|
|
824
863
|
attributeDefinitionsForModel({modelClass, modelConfig}) {
|
|
@@ -838,13 +877,25 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
838
877
|
}
|
|
839
878
|
|
|
840
879
|
if (Array.isArray(attributes)) {
|
|
841
|
-
return attributes.map((
|
|
842
|
-
|
|
880
|
+
return attributes.map((attributeDefinition) => {
|
|
881
|
+
/** @type {FrontendAttributeConfig | null} */
|
|
882
|
+
let attributeConfig = null
|
|
883
|
+
let attributeName
|
|
884
|
+
|
|
885
|
+
if (typeof attributeDefinition == "string") {
|
|
886
|
+
attributeName = attributeDefinition
|
|
887
|
+
attributeConfig = this.frontendAttributeConfigForModelAttribute({attributeName, modelClass})
|
|
888
|
+
} else if (attributeDefinition && typeof attributeDefinition == "object" && !Array.isArray(attributeDefinition)) {
|
|
889
|
+
attributeConfig = /** @type {FrontendAttributeConfig} */ (attributeDefinition)
|
|
890
|
+
attributeName = attributeConfig.name
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (typeof attributeName != "string" || attributeName.length < 1) {
|
|
894
|
+
throw new Error(`Expected frontend model attribute array entries to be strings or objects with a name, got: ${JSON.stringify(attributeDefinition)}`)
|
|
895
|
+
}
|
|
843
896
|
|
|
844
897
|
return {
|
|
845
|
-
jsDocType: this.jsDocTypeForFrontendAttribute({
|
|
846
|
-
attributeConfig: this.frontendAttributeConfigForModelAttribute({attributeName, modelClass})
|
|
847
|
-
}),
|
|
898
|
+
jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig}),
|
|
848
899
|
name: attributeName
|
|
849
900
|
}
|
|
850
901
|
})
|
|
@@ -856,9 +907,10 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
856
907
|
|
|
857
908
|
return Object.keys(attributes).map((attributeName) => {
|
|
858
909
|
const attributeConfig = attributes[attributeName]
|
|
910
|
+
const normalizedAttributeConfig = attributeConfig && typeof attributeConfig === "object" ? attributeConfig : null
|
|
859
911
|
|
|
860
912
|
return {
|
|
861
|
-
jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig}),
|
|
913
|
+
jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: normalizedAttributeConfig}),
|
|
862
914
|
name: attributeName
|
|
863
915
|
}
|
|
864
916
|
})
|
|
@@ -867,7 +919,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
867
919
|
/**
|
|
868
920
|
* Runs js doc type for frontend attribute.
|
|
869
921
|
* @param {object} args - Arguments.
|
|
870
|
-
* @param {
|
|
922
|
+
* @param {FrontendAttributeConfig | null | undefined} args.attributeConfig - Attribute configuration value.
|
|
871
923
|
* @returns {string} - JSDoc type.
|
|
872
924
|
*/
|
|
873
925
|
jsDocTypeForFrontendAttribute({attributeConfig}) {
|
|
@@ -882,12 +934,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
882
934
|
|
|
883
935
|
/**
|
|
884
936
|
* Runs js doc type for frontend attribute base type.
|
|
885
|
-
* @param {
|
|
937
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
886
938
|
* @returns {string} - Non-nullable JSDoc type.
|
|
887
939
|
*/
|
|
888
940
|
jsDocTypeForFrontendAttributeBaseType(attributeConfig) {
|
|
889
941
|
if (!attributeConfig || typeof attributeConfig !== "object") {
|
|
890
|
-
return "
|
|
942
|
+
return "FrontendModelAttributeValue"
|
|
891
943
|
}
|
|
892
944
|
|
|
893
945
|
const type = this.frontendAttributeTypeValue(attributeConfig)
|
|
@@ -895,7 +947,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
895
947
|
if (type == "boolean") {
|
|
896
948
|
return "boolean"
|
|
897
949
|
} else if (type == "json" || type == "jsonb") {
|
|
898
|
-
return "
|
|
950
|
+
return "FrontendModelTransportValue"
|
|
899
951
|
} else if (type && ["blob", "char", "nvarchar", "varchar", "text", "longtext", "uuid", "character varying"].includes(type)) {
|
|
900
952
|
return "string"
|
|
901
953
|
} else if (type && ["bit", "bigint", "decimal", "double", "double precision", "float", "int", "integer", "numeric", "real", "smallint", "tinyint"].includes(type)) {
|
|
@@ -903,13 +955,13 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
903
955
|
} else if (type && ["date", "datetime", "timestamp", "timestamp without time zone", "timestamptz"].includes(type)) {
|
|
904
956
|
return "Date"
|
|
905
957
|
} else {
|
|
906
|
-
return "
|
|
958
|
+
return "FrontendModelAttributeValue"
|
|
907
959
|
}
|
|
908
960
|
}
|
|
909
961
|
|
|
910
962
|
/**
|
|
911
963
|
* Runs frontend attribute can be null.
|
|
912
|
-
* @param {
|
|
964
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
913
965
|
* @returns {boolean} - Whether the attribute allows null values.
|
|
914
966
|
*/
|
|
915
967
|
frontendAttributeCanBeNull(attributeConfig) {
|
|
@@ -926,7 +978,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
926
978
|
|
|
927
979
|
/**
|
|
928
980
|
* Runs frontend attribute type value.
|
|
929
|
-
* @param {
|
|
981
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
930
982
|
* @returns {string | null} - Normalized column type.
|
|
931
983
|
*/
|
|
932
984
|
frontendAttributeTypeValue(attributeConfig) {
|
|
@@ -952,7 +1004,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
952
1004
|
* @param {object} args - Arguments.
|
|
953
1005
|
* @param {string} args.attributeName - Frontend model attribute name.
|
|
954
1006
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
955
|
-
* @returns {
|
|
1007
|
+
* @returns {FrontendAttributeConfig | null} - Attribute config inferred from the backend model when available.
|
|
956
1008
|
*/
|
|
957
1009
|
frontendAttributeConfigForModelAttribute({attributeName, modelClass}) {
|
|
958
1010
|
if (!modelClass) {
|
|
@@ -972,8 +1024,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
972
1024
|
* Runs relationships for model.
|
|
973
1025
|
* @param {object} args - Arguments.
|
|
974
1026
|
* @param {string} args.className - Model class name.
|
|
975
|
-
* @param {
|
|
976
|
-
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass]
|
|
1027
|
+
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
1028
|
+
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
977
1029
|
* @returns {Array<{autoload: boolean, relationshipName: string, targetClassName: string, targetFileName: string, type: "belongsTo" | "hasOne" | "hasMany"}>} - Relationships.
|
|
978
1030
|
*/
|
|
979
1031
|
relationshipsForModel({className, modelConfig, resourceClass}) {
|
|
@@ -995,7 +1047,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
995
1047
|
* @param {object} args - Arguments.
|
|
996
1048
|
* @param {string} args.className - Model class name.
|
|
997
1049
|
* @param {string} args.relationshipName - Relationship name.
|
|
998
|
-
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass]
|
|
1050
|
+
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
999
1051
|
* @returns {{autoload: boolean, relationshipName: string, targetClassName: string, targetFileName: string, type: "belongsTo" | "hasOne" | "hasMany"}} Inferred relationship definition.
|
|
1000
1052
|
*/
|
|
1001
1053
|
inferredRelationshipDefinition({className, relationshipName, resourceClass}) {
|
|
@@ -2605,8 +2605,17 @@ export default class FrontendModelController extends Controller {
|
|
|
2605
2605
|
* Types the following value.
|
|
2606
2606
|
@type {typeof import("./database/record/index.js").default} */ (model.constructor)
|
|
2607
2607
|
const relationshipsMap = modelClass.getRelationshipsMap()
|
|
2608
|
+
const resource = this._serializationResourceInstanceForModel(model)
|
|
2609
|
+
const resourceConfiguration = resource ? resource.resourceConfiguration() : null
|
|
2610
|
+
const exposedRelationships = new Set(
|
|
2611
|
+
resourceConfiguration && Array.isArray(resourceConfiguration.relationships)
|
|
2612
|
+
? resourceConfiguration.relationships
|
|
2613
|
+
: []
|
|
2614
|
+
)
|
|
2608
2615
|
|
|
2609
2616
|
for (const relationshipName in relationshipsMap) {
|
|
2617
|
+
if (!exposedRelationships.has(relationshipName)) continue
|
|
2618
|
+
|
|
2610
2619
|
const relationship = model.getRelationshipByName(relationshipName)
|
|
2611
2620
|
|
|
2612
2621
|
if (!relationship.getPreloaded()) continue
|