velocious 1.0.469 → 1.0.471
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 +4 -2
- package/build/configuration-types.js +1 -0
- package/build/configuration.js +27 -1
- package/build/controller.js +13 -1
- package/build/database/datetime-storage.js +49 -5
- package/build/database/record/index.js +14 -4
- package/build/environment-handlers/base.js +46 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +487 -40
- package/build/environment-handlers/node.js +84 -8
- package/build/frontend-model-controller.js +7 -7
- package/build/frontend-models/base.js +75 -9
- package/build/frontend-models/transport-serialization.js +41 -8
- package/build/frontend-models/websocket-channel.js +18 -1
- package/build/frontend-models/websocket-publishers.js +12 -1
- package/build/http-server/client/request-runner.js +13 -1
- package/build/src/configuration-types.d.ts +5 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/configuration.d.ts +14 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +25 -2
- package/build/src/controller.d.ts +5 -0
- package/build/src/controller.d.ts.map +1 -1
- package/build/src/controller.js +12 -2
- package/build/src/database/datetime-storage.d.ts +12 -4
- package/build/src/database/datetime-storage.d.ts.map +1 -1
- package/build/src/database/datetime-storage.js +48 -6
- package/build/src/database/record/index.d.ts +5 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +13 -5
- package/build/src/environment-handlers/base.d.ts +19 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +41 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +253 -40
- 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 +425 -41
- package/build/src/environment-handlers/node.d.ts +1 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +76 -9
- package/build/src/frontend-model-controller.js +8 -8
- package/build/src/frontend-models/base.d.ts +4 -0
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +64 -9
- package/build/src/frontend-models/transport-serialization.d.ts +30 -1
- package/build/src/frontend-models/transport-serialization.d.ts.map +1 -1
- package/build/src/frontend-models/transport-serialization.js +34 -9
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +16 -2
- package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-publishers.js +12 -2
- package/build/src/http-server/client/request-runner.d.ts.map +1 -1
- package/build/src/http-server/client/request-runner.js +13 -2
- package/build/src/time-zone.d.ts +16 -0
- package/build/src/time-zone.d.ts.map +1 -0
- package/build/src/time-zone.js +56 -0
- package/build/time-zone.js +65 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/scripts/test-browser.js +1 -0
- package/src/configuration-types.js +1 -0
- package/src/configuration.js +27 -1
- package/src/controller.js +13 -1
- package/src/database/datetime-storage.js +49 -5
- package/src/database/record/index.js +14 -4
- package/src/environment-handlers/base.js +46 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +487 -40
- package/src/environment-handlers/node.js +84 -8
- package/src/frontend-model-controller.js +7 -7
- package/src/frontend-models/base.js +75 -9
- package/src/frontend-models/transport-serialization.js +41 -8
- package/src/frontend-models/websocket-channel.js +18 -1
- package/src/frontend-models/websocket-publishers.js +12 -1
- package/src/http-server/client/request-runner.js +13 -1
- package/src/time-zone.js +65 -0
|
@@ -23,13 +23,34 @@ import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigura
|
|
|
23
23
|
* Permit spec returned by frontend-model resources during generation.
|
|
24
24
|
* @typedef {Array<string | Record<string, FrontendModelGeneratorPermitSpec>>} FrontendModelGeneratorPermitSpec
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* JSDoc import alias extracted from a backend resource source file.
|
|
28
|
+
* @typedef {object} ResourceJsDocImportAlias
|
|
29
|
+
* @property {string} importedName - Exported type name.
|
|
30
|
+
* @property {string} specifier - Import specifier from the source file.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* JSDoc return type extracted from a backend resource method.
|
|
34
|
+
* @typedef {object} ResourceMethodReturnType
|
|
35
|
+
* @property {Map<string, ResourceJsDocImportAlias>} importAliases - Import aliases visible in the source file.
|
|
36
|
+
* @property {string | null} sourceFile - Source file that declared the method.
|
|
37
|
+
* @property {string} type - JSDoc return type.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* JSDoc parameter type extracted from a backend resource method.
|
|
41
|
+
* @typedef {object} ResourceMethodParameterType
|
|
42
|
+
* @property {Map<string, ResourceJsDocImportAlias>} importAliases - Import aliases visible in the source file.
|
|
43
|
+
* @property {string | null} name - Parameter name.
|
|
44
|
+
* @property {string | null} sourceFile - Source file that declared the method.
|
|
45
|
+
* @property {string} type - JSDoc parameter type.
|
|
46
|
+
*/
|
|
26
47
|
|
|
27
48
|
/** Node CLI command that generates frontend model classes from backend project resource config. */
|
|
28
49
|
export default class DbGenerateFrontendModels extends BaseCommand {
|
|
29
|
-
/** @type {Map<string,
|
|
50
|
+
/** @type {Map<string, ResourceMethodReturnType> | null} */
|
|
30
51
|
_resourceMethodReturnTypes = null
|
|
31
52
|
|
|
32
|
-
/** @type {Map<string,
|
|
53
|
+
/** @type {Map<string, ResourceMethodParameterType[]> | null} */
|
|
33
54
|
_resourceMethodParameterTypes = null
|
|
34
55
|
|
|
35
56
|
/**
|
|
@@ -128,6 +149,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
128
149
|
|
|
129
150
|
const fileContent = await this.buildModelFileContent({
|
|
130
151
|
className,
|
|
152
|
+
frontendModelFilePath: filePath,
|
|
131
153
|
importPath,
|
|
132
154
|
modelClass: resourceClass ? resourceClass.modelClass() : configuration.getModelClasses()[className],
|
|
133
155
|
modelConfig,
|
|
@@ -253,13 +275,14 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
253
275
|
* Runs build model file content.
|
|
254
276
|
* @param {object} args - Method args.
|
|
255
277
|
* @param {string} args.className - Model class name.
|
|
278
|
+
* @param {string} args.frontendModelFilePath - Generated frontend model file path.
|
|
256
279
|
* @param {string} args.importPath - Base class import path.
|
|
257
280
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
258
281
|
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
259
282
|
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
260
283
|
* @returns {Promise<string>} - Generated file content.
|
|
261
284
|
*/
|
|
262
|
-
async buildModelFileContent({className, importPath, modelClass, modelConfig, resourceClass}) {
|
|
285
|
+
async buildModelFileContent({className, frontendModelFilePath, importPath, modelClass, modelConfig, resourceClass}) {
|
|
263
286
|
const attributes = await this.attributeDefinitionsForModel({className, modelClass, modelConfig, resourceClass})
|
|
264
287
|
const relationships = this.relationshipsForModel({className, modelConfig, resourceClass})
|
|
265
288
|
const attachments = modelConfig.attachments && typeof modelConfig.attachments === "object"
|
|
@@ -292,6 +315,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
292
315
|
const commandMetadata = await this.commandMetadataWithResourceJsDoc({
|
|
293
316
|
commandMetadata: declaredCommandMetadata,
|
|
294
317
|
commandNames: [...Object.keys(collectionCommands), ...Object.keys(memberCommands)],
|
|
318
|
+
frontendModelFilePath,
|
|
295
319
|
resourceClass
|
|
296
320
|
})
|
|
297
321
|
const builtInCollectionCommandsAreDefault = builtInCollectionCommands.create === "create" && builtInCollectionCommands.index === "index"
|
|
@@ -633,7 +657,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
633
657
|
/**
|
|
634
658
|
* Runs write attributes typedef.
|
|
635
659
|
* @param {object} args - Arguments.
|
|
636
|
-
* @param {Array<{jsDocType: string, name: string}>} args.attributes - Generated read attributes.
|
|
660
|
+
* @param {Array<{jsDocType: string, name: string, writeJsDocType: string}>} args.attributes - Generated read attributes.
|
|
637
661
|
* @param {string} args.attributesTypeName - Generated read attributes typedef name.
|
|
638
662
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
639
663
|
* @param {Array<{attributes: Array<{name: string, type: string}>, relationshipName: string, typeName: string}>} args.nestedWriteTypes - Nested write typedefs.
|
|
@@ -691,7 +715,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
691
715
|
|
|
692
716
|
/**
|
|
693
717
|
* Runs frontend write attribute type.
|
|
694
|
-
* @param {{attribute: {jsDocType: string, name: string} | undefined, attributeName: string, attributesTypeName: string, resourceClass: import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined}} args - Arguments.
|
|
718
|
+
* @param {{attribute: {jsDocType: string, name: string, writeJsDocType: string} | undefined, attributeName: string, attributesTypeName: string, resourceClass: import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined}} args - Arguments.
|
|
695
719
|
* @returns {Promise<string>} - JSDoc type for the permitted write field.
|
|
696
720
|
*/
|
|
697
721
|
async frontendWriteAttributeType({attribute, attributeName, attributesTypeName, resourceClass}) {
|
|
@@ -703,6 +727,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
703
727
|
|
|
704
728
|
if (attribute.jsDocType.trim() === "null") return "FrontendModelAttributeValue"
|
|
705
729
|
|
|
730
|
+
if (attribute.writeJsDocType !== attribute.jsDocType) return attribute.writeJsDocType
|
|
731
|
+
|
|
706
732
|
return `${attributesTypeName}[${JSON.stringify(attribute.name)}] | null`
|
|
707
733
|
}
|
|
708
734
|
|
|
@@ -823,7 +849,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
823
849
|
|
|
824
850
|
return {
|
|
825
851
|
name: resolvedAttributeName,
|
|
826
|
-
type: attributeConfig ? this.
|
|
852
|
+
type: attributeConfig ? this.jsDocTypeForFrontendWriteAttribute({attributeConfig}) : "FrontendModelAttributeValue"
|
|
827
853
|
}
|
|
828
854
|
})
|
|
829
855
|
}
|
|
@@ -979,7 +1005,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
979
1005
|
* @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
|
|
980
1006
|
* @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
|
|
981
1007
|
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
|
|
982
|
-
* @returns {Promise<Array<{jsDocType: string, name: string}>>} - Attribute definitions.
|
|
1008
|
+
* @returns {Promise<Array<{jsDocType: string, name: string, writeJsDocType: string}>>} - Attribute definitions.
|
|
983
1009
|
*/
|
|
984
1010
|
async attributeDefinitionsForModel({className, modelClass, modelConfig, resourceClass}) {
|
|
985
1011
|
let attributes = modelConfig.attributes
|
|
@@ -1028,7 +1054,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1028
1054
|
|
|
1029
1055
|
attributeDefinitions.push({
|
|
1030
1056
|
jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: frontendAttributeConfig}),
|
|
1031
|
-
name: attributeName
|
|
1057
|
+
name: attributeName,
|
|
1058
|
+
writeJsDocType: this.jsDocTypeForFrontendWriteAttribute({attributeConfig: frontendAttributeConfig})
|
|
1032
1059
|
})
|
|
1033
1060
|
}
|
|
1034
1061
|
|
|
@@ -1061,7 +1088,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1061
1088
|
|
|
1062
1089
|
attributeDefinitions.push({
|
|
1063
1090
|
jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: frontendAttributeConfig}),
|
|
1064
|
-
name: attributeName
|
|
1091
|
+
name: attributeName,
|
|
1092
|
+
writeJsDocType: this.jsDocTypeForFrontendWriteAttribute({attributeConfig: frontendAttributeConfig})
|
|
1065
1093
|
})
|
|
1066
1094
|
}
|
|
1067
1095
|
|
|
@@ -1236,6 +1264,39 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1236
1264
|
return `${jsDocType} | null`
|
|
1237
1265
|
}
|
|
1238
1266
|
|
|
1267
|
+
/**
|
|
1268
|
+
* Runs js doc type for frontend write attribute.
|
|
1269
|
+
* @param {object} args - Arguments.
|
|
1270
|
+
* @param {FrontendAttributeConfig | null | undefined} args.attributeConfig - Attribute configuration value.
|
|
1271
|
+
* @returns {string} - JSDoc type accepted by create/update payloads.
|
|
1272
|
+
*/
|
|
1273
|
+
jsDocTypeForFrontendWriteAttribute({attributeConfig}) {
|
|
1274
|
+
if (attributeConfig && typeof attributeConfig.jsDocType == "string" && attributeConfig.jsDocType.length > 0) {
|
|
1275
|
+
return attributeConfig.jsDocType
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const jsDocType = this.jsDocTypeForFrontendWriteAttributeBaseType(attributeConfig)
|
|
1279
|
+
|
|
1280
|
+
if (!this.frontendAttributeCanBeNull(attributeConfig)) {
|
|
1281
|
+
return jsDocType
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return `${jsDocType} | null`
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Runs js doc type for frontend write attribute base type.
|
|
1289
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
1290
|
+
* @returns {string} - Non-nullable JSDoc type accepted by create/update payloads.
|
|
1291
|
+
*/
|
|
1292
|
+
jsDocTypeForFrontendWriteAttributeBaseType(attributeConfig) {
|
|
1293
|
+
const readType = this.jsDocTypeForFrontendAttributeBaseType(attributeConfig)
|
|
1294
|
+
|
|
1295
|
+
if (!this.frontendAttributeTypeIsTemporal(attributeConfig)) return readType
|
|
1296
|
+
|
|
1297
|
+
return `${readType} | string`
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1239
1300
|
/**
|
|
1240
1301
|
* Runs js doc type for frontend attribute base type.
|
|
1241
1302
|
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
@@ -1256,13 +1317,26 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1256
1317
|
return "string"
|
|
1257
1318
|
} else if (type && ["bit", "bigint", "decimal", "double", "double precision", "float", "int", "integer", "numeric", "real", "smallint", "tinyint"].includes(type)) {
|
|
1258
1319
|
return "number"
|
|
1259
|
-
} else if (
|
|
1320
|
+
} else if (this.frontendAttributeTypeIsTemporal(attributeConfig)) {
|
|
1260
1321
|
return "Date"
|
|
1261
1322
|
} else {
|
|
1262
1323
|
return "FrontendModelAttributeValue"
|
|
1263
1324
|
}
|
|
1264
1325
|
}
|
|
1265
1326
|
|
|
1327
|
+
/**
|
|
1328
|
+
* Runs frontend attribute type is temporal.
|
|
1329
|
+
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
1330
|
+
* @returns {boolean} - Whether the attribute represents a date/time value.
|
|
1331
|
+
*/
|
|
1332
|
+
frontendAttributeTypeIsTemporal(attributeConfig) {
|
|
1333
|
+
if (!attributeConfig || typeof attributeConfig !== "object") return false
|
|
1334
|
+
|
|
1335
|
+
const type = this.frontendAttributeTypeValue(attributeConfig)
|
|
1336
|
+
|
|
1337
|
+
return type ? ["date", "datetime", "timestamp", "timestamp without time zone", "timestamptz"].includes(type) : false
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1266
1340
|
/**
|
|
1267
1341
|
* Runs frontend attribute can be null.
|
|
1268
1342
|
* @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute configuration value.
|
|
@@ -1437,25 +1511,209 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1437
1511
|
|
|
1438
1512
|
/**
|
|
1439
1513
|
* Rewrites a custom-command param/return JSDoc type so it resolves in the generated
|
|
1440
|
-
* frontend model:
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1445
|
-
* @param {string}
|
|
1514
|
+
* frontend model: backend model imports are mapped to generated frontend model
|
|
1515
|
+
* imports, and otherwise non-frontend-resolvable identifiers become `any` in place
|
|
1516
|
+
* so sibling scalar fields keep their real types. The word boundary avoids matching
|
|
1517
|
+
* the capitalized middle of a camelCase property name (e.g. `adjustedTotalCents`).
|
|
1518
|
+
* @param {object} args - Arguments.
|
|
1519
|
+
* @param {string | null} args.frontendModelFilePath - Generated frontend model file path.
|
|
1520
|
+
* @param {Map<string, ResourceJsDocImportAlias>} args.importAliases - Import aliases visible to the source method.
|
|
1521
|
+
* @param {string} args.jsDocType - Resolved (Promise-unwrapped) JSDoc type.
|
|
1522
|
+
* @param {string | null} args.sourceFile - Source file that declared the method.
|
|
1446
1523
|
* @returns {string} - A frontend-resolvable JSDoc type.
|
|
1447
1524
|
*/
|
|
1448
|
-
frontendResolvableCommandJsDocType(jsDocType) {
|
|
1525
|
+
frontendResolvableCommandJsDocType({frontendModelFilePath, importAliases, jsDocType, sourceFile}) {
|
|
1449
1526
|
const safeTypeIdentifiers = this.frontendResolvableTypeIdentifiers()
|
|
1527
|
+
/** @type {string[]} */
|
|
1528
|
+
const preservedImports = []
|
|
1529
|
+
/**
|
|
1530
|
+
* Stores an import expression behind a lowercase placeholder while generic
|
|
1531
|
+
* identifier cleanup runs.
|
|
1532
|
+
* @param {string} importExpression - Import expression to preserve.
|
|
1533
|
+
* @returns {string} Placeholder inserted into the type string.
|
|
1534
|
+
*/
|
|
1535
|
+
const preserveImportExpression = (importExpression) => {
|
|
1536
|
+
const placeholder = `__velocious_import_placeholder_${preservedImports.length}__`
|
|
1537
|
+
|
|
1538
|
+
preservedImports.push(importExpression)
|
|
1539
|
+
|
|
1540
|
+
return placeholder
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
this.assertNoBackendLocalCommandTypeExpressions(jsDocType)
|
|
1544
|
+
|
|
1545
|
+
const withRewrittenInlineImports = jsDocType
|
|
1546
|
+
// A type that reaches into a backend source file via `import("...")`
|
|
1547
|
+
// (optionally `.Member` and `[]`) is frontend-resolvable only when it
|
|
1548
|
+
// points at a generated model file; other backend-local imports collapse
|
|
1549
|
+
// to `any` so helper/service implementation details do not leak.
|
|
1550
|
+
.replace(/import\(\s*["']([^"']*)["']\s*\)((?:\s*\.\s*[A-Za-z_$][\w$]*)*)((?:\s*\[\s*\])*)/g, (_match, specifier, memberChain, arraySuffix) => {
|
|
1551
|
+
const rewrittenSpecifier = this.frontendResolvableJsDocImportSpecifier({
|
|
1552
|
+
frontendModelFilePath,
|
|
1553
|
+
sourceFile,
|
|
1554
|
+
specifier
|
|
1555
|
+
})
|
|
1450
1556
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1557
|
+
if (!rewrittenSpecifier) return "any"
|
|
1558
|
+
|
|
1559
|
+
return preserveImportExpression(`import(${JSON.stringify(rewrittenSpecifier)})${memberChain.replace(/\s+/g, "")}${arraySuffix.replace(/\s+/g, "")}`)
|
|
1560
|
+
})
|
|
1561
|
+
|
|
1562
|
+
let withRewrittenAliases = withRewrittenInlineImports
|
|
1563
|
+
|
|
1564
|
+
for (const [aliasName, importAlias] of importAliases) {
|
|
1565
|
+
const rewrittenSpecifier = this.frontendResolvableJsDocImportSpecifier({
|
|
1566
|
+
frontendModelFilePath,
|
|
1567
|
+
sourceFile,
|
|
1568
|
+
specifier: importAlias.specifier
|
|
1569
|
+
})
|
|
1570
|
+
|
|
1571
|
+
if (!rewrittenSpecifier) continue
|
|
1572
|
+
|
|
1573
|
+
const aliasRegex = new RegExp(`\\b${this.escapeRegExp(aliasName)}\\b`, "g")
|
|
1574
|
+
|
|
1575
|
+
withRewrittenAliases = withRewrittenAliases.replace(aliasRegex, preserveImportExpression(`import(${JSON.stringify(rewrittenSpecifier)}).${importAlias.importedName}`))
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const sanitized = withRewrittenAliases
|
|
1456
1579
|
// Remaining capitalized identifiers are model classes or otherwise non-resolvable
|
|
1457
1580
|
// types; downgrade each in place so sibling scalar fields keep their real types.
|
|
1458
1581
|
.replace(/\b[A-Z][A-Za-z0-9_$]*/g, (identifier) => safeTypeIdentifiers.has(identifier) ? identifier : "any")
|
|
1582
|
+
|
|
1583
|
+
return preservedImports.reduce(
|
|
1584
|
+
(type, importExpression, index) => type.replaceAll(`__velocious_import_placeholder_${index}__`, importExpression),
|
|
1585
|
+
sanitized
|
|
1586
|
+
)
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Raises when a command JSDoc type references a backend-local helper expression.
|
|
1591
|
+
* @param {string} jsDocType - Command JSDoc type.
|
|
1592
|
+
* @returns {void} No return value.
|
|
1593
|
+
*/
|
|
1594
|
+
assertNoBackendLocalCommandTypeExpressions(jsDocType) {
|
|
1595
|
+
const localReturnTypeMatch = jsDocType.match(/\b(?:Awaited\s*<\s*)?ReturnType\s*<\s*typeof\s+[A-Za-z_$][\w$]*\s*>\s*>?/)
|
|
1596
|
+
|
|
1597
|
+
if (!localReturnTypeMatch) return
|
|
1598
|
+
|
|
1599
|
+
throw new Error(`Custom command JSDoc type cannot use backend-local ReturnType expressions in generated frontend models: ${localReturnTypeMatch[0]}. Move the payload shape to a shared typedef and return that type from the command method.`)
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Runs frontend resolvable js doc import specifier.
|
|
1604
|
+
* @param {object} args - Arguments.
|
|
1605
|
+
* @param {string | null} args.frontendModelFilePath - Generated frontend model file path.
|
|
1606
|
+
* @param {string | null} args.sourceFile - Source file that declared the JSDoc type.
|
|
1607
|
+
* @param {string} args.specifier - Source-file import specifier.
|
|
1608
|
+
* @returns {string | null} - Rewritten frontend-model import specifier, or null when backend-local.
|
|
1609
|
+
*/
|
|
1610
|
+
frontendResolvableJsDocImportSpecifier({frontendModelFilePath, sourceFile, specifier}) {
|
|
1611
|
+
if (!sourceFile || !frontendModelFilePath) return null
|
|
1612
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) return specifier
|
|
1613
|
+
|
|
1614
|
+
const importedPath = path.resolve(path.dirname(sourceFile), specifier)
|
|
1615
|
+
const modelImportSpecifier = this.frontendModelImportSpecifierForBackendModelPath({
|
|
1616
|
+
frontendModelFilePath,
|
|
1617
|
+
importedPath
|
|
1618
|
+
})
|
|
1619
|
+
|
|
1620
|
+
if (modelImportSpecifier) return modelImportSpecifier
|
|
1621
|
+
|
|
1622
|
+
if (this.filePathIsWithinAnyDirectory({directories: this.frontendModelJsDocSourceDirectories(), filePath: importedPath})) {
|
|
1623
|
+
return null
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
return this.relativeImportSpecifier({fromFile: frontendModelFilePath, toFile: importedPath})
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* Runs frontend model import specifier for backend model path.
|
|
1631
|
+
* @param {object} args - Arguments.
|
|
1632
|
+
* @param {string} args.frontendModelFilePath - Generated frontend model file path.
|
|
1633
|
+
* @param {string} args.importedPath - Source-file import path resolved from JSDoc.
|
|
1634
|
+
* @returns {string | null} - Generated frontend-model import specifier, or null when the path is not a registered model file.
|
|
1635
|
+
*/
|
|
1636
|
+
frontendModelImportSpecifierForBackendModelPath({frontendModelFilePath, importedPath}) {
|
|
1637
|
+
const frontendModelsDirectory = path.dirname(frontendModelFilePath)
|
|
1638
|
+
const importedModelPath = importedPath.endsWith(".js") ? importedPath : `${importedPath}.js`
|
|
1639
|
+
|
|
1640
|
+
for (const modelFileName of this.generatedFrontendModelFileNames()) {
|
|
1641
|
+
for (const sourceDirectory of this.frontendModelJsDocSourceDirectories()) {
|
|
1642
|
+
const modelsDirectory = path.join(sourceDirectory, "models")
|
|
1643
|
+
const candidateModelPath = path.join(modelsDirectory, modelFileName)
|
|
1644
|
+
|
|
1645
|
+
if (path.resolve(candidateModelPath) !== path.resolve(importedModelPath)) continue
|
|
1646
|
+
|
|
1647
|
+
return this.relativeImportSpecifier({
|
|
1648
|
+
fromFile: frontendModelFilePath,
|
|
1649
|
+
toFile: path.join(frontendModelsDirectory, modelFileName)
|
|
1650
|
+
})
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
return null
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Runs generated frontend model file names.
|
|
1659
|
+
* @returns {Set<string>} - Frontend model filenames that this generation run can emit.
|
|
1660
|
+
*/
|
|
1661
|
+
generatedFrontendModelFileNames() {
|
|
1662
|
+
/** @type {Set<string>} */
|
|
1663
|
+
const fileNames = new Set()
|
|
1664
|
+
|
|
1665
|
+
for (const backendProject of this.getConfiguration().getBackendProjects()) {
|
|
1666
|
+
const resources = this.resourcesForBackendProject(backendProject)
|
|
1667
|
+
|
|
1668
|
+
for (const resourceModelName of Object.keys(resources)) {
|
|
1669
|
+
const className = inflection.camelize(resourceModelName.replaceAll("-", "_"))
|
|
1670
|
+
|
|
1671
|
+
fileNames.add(`${inflection.dasherize(inflection.underscore(className))}.js`)
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
return fileNames
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
/**
|
|
1679
|
+
* Runs relative import specifier.
|
|
1680
|
+
* @param {object} args - Arguments.
|
|
1681
|
+
* @param {string} args.fromFile - Source file that will contain the import expression.
|
|
1682
|
+
* @param {string} args.toFile - File being imported.
|
|
1683
|
+
* @returns {string} - Relative import specifier.
|
|
1684
|
+
*/
|
|
1685
|
+
relativeImportSpecifier({fromFile, toFile}) {
|
|
1686
|
+
let relativeSpecifier = path.relative(path.dirname(fromFile), toFile).split(path.sep).join("/")
|
|
1687
|
+
|
|
1688
|
+
if (!relativeSpecifier.startsWith(".")) {
|
|
1689
|
+
relativeSpecifier = `./${relativeSpecifier}`
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
return relativeSpecifier
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Runs file path is within any directory.
|
|
1697
|
+
* @param {object} args - Arguments.
|
|
1698
|
+
* @param {string[]} args.directories - Candidate parent directories.
|
|
1699
|
+
* @param {string} args.filePath - File path to test.
|
|
1700
|
+
* @returns {boolean} - Whether the file path is under one candidate directory.
|
|
1701
|
+
*/
|
|
1702
|
+
filePathIsWithinAnyDirectory({directories, filePath}) {
|
|
1703
|
+
return directories.some((directory) => {
|
|
1704
|
+
const relativePath = path.relative(path.resolve(directory), path.resolve(filePath))
|
|
1705
|
+
|
|
1706
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
|
1707
|
+
})
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/**
|
|
1711
|
+
* Escapes text for use inside a RegExp.
|
|
1712
|
+
* @param {string} value - Value to escape.
|
|
1713
|
+
* @returns {string} - RegExp-safe value.
|
|
1714
|
+
*/
|
|
1715
|
+
escapeRegExp(value) {
|
|
1716
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
1459
1717
|
}
|
|
1460
1718
|
|
|
1461
1719
|
/**
|
|
@@ -1474,10 +1732,15 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1474
1732
|
|
|
1475
1733
|
if (metadata.args.length > 0) {
|
|
1476
1734
|
const parameterNames = metadata.args.map((arg) => arg.name)
|
|
1735
|
+
// A single args object whose every field is optional accepts `{}`, so default
|
|
1736
|
+
// the parameter and mark it optional — callers can then omit it entirely
|
|
1737
|
+
// (`record.command()` instead of `record.command({})`). Required-field args keep
|
|
1738
|
+
// the mandatory parameter (a `{}` default wouldn't satisfy their type).
|
|
1739
|
+
const defaultsToEmptyObject = metadata.args.length === 1 && this.argTypeAcceptsEmptyObject(metadata.args[0].type)
|
|
1477
1740
|
|
|
1478
1741
|
return {
|
|
1479
|
-
paramDocs: metadata.args.map((arg) => ` * @param {${arg.type}} ${arg.name} - Command argument.\n`).join(""),
|
|
1480
|
-
parameters: parameterNames.join(", "),
|
|
1742
|
+
paramDocs: metadata.args.map((arg) => ` * @param {${arg.type}} ${defaultsToEmptyObject ? `[${arg.name}]` : arg.name} - Command argument.\n`).join(""),
|
|
1743
|
+
parameters: defaultsToEmptyObject ? `${parameterNames[0]} = {}` : parameterNames.join(", "),
|
|
1481
1744
|
payloadArguments: `[${parameterNames.join(", ")}]`,
|
|
1482
1745
|
returnType
|
|
1483
1746
|
}
|
|
@@ -1491,6 +1754,124 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1491
1754
|
}
|
|
1492
1755
|
}
|
|
1493
1756
|
|
|
1757
|
+
/**
|
|
1758
|
+
* Whether a single command-args JSDoc type is known to accept an empty object `{}`:
|
|
1759
|
+
* a single balanced object literal whose top-level members are all optional (`name?:`)
|
|
1760
|
+
* or index signatures (`[k: ...]:`). Anything else returns false so the parameter stays
|
|
1761
|
+
* required — including a required member, a non-object-literal (a positional `number`,
|
|
1762
|
+
* a `Record<...>` / `Partial<...>` whose key/wrapper may still require data), and any
|
|
1763
|
+
* intersection/union (e.g. `{a?: x} & {b: string}`), where `{}` is not assignable.
|
|
1764
|
+
* @param {string} type - The arg's JSDoc type string.
|
|
1765
|
+
* @returns {boolean} - Whether the generated parameter can default to `{}`.
|
|
1766
|
+
*/
|
|
1767
|
+
argTypeAcceptsEmptyObject(type) {
|
|
1768
|
+
const trimmedType = type.trim()
|
|
1769
|
+
|
|
1770
|
+
// Must be a single balanced object literal: starts with `{`, ends with `}`, and the
|
|
1771
|
+
// opening brace closes only at the final character. This rejects intersections/unions
|
|
1772
|
+
// like `{a?: x} & {b: string}` that merely happen to start `{` and end `}`.
|
|
1773
|
+
if (!(trimmedType.startsWith("{") && trimmedType.endsWith("}"))) return false
|
|
1774
|
+
if (!this.isSingleBalancedObjectLiteral(trimmedType)) return false
|
|
1775
|
+
|
|
1776
|
+
const inner = trimmedType.slice(1, -1)
|
|
1777
|
+
|
|
1778
|
+
for (const member of this.splitTopLevelTypeMembers(inner)) {
|
|
1779
|
+
const colonIndex = this.topLevelColonIndex(member)
|
|
1780
|
+
|
|
1781
|
+
// No top-level colon: a call/construct/mapped signature or malformed member —
|
|
1782
|
+
// can't confirm it's optional, so treat the type as not empty-defaultable.
|
|
1783
|
+
if (colonIndex < 0) return false
|
|
1784
|
+
|
|
1785
|
+
const key = member.slice(0, colonIndex).trim()
|
|
1786
|
+
|
|
1787
|
+
// Index signatures (`[k: string]`) don't require a value; optional props end in `?`.
|
|
1788
|
+
// Anything else is a required property, so `{}` would not satisfy the type.
|
|
1789
|
+
if (!key.startsWith("[") && !key.endsWith("?")) return false
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return true
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* Splits the inner body of an object-literal type into its top-level members,
|
|
1797
|
+
* respecting nested `{}` / `[]` / `<>` / `()` so field types like `string[] | null`
|
|
1798
|
+
* or `{a: b}` aren't split mid-type. Members are separated by `,` or `;`.
|
|
1799
|
+
* @param {string} inner - Object-literal body (without the outer braces).
|
|
1800
|
+
* @returns {string[]} - Trimmed non-empty top-level members.
|
|
1801
|
+
*/
|
|
1802
|
+
splitTopLevelTypeMembers(inner) {
|
|
1803
|
+
const members = []
|
|
1804
|
+
let depth = 0
|
|
1805
|
+
let start = 0
|
|
1806
|
+
|
|
1807
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
1808
|
+
const character = inner[index]
|
|
1809
|
+
|
|
1810
|
+
if (character === "{" || character === "[" || character === "<" || character === "(") {
|
|
1811
|
+
depth += 1
|
|
1812
|
+
} else if (character === "}" || character === "]" || character === ">" || character === ")") {
|
|
1813
|
+
depth -= 1
|
|
1814
|
+
} else if ((character === "," || character === ";") && depth === 0) {
|
|
1815
|
+
members.push(inner.slice(start, index))
|
|
1816
|
+
start = index + 1
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
members.push(inner.slice(start))
|
|
1821
|
+
|
|
1822
|
+
return members.map((member) => member.trim()).filter((member) => member.length > 0)
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Index of the first top-level `:` in an object-literal member, ignoring colons
|
|
1827
|
+
* nested inside `{}` / `[]` / `<>` / `()` (e.g. an index signature `[k: string]`).
|
|
1828
|
+
* @param {string} member - A single object-literal member.
|
|
1829
|
+
* @returns {number} - The colon index, or -1 when none is found at the top level.
|
|
1830
|
+
*/
|
|
1831
|
+
topLevelColonIndex(member) {
|
|
1832
|
+
let depth = 0
|
|
1833
|
+
|
|
1834
|
+
for (let index = 0; index < member.length; index += 1) {
|
|
1835
|
+
const character = member[index]
|
|
1836
|
+
|
|
1837
|
+
if (character === "{" || character === "[" || character === "<" || character === "(") {
|
|
1838
|
+
depth += 1
|
|
1839
|
+
} else if (character === "}" || character === "]" || character === ">" || character === ")") {
|
|
1840
|
+
depth -= 1
|
|
1841
|
+
} else if (character === ":" && depth === 0) {
|
|
1842
|
+
return index
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
return -1
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
/**
|
|
1850
|
+
* Whether the type is a single balanced object literal — its leading `{` closes only
|
|
1851
|
+
* at the final character. Rejects top-level intersections/unions like `{a?: x} & {b: y}`
|
|
1852
|
+
* or `{a?: x} | string` whose brace depth returns to 0 before the end.
|
|
1853
|
+
* @param {string} type - A trimmed type string that starts with `{` and ends with `}`.
|
|
1854
|
+
* @returns {boolean} - Whether the braces wrap the whole type.
|
|
1855
|
+
*/
|
|
1856
|
+
isSingleBalancedObjectLiteral(type) {
|
|
1857
|
+
let depth = 0
|
|
1858
|
+
|
|
1859
|
+
for (let index = 0; index < type.length; index += 1) {
|
|
1860
|
+
const character = type[index]
|
|
1861
|
+
|
|
1862
|
+
if (character === "{" || character === "[" || character === "<" || character === "(") {
|
|
1863
|
+
depth += 1
|
|
1864
|
+
} else if (character === "}" || character === "]" || character === ">" || character === ")") {
|
|
1865
|
+
depth -= 1
|
|
1866
|
+
|
|
1867
|
+
// The opening brace balanced before the end, so something follows the literal.
|
|
1868
|
+
if (depth === 0 && index < type.length - 1) return false
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
return depth === 0
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1494
1875
|
/**
|
|
1495
1876
|
* Enriches custom-command metadata by deriving a command's typed args and return
|
|
1496
1877
|
* type from the backend resource method's `@param`/`@returns` JSDoc when they are
|
|
@@ -1502,10 +1883,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1502
1883
|
* @param {object} args - Arguments.
|
|
1503
1884
|
* @param {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} args.commandMetadata - Declared per-command metadata.
|
|
1504
1885
|
* @param {string[]} args.commandNames - Command method names to resolve.
|
|
1886
|
+
* @param {string} args.frontendModelFilePath - Generated frontend model file path.
|
|
1505
1887
|
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
|
|
1506
1888
|
* @returns {Promise<Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>>} - Enriched metadata.
|
|
1507
1889
|
*/
|
|
1508
|
-
async commandMetadataWithResourceJsDoc({commandMetadata, commandNames, resourceClass}) {
|
|
1890
|
+
async commandMetadataWithResourceJsDoc({commandMetadata, commandNames, frontendModelFilePath, resourceClass}) {
|
|
1509
1891
|
if (!resourceClass) return commandMetadata
|
|
1510
1892
|
|
|
1511
1893
|
/** @type {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} */
|
|
@@ -1524,10 +1906,15 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1524
1906
|
let returnType = declared.returnType
|
|
1525
1907
|
|
|
1526
1908
|
if (!returnType) {
|
|
1527
|
-
const jsDocReturnType = await this.
|
|
1909
|
+
const jsDocReturnType = await this.resourceMethodReturnTypeDefinition({methodName: commandName, sourceClassName})
|
|
1528
1910
|
|
|
1529
1911
|
if (jsDocReturnType) {
|
|
1530
|
-
returnType = this.frontendResolvableCommandJsDocType(
|
|
1912
|
+
returnType = this.frontendResolvableCommandJsDocType({
|
|
1913
|
+
frontendModelFilePath,
|
|
1914
|
+
importAliases: jsDocReturnType.importAliases,
|
|
1915
|
+
jsDocType: this.unwrappedPromiseJsDocType({jsDocType: jsDocReturnType.type}),
|
|
1916
|
+
sourceFile: jsDocReturnType.sourceFile
|
|
1917
|
+
})
|
|
1531
1918
|
}
|
|
1532
1919
|
}
|
|
1533
1920
|
|
|
@@ -1543,7 +1930,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1543
1930
|
if (topLevelParameters.length > 0) {
|
|
1544
1931
|
args = topLevelParameters.map((parameter) => ({
|
|
1545
1932
|
name: /** @type {string} */ (parameter.name),
|
|
1546
|
-
type: this.frontendResolvableCommandJsDocType(
|
|
1933
|
+
type: this.frontendResolvableCommandJsDocType({
|
|
1934
|
+
frontendModelFilePath,
|
|
1935
|
+
importAliases: parameter.importAliases,
|
|
1936
|
+
jsDocType: parameter.type,
|
|
1937
|
+
sourceFile: parameter.sourceFile
|
|
1938
|
+
})
|
|
1547
1939
|
}))
|
|
1548
1940
|
}
|
|
1549
1941
|
}
|
|
@@ -1615,6 +2007,19 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1615
2007
|
* @returns {Promise<string | null>} - JSDoc return type when documented.
|
|
1616
2008
|
*/
|
|
1617
2009
|
async resourceMethodReturnType({methodName, sourceClassName}) {
|
|
2010
|
+
const returnType = await this.resourceMethodReturnTypeDefinition({methodName, sourceClassName})
|
|
2011
|
+
|
|
2012
|
+
return returnType ? returnType.type : null
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Runs resource method return type definition.
|
|
2017
|
+
* @param {object} args - Arguments.
|
|
2018
|
+
* @param {string} args.methodName - Method name.
|
|
2019
|
+
* @param {string} args.sourceClassName - Source class name.
|
|
2020
|
+
* @returns {Promise<ResourceMethodReturnType | null>} - JSDoc return type definition when documented.
|
|
2021
|
+
*/
|
|
2022
|
+
async resourceMethodReturnTypeDefinition({methodName, sourceClassName}) {
|
|
1618
2023
|
const resourceMethodReturnTypes = await this.resourceMethodReturnTypes()
|
|
1619
2024
|
const returnTypeKey = `${sourceClassName}.${methodName}`
|
|
1620
2025
|
|
|
@@ -1622,7 +2027,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1622
2027
|
|
|
1623
2028
|
const returnType = resourceMethodReturnTypes.get(returnTypeKey)
|
|
1624
2029
|
|
|
1625
|
-
if (
|
|
2030
|
+
if (!returnType) {
|
|
2031
|
+
throw new Error(`Expected JSDoc return type for ${returnTypeKey}`)
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if (typeof returnType.type != "string" || returnType.type.length < 1) {
|
|
1626
2035
|
throw new Error(`Expected non-empty JSDoc return type for ${returnTypeKey}`)
|
|
1627
2036
|
}
|
|
1628
2037
|
|
|
@@ -1653,7 +2062,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1653
2062
|
/**
|
|
1654
2063
|
* Runs resource method parameters.
|
|
1655
2064
|
* @param {{methodName: string, sourceClassName: string}} args - Arguments.
|
|
1656
|
-
* @returns {Promise<
|
|
2065
|
+
* @returns {Promise<ResourceMethodParameterType[] | null>} - JSDoc parameters (name + type) when documented.
|
|
1657
2066
|
*/
|
|
1658
2067
|
async resourceMethodParameters({methodName, sourceClassName}) {
|
|
1659
2068
|
const resourceMethodParameterTypes = await this.resourceMethodParameterTypes()
|
|
@@ -1672,7 +2081,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1672
2081
|
|
|
1673
2082
|
/**
|
|
1674
2083
|
* Runs resource method return types.
|
|
1675
|
-
* @returns {Promise<Map<string,
|
|
2084
|
+
* @returns {Promise<Map<string, ResourceMethodReturnType>>} - Resource method return types keyed by ClassName.methodName.
|
|
1676
2085
|
*/
|
|
1677
2086
|
async resourceMethodReturnTypes() {
|
|
1678
2087
|
if (this._resourceMethodReturnTypes) return this._resourceMethodReturnTypes
|
|
@@ -1683,7 +2092,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1683
2092
|
for (const sourceFile of sourceFiles) {
|
|
1684
2093
|
const sourceText = await fs.readFile(sourceFile, "utf8")
|
|
1685
2094
|
|
|
1686
|
-
this.addResourceMethodReturnTypesFromSource({returnTypes, sourceText})
|
|
2095
|
+
this.addResourceMethodReturnTypesFromSource({returnTypes, sourceFile, sourceText})
|
|
1687
2096
|
}
|
|
1688
2097
|
|
|
1689
2098
|
this._resourceMethodReturnTypes = returnTypes
|
|
@@ -1693,7 +2102,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1693
2102
|
|
|
1694
2103
|
/**
|
|
1695
2104
|
* Runs resource method parameter types.
|
|
1696
|
-
* @returns {Promise<Map<string,
|
|
2105
|
+
* @returns {Promise<Map<string, ResourceMethodParameterType[]>>} - Resource method parameters keyed by ClassName.methodName.
|
|
1697
2106
|
*/
|
|
1698
2107
|
async resourceMethodParameterTypes() {
|
|
1699
2108
|
if (this._resourceMethodParameterTypes) return this._resourceMethodParameterTypes
|
|
@@ -1704,7 +2113,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1704
2113
|
for (const sourceFile of sourceFiles) {
|
|
1705
2114
|
const sourceText = await fs.readFile(sourceFile, "utf8")
|
|
1706
2115
|
|
|
1707
|
-
this.addResourceMethodParameterTypesFromSource({parameterTypes, sourceText})
|
|
2116
|
+
this.addResourceMethodParameterTypesFromSource({parameterTypes, sourceFile, sourceText})
|
|
1708
2117
|
}
|
|
1709
2118
|
|
|
1710
2119
|
this._resourceMethodParameterTypes = parameterTypes
|
|
@@ -1745,12 +2154,14 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1745
2154
|
/**
|
|
1746
2155
|
* Adds resource method return types from source.
|
|
1747
2156
|
* @param {object} args - Arguments.
|
|
1748
|
-
* @param {Map<string,
|
|
2157
|
+
* @param {Map<string, ResourceMethodReturnType>} args.returnTypes - Mutable return types map.
|
|
2158
|
+
* @param {string | null} [args.sourceFile] - Source file path.
|
|
1749
2159
|
* @param {string} args.sourceText - Source text.
|
|
1750
2160
|
* @returns {void}
|
|
1751
2161
|
*/
|
|
1752
|
-
addResourceMethodReturnTypesFromSource({returnTypes, sourceText}) {
|
|
2162
|
+
addResourceMethodReturnTypesFromSource({returnTypes, sourceFile = null, sourceText}) {
|
|
1753
2163
|
const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
|
|
2164
|
+
const importAliases = this.jsDocImportAliasesFromSource(sourceText)
|
|
1754
2165
|
let classMatch
|
|
1755
2166
|
|
|
1756
2167
|
while ((classMatch = classRegex.exec(sourceText))) {
|
|
@@ -1782,7 +2193,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1782
2193
|
const returnType = this.jsDocReturnType(jsDocMatch[1])
|
|
1783
2194
|
|
|
1784
2195
|
if (returnType) {
|
|
1785
|
-
returnTypes.set(`${className}.${methodName}`, returnType)
|
|
2196
|
+
returnTypes.set(`${className}.${methodName}`, {importAliases, sourceFile, type: returnType})
|
|
1786
2197
|
}
|
|
1787
2198
|
}
|
|
1788
2199
|
|
|
@@ -1792,11 +2203,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1792
2203
|
|
|
1793
2204
|
/**
|
|
1794
2205
|
* Adds resource method parameter types from source.
|
|
1795
|
-
* @param {{parameterTypes: Map<string,
|
|
2206
|
+
* @param {{parameterTypes: Map<string, ResourceMethodParameterType[]>, sourceFile?: string | null, sourceText: string}} args - Arguments.
|
|
1796
2207
|
* @returns {void}
|
|
1797
2208
|
*/
|
|
1798
|
-
addResourceMethodParameterTypesFromSource({parameterTypes, sourceText}) {
|
|
2209
|
+
addResourceMethodParameterTypesFromSource({parameterTypes, sourceFile = null, sourceText}) {
|
|
1799
2210
|
const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
|
|
2211
|
+
const importAliases = this.jsDocImportAliasesFromSource(sourceText)
|
|
1800
2212
|
let classMatch
|
|
1801
2213
|
|
|
1802
2214
|
while ((classMatch = classRegex.exec(sourceText))) {
|
|
@@ -1827,7 +2239,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1827
2239
|
const jsDocParameters = this.jsDocParameters(jsDocMatch[1])
|
|
1828
2240
|
|
|
1829
2241
|
if (jsDocParameters.length > 0) {
|
|
1830
|
-
parameterTypes.set(`${className}.${methodName}`, jsDocParameters)
|
|
2242
|
+
parameterTypes.set(`${className}.${methodName}`, jsDocParameters.map((parameter) => ({...parameter, importAliases, sourceFile})))
|
|
1831
2243
|
}
|
|
1832
2244
|
}
|
|
1833
2245
|
|
|
@@ -1835,6 +2247,41 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1835
2247
|
}
|
|
1836
2248
|
}
|
|
1837
2249
|
|
|
2250
|
+
/**
|
|
2251
|
+
* Runs JSDoc import aliases from source.
|
|
2252
|
+
* @param {string} sourceText - Source text.
|
|
2253
|
+
* @returns {Map<string, ResourceJsDocImportAlias>} - Import aliases keyed by local name.
|
|
2254
|
+
*/
|
|
2255
|
+
jsDocImportAliasesFromSource(sourceText) {
|
|
2256
|
+
const importAliases = new Map()
|
|
2257
|
+
const importRegex = /@import\s*\{\s*([^}]+?)\s*\}\s*from\s*["']([^"']+)["']/g
|
|
2258
|
+
let importMatch
|
|
2259
|
+
|
|
2260
|
+
while ((importMatch = importRegex.exec(sourceText))) {
|
|
2261
|
+
const importList = importMatch[1]
|
|
2262
|
+
const specifier = importMatch[2]
|
|
2263
|
+
|
|
2264
|
+
for (const rawImportEntry of importList.split(",")) {
|
|
2265
|
+
const importEntry = rawImportEntry.trim()
|
|
2266
|
+
|
|
2267
|
+
if (importEntry.length < 1) continue
|
|
2268
|
+
|
|
2269
|
+
const entryMatch = importEntry.match(/^(default|[A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/)
|
|
2270
|
+
|
|
2271
|
+
if (!entryMatch) {
|
|
2272
|
+
throw new Error(`Could not parse JSDoc @import entry: ${importEntry}`)
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
const importedName = entryMatch[1]
|
|
2276
|
+
const aliasName = entryMatch[2] || importedName
|
|
2277
|
+
|
|
2278
|
+
importAliases.set(aliasName, {importedName, specifier})
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
return importAliases
|
|
2283
|
+
}
|
|
2284
|
+
|
|
1838
2285
|
/**
|
|
1839
2286
|
* Runs js doc return type.
|
|
1840
2287
|
* @param {string} jsDocText - JSDoc text inside comment markers.
|