velocious 1.0.470 → 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 +362 -38
- 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 +219 -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 +312 -39
- 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 +362 -38
- 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
|
/**
|
|
@@ -1625,10 +1883,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1625
1883
|
* @param {object} args - Arguments.
|
|
1626
1884
|
* @param {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} args.commandMetadata - Declared per-command metadata.
|
|
1627
1885
|
* @param {string[]} args.commandNames - Command method names to resolve.
|
|
1886
|
+
* @param {string} args.frontendModelFilePath - Generated frontend model file path.
|
|
1628
1887
|
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
|
|
1629
1888
|
* @returns {Promise<Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>>} - Enriched metadata.
|
|
1630
1889
|
*/
|
|
1631
|
-
async commandMetadataWithResourceJsDoc({commandMetadata, commandNames, resourceClass}) {
|
|
1890
|
+
async commandMetadataWithResourceJsDoc({commandMetadata, commandNames, frontendModelFilePath, resourceClass}) {
|
|
1632
1891
|
if (!resourceClass) return commandMetadata
|
|
1633
1892
|
|
|
1634
1893
|
/** @type {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} */
|
|
@@ -1647,10 +1906,15 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1647
1906
|
let returnType = declared.returnType
|
|
1648
1907
|
|
|
1649
1908
|
if (!returnType) {
|
|
1650
|
-
const jsDocReturnType = await this.
|
|
1909
|
+
const jsDocReturnType = await this.resourceMethodReturnTypeDefinition({methodName: commandName, sourceClassName})
|
|
1651
1910
|
|
|
1652
1911
|
if (jsDocReturnType) {
|
|
1653
|
-
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
|
+
})
|
|
1654
1918
|
}
|
|
1655
1919
|
}
|
|
1656
1920
|
|
|
@@ -1666,7 +1930,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1666
1930
|
if (topLevelParameters.length > 0) {
|
|
1667
1931
|
args = topLevelParameters.map((parameter) => ({
|
|
1668
1932
|
name: /** @type {string} */ (parameter.name),
|
|
1669
|
-
type: this.frontendResolvableCommandJsDocType(
|
|
1933
|
+
type: this.frontendResolvableCommandJsDocType({
|
|
1934
|
+
frontendModelFilePath,
|
|
1935
|
+
importAliases: parameter.importAliases,
|
|
1936
|
+
jsDocType: parameter.type,
|
|
1937
|
+
sourceFile: parameter.sourceFile
|
|
1938
|
+
})
|
|
1670
1939
|
}))
|
|
1671
1940
|
}
|
|
1672
1941
|
}
|
|
@@ -1738,6 +2007,19 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1738
2007
|
* @returns {Promise<string | null>} - JSDoc return type when documented.
|
|
1739
2008
|
*/
|
|
1740
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}) {
|
|
1741
2023
|
const resourceMethodReturnTypes = await this.resourceMethodReturnTypes()
|
|
1742
2024
|
const returnTypeKey = `${sourceClassName}.${methodName}`
|
|
1743
2025
|
|
|
@@ -1745,7 +2027,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1745
2027
|
|
|
1746
2028
|
const returnType = resourceMethodReturnTypes.get(returnTypeKey)
|
|
1747
2029
|
|
|
1748
|
-
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) {
|
|
1749
2035
|
throw new Error(`Expected non-empty JSDoc return type for ${returnTypeKey}`)
|
|
1750
2036
|
}
|
|
1751
2037
|
|
|
@@ -1776,7 +2062,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1776
2062
|
/**
|
|
1777
2063
|
* Runs resource method parameters.
|
|
1778
2064
|
* @param {{methodName: string, sourceClassName: string}} args - Arguments.
|
|
1779
|
-
* @returns {Promise<
|
|
2065
|
+
* @returns {Promise<ResourceMethodParameterType[] | null>} - JSDoc parameters (name + type) when documented.
|
|
1780
2066
|
*/
|
|
1781
2067
|
async resourceMethodParameters({methodName, sourceClassName}) {
|
|
1782
2068
|
const resourceMethodParameterTypes = await this.resourceMethodParameterTypes()
|
|
@@ -1795,7 +2081,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1795
2081
|
|
|
1796
2082
|
/**
|
|
1797
2083
|
* Runs resource method return types.
|
|
1798
|
-
* @returns {Promise<Map<string,
|
|
2084
|
+
* @returns {Promise<Map<string, ResourceMethodReturnType>>} - Resource method return types keyed by ClassName.methodName.
|
|
1799
2085
|
*/
|
|
1800
2086
|
async resourceMethodReturnTypes() {
|
|
1801
2087
|
if (this._resourceMethodReturnTypes) return this._resourceMethodReturnTypes
|
|
@@ -1806,7 +2092,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1806
2092
|
for (const sourceFile of sourceFiles) {
|
|
1807
2093
|
const sourceText = await fs.readFile(sourceFile, "utf8")
|
|
1808
2094
|
|
|
1809
|
-
this.addResourceMethodReturnTypesFromSource({returnTypes, sourceText})
|
|
2095
|
+
this.addResourceMethodReturnTypesFromSource({returnTypes, sourceFile, sourceText})
|
|
1810
2096
|
}
|
|
1811
2097
|
|
|
1812
2098
|
this._resourceMethodReturnTypes = returnTypes
|
|
@@ -1816,7 +2102,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1816
2102
|
|
|
1817
2103
|
/**
|
|
1818
2104
|
* Runs resource method parameter types.
|
|
1819
|
-
* @returns {Promise<Map<string,
|
|
2105
|
+
* @returns {Promise<Map<string, ResourceMethodParameterType[]>>} - Resource method parameters keyed by ClassName.methodName.
|
|
1820
2106
|
*/
|
|
1821
2107
|
async resourceMethodParameterTypes() {
|
|
1822
2108
|
if (this._resourceMethodParameterTypes) return this._resourceMethodParameterTypes
|
|
@@ -1827,7 +2113,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1827
2113
|
for (const sourceFile of sourceFiles) {
|
|
1828
2114
|
const sourceText = await fs.readFile(sourceFile, "utf8")
|
|
1829
2115
|
|
|
1830
|
-
this.addResourceMethodParameterTypesFromSource({parameterTypes, sourceText})
|
|
2116
|
+
this.addResourceMethodParameterTypesFromSource({parameterTypes, sourceFile, sourceText})
|
|
1831
2117
|
}
|
|
1832
2118
|
|
|
1833
2119
|
this._resourceMethodParameterTypes = parameterTypes
|
|
@@ -1868,12 +2154,14 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1868
2154
|
/**
|
|
1869
2155
|
* Adds resource method return types from source.
|
|
1870
2156
|
* @param {object} args - Arguments.
|
|
1871
|
-
* @param {Map<string,
|
|
2157
|
+
* @param {Map<string, ResourceMethodReturnType>} args.returnTypes - Mutable return types map.
|
|
2158
|
+
* @param {string | null} [args.sourceFile] - Source file path.
|
|
1872
2159
|
* @param {string} args.sourceText - Source text.
|
|
1873
2160
|
* @returns {void}
|
|
1874
2161
|
*/
|
|
1875
|
-
addResourceMethodReturnTypesFromSource({returnTypes, sourceText}) {
|
|
2162
|
+
addResourceMethodReturnTypesFromSource({returnTypes, sourceFile = null, sourceText}) {
|
|
1876
2163
|
const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
|
|
2164
|
+
const importAliases = this.jsDocImportAliasesFromSource(sourceText)
|
|
1877
2165
|
let classMatch
|
|
1878
2166
|
|
|
1879
2167
|
while ((classMatch = classRegex.exec(sourceText))) {
|
|
@@ -1905,7 +2193,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1905
2193
|
const returnType = this.jsDocReturnType(jsDocMatch[1])
|
|
1906
2194
|
|
|
1907
2195
|
if (returnType) {
|
|
1908
|
-
returnTypes.set(`${className}.${methodName}`, returnType)
|
|
2196
|
+
returnTypes.set(`${className}.${methodName}`, {importAliases, sourceFile, type: returnType})
|
|
1909
2197
|
}
|
|
1910
2198
|
}
|
|
1911
2199
|
|
|
@@ -1915,11 +2203,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1915
2203
|
|
|
1916
2204
|
/**
|
|
1917
2205
|
* Adds resource method parameter types from source.
|
|
1918
|
-
* @param {{parameterTypes: Map<string,
|
|
2206
|
+
* @param {{parameterTypes: Map<string, ResourceMethodParameterType[]>, sourceFile?: string | null, sourceText: string}} args - Arguments.
|
|
1919
2207
|
* @returns {void}
|
|
1920
2208
|
*/
|
|
1921
|
-
addResourceMethodParameterTypesFromSource({parameterTypes, sourceText}) {
|
|
2209
|
+
addResourceMethodParameterTypesFromSource({parameterTypes, sourceFile = null, sourceText}) {
|
|
1922
2210
|
const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
|
|
2211
|
+
const importAliases = this.jsDocImportAliasesFromSource(sourceText)
|
|
1923
2212
|
let classMatch
|
|
1924
2213
|
|
|
1925
2214
|
while ((classMatch = classRegex.exec(sourceText))) {
|
|
@@ -1950,7 +2239,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1950
2239
|
const jsDocParameters = this.jsDocParameters(jsDocMatch[1])
|
|
1951
2240
|
|
|
1952
2241
|
if (jsDocParameters.length > 0) {
|
|
1953
|
-
parameterTypes.set(`${className}.${methodName}`, jsDocParameters)
|
|
2242
|
+
parameterTypes.set(`${className}.${methodName}`, jsDocParameters.map((parameter) => ({...parameter, importAliases, sourceFile})))
|
|
1954
2243
|
}
|
|
1955
2244
|
}
|
|
1956
2245
|
|
|
@@ -1958,6 +2247,41 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1958
2247
|
}
|
|
1959
2248
|
}
|
|
1960
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
|
+
|
|
1961
2285
|
/**
|
|
1962
2286
|
* Runs js doc return type.
|
|
1963
2287
|
* @param {string} jsDocText - JSDoc text inside comment markers.
|