velocious 1.0.467 → 1.0.469
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 +5 -3
- package/build/configuration-types.js +9 -1
- package/build/configuration.js +6 -1
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +168 -36
- package/build/error-reporting/request-details.js +308 -0
- package/build/frontend-model-controller.js +50 -2
- package/build/src/configuration-types.d.ts +23 -1
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +9 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +7 -2
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +91 -9
- 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 +149 -36
- package/build/src/error-reporting/request-details.d.ts +7 -0
- package/build/src/error-reporting/request-details.d.ts.map +1 -0
- package/build/src/error-reporting/request-details.js +278 -0
- package/build/src/frontend-model-controller.d.ts +16 -0
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +39 -3
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/configuration-types.js +9 -1
- package/src/configuration.js +6 -1
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +168 -36
- package/src/error-reporting/request-details.js +308 -0
- package/src/frontend-model-controller.js +50 -2
package/README.md
CHANGED
|
@@ -591,16 +591,18 @@ const configuration = new Configuration({
|
|
|
591
591
|
|
|
592
592
|
This opt-in is ignored in `production`; production frontend-model responses never include internal exception details.
|
|
593
593
|
|
|
594
|
-
Backends can append client-safe metadata to frontend-model error responses with `configuration.addClientErrorPayloadReporter(...)`. Reporters receive the caught `error`, the current `request`, and a small `context` object, and should only return fields that are safe for clients to see. Frontend-model endpoint failures include `context.frontendModelEndpoint`, `action`, `commandType`, `model`, `requestId`, and `expectedError`. This is useful for attaching an error-reporting URL while keeping the normal production error message generic:
|
|
594
|
+
Backends can append client-safe metadata to frontend-model error responses with `configuration.addClientErrorPayloadReporter(...)`. Reporters receive the caught `error`, the current `request`, a safe `requestDetails` snapshot, and a small `context` object, and should only return fields that are safe for clients to see. Frontend-model endpoint failures include `context.frontendModelEndpoint`, `action`, `commandType`, `model`, `requestId`, and `expectedError`. This is useful for attaching an error-reporting URL while keeping the normal production error message generic:
|
|
595
595
|
|
|
596
596
|
```js
|
|
597
|
-
configuration.addClientErrorPayloadReporter(async ({error,
|
|
598
|
-
const report = await reportErrorToService({error,
|
|
597
|
+
configuration.addClientErrorPayloadReporter(async ({error, requestDetails, context}) => {
|
|
598
|
+
const report = await reportErrorToService({error, requestDetails, context})
|
|
599
599
|
|
|
600
600
|
return {bugReportUrl: report.url}
|
|
601
601
|
})
|
|
602
602
|
```
|
|
603
603
|
|
|
604
|
+
`requestDetails` includes `httpMethod`, `path`, and a parsed `body` snapshot when available. The body snapshot redacts common secret keys, truncates large strings and arrays, summarizes uploaded files and buffers without bytes, and compacts oversized frontend-model batches while preserving `requestId`, `model`, `commandType` / `customPath`, and payload shape.
|
|
605
|
+
|
|
604
606
|
For sqlite web databases, Velocious defaults to `https://sql.js.org/dist/<file>` for `sql.js` wasm loading. You can override wasm resolution per database config with `locateFile`:
|
|
605
607
|
|
|
606
608
|
```js
|
|
@@ -212,6 +212,13 @@
|
|
|
212
212
|
* @typedef {Record<string, import("./frontend-models/query.js").FrontendModelTransportValue>} ClientErrorPayloadReporterPayload
|
|
213
213
|
*/
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @typedef {object} ErrorRequestDetails
|
|
217
|
+
* @property {?} [body] - Sanitized parsed request body, when available.
|
|
218
|
+
* @property {string} httpMethod - Request HTTP method.
|
|
219
|
+
* @property {string} path - Request path.
|
|
220
|
+
*/
|
|
221
|
+
|
|
215
222
|
/**
|
|
216
223
|
* @typedef {object} ClientErrorPayloadContext
|
|
217
224
|
* @property {string} controller - Controller class name.
|
|
@@ -227,7 +234,8 @@
|
|
|
227
234
|
* @typedef {function({
|
|
228
235
|
* context: ClientErrorPayloadContext,
|
|
229
236
|
* error: Error,
|
|
230
|
-
* request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default | undefined
|
|
237
|
+
* request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default | undefined,
|
|
238
|
+
* requestDetails: ErrorRequestDetails | null
|
|
231
239
|
* }): Promise<ClientErrorPayloadReporterPayload | void> | ClientErrorPayloadReporterPayload | void} ClientErrorPayloadReporterType
|
|
232
240
|
*/
|
|
233
241
|
|
package/build/configuration.js
CHANGED
|
@@ -18,6 +18,7 @@ import Ability from "./authorization/ability.js"
|
|
|
18
18
|
import EventEmitter from "./utils/event-emitter.js"
|
|
19
19
|
import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channel-subscribers.js"
|
|
20
20
|
import {CurrentConfigurationNotSetError, currentConfiguration, setCurrentConfiguration} from "./current-configuration.js"
|
|
21
|
+
import {requestDetails} from "./error-reporting/request-details.js"
|
|
21
22
|
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
|
|
22
23
|
import PluginRoutes from "./routes/plugin-routes.js"
|
|
23
24
|
import restArgsError from "./utils/rest-args-error.js"
|
|
@@ -2411,9 +2412,13 @@ export default class VelociousConfiguration {
|
|
|
2411
2412
|
async clientErrorPayloadForError(args) {
|
|
2412
2413
|
/** @type {import("./configuration-types.js").ClientErrorPayloadReporterPayload} */
|
|
2413
2414
|
const payload = {}
|
|
2415
|
+
const details = requestDetails(args.request)
|
|
2414
2416
|
|
|
2415
2417
|
for (const reporter of this._clientErrorPayloadReporters) {
|
|
2416
|
-
const reporterPayload = await reporter(
|
|
2418
|
+
const reporterPayload = await reporter({
|
|
2419
|
+
...args,
|
|
2420
|
+
requestDetails: details
|
|
2421
|
+
})
|
|
2417
2422
|
|
|
2418
2423
|
if (reporterPayload && typeof reporterPayload === "object") {
|
|
2419
2424
|
Object.assign(payload, reporterPayload)
|
|
@@ -29,7 +29,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
29
29
|
/** @type {Map<string, string> | null} */
|
|
30
30
|
_resourceMethodReturnTypes = null
|
|
31
31
|
|
|
32
|
-
/** @type {Map<string, string
|
|
32
|
+
/** @type {Map<string, Array<{name: string | null, type: string}>> | null} */
|
|
33
33
|
_resourceMethodParameterTypes = null
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -288,7 +288,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
288
288
|
}
|
|
289
289
|
const collectionCommands = modelConfig.collectionCommands
|
|
290
290
|
const memberCommands = modelConfig.memberCommands
|
|
291
|
-
const
|
|
291
|
+
const declaredCommandMetadata = modelConfig.commandMetadata || {}
|
|
292
|
+
const commandMetadata = await this.commandMetadataWithResourceJsDoc({
|
|
293
|
+
commandMetadata: declaredCommandMetadata,
|
|
294
|
+
commandNames: [...Object.keys(collectionCommands), ...Object.keys(memberCommands)],
|
|
295
|
+
resourceClass
|
|
296
|
+
})
|
|
292
297
|
const builtInCollectionCommandsAreDefault = builtInCollectionCommands.create === "create" && builtInCollectionCommands.index === "index"
|
|
293
298
|
const builtInMemberCommandsAreDefault = builtInMemberCommands.attach === "attach"
|
|
294
299
|
&& builtInMemberCommands.destroy === "destroy"
|
|
@@ -472,12 +477,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
472
477
|
fileContent += ` * @returns {Promise<${signature.returnType}>} - Command response.\n`
|
|
473
478
|
fileContent += " */\n"
|
|
474
479
|
fileContent += ` static async ${methodName}(${signature.parameters}) {\n`
|
|
475
|
-
fileContent +=
|
|
480
|
+
fileContent += ` return /** @type {${signature.returnType}} */ (await this.executeCustomCommand({\n`
|
|
476
481
|
fileContent += ` commandName: ${JSON.stringify(collectionCommands[methodName])},\n`
|
|
477
482
|
fileContent += ` commandType: ${JSON.stringify(collectionCommands[methodName])},\n`
|
|
478
483
|
fileContent += ` payload: ${className}.normalizeCustomCommandPayloadArguments(${signature.payloadArguments}),\n`
|
|
479
484
|
fileContent += " resourcePath: this.resourcePath()\n"
|
|
480
|
-
fileContent += " })\n"
|
|
485
|
+
fileContent += " }))\n"
|
|
481
486
|
fileContent += " }\n"
|
|
482
487
|
}
|
|
483
488
|
|
|
@@ -491,13 +496,13 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
491
496
|
fileContent += ` * @returns {Promise<${signature.returnType}>} - Command response.\n`
|
|
492
497
|
fileContent += " */\n"
|
|
493
498
|
fileContent += ` async ${methodName}(${signature.parameters}) {\n`
|
|
494
|
-
fileContent += ` return await ${className}.executeCustomCommand({\n`
|
|
499
|
+
fileContent += ` return /** @type {${signature.returnType}} */ (await ${className}.executeCustomCommand({\n`
|
|
495
500
|
fileContent += ` commandName: ${JSON.stringify(memberCommands[methodName])},\n`
|
|
496
501
|
fileContent += ` commandType: ${JSON.stringify(memberCommands[methodName])},\n`
|
|
497
502
|
fileContent += " memberId: this.primaryKeyValue(),\n"
|
|
498
503
|
fileContent += ` payload: ${className}.normalizeCustomCommandPayloadArguments(${signature.payloadArguments}),\n`
|
|
499
504
|
fileContent += ` resourcePath: ${className}.resourcePath()\n`
|
|
500
|
-
fileContent += " })\n"
|
|
505
|
+
fileContent += " }))\n"
|
|
501
506
|
fileContent += " }\n"
|
|
502
507
|
}
|
|
503
508
|
|
|
@@ -1406,11 +1411,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1406
1411
|
* @returns {string} - A frontend-resolvable attribute type.
|
|
1407
1412
|
*/
|
|
1408
1413
|
frontendResolvableAttributeJsDocType(jsDocType) {
|
|
1409
|
-
const safeTypeIdentifiers =
|
|
1410
|
-
"Array", "Date", "Exclude", "Extract", "FrontendModelAttributeValue", "FrontendModelTransportValue",
|
|
1411
|
-
"Map", "NonNullable", "Omit", "Partial", "Pick", "Promise", "Readonly", "ReadonlyArray", "Record",
|
|
1412
|
-
"Required", "ReturnType", "Set"
|
|
1413
|
-
])
|
|
1414
|
+
const safeTypeIdentifiers = this.frontendResolvableTypeIdentifiers()
|
|
1414
1415
|
const referencedIdentifiers = jsDocType.match(/[A-Z][A-Za-z0-9_$]*/g) || []
|
|
1415
1416
|
|
|
1416
1417
|
if (referencedIdentifiers.some((identifier) => !safeTypeIdentifiers.has(identifier))) {
|
|
@@ -1420,6 +1421,43 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1420
1421
|
return jsDocType
|
|
1421
1422
|
}
|
|
1422
1423
|
|
|
1424
|
+
/**
|
|
1425
|
+
* Capitalized identifiers a generated frontend model can resolve on its own
|
|
1426
|
+
* (primitives are lower-case and matched separately), so only framework-owned
|
|
1427
|
+
* and builtin generic types are listed.
|
|
1428
|
+
* @returns {Set<string>} - Frontend-resolvable type identifiers.
|
|
1429
|
+
*/
|
|
1430
|
+
frontendResolvableTypeIdentifiers() {
|
|
1431
|
+
return new Set([
|
|
1432
|
+
"Array", "Date", "Exclude", "Extract", "FrontendModelAttributeValue", "FrontendModelTransportValue",
|
|
1433
|
+
"Map", "NonNullable", "Omit", "Partial", "Pick", "Promise", "Readonly", "ReadonlyArray", "Record",
|
|
1434
|
+
"Required", "ReturnType", "Set"
|
|
1435
|
+
])
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Rewrites a custom-command param/return JSDoc type so it resolves in the generated
|
|
1440
|
+
* frontend model: each model-class (or otherwise non-frontend-resolvable) identifier
|
|
1441
|
+
* becomes `any` in place, keeping the surrounding object fields typed. A command-result
|
|
1442
|
+
* field holding a model arrives as a serialized transport value, so the consumer hydrates
|
|
1443
|
+
* it with `Model.instantiateFromResponse(...)`. The word boundary avoids matching the
|
|
1444
|
+
* capitalized middle of a camelCase property name (e.g. `adjustedTotalCents`).
|
|
1445
|
+
* @param {string} jsDocType - Resolved (Promise-unwrapped) JSDoc type.
|
|
1446
|
+
* @returns {string} - A frontend-resolvable JSDoc type.
|
|
1447
|
+
*/
|
|
1448
|
+
frontendResolvableCommandJsDocType(jsDocType) {
|
|
1449
|
+
const safeTypeIdentifiers = this.frontendResolvableTypeIdentifiers()
|
|
1450
|
+
|
|
1451
|
+
return jsDocType
|
|
1452
|
+
// A type that reaches into a backend source file via `import("...")` (optionally
|
|
1453
|
+
// `.Member` and `[]`) can't resolve from the generated frontend model and would type
|
|
1454
|
+
// a serialized result as a backend model instance, so collapse it to `any`.
|
|
1455
|
+
.replace(/import\(\s*["'][^"']*["']\s*\)(\s*\.\s*[A-Za-z_$][\w$]*)*(\s*\[\s*\])*/g, "any")
|
|
1456
|
+
// Remaining capitalized identifiers are model classes or otherwise non-resolvable
|
|
1457
|
+
// types; downgrade each in place so sibling scalar fields keep their real types.
|
|
1458
|
+
.replace(/\b[A-Z][A-Za-z0-9_$]*/g, (identifier) => safeTypeIdentifiers.has(identifier) ? identifier : "any")
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1423
1461
|
/**
|
|
1424
1462
|
* Builds the JSDoc param block, parameter list, payload-argument expression, and
|
|
1425
1463
|
* return type for a custom command method. With declared `args` each becomes a
|
|
@@ -1453,6 +1491,69 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1453
1491
|
}
|
|
1454
1492
|
}
|
|
1455
1493
|
|
|
1494
|
+
/**
|
|
1495
|
+
* Enriches custom-command metadata by deriving a command's typed args and return
|
|
1496
|
+
* type from the backend resource method's `@param`/`@returns` JSDoc when they are
|
|
1497
|
+
* not already declared in `resourceConfig`. Precedence: explicit `resourceConfig`
|
|
1498
|
+
* `{args, returnType}` wins, then the derived backend-method JSDoc, then the generic
|
|
1499
|
+
* default. Model-class identifiers in the derived types are downgraded to `any`
|
|
1500
|
+
* because the frontend receives a serialized record, not a model instance, which the
|
|
1501
|
+
* consumer hydrates with `Model.instantiateFromResponse(...)`.
|
|
1502
|
+
* @param {object} args - Arguments.
|
|
1503
|
+
* @param {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} args.commandMetadata - Declared per-command metadata.
|
|
1504
|
+
* @param {string[]} args.commandNames - Command method names to resolve.
|
|
1505
|
+
* @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
|
|
1506
|
+
* @returns {Promise<Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>>} - Enriched metadata.
|
|
1507
|
+
*/
|
|
1508
|
+
async commandMetadataWithResourceJsDoc({commandMetadata, commandNames, resourceClass}) {
|
|
1509
|
+
if (!resourceClass) return commandMetadata
|
|
1510
|
+
|
|
1511
|
+
/** @type {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} */
|
|
1512
|
+
const enriched = {...commandMetadata}
|
|
1513
|
+
|
|
1514
|
+
for (const commandName of commandNames) {
|
|
1515
|
+
const declared = commandMetadata[commandName] || {args: [], returnType: null}
|
|
1516
|
+
const sourceClassName = this.methodOwnerClassName({methodName: commandName, targetClass: resourceClass})
|
|
1517
|
+
|
|
1518
|
+
if (!sourceClassName) {
|
|
1519
|
+
enriched[commandName] = declared
|
|
1520
|
+
|
|
1521
|
+
continue
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
let returnType = declared.returnType
|
|
1525
|
+
|
|
1526
|
+
if (!returnType) {
|
|
1527
|
+
const jsDocReturnType = await this.resourceMethodReturnType({methodName: commandName, sourceClassName})
|
|
1528
|
+
|
|
1529
|
+
if (jsDocReturnType) {
|
|
1530
|
+
returnType = this.frontendResolvableCommandJsDocType(this.unwrappedPromiseJsDocType({jsDocType: jsDocReturnType}))
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
let args = declared.args
|
|
1535
|
+
|
|
1536
|
+
if (!args || args.length === 0) {
|
|
1537
|
+
const jsDocParameters = await this.resourceMethodParameters({methodName: commandName, sourceClassName})
|
|
1538
|
+
// Skip object-property tags (`@param {string} args.message`); only the
|
|
1539
|
+
// top-level parameters map to method arguments, otherwise the shared
|
|
1540
|
+
// `@param {object} args` + property style would emit `name(args, args)`.
|
|
1541
|
+
const topLevelParameters = (jsDocParameters || []).filter((parameter) => typeof parameter.name === "string" && !parameter.name.includes("."))
|
|
1542
|
+
|
|
1543
|
+
if (topLevelParameters.length > 0) {
|
|
1544
|
+
args = topLevelParameters.map((parameter) => ({
|
|
1545
|
+
name: /** @type {string} */ (parameter.name),
|
|
1546
|
+
type: this.frontendResolvableCommandJsDocType(parameter.type)
|
|
1547
|
+
}))
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
enriched[commandName] = {args: args || [], returnType: returnType || null}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
return enriched
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1456
1557
|
/**
|
|
1457
1558
|
* Runs unwrapped promise js doc type.
|
|
1458
1559
|
* @param {object} args - Arguments.
|
|
@@ -1534,26 +1635,39 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1534
1635
|
* @returns {Promise<string | null>} - JSDoc parameter type when documented.
|
|
1535
1636
|
*/
|
|
1536
1637
|
async resourceMethodParameterType({methodName, parameterIndex, sourceClassName}) {
|
|
1537
|
-
const
|
|
1538
|
-
const parameterTypesKey = `${sourceClassName}.${methodName}`
|
|
1638
|
+
const parameters = await this.resourceMethodParameters({methodName, sourceClassName})
|
|
1539
1639
|
|
|
1540
|
-
if (!
|
|
1640
|
+
if (!parameters) return null
|
|
1641
|
+
|
|
1642
|
+
const parameter = parameters[parameterIndex]
|
|
1541
1643
|
|
|
1542
|
-
|
|
1644
|
+
if (parameter === undefined) return null
|
|
1543
1645
|
|
|
1544
|
-
if (
|
|
1545
|
-
throw new Error(`Expected JSDoc parameter
|
|
1646
|
+
if (parameter.type.length < 1) {
|
|
1647
|
+
throw new Error(`Expected non-empty JSDoc parameter type for ${sourceClassName}.${methodName} parameter ${parameterIndex}`)
|
|
1546
1648
|
}
|
|
1547
1649
|
|
|
1548
|
-
|
|
1650
|
+
return parameter.type
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* Runs resource method parameters.
|
|
1655
|
+
* @param {{methodName: string, sourceClassName: string}} args - Arguments.
|
|
1656
|
+
* @returns {Promise<Array<{name: string | null, type: string}> | null>} - JSDoc parameters (name + type) when documented.
|
|
1657
|
+
*/
|
|
1658
|
+
async resourceMethodParameters({methodName, sourceClassName}) {
|
|
1659
|
+
const resourceMethodParameterTypes = await this.resourceMethodParameterTypes()
|
|
1660
|
+
const parameterTypesKey = `${sourceClassName}.${methodName}`
|
|
1661
|
+
|
|
1662
|
+
if (!resourceMethodParameterTypes.has(parameterTypesKey)) return null
|
|
1549
1663
|
|
|
1550
|
-
|
|
1664
|
+
const parameters = resourceMethodParameterTypes.get(parameterTypesKey)
|
|
1551
1665
|
|
|
1552
|
-
if (
|
|
1553
|
-
throw new Error(`Expected
|
|
1666
|
+
if (!parameters) {
|
|
1667
|
+
throw new Error(`Expected JSDoc parameters for ${parameterTypesKey}`)
|
|
1554
1668
|
}
|
|
1555
1669
|
|
|
1556
|
-
return
|
|
1670
|
+
return parameters
|
|
1557
1671
|
}
|
|
1558
1672
|
|
|
1559
1673
|
/**
|
|
@@ -1579,7 +1693,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1579
1693
|
|
|
1580
1694
|
/**
|
|
1581
1695
|
* Runs resource method parameter types.
|
|
1582
|
-
* @returns {Promise<Map<string, string
|
|
1696
|
+
* @returns {Promise<Map<string, Array<{name: string | null, type: string}>>>} - Resource method parameters keyed by ClassName.methodName.
|
|
1583
1697
|
*/
|
|
1584
1698
|
async resourceMethodParameterTypes() {
|
|
1585
1699
|
if (this._resourceMethodParameterTypes) return this._resourceMethodParameterTypes
|
|
@@ -1678,7 +1792,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1678
1792
|
|
|
1679
1793
|
/**
|
|
1680
1794
|
* Adds resource method parameter types from source.
|
|
1681
|
-
* @param {{parameterTypes: Map<string, string
|
|
1795
|
+
* @param {{parameterTypes: Map<string, Array<{name: string | null, type: string}>>, sourceText: string}} args - Arguments.
|
|
1682
1796
|
* @returns {void}
|
|
1683
1797
|
*/
|
|
1684
1798
|
addResourceMethodParameterTypesFromSource({parameterTypes, sourceText}) {
|
|
@@ -1710,10 +1824,10 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1710
1824
|
if (!methodMatch) continue
|
|
1711
1825
|
|
|
1712
1826
|
const methodName = methodMatch[1]
|
|
1713
|
-
const
|
|
1827
|
+
const jsDocParameters = this.jsDocParameters(jsDocMatch[1])
|
|
1714
1828
|
|
|
1715
|
-
if (
|
|
1716
|
-
parameterTypes.set(`${className}.${methodName}`,
|
|
1829
|
+
if (jsDocParameters.length > 0) {
|
|
1830
|
+
parameterTypes.set(`${className}.${methodName}`, jsDocParameters)
|
|
1717
1831
|
}
|
|
1718
1832
|
}
|
|
1719
1833
|
|
|
@@ -1738,7 +1852,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1738
1852
|
throw new Error(`Could not parse JSDoc return type from: ${jsDocText}`)
|
|
1739
1853
|
}
|
|
1740
1854
|
|
|
1741
|
-
const returnType = jsDocText.slice(typeOpenIndex + 1, typeCloseIndex)
|
|
1855
|
+
const returnType = this.normalizeJsDocType(jsDocText.slice(typeOpenIndex + 1, typeCloseIndex))
|
|
1742
1856
|
|
|
1743
1857
|
if (returnType.length < 1) {
|
|
1744
1858
|
throw new Error(`Expected non-empty JSDoc return type in: ${jsDocText}`)
|
|
@@ -1748,12 +1862,24 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1748
1862
|
}
|
|
1749
1863
|
|
|
1750
1864
|
/**
|
|
1751
|
-
*
|
|
1865
|
+
* Collapses a JSDoc type spanning multiple comment lines into a single line so it can
|
|
1866
|
+
* be emitted into an inline type-assertion cast. A multiline backend return type keeps
|
|
1867
|
+
* its leading continuation asterisks in the captured substring, which are invalid inside
|
|
1868
|
+
* an inline cast and make TypeScript read the asserted type as `undefined`.
|
|
1869
|
+
* @param {string} jsDocType - Raw captured JSDoc type, possibly multiline.
|
|
1870
|
+
* @returns {string} - Single-line JSDoc type.
|
|
1871
|
+
*/
|
|
1872
|
+
normalizeJsDocType(jsDocType) {
|
|
1873
|
+
return jsDocType.replace(/\s*\n\s*\*?[ \t]*/g, " ").trim()
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* Runs js doc parameters.
|
|
1752
1878
|
* @param {string} jsDocText - JSDoc text inside comment markers.
|
|
1753
|
-
* @returns {string
|
|
1879
|
+
* @returns {Array<{name: string | null, type: string}>} - JSDoc parameters (name + type) in declaration order.
|
|
1754
1880
|
*/
|
|
1755
|
-
|
|
1756
|
-
const
|
|
1881
|
+
jsDocParameters(jsDocText) {
|
|
1882
|
+
const parameters = []
|
|
1757
1883
|
const paramRegex = /@param\s*\{/g
|
|
1758
1884
|
let _paramMatch
|
|
1759
1885
|
|
|
@@ -1765,17 +1891,23 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
1765
1891
|
throw new Error(`Could not parse JSDoc parameter type from: ${jsDocText}`)
|
|
1766
1892
|
}
|
|
1767
1893
|
|
|
1768
|
-
const
|
|
1894
|
+
const type = this.normalizeJsDocType(jsDocText.slice(typeOpenIndex + 1, typeCloseIndex))
|
|
1769
1895
|
|
|
1770
|
-
if (
|
|
1896
|
+
if (type.length < 1) {
|
|
1771
1897
|
throw new Error(`Expected non-empty JSDoc parameter type in: ${jsDocText}`)
|
|
1772
1898
|
}
|
|
1773
1899
|
|
|
1774
|
-
|
|
1900
|
+
// After the closing brace the parameter name follows (optionally bracketed
|
|
1901
|
+
// for `@param {type} [name]`). Capture the leading name token — including any
|
|
1902
|
+
// dotted path so object-property tags like `@param {string} args.message` stay
|
|
1903
|
+
// distinguishable from the top-level `@param {object} args` parameter.
|
|
1904
|
+
const nameMatch = jsDocText.slice(typeCloseIndex + 1).match(/^\s*\[?\s*([A-Za-z_$][\w$.]*)/)
|
|
1905
|
+
|
|
1906
|
+
parameters.push({name: nameMatch ? nameMatch[1] : null, type})
|
|
1775
1907
|
paramRegex.lastIndex = typeCloseIndex + 1
|
|
1776
1908
|
}
|
|
1777
1909
|
|
|
1778
|
-
return
|
|
1910
|
+
return parameters
|
|
1779
1911
|
}
|
|
1780
1912
|
|
|
1781
1913
|
/**
|