@sdk-it/generic 0.46.3 → 0.46.4
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 +40 -0
- package/dist/index.js +190 -5
- package/dist/index.js.map +2 -2
- package/dist/lib/generic.d.ts +17 -3
- package/dist/lib/generic.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -204,6 +204,46 @@ const { paths, components } = await analyze('./tsconfig.json', {
|
|
|
204
204
|
The injected file must be loadable by the Node.js process running the
|
|
205
205
|
analyzer.
|
|
206
206
|
|
|
207
|
+
## Derive operation security from middleware
|
|
208
|
+
|
|
209
|
+
Declare each OpenAPI security scheme once, then associate authentication and
|
|
210
|
+
authorization middleware by its exported symbol and source file:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { fileURLToPath } from 'node:url';
|
|
214
|
+
|
|
215
|
+
import { analyze } from '@sdk-it/generic';
|
|
216
|
+
import { responseAnalyzer } from '@sdk-it/hono';
|
|
217
|
+
|
|
218
|
+
const authModule = fileURLToPath(new URL('./src/auth.ts', import.meta.url));
|
|
219
|
+
|
|
220
|
+
const { paths, components } = await analyze('./tsconfig.json', {
|
|
221
|
+
responseAnalyzer,
|
|
222
|
+
securitySchemes: {
|
|
223
|
+
bearer: { type: 'http', scheme: 'bearer' },
|
|
224
|
+
},
|
|
225
|
+
middlewareSecurity: [
|
|
226
|
+
{
|
|
227
|
+
middleware: { import: 'authenticate', from: authModule },
|
|
228
|
+
security: [{ bearer: [] }],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
middleware: { import: 'authorise', from: authModule },
|
|
232
|
+
security: [{ bearer: [] }],
|
|
233
|
+
values: { scheme: 'bearer', argument: 0, mode: 'any' },
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The analyzer resolves imported symbols, so local import aliases do not change
|
|
240
|
+
the mapping. `mode: 'any'` emits one OpenAPI alternative per statically known
|
|
241
|
+
argument value; `mode: 'all'` keeps every value in one requirement. Multiple
|
|
242
|
+
matched middleware are combined as an OpenAPI AND requirement. A route with no
|
|
243
|
+
matched security middleware receives `security: []`. Dynamic values fail
|
|
244
|
+
analysis because they cannot be represented accurately in the generated
|
|
245
|
+
document.
|
|
246
|
+
|
|
207
247
|
## Hide an operation
|
|
208
248
|
|
|
209
249
|
Add `@access private` to exclude a route from the generated OpenAPI document:
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// packages/generic/src/lib/generic.ts
|
|
2
2
|
import debug from "debug";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
3
5
|
import { camelcase } from "stringcase";
|
|
4
6
|
import ts from "typescript";
|
|
5
7
|
import {
|
|
@@ -32,6 +34,172 @@ function isLocalFunction(symbol) {
|
|
|
32
34
|
}
|
|
33
35
|
return !isExternalFunction(symbol);
|
|
34
36
|
}
|
|
37
|
+
function resolvedSymbol(expression, typeChecker) {
|
|
38
|
+
const location = ts.isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
39
|
+
const symbol = typeChecker.getSymbolAtLocation(location);
|
|
40
|
+
return symbol && symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;
|
|
41
|
+
}
|
|
42
|
+
function realPath(path) {
|
|
43
|
+
const resolved = resolve(path);
|
|
44
|
+
try {
|
|
45
|
+
return realpathSync.native(resolved);
|
|
46
|
+
} catch {
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function matchesMiddleware(symbol, middleware) {
|
|
51
|
+
if (symbol?.name !== middleware.import) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const from = realPath(middleware.from);
|
|
55
|
+
return (symbol.declarations ?? []).some(
|
|
56
|
+
(declaration) => realPath(declaration.getSourceFile().fileName) === from
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function middlewareSecurity(args, security, typeChecker) {
|
|
60
|
+
let requirements;
|
|
61
|
+
for (const arg of args) {
|
|
62
|
+
const call = ts.isCallExpression(arg) ? arg : void 0;
|
|
63
|
+
const reference = call ? call.expression : arg;
|
|
64
|
+
if (!ts.isIdentifier(reference) && !ts.isPropertyAccessExpression(reference)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const symbol = resolvedSymbol(reference, typeChecker);
|
|
68
|
+
const rule = security.rules.find(
|
|
69
|
+
({ middleware }) => matchesMiddleware(symbol, middleware)
|
|
70
|
+
);
|
|
71
|
+
if (!rule) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
security.matched.add(rule);
|
|
75
|
+
if (rule.values && !call) {
|
|
76
|
+
throw new TypeError(
|
|
77
|
+
`Security middleware ${rule.middleware.import} must be called so argument ${rule.values.argument} can be read
|
|
78
|
+
at ${nodeLocation(arg) ?? "unknown"}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const requirement = rule.values && call ? securityWithValues(call, rule, rule.values, typeChecker) : structuredClone(rule.security);
|
|
82
|
+
requirements = requirements ? andSecurity(requirements, requirement) : requirement;
|
|
83
|
+
}
|
|
84
|
+
return requirements ?? [];
|
|
85
|
+
}
|
|
86
|
+
function securityWithValues(call, rule, valuesConfig, typeChecker) {
|
|
87
|
+
const expression = call.arguments[valuesConfig.argument];
|
|
88
|
+
if (!expression) {
|
|
89
|
+
throw new TypeError(
|
|
90
|
+
`Security middleware ${rule.middleware.import} requires argument ${valuesConfig.argument}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const resolved = constantStrings(expression, typeChecker);
|
|
94
|
+
if (!resolved) {
|
|
95
|
+
throw new TypeError(
|
|
96
|
+
`Could not statically resolve security values at ${nodeLocation(expression) ?? "unknown"}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (resolved.length === 0) {
|
|
100
|
+
return structuredClone(rule.security);
|
|
101
|
+
}
|
|
102
|
+
const values = [...new Set(resolved)];
|
|
103
|
+
return rule.security.flatMap((requirement) => {
|
|
104
|
+
const existing = requirement[valuesConfig.scheme];
|
|
105
|
+
if (!existing) {
|
|
106
|
+
throw new TypeError(
|
|
107
|
+
`Security scheme ${valuesConfig.scheme} is not present in the middleware requirement`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (valuesConfig.mode === "all") {
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
...structuredClone(requirement),
|
|
114
|
+
[valuesConfig.scheme]: [.../* @__PURE__ */ new Set([...existing, ...values])]
|
|
115
|
+
}
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
return values.map((value) => ({
|
|
119
|
+
...structuredClone(requirement),
|
|
120
|
+
[valuesConfig.scheme]: [.../* @__PURE__ */ new Set([...existing, value])]
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function constantStrings(expression, typeChecker, visited = /* @__PURE__ */ new Set()) {
|
|
125
|
+
while (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isNonNullExpression(expression)) {
|
|
126
|
+
expression = expression.expression;
|
|
127
|
+
}
|
|
128
|
+
if (ts.isStringLiteralLike(expression)) {
|
|
129
|
+
return [expression.text];
|
|
130
|
+
}
|
|
131
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
132
|
+
const result = [];
|
|
133
|
+
for (const element of expression.elements) {
|
|
134
|
+
if (ts.isOmittedExpression(element)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const values = constantStrings(
|
|
138
|
+
ts.isSpreadElement(element) ? element.expression : element,
|
|
139
|
+
typeChecker,
|
|
140
|
+
visited
|
|
141
|
+
);
|
|
142
|
+
if (!values) {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
result.push(...values);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
150
|
+
const value = typeChecker.getConstantValue(expression);
|
|
151
|
+
if (typeof value === "string") {
|
|
152
|
+
return [value];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression)) {
|
|
156
|
+
const symbol = resolvedSymbol(expression, typeChecker);
|
|
157
|
+
for (const declaration of symbol?.declarations ?? []) {
|
|
158
|
+
if (visited.has(declaration)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
visited.add(declaration);
|
|
162
|
+
try {
|
|
163
|
+
if ((ts.isVariableDeclaration(declaration) || ts.isEnumMember(declaration)) && declaration.initializer) {
|
|
164
|
+
const values = constantStrings(
|
|
165
|
+
declaration.initializer,
|
|
166
|
+
typeChecker,
|
|
167
|
+
visited
|
|
168
|
+
);
|
|
169
|
+
if (values) {
|
|
170
|
+
return values;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} finally {
|
|
174
|
+
visited.delete(declaration);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
function andSecurity(left, right) {
|
|
181
|
+
const combined = left.flatMap(
|
|
182
|
+
(leftRequirement) => right.map((rightRequirement) => {
|
|
183
|
+
const requirement = structuredClone(leftRequirement);
|
|
184
|
+
for (const [scheme, values] of Object.entries(rightRequirement)) {
|
|
185
|
+
requirement[scheme] = [
|
|
186
|
+
.../* @__PURE__ */ new Set([...requirement[scheme] ?? [], ...values])
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
return requirement;
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
return [
|
|
193
|
+
...new Map(
|
|
194
|
+
combined.map((requirement) => [requirementKey(requirement), requirement])
|
|
195
|
+
).values()
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function requirementKey(requirement) {
|
|
199
|
+
return JSON.stringify(
|
|
200
|
+
Object.keys(requirement).sort().map((scheme) => [scheme, [...requirement[scheme]].sort()])
|
|
201
|
+
);
|
|
202
|
+
}
|
|
35
203
|
var returnTokens = (node, typeChecker, options) => {
|
|
36
204
|
const tokens = [];
|
|
37
205
|
const consider3rdParty = options?.consider3rdParty ?? false;
|
|
@@ -156,7 +324,7 @@ function parseJSDocComment(node) {
|
|
|
156
324
|
summary
|
|
157
325
|
};
|
|
158
326
|
}
|
|
159
|
-
function visit(node, responseAnalyzer2, paths, typeChecker, typeDeriver) {
|
|
327
|
+
function visit(node, responseAnalyzer2, paths, typeChecker, typeDeriver, security) {
|
|
160
328
|
if (!ts.isCallExpression(node) || node.arguments.length < 2) {
|
|
161
329
|
return moveOn();
|
|
162
330
|
}
|
|
@@ -272,12 +440,15 @@ function visit(node, responseAnalyzer2, paths, typeChecker, typeDeriver) {
|
|
|
272
440
|
toSelectors(props),
|
|
273
441
|
responses,
|
|
274
442
|
sourceFile.fileName,
|
|
275
|
-
|
|
443
|
+
{
|
|
444
|
+
...metadata,
|
|
445
|
+
security: security ? middlewareSecurity(node.arguments.slice(1, -1), security, typeChecker) : void 0
|
|
446
|
+
}
|
|
276
447
|
);
|
|
277
448
|
function moveOn() {
|
|
278
449
|
ts.forEachChild(
|
|
279
450
|
node,
|
|
280
|
-
(node2) => visit(node2, responseAnalyzer2, paths, typeChecker, typeDeriver)
|
|
451
|
+
(node2) => visit(node2, responseAnalyzer2, paths, typeChecker, typeDeriver, security)
|
|
281
452
|
);
|
|
282
453
|
}
|
|
283
454
|
}
|
|
@@ -324,6 +495,10 @@ async function analyze(tsconfigPath, config) {
|
|
|
324
495
|
imports: config.imports ?? [],
|
|
325
496
|
onOperation: config.onOperation
|
|
326
497
|
});
|
|
498
|
+
const security = config.middlewareSecurity && {
|
|
499
|
+
rules: config.middlewareSecurity,
|
|
500
|
+
matched: /* @__PURE__ */ new Set()
|
|
501
|
+
};
|
|
327
502
|
for (const sourceFile of program.getSourceFiles()) {
|
|
328
503
|
logger(`Analyzing ${sourceFile.fileName}`);
|
|
329
504
|
if (!sourceFile.isDeclarationFile) {
|
|
@@ -343,7 +518,16 @@ async function analyze(tsconfigPath, config) {
|
|
|
343
518
|
},
|
|
344
519
|
paths,
|
|
345
520
|
typeChecker,
|
|
346
|
-
typeDeriver
|
|
521
|
+
typeDeriver,
|
|
522
|
+
security
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
for (const rule of security?.rules ?? []) {
|
|
527
|
+
if (!security?.matched.has(rule)) {
|
|
528
|
+
console.warn(
|
|
529
|
+
`\u26A0 Security middleware ${rule.middleware.import} from ${rule.middleware.from} matched no route
|
|
530
|
+
routes it protects are documented as public`
|
|
347
531
|
);
|
|
348
532
|
}
|
|
349
533
|
}
|
|
@@ -354,7 +538,8 @@ async function analyze(tsconfigPath, config) {
|
|
|
354
538
|
(acc, [key, value]) => ({ ...acc, [key]: toSchema(value) }),
|
|
355
539
|
{}
|
|
356
540
|
)
|
|
357
|
-
}
|
|
541
|
+
},
|
|
542
|
+
securitySchemes: config.securitySchemes
|
|
358
543
|
};
|
|
359
544
|
return {
|
|
360
545
|
paths: await paths.getPaths(),
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/lib/generic.ts", "../src/lib/response-analyzer.ts"],
|
|
4
|
-
"sourcesContent": ["import debug from 'debug';\nimport type { ComponentsObject } from 'openapi3-ts/oas31';\nimport { camelcase } from 'stringcase';\nimport ts from 'typescript';\n\nimport {\n type InjectImport,\n type NaunceResponseAnalyzer,\n type OnOperation,\n Paths,\n type ResponseAnalyzerFn,\n type ResponseItem,\n type Selector,\n type SemanticSource,\n TypeDeriver,\n getProgram,\n isCallExpression,\n isHttpMethod,\n nodeLocation,\n toSchema,\n} from '@sdk-it/core';\n\n/**\n * Gets the file path from a symbol's first declaration\n */\nfunction symbolFile(symbol: ts.Symbol | undefined): string | undefined {\n if (!symbol) {\n return undefined;\n }\n\n const declarations = symbol.declarations ?? [];\n if (declarations.length === 0) {\n return undefined;\n }\n\n const sourceFile = declarations[0].getSourceFile();\n return sourceFile?.fileName;\n}\n\n/**\n * Determines if a symbol is from an external library (node_modules)\n */\nfunction isExternalFunction(symbol: ts.Symbol | undefined): boolean {\n const fileName = symbolFile(symbol);\n return fileName ? fileName.includes('node_modules') : false;\n}\n\n/**\n * Determines if a symbol refers to a local function (not from node_modules)\n */\nfunction isLocalFunction(symbol: ts.Symbol | undefined): boolean {\n if (!symbol) {\n return false;\n }\n\n return !isExternalFunction(symbol);\n}\n\nexport const returnTokens = (\n node: ts.Node,\n typeChecker?: ts.TypeChecker,\n options?: { consider3rdParty?: boolean; maxDepth?: number },\n) => {\n const tokens: { token: string; node: ts.Expression }[] = [];\n const consider3rdParty = options?.consider3rdParty ?? false;\n const maxDepth = options?.maxDepth ?? 5;\n // Track visited function declarations to prevent infinite recursion\n const visitedFunctions = new Set<ts.Declaration>();\n\n const visitor = (node: ts.Node, depth: number): void => {\n // Skip if we've exceeded max depth\n if (depth > maxDepth) {\n return;\n }\n\n if (ts.isThrowStatement(node)) {\n if (ts.isNewExpression(node.expression)) {\n tokens.push({\n token: `throw.new.${node.expression.expression.getText()}`,\n node: node.expression,\n });\n }\n }\n\n if (ts.isReturnStatement(node) && node.expression) {\n if (ts.isCallExpression(node.expression)) {\n tokens.push({\n token: node.expression.expression.getText(),\n node: node.expression,\n });\n }\n if (ts.isNewExpression(node.expression)) {\n tokens.push({\n token: `new.${node.expression.expression.getText()}`,\n node: node.expression,\n });\n }\n // Continue traversing into the returned expression (e.g., arrow functions)\n // This handles: return async (c, next) => { throw ... }\n ts.forEachChild(node.expression, (child) => visitor(child, depth));\n return;\n }\n\n // If we encounter a call expression and have a type checker, follow it\n if (ts.isCallExpression(node) && typeChecker && depth < maxDepth) {\n const callExpression = node;\n\n // Try to resolve the function being called\n let symbol: ts.Symbol | undefined;\n\n if (ts.isIdentifier(callExpression.expression)) {\n symbol = typeChecker.getSymbolAtLocation(callExpression.expression);\n } else if (ts.isPropertyAccessExpression(callExpression.expression)) {\n symbol = typeChecker.getSymbolAtLocation(\n callExpression.expression.name,\n );\n }\n\n // Resolve aliases\n if (symbol && symbol.flags & ts.SymbolFlags.Alias) {\n symbol = typeChecker.getAliasedSymbol(symbol);\n }\n\n // Check if we should follow this function\n const shouldFollow = consider3rdParty || isLocalFunction(symbol);\n\n if (shouldFollow && symbol) {\n const declarations = symbol?.declarations ?? [];\n\n for (const declaration of declarations) {\n // Skip if we've already visited this function (prevent infinite recursion)\n if (visitedFunctions.has(declaration)) {\n continue;\n }\n\n if (isFunctionWithBody(declaration) && declaration.body) {\n visitedFunctions.add(declaration);\n // Recursively visit the function body with incremented depth\n visitor(declaration.body, depth + 1);\n }\n }\n }\n }\n\n ts.forEachChild(node, (child) => visitor(child, depth));\n };\n\n visitor(node, 0);\n return tokens;\n};\n\nconst logger = debug('@sdk-it/generic');\n\nconst jsDocsTags = [\n 'openapi',\n 'tags',\n 'description',\n 'summary',\n 'access',\n 'tool',\n 'toolDescription',\n] as const;\ntype JSDocsTags = (typeof jsDocsTags)[number];\n\nfunction parseJSDocComment(node: ts.Node) {\n let tags: string[] = [];\n let name = '';\n let description = '';\n let summary = '';\n let access = '';\n let tool = '';\n let toolDescription = '';\n\n for (const tag of ts.getAllJSDocTags(node, (tag): tag is ts.JSDocTag =>\n jsDocsTags.includes(tag.tagName.text as JSDocsTags),\n )) {\n if (typeof tag.comment !== 'string') {\n continue;\n }\n switch (tag.tagName.text as JSDocsTags) {\n case 'openapi':\n name = tag.comment;\n break;\n case 'tags':\n tags = tag.comment.split(',').map((tag) => tag.trim());\n break;\n case 'description':\n description = tag.comment;\n break;\n case 'summary':\n summary = tag.comment;\n break;\n case 'access':\n access = tag.comment.trim().toLowerCase();\n break;\n case 'tool':\n tool = tag.comment.trim();\n break;\n case 'toolDescription':\n toolDescription = tag.comment.trim();\n break;\n }\n }\n return {\n name,\n tags,\n description,\n access,\n tool,\n toolDescription,\n summary,\n };\n}\n\nfunction visit(\n node: ts.Node,\n responseAnalyzer: (\n handler: ts.ArrowFunction | ts.FunctionExpression,\n token: string,\n node: ts.Node,\n ) => ResponseItem[],\n paths: Paths,\n typeChecker: ts.TypeChecker,\n typeDeriver: TypeDeriver,\n) {\n if (!ts.isCallExpression(node) || node.arguments.length < 2) {\n return moveOn();\n }\n if (\n !ts.isPropertyAccessExpression(node.expression) ||\n !ts.isIdentifier(node.expression.name) ||\n !isHttpMethod(node.expression.name.text)\n ) {\n return moveOn();\n }\n\n const [pathNode] = node.arguments;\n if (!ts.isStringLiteral(pathNode)) {\n return moveOn();\n }\n const method = node.expression.name.text;\n const path = pathNode.text;\n const validate = node.arguments.find((arg) =>\n isCallExpression(arg, 'validate'),\n );\n if (!validate) {\n return moveOn();\n }\n const handler = node.arguments.at(-1);\n if (!handler || !ts.isArrowFunction(handler)) {\n return moveOn();\n }\n const metadata = parseJSDocComment(node.parent);\n // Skip endpoints marked as private access\n if (metadata.access === 'private') {\n return moveOn();\n }\n const operationName =\n metadata.name ||\n camelcase(`${method} ${path.replace(/[^a-zA-Z0-9]/g, '')}`);\n if (!validate.arguments.length) {\n return moveOn();\n }\n\n let selector: ts.Expression | undefined;\n let contentType: ts.Expression | undefined;\n if (validate.arguments.length === 2) {\n contentType = validate.arguments[0];\n selector = validate.arguments[1];\n } else {\n selector = validate.arguments[0];\n }\n if (!ts.isArrowFunction(selector)) {\n return moveOn();\n }\n if (\n !selector ||\n !ts.isParenthesizedExpression(selector.body) ||\n !ts.isObjectLiteralExpression(selector.body.expression)\n ) {\n return moveOn();\n }\n\n // Collect all middleware declarations for analysis\n const middlewareDeclarations: { node: ts.Node; name?: string }[] = [];\n\n // slice(1, -1) to skip first (path) and last (handler) arguments\n // and skip the validate middleware - it's handled separately\n for (const arg of node.arguments.slice(1, -1)) {\n if (ts.isCallExpression(arg)) {\n // Try to resolve the factory function declaration\n if (ts.isIdentifier(arg.expression)) {\n const middlewareFnName = arg.expression.text;\n let symbol = typeChecker.getSymbolAtLocation(arg.expression);\n\n // If symbol has alias, resolve to the actual symbol\n if (symbol && symbol.flags & ts.SymbolFlags.Alias) {\n symbol = typeChecker.getAliasedSymbol(symbol);\n }\n\n const allDeclarations = [\n symbol?.valueDeclaration,\n ...(symbol?.declarations ?? []),\n ].filter((it) => !!it);\n\n let declaration = allDeclarations.find(isFunctionWithBody);\n\n // If not found, check for variable declarations with function initializers\n if (!declaration) {\n for (const decl of allDeclarations) {\n if (ts.isVariableDeclaration(decl) && decl.initializer) {\n if (isFunctionWithBody(decl.initializer)) {\n declaration = decl.initializer;\n break;\n }\n }\n }\n }\n\n if (declaration) {\n middlewareDeclarations.push({\n node: declaration,\n name: middlewareFnName,\n });\n }\n }\n\n // Also check if the call expression argument itself is an arrow function\n // e.g., middleware((ctx) => {...})\n // But skip if the function name is 'validate'\n if (\n ts.isIdentifier(arg.expression) &&\n arg.expression.text === 'validate'\n ) {\n continue;\n }\n const firstArg = arg.arguments[0];\n if (isFunctionWithBody(firstArg)) {\n middlewareDeclarations.push({ node: firstArg });\n }\n }\n }\n\n const props = selector.body.expression.properties.filter(\n ts.isPropertyAssignment,\n );\n\n const sourceFile = node.getSourceFile();\n\n typeDeriver.setTrace({\n file: sourceFile.fileName,\n operation: `${method.toUpperCase()} ${path}`,\n });\n\n const responses: ResponseItem[] = [];\n\n // Analyze all middlewares for potential responses\n for (const middleware of middlewareDeclarations) {\n for (const { token, node } of returnTokens(middleware.node, typeChecker)) {\n const items = responseAnalyzer(middleware.node as any, token, node);\n if (middleware.name) {\n for (const item of items) {\n item.middlewareName = middleware.name;\n }\n }\n responses.push(...items);\n }\n }\n\n // Analyze the main handler for responses\n for (const { token, node } of returnTokens(handler, typeChecker)) {\n responses.push(...responseAnalyzer(handler, token, node));\n }\n\n paths.addPath(\n operationName,\n path,\n method,\n contentType\n ? ts.isStringLiteral(contentType)\n ? contentType.text\n : undefined\n : undefined,\n toSelectors(props),\n responses,\n sourceFile.fileName,\n metadata,\n );\n\n function moveOn() {\n ts.forEachChild(node, (node) =>\n visit(node, responseAnalyzer, paths, typeChecker, typeDeriver),\n );\n }\n}\n\nfunction toSelectors(props: ts.PropertyAssignment[]) {\n const selectors: Selector[] = [];\n for (const prop of props) {\n if (!ts.isObjectLiteralExpression(prop.initializer)) {\n continue;\n }\n const name = prop.name.getText();\n const select = prop.initializer.properties\n .filter(ts.isPropertyAssignment)\n .find((prop) => prop.name.getText() === 'select');\n if (!select) {\n console.warn(\n `\\u26a0 No select found in ${name}\\n at ${nodeLocation(prop) ?? 'unknown'}`,\n );\n continue;\n }\n const against = prop.initializer.properties\n .filter(ts.isPropertyAssignment)\n .find((prop) => prop.name.getText() === 'against');\n if (!against) {\n console.warn(\n `\\u26a0 No against found in ${name}\\n at ${nodeLocation(prop) ?? 'unknown'}`,\n );\n continue;\n }\n const [, source, selectText] = select.initializer.getText().split('.');\n selectors.push({\n name: selectText,\n against: against.initializer.getText(),\n source: source as SemanticSource,\n });\n }\n return selectors;\n}\n\nexport async function analyze(\n tsconfigPath: string,\n config: {\n /**\n * Additional code to inject before resolving zod schemas\n */\n imports?: InjectImport[];\n typesMap?: Record<string, string>;\n responseAnalyzer: ResponseAnalyzerFn | NaunceResponseAnalyzer;\n onOperation?: OnOperation;\n },\n) {\n logger(`Parsing tsconfig`);\n const program = getProgram(tsconfigPath);\n logger(`Program created`);\n const typeChecker = program.getTypeChecker();\n\n logger(`Type checker created`);\n const typeDeriver = new TypeDeriver(typeChecker, config.typesMap);\n const paths = new Paths({\n imports: config.imports ?? [],\n onOperation: config.onOperation,\n });\n\n for (const sourceFile of program.getSourceFiles()) {\n logger(`Analyzing ${sourceFile.fileName}`);\n if (!sourceFile.isDeclarationFile) {\n logger(`Visiting ${sourceFile.fileName}`);\n visit(\n sourceFile,\n (handler, token, node) => {\n const responseAnalyzer = config.responseAnalyzer;\n if (typeof responseAnalyzer !== 'function') {\n const naunce =\n responseAnalyzer[token] || responseAnalyzer['default'];\n if (!naunce) {\n throw new Error(`No response analyzer for token ${token}`);\n }\n return naunce(handler, typeDeriver, node);\n }\n return responseAnalyzer(handler, typeDeriver);\n },\n paths,\n typeChecker,\n typeDeriver,\n );\n }\n }\n\n const components: ComponentsObject = {\n schemas: {\n ...paths.getSharedSchemas(),\n ...Object.entries(typeDeriver.collector).reduce(\n (acc, [key, value]) => ({ ...acc, [key]: toSchema(value) }),\n {},\n ),\n },\n };\n\n return {\n paths: await paths.getPaths(),\n tags: paths.getTags(),\n components,\n };\n}\n\nexport type Serialized = ReturnType<typeof analyze>;\n\nfunction isFunctionWithBody(\n node: ts.Node | ts.Declaration | undefined,\n): node is ts.FunctionLikeDeclaration & { body: ts.Block | ts.Expression } {\n if (!node) {\n return false;\n }\n return (\n (ts.isFunctionDeclaration(node) ||\n ts.isFunctionExpression(node) ||\n ts.isArrowFunction(node) ||\n ts.isMethodDeclaration(node) ||\n ts.isConstructorDeclaration(node) ||\n ts.isGetAccessor(node) ||\n ts.isSetAccessor(node)) &&\n !!node.body\n );\n}\n", "import ts from 'typescript';\n\nimport type {\n NaunceResponseAnalyzer,\n ResponseItem,\n TypeDeriver,\n} from '@sdk-it/core';\n\nconst handlerVisitor: (\n on: (\n node: ts.Node | undefined,\n statusCode: ts.Node | undefined,\n headers: ts.Node | undefined,\n contentType: string,\n ) => void,\n) => ts.Visitor = (callback) => {\n return (node: ts.Node) => {\n if (ts.isReturnStatement(node) && node.expression) {\n if (\n ts.isCallExpression(node.expression) &&\n ts.isPropertyAccessExpression(node.expression.expression)\n ) {\n const propAccess = node.expression.expression;\n if (\n ts.isIdentifier(propAccess.expression) &&\n propAccess.expression.text === 'output'\n ) {\n let contentType = 'application/json';\n const callerMethod = propAccess.name.text;\n const [body, statusCode, headers] = node.expression.arguments;\n if (callerMethod === 'attachment') {\n contentType = 'application/octet-stream';\n }\n if (!body) {\n contentType = 'empty';\n }\n callback(body, statusCode, headers, contentType);\n }\n }\n }\n return ts.forEachChild(node, handlerVisitor(callback));\n };\n};\n\nfunction toResponses(\n handler: ts.ArrowFunction | ts.FunctionExpression,\n deriver: TypeDeriver,\n) {\n const responsesList: ResponseItem[] = [];\n const visit = handlerVisitor((node, statusCode, headers, contentType) => {\n responsesList.push({\n headers: headers ? Object.keys(deriver.serializeNode(headers)) : [],\n contentType,\n statusCode: statusCode ? resolveStatusCode(statusCode) : '200',\n response: node ? deriver.serializeNode(node) : undefined,\n });\n });\n visit(handler.body);\n return responsesList;\n}\n\nfunction resolveStatusCode(node: ts.Node) {\n if (ts.isNumericLiteral(node)) {\n return node.text;\n }\n throw new Error(`Could not resolve status code`);\n}\n\nexport function defaultResponseAnalyzer(\n handler: ts.ArrowFunction | ts.FunctionExpression,\n deriver: TypeDeriver,\n) {\n try {\n return toResponses(handler, deriver);\n } catch (error) {\n console.error('Error analyzing response\\n', handler.getText());\n throw error;\n }\n}\n\nexport const responseAnalyzer: NaunceResponseAnalyzer = {\n 'throw.new.ProblemDetailsException': (handler, deriver, node) => {\n if (ts.isNewExpression(node)) {\n const [problem] = node.arguments ?? [];\n if (!ts.isObjectLiteralExpression(problem)) {\n return [];\n }\n const properties = problem.properties.reduce<Record<string, string>>(\n (acc, prop) => {\n if (ts.isPropertyAssignment(prop)) {\n const key = prop.name.getText();\n if (ts.isLiteralExpression(prop.initializer)) {\n acc[key] = prop.initializer.text;\n } else {\n acc[key] = prop.initializer.getText();\n }\n }\n return acc;\n },\n {},\n );\n return [\n {\n contentType: 'application/problem+json',\n headers: [],\n statusCode: properties.status,\n response: deriver.serializeNode(problem),\n },\n ];\n }\n return [];\n },\n default: defaultResponseAnalyzer,\n};\n\nexport default responseAnalyzer;\n"],
|
|
5
|
-
"mappings": ";AAAA,OAAO,WAAW;AAElB,SAAS,iBAAiB;AAC1B,OAAO,QAAQ;AAEf;AAAA,EAIE;AAAA,EAKA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAKP,SAAS,WAAW,QAAmD;AACrE,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO,gBAAgB,CAAC;AAC7C,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,aAAa,CAAC,EAAE,cAAc;AACjD,SAAO,YAAY;AACrB;AAKA,SAAS,mBAAmB,QAAwC;AAClE,QAAM,WAAW,WAAW,MAAM;AAClC,SAAO,WAAW,SAAS,SAAS,cAAc,IAAI;AACxD;AAKA,SAAS,gBAAgB,QAAwC;AAC/D,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,SAAO,CAAC,mBAAmB,MAAM;AACnC;AAEO,IAAM,eAAe,CAC1B,MACA,aACA,YACG;AACH,QAAM,SAAmD,CAAC;AAC1D,QAAM,mBAAmB,SAAS,oBAAoB;AACtD,QAAM,WAAW,SAAS,YAAY;AAEtC,QAAM,mBAAmB,oBAAI,IAAoB;AAEjD,QAAM,UAAU,CAACA,OAAe,UAAwB;AAEtD,QAAI,QAAQ,UAAU;AACpB;AAAA,IACF;AAEA,QAAI,GAAG,iBAAiBA,KAAI,GAAG;AAC7B,UAAI,GAAG,gBAAgBA,MAAK,UAAU,GAAG;AACvC,eAAO,KAAK;AAAA,UACV,OAAO,aAAaA,MAAK,WAAW,WAAW,QAAQ,CAAC;AAAA,UACxD,MAAMA,MAAK;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,GAAG,kBAAkBA,KAAI,KAAKA,MAAK,YAAY;AACjD,UAAI,GAAG,iBAAiBA,MAAK,UAAU,GAAG;AACxC,eAAO,KAAK;AAAA,UACV,OAAOA,MAAK,WAAW,WAAW,QAAQ;AAAA,UAC1C,MAAMA,MAAK;AAAA,QACb,CAAC;AAAA,MACH;AACA,UAAI,GAAG,gBAAgBA,MAAK,UAAU,GAAG;AACvC,eAAO,KAAK;AAAA,UACV,OAAO,OAAOA,MAAK,WAAW,WAAW,QAAQ,CAAC;AAAA,UAClD,MAAMA,MAAK;AAAA,QACb,CAAC;AAAA,MACH;AAGA,SAAG,aAAaA,MAAK,YAAY,CAAC,UAAU,QAAQ,OAAO,KAAK,CAAC;AACjE;AAAA,IACF;AAGA,QAAI,GAAG,iBAAiBA,KAAI,KAAK,eAAe,QAAQ,UAAU;AAChE,YAAM,iBAAiBA;AAGvB,UAAI;AAEJ,UAAI,GAAG,aAAa,eAAe,UAAU,GAAG;AAC9C,iBAAS,YAAY,oBAAoB,eAAe,UAAU;AAAA,MACpE,WAAW,GAAG,2BAA2B,eAAe,UAAU,GAAG;AACnE,iBAAS,YAAY;AAAA,UACnB,eAAe,WAAW;AAAA,QAC5B;AAAA,MACF;AAGA,UAAI,UAAU,OAAO,QAAQ,GAAG,YAAY,OAAO;AACjD,iBAAS,YAAY,iBAAiB,MAAM;AAAA,MAC9C;AAGA,YAAM,eAAe,oBAAoB,gBAAgB,MAAM;AAE/D,UAAI,gBAAgB,QAAQ;AAC1B,cAAM,eAAe,QAAQ,gBAAgB,CAAC;AAE9C,mBAAW,eAAe,cAAc;AAEtC,cAAI,iBAAiB,IAAI,WAAW,GAAG;AACrC;AAAA,UACF;AAEA,cAAI,mBAAmB,WAAW,KAAK,YAAY,MAAM;AACvD,6BAAiB,IAAI,WAAW;AAEhC,oBAAQ,YAAY,MAAM,QAAQ,CAAC;AAAA,UACrC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,OAAG,aAAaA,OAAM,CAAC,UAAU,QAAQ,OAAO,KAAK,CAAC;AAAA,EACxD;AAEA,UAAQ,MAAM,CAAC;AACf,SAAO;AACT;AAEA,IAAM,SAAS,MAAM,iBAAiB;AAEtC,IAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,SAAS,kBAAkB,MAAe;AACxC,MAAI,OAAiB,CAAC;AACtB,MAAI,OAAO;AACX,MAAI,cAAc;AAClB,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,OAAO;AACX,MAAI,kBAAkB;AAEtB,aAAW,OAAO,GAAG;AAAA,IAAgB;AAAA,IAAM,CAACC,SAC1C,WAAW,SAASA,KAAI,QAAQ,IAAkB;AAAA,EACpD,GAAG;AACD,QAAI,OAAO,IAAI,YAAY,UAAU;AACnC;AAAA,IACF;AACA,YAAQ,IAAI,QAAQ,MAAoB;AAAA,MACtC,KAAK;AACH,eAAO,IAAI;AACX;AAAA,MACF,KAAK;AACH,eAAO,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,CAACA,SAAQA,KAAI,KAAK,CAAC;AACrD;AAAA,MACF,KAAK;AACH,sBAAc,IAAI;AAClB;AAAA,MACF,KAAK;AACH,kBAAU,IAAI;AACd;AAAA,MACF,KAAK;AACH,iBAAS,IAAI,QAAQ,KAAK,EAAE,YAAY;AACxC;AAAA,MACF,KAAK;AACH,eAAO,IAAI,QAAQ,KAAK;AACxB;AAAA,MACF,KAAK;AACH,0BAAkB,IAAI,QAAQ,KAAK;AACnC;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,MACP,MACAC,mBAKA,OACA,aACA,aACA;AACA,MAAI,CAAC,GAAG,iBAAiB,IAAI,KAAK,KAAK,UAAU,SAAS,GAAG;AAC3D,WAAO,OAAO;AAAA,EAChB;AACA,MACE,CAAC,GAAG,2BAA2B,KAAK,UAAU,KAC9C,CAAC,GAAG,aAAa,KAAK,WAAW,IAAI,KACrC,CAAC,aAAa,KAAK,WAAW,KAAK,IAAI,GACvC;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,CAAC,QAAQ,IAAI,KAAK;AACxB,MAAI,CAAC,GAAG,gBAAgB,QAAQ,GAAG;AACjC,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,SAAS,KAAK,WAAW,KAAK;AACpC,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,KAAK,UAAU;AAAA,IAAK,CAAC,QACpC,iBAAiB,KAAK,UAAU;AAAA,EAClC;AACA,MAAI,CAAC,UAAU;AACb,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,UAAU,KAAK,UAAU,GAAG,EAAE;AACpC,MAAI,CAAC,WAAW,CAAC,GAAG,gBAAgB,OAAO,GAAG;AAC5C,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,WAAW,kBAAkB,KAAK,MAAM;AAE9C,MAAI,SAAS,WAAW,WAAW;AACjC,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,gBACJ,SAAS,QACT,UAAU,GAAG,MAAM,IAAI,KAAK,QAAQ,iBAAiB,EAAE,CAAC,EAAE;AAC5D,MAAI,CAAC,SAAS,UAAU,QAAQ;AAC9B,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS,UAAU,WAAW,GAAG;AACnC,kBAAc,SAAS,UAAU,CAAC;AAClC,eAAW,SAAS,UAAU,CAAC;AAAA,EACjC,OAAO;AACL,eAAW,SAAS,UAAU,CAAC;AAAA,EACjC;AACA,MAAI,CAAC,GAAG,gBAAgB,QAAQ,GAAG;AACjC,WAAO,OAAO;AAAA,EAChB;AACA,MACE,CAAC,YACD,CAAC,GAAG,0BAA0B,SAAS,IAAI,KAC3C,CAAC,GAAG,0BAA0B,SAAS,KAAK,UAAU,GACtD;AACA,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,yBAA6D,CAAC;AAIpE,aAAW,OAAO,KAAK,UAAU,MAAM,GAAG,EAAE,GAAG;AAC7C,QAAI,GAAG,iBAAiB,GAAG,GAAG;AAE5B,UAAI,GAAG,aAAa,IAAI,UAAU,GAAG;AACnC,cAAM,mBAAmB,IAAI,WAAW;AACxC,YAAI,SAAS,YAAY,oBAAoB,IAAI,UAAU;AAG3D,YAAI,UAAU,OAAO,QAAQ,GAAG,YAAY,OAAO;AACjD,mBAAS,YAAY,iBAAiB,MAAM;AAAA,QAC9C;AAEA,cAAM,kBAAkB;AAAA,UACtB,QAAQ;AAAA,UACR,GAAI,QAAQ,gBAAgB,CAAC;AAAA,QAC/B,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE;AAErB,YAAI,cAAc,gBAAgB,KAAK,kBAAkB;AAGzD,YAAI,CAAC,aAAa;AAChB,qBAAW,QAAQ,iBAAiB;AAClC,gBAAI,GAAG,sBAAsB,IAAI,KAAK,KAAK,aAAa;AACtD,kBAAI,mBAAmB,KAAK,WAAW,GAAG;AACxC,8BAAc,KAAK;AACnB;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,YAAI,aAAa;AACf,iCAAuB,KAAK;AAAA,YAC1B,MAAM;AAAA,YACN,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAAA,MACF;AAKA,UACE,GAAG,aAAa,IAAI,UAAU,KAC9B,IAAI,WAAW,SAAS,YACxB;AACA;AAAA,MACF;AACA,YAAM,WAAW,IAAI,UAAU,CAAC;AAChC,UAAI,mBAAmB,QAAQ,GAAG;AAChC,+BAAuB,KAAK,EAAE,MAAM,SAAS,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,SAAS,KAAK,WAAW,WAAW;AAAA,IAChD,GAAG;AAAA,EACL;AAEA,QAAM,aAAa,KAAK,cAAc;AAEtC,cAAY,SAAS;AAAA,IACnB,MAAM,WAAW;AAAA,IACjB,WAAW,GAAG,OAAO,YAAY,CAAC,IAAI,IAAI;AAAA,EAC5C,CAAC;AAED,QAAM,YAA4B,CAAC;AAGnC,aAAW,cAAc,wBAAwB;AAC/C,eAAW,EAAE,OAAO,MAAAF,MAAK,KAAK,aAAa,WAAW,MAAM,WAAW,GAAG;AACxE,YAAM,QAAQE,kBAAiB,WAAW,MAAa,OAAOF,KAAI;AAClE,UAAI,WAAW,MAAM;AACnB,mBAAW,QAAQ,OAAO;AACxB,eAAK,iBAAiB,WAAW;AAAA,QACnC;AAAA,MACF;AACA,gBAAU,KAAK,GAAG,KAAK;AAAA,IACzB;AAAA,EACF;AAGA,aAAW,EAAE,OAAO,MAAAA,MAAK,KAAK,aAAa,SAAS,WAAW,GAAG;AAChE,cAAU,KAAK,GAAGE,kBAAiB,SAAS,OAAOF,KAAI,CAAC;AAAA,EAC1D;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,cACI,GAAG,gBAAgB,WAAW,IAC5B,YAAY,OACZ,SACF;AAAA,IACJ,YAAY,KAAK;AAAA,IACjB;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AAEA,WAAS,SAAS;AAChB,OAAG;AAAA,MAAa;AAAA,MAAM,CAACA,UACrB,MAAMA,OAAME,mBAAkB,OAAO,aAAa,WAAW;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,SAAS,YAAY,OAAgC;AACnD,QAAM,YAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACnD;AAAA,IACF;AACA,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,UAAM,SAAS,KAAK,YAAY,WAC7B,OAAO,GAAG,oBAAoB,EAC9B,KAAK,CAACC,UAASA,MAAK,KAAK,QAAQ,MAAM,QAAQ;AAClD,QAAI,CAAC,QAAQ;AACX,cAAQ;AAAA,QACN,6BAA6B,IAAI;AAAA,OAAU,aAAa,IAAI,KAAK,SAAS;AAAA,MAC5E;AACA;AAAA,IACF;AACA,UAAM,UAAU,KAAK,YAAY,WAC9B,OAAO,GAAG,oBAAoB,EAC9B,KAAK,CAACA,UAASA,MAAK,KAAK,QAAQ,MAAM,SAAS;AACnD,QAAI,CAAC,SAAS;AACZ,cAAQ;AAAA,QACN,8BAA8B,IAAI;AAAA,OAAU,aAAa,IAAI,KAAK,SAAS;AAAA,MAC7E;AACA;AAAA,IACF;AACA,UAAM,CAAC,EAAE,QAAQ,UAAU,IAAI,OAAO,YAAY,QAAQ,EAAE,MAAM,GAAG;AACrE,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,SAAS,QAAQ,YAAY,QAAQ;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAsB,QACpB,cACA,QASA;AACA,SAAO,kBAAkB;AACzB,QAAM,UAAU,WAAW,YAAY;AACvC,SAAO,iBAAiB;AACxB,QAAM,cAAc,QAAQ,eAAe;AAE3C,SAAO,sBAAsB;AAC7B,QAAM,cAAc,IAAI,YAAY,aAAa,OAAO,QAAQ;AAChE,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,SAAS,OAAO,WAAW,CAAC;AAAA,IAC5B,aAAa,OAAO;AAAA,EACtB,CAAC;AAED,aAAW,cAAc,QAAQ,eAAe,GAAG;AACjD,WAAO,aAAa,WAAW,QAAQ,EAAE;AACzC,QAAI,CAAC,WAAW,mBAAmB;AACjC,aAAO,YAAY,WAAW,QAAQ,EAAE;AACxC;AAAA,QACE;AAAA,QACA,CAAC,SAAS,OAAO,SAAS;AACxB,gBAAMD,oBAAmB,OAAO;AAChC,cAAI,OAAOA,sBAAqB,YAAY;AAC1C,kBAAM,SACJA,kBAAiB,KAAK,KAAKA,kBAAiB,SAAS;AACvD,gBAAI,CAAC,QAAQ;AACX,oBAAM,IAAI,MAAM,kCAAkC,KAAK,EAAE;AAAA,YAC3D;AACA,mBAAO,OAAO,SAAS,aAAa,IAAI;AAAA,UAC1C;AACA,iBAAOA,kBAAiB,SAAS,WAAW;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAA+B;AAAA,IACnC,SAAS;AAAA,MACP,GAAG,MAAM,iBAAiB;AAAA,MAC1B,GAAG,OAAO,QAAQ,YAAY,SAAS,EAAE;AAAA,QACvC,CAAC,KAAK,CAAC,KAAK,KAAK,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,SAAS,KAAK,EAAE;AAAA,QACzD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,MAAM,MAAM,SAAS;AAAA,IAC5B,MAAM,MAAM,QAAQ;AAAA,IACpB;AAAA,EACF;AACF;AAIA,SAAS,mBACP,MACyE;AACzE,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,UACG,GAAG,sBAAsB,IAAI,KAC5B,GAAG,qBAAqB,IAAI,KAC5B,GAAG,gBAAgB,IAAI,KACvB,GAAG,oBAAoB,IAAI,KAC3B,GAAG,yBAAyB,IAAI,KAChC,GAAG,cAAc,IAAI,KACrB,GAAG,cAAc,IAAI,MACvB,CAAC,CAAC,KAAK;AAEX;;;ACngBA,OAAOE,SAAQ;AAQf,IAAM,iBAOY,CAAC,aAAa;AAC9B,SAAO,CAAC,SAAkB;AACxB,QAAIA,IAAG,kBAAkB,IAAI,KAAK,KAAK,YAAY;AACjD,UACEA,IAAG,iBAAiB,KAAK,UAAU,KACnCA,IAAG,2BAA2B,KAAK,WAAW,UAAU,GACxD;AACA,cAAM,aAAa,KAAK,WAAW;AACnC,YACEA,IAAG,aAAa,WAAW,UAAU,KACrC,WAAW,WAAW,SAAS,UAC/B;AACA,cAAI,cAAc;AAClB,gBAAM,eAAe,WAAW,KAAK;AACrC,gBAAM,CAAC,MAAM,YAAY,OAAO,IAAI,KAAK,WAAW;AACpD,cAAI,iBAAiB,cAAc;AACjC,0BAAc;AAAA,UAChB;AACA,cAAI,CAAC,MAAM;AACT,0BAAc;AAAA,UAChB;AACA,mBAAS,MAAM,YAAY,SAAS,WAAW;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AACA,WAAOA,IAAG,aAAa,MAAM,eAAe,QAAQ,CAAC;AAAA,EACvD;AACF;AAEA,SAAS,YACP,SACA,SACA;AACA,QAAM,gBAAgC,CAAC;AACvC,QAAMC,SAAQ,eAAe,CAAC,MAAM,YAAY,SAAS,gBAAgB;AACvE,kBAAc,KAAK;AAAA,MACjB,SAAS,UAAU,OAAO,KAAK,QAAQ,cAAc,OAAO,CAAC,IAAI,CAAC;AAAA,MAClE;AAAA,MACA,YAAY,aAAa,kBAAkB,UAAU,IAAI;AAAA,MACzD,UAAU,OAAO,QAAQ,cAAc,IAAI,IAAI;AAAA,IACjD,CAAC;AAAA,EACH,CAAC;AACD,EAAAA,OAAM,QAAQ,IAAI;AAClB,SAAO;AACT;AAEA,SAAS,kBAAkB,MAAe;AACxC,MAAID,IAAG,iBAAiB,IAAI,GAAG;AAC7B,WAAO,KAAK;AAAA,EACd;AACA,QAAM,IAAI,MAAM,+BAA+B;AACjD;AAEO,SAAS,wBACd,SACA,SACA;AACA,MAAI;AACF,WAAO,YAAY,SAAS,OAAO;AAAA,EACrC,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,QAAQ,QAAQ,CAAC;AAC7D,UAAM;AAAA,EACR;AACF;AAEO,IAAM,mBAA2C;AAAA,EACtD,qCAAqC,CAAC,SAAS,SAAS,SAAS;AAC/D,QAAIA,IAAG,gBAAgB,IAAI,GAAG;AAC5B,YAAM,CAAC,OAAO,IAAI,KAAK,aAAa,CAAC;AACrC,UAAI,CAACA,IAAG,0BAA0B,OAAO,GAAG;AAC1C,eAAO,CAAC;AAAA,MACV;AACA,YAAM,aAAa,QAAQ,WAAW;AAAA,QACpC,CAAC,KAAK,SAAS;AACb,cAAIA,IAAG,qBAAqB,IAAI,GAAG;AACjC,kBAAM,MAAM,KAAK,KAAK,QAAQ;AAC9B,gBAAIA,IAAG,oBAAoB,KAAK,WAAW,GAAG;AAC5C,kBAAI,GAAG,IAAI,KAAK,YAAY;AAAA,YAC9B,OAAO;AACL,kBAAI,GAAG,IAAI,KAAK,YAAY,QAAQ;AAAA,YACtC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,QACA,CAAC;AAAA,MACH;AACA,aAAO;AAAA,QACL;AAAA,UACE,aAAa;AAAA,UACb,SAAS,CAAC;AAAA,UACV,YAAY,WAAW;AAAA,UACvB,UAAU,QAAQ,cAAc,OAAO;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC;AAAA,EACV;AAAA,EACA,SAAS;AACX;",
|
|
4
|
+
"sourcesContent": ["import debug from 'debug';\nimport { realpathSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { SecurityRequirementObject } from 'openapi3-ts/oas31';\nimport { camelcase } from 'stringcase';\nimport ts from 'typescript';\n\nimport {\n type InjectImport,\n type NaunceResponseAnalyzer,\n type OnOperation,\n type OpenAPIComponentsObject,\n type OpenAPISecuritySchemeObject,\n Paths,\n type ResponseAnalyzerFn,\n type ResponseItem,\n type Selector,\n type SemanticSource,\n TypeDeriver,\n getProgram,\n isCallExpression,\n isHttpMethod,\n nodeLocation,\n toSchema,\n} from '@sdk-it/core';\n\n/**\n * Gets the file path from a symbol's first declaration\n */\nfunction symbolFile(symbol: ts.Symbol | undefined): string | undefined {\n if (!symbol) {\n return undefined;\n }\n\n const declarations = symbol.declarations ?? [];\n if (declarations.length === 0) {\n return undefined;\n }\n\n const sourceFile = declarations[0].getSourceFile();\n return sourceFile?.fileName;\n}\n\n/**\n * Determines if a symbol is from an external library (node_modules)\n */\nfunction isExternalFunction(symbol: ts.Symbol | undefined): boolean {\n const fileName = symbolFile(symbol);\n return fileName ? fileName.includes('node_modules') : false;\n}\n\n/**\n * Determines if a symbol refers to a local function (not from node_modules)\n */\nfunction isLocalFunction(symbol: ts.Symbol | undefined): boolean {\n if (!symbol) {\n return false;\n }\n\n return !isExternalFunction(symbol);\n}\n\nexport interface MiddlewareSecurityRule {\n middleware: {\n import: string;\n from: string;\n };\n security: SecurityRequirementObject[];\n values?: {\n scheme: string;\n argument: number;\n mode: 'any' | 'all';\n };\n}\n\nfunction resolvedSymbol(\n expression: ts.LeftHandSideExpression,\n typeChecker: ts.TypeChecker,\n) {\n const location = ts.isPropertyAccessExpression(expression)\n ? expression.name\n : expression;\n const symbol = typeChecker.getSymbolAtLocation(location);\n return symbol && symbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(symbol)\n : symbol;\n}\n\n/**\n * Symlinked working directories (macOS `/tmp` -> `/private/tmp`) and Windows\n * drive-letter casing make two spellings of the same file compare unequal.\n */\nfunction realPath(path: string) {\n const resolved = resolve(path);\n try {\n return realpathSync.native(resolved);\n } catch {\n return resolved;\n }\n}\n\nfunction matchesMiddleware(\n symbol: ts.Symbol | undefined,\n middleware: MiddlewareSecurityRule['middleware'],\n) {\n if (symbol?.name !== middleware.import) {\n return false;\n }\n const from = realPath(middleware.from);\n return (symbol.declarations ?? []).some(\n (declaration) => realPath(declaration.getSourceFile().fileName) === from,\n );\n}\n\ninterface SecurityAnalysis {\n rules: readonly MiddlewareSecurityRule[];\n matched: Set<MiddlewareSecurityRule>;\n}\n\nfunction middlewareSecurity(\n args: readonly ts.Expression[],\n security: SecurityAnalysis,\n typeChecker: ts.TypeChecker,\n): SecurityRequirementObject[] {\n let requirements: SecurityRequirementObject[] | undefined;\n for (const arg of args) {\n const call = ts.isCallExpression(arg) ? arg : undefined;\n const reference = call ? call.expression : arg;\n if (\n !ts.isIdentifier(reference) &&\n !ts.isPropertyAccessExpression(reference)\n ) {\n continue;\n }\n const symbol = resolvedSymbol(reference, typeChecker);\n const rule = security.rules.find(({ middleware }) =>\n matchesMiddleware(symbol, middleware),\n );\n if (!rule) {\n continue;\n }\n security.matched.add(rule);\n if (rule.values && !call) {\n throw new TypeError(\n `Security middleware ${rule.middleware.import} must be called so argument ${rule.values.argument} can be read\\n at ${nodeLocation(arg) ?? 'unknown'}`,\n );\n }\n const requirement =\n rule.values && call\n ? securityWithValues(call, rule, rule.values, typeChecker)\n : structuredClone(rule.security);\n requirements = requirements\n ? andSecurity(requirements, requirement)\n : requirement;\n }\n return requirements ?? [];\n}\n\nfunction securityWithValues(\n call: ts.CallExpression,\n rule: MiddlewareSecurityRule,\n valuesConfig: NonNullable<MiddlewareSecurityRule['values']>,\n typeChecker: ts.TypeChecker,\n) {\n const expression = call.arguments[valuesConfig.argument];\n if (!expression) {\n throw new TypeError(\n `Security middleware ${rule.middleware.import} requires argument ${valuesConfig.argument}`,\n );\n }\n const resolved = constantStrings(expression, typeChecker);\n if (!resolved) {\n throw new TypeError(\n `Could not statically resolve security values at ${nodeLocation(expression) ?? 'unknown'}`,\n );\n }\n if (resolved.length === 0) {\n return structuredClone(rule.security);\n }\n const values = [...new Set(resolved)];\n return rule.security.flatMap((requirement) => {\n const existing = requirement[valuesConfig.scheme];\n if (!existing) {\n throw new TypeError(\n `Security scheme ${valuesConfig.scheme} is not present in the middleware requirement`,\n );\n }\n if (valuesConfig.mode === 'all') {\n return [\n {\n ...structuredClone(requirement),\n [valuesConfig.scheme]: [...new Set([...existing, ...values])],\n },\n ];\n }\n return values.map((value) => ({\n ...structuredClone(requirement),\n [valuesConfig.scheme]: [...new Set([...existing, value])],\n }));\n });\n}\n\nfunction constantStrings(\n expression: ts.Expression,\n typeChecker: ts.TypeChecker,\n visited = new Set<ts.Declaration>(),\n): string[] | undefined {\n while (\n ts.isParenthesizedExpression(expression) ||\n ts.isAsExpression(expression) ||\n ts.isTypeAssertionExpression(expression) ||\n ts.isSatisfiesExpression(expression) ||\n ts.isNonNullExpression(expression)\n ) {\n expression = expression.expression;\n }\n if (ts.isStringLiteralLike(expression)) {\n return [expression.text];\n }\n if (ts.isArrayLiteralExpression(expression)) {\n const result: string[] = [];\n for (const element of expression.elements) {\n if (ts.isOmittedExpression(element)) {\n continue;\n }\n const values = constantStrings(\n ts.isSpreadElement(element) ? element.expression : element,\n typeChecker,\n visited,\n );\n if (!values) {\n return undefined;\n }\n result.push(...values);\n }\n return result;\n }\n if (ts.isPropertyAccessExpression(expression)) {\n const value = typeChecker.getConstantValue(expression);\n if (typeof value === 'string') {\n return [value];\n }\n }\n if (\n ts.isIdentifier(expression) ||\n ts.isPropertyAccessExpression(expression)\n ) {\n const symbol = resolvedSymbol(expression, typeChecker);\n for (const declaration of symbol?.declarations ?? []) {\n // `visited` tracks the current recursion path, not everything ever seen,\n // so the same constant can be referenced more than once in one expression.\n if (visited.has(declaration)) {\n continue;\n }\n visited.add(declaration);\n try {\n if (\n (ts.isVariableDeclaration(declaration) ||\n ts.isEnumMember(declaration)) &&\n declaration.initializer\n ) {\n const values = constantStrings(\n declaration.initializer,\n typeChecker,\n visited,\n );\n if (values) {\n return values;\n }\n }\n } finally {\n visited.delete(declaration);\n }\n }\n }\n return undefined;\n}\n\nfunction andSecurity(\n left: SecurityRequirementObject[],\n right: SecurityRequirementObject[],\n) {\n const combined = left.flatMap((leftRequirement) =>\n right.map((rightRequirement) => {\n const requirement = structuredClone(leftRequirement);\n for (const [scheme, values] of Object.entries(rightRequirement)) {\n requirement[scheme] = [\n ...new Set([...(requirement[scheme] ?? []), ...values]),\n ];\n }\n return requirement;\n }),\n );\n return [\n ...new Map(\n combined.map((requirement) => [requirementKey(requirement), requirement]),\n ).values(),\n ];\n}\n\n/**\n * Scheme and value order carry no meaning in a security requirement, so the\n * identity key has to ignore both.\n */\nfunction requirementKey(requirement: SecurityRequirementObject) {\n return JSON.stringify(\n Object.keys(requirement)\n .sort()\n .map((scheme) => [scheme, [...requirement[scheme]].sort()]),\n );\n}\n\nexport const returnTokens = (\n node: ts.Node,\n typeChecker?: ts.TypeChecker,\n options?: { consider3rdParty?: boolean; maxDepth?: number },\n) => {\n const tokens: { token: string; node: ts.Expression }[] = [];\n const consider3rdParty = options?.consider3rdParty ?? false;\n const maxDepth = options?.maxDepth ?? 5;\n // Track visited function declarations to prevent infinite recursion\n const visitedFunctions = new Set<ts.Declaration>();\n\n const visitor = (node: ts.Node, depth: number): void => {\n // Skip if we've exceeded max depth\n if (depth > maxDepth) {\n return;\n }\n\n if (ts.isThrowStatement(node)) {\n if (ts.isNewExpression(node.expression)) {\n tokens.push({\n token: `throw.new.${node.expression.expression.getText()}`,\n node: node.expression,\n });\n }\n }\n\n if (ts.isReturnStatement(node) && node.expression) {\n if (ts.isCallExpression(node.expression)) {\n tokens.push({\n token: node.expression.expression.getText(),\n node: node.expression,\n });\n }\n if (ts.isNewExpression(node.expression)) {\n tokens.push({\n token: `new.${node.expression.expression.getText()}`,\n node: node.expression,\n });\n }\n // Continue traversing into the returned expression (e.g., arrow functions)\n // This handles: return async (c, next) => { throw ... }\n ts.forEachChild(node.expression, (child) => visitor(child, depth));\n return;\n }\n\n // If we encounter a call expression and have a type checker, follow it\n if (ts.isCallExpression(node) && typeChecker && depth < maxDepth) {\n const callExpression = node;\n\n // Try to resolve the function being called\n let symbol: ts.Symbol | undefined;\n\n if (ts.isIdentifier(callExpression.expression)) {\n symbol = typeChecker.getSymbolAtLocation(callExpression.expression);\n } else if (ts.isPropertyAccessExpression(callExpression.expression)) {\n symbol = typeChecker.getSymbolAtLocation(\n callExpression.expression.name,\n );\n }\n\n // Resolve aliases\n if (symbol && symbol.flags & ts.SymbolFlags.Alias) {\n symbol = typeChecker.getAliasedSymbol(symbol);\n }\n\n // Check if we should follow this function\n const shouldFollow = consider3rdParty || isLocalFunction(symbol);\n\n if (shouldFollow && symbol) {\n const declarations = symbol?.declarations ?? [];\n\n for (const declaration of declarations) {\n // Skip if we've already visited this function (prevent infinite recursion)\n if (visitedFunctions.has(declaration)) {\n continue;\n }\n\n if (isFunctionWithBody(declaration) && declaration.body) {\n visitedFunctions.add(declaration);\n // Recursively visit the function body with incremented depth\n visitor(declaration.body, depth + 1);\n }\n }\n }\n }\n\n ts.forEachChild(node, (child) => visitor(child, depth));\n };\n\n visitor(node, 0);\n return tokens;\n};\n\nconst logger = debug('@sdk-it/generic');\n\nconst jsDocsTags = [\n 'openapi',\n 'tags',\n 'description',\n 'summary',\n 'access',\n 'tool',\n 'toolDescription',\n] as const;\ntype JSDocsTags = (typeof jsDocsTags)[number];\n\nfunction parseJSDocComment(node: ts.Node) {\n let tags: string[] = [];\n let name = '';\n let description = '';\n let summary = '';\n let access = '';\n let tool = '';\n let toolDescription = '';\n\n for (const tag of ts.getAllJSDocTags(node, (tag): tag is ts.JSDocTag =>\n jsDocsTags.includes(tag.tagName.text as JSDocsTags),\n )) {\n if (typeof tag.comment !== 'string') {\n continue;\n }\n switch (tag.tagName.text as JSDocsTags) {\n case 'openapi':\n name = tag.comment;\n break;\n case 'tags':\n tags = tag.comment.split(',').map((tag) => tag.trim());\n break;\n case 'description':\n description = tag.comment;\n break;\n case 'summary':\n summary = tag.comment;\n break;\n case 'access':\n access = tag.comment.trim().toLowerCase();\n break;\n case 'tool':\n tool = tag.comment.trim();\n break;\n case 'toolDescription':\n toolDescription = tag.comment.trim();\n break;\n }\n }\n return {\n name,\n tags,\n description,\n access,\n tool,\n toolDescription,\n summary,\n };\n}\n\nfunction visit(\n node: ts.Node,\n responseAnalyzer: (\n handler: ts.ArrowFunction | ts.FunctionExpression,\n token: string,\n node: ts.Node,\n ) => ResponseItem[],\n paths: Paths,\n typeChecker: ts.TypeChecker,\n typeDeriver: TypeDeriver,\n security?: SecurityAnalysis,\n) {\n if (!ts.isCallExpression(node) || node.arguments.length < 2) {\n return moveOn();\n }\n if (\n !ts.isPropertyAccessExpression(node.expression) ||\n !ts.isIdentifier(node.expression.name) ||\n !isHttpMethod(node.expression.name.text)\n ) {\n return moveOn();\n }\n\n const [pathNode] = node.arguments;\n if (!ts.isStringLiteral(pathNode)) {\n return moveOn();\n }\n const method = node.expression.name.text;\n const path = pathNode.text;\n const validate = node.arguments.find((arg) =>\n isCallExpression(arg, 'validate'),\n );\n if (!validate) {\n return moveOn();\n }\n const handler = node.arguments.at(-1);\n if (!handler || !ts.isArrowFunction(handler)) {\n return moveOn();\n }\n const metadata = parseJSDocComment(node.parent);\n // Skip endpoints marked as private access\n if (metadata.access === 'private') {\n return moveOn();\n }\n const operationName =\n metadata.name ||\n camelcase(`${method} ${path.replace(/[^a-zA-Z0-9]/g, '')}`);\n if (!validate.arguments.length) {\n return moveOn();\n }\n\n let selector: ts.Expression | undefined;\n let contentType: ts.Expression | undefined;\n if (validate.arguments.length === 2) {\n contentType = validate.arguments[0];\n selector = validate.arguments[1];\n } else {\n selector = validate.arguments[0];\n }\n if (!ts.isArrowFunction(selector)) {\n return moveOn();\n }\n if (\n !selector ||\n !ts.isParenthesizedExpression(selector.body) ||\n !ts.isObjectLiteralExpression(selector.body.expression)\n ) {\n return moveOn();\n }\n\n // Collect all middleware declarations for analysis\n const middlewareDeclarations: { node: ts.Node; name?: string }[] = [];\n\n // slice(1, -1) to skip first (path) and last (handler) arguments\n // and skip the validate middleware - it's handled separately\n for (const arg of node.arguments.slice(1, -1)) {\n if (ts.isCallExpression(arg)) {\n // Try to resolve the factory function declaration\n if (ts.isIdentifier(arg.expression)) {\n const middlewareFnName = arg.expression.text;\n let symbol = typeChecker.getSymbolAtLocation(arg.expression);\n\n // If symbol has alias, resolve to the actual symbol\n if (symbol && symbol.flags & ts.SymbolFlags.Alias) {\n symbol = typeChecker.getAliasedSymbol(symbol);\n }\n\n const allDeclarations = [\n symbol?.valueDeclaration,\n ...(symbol?.declarations ?? []),\n ].filter((it) => !!it);\n\n let declaration = allDeclarations.find(isFunctionWithBody);\n\n // If not found, check for variable declarations with function initializers\n if (!declaration) {\n for (const decl of allDeclarations) {\n if (ts.isVariableDeclaration(decl) && decl.initializer) {\n if (isFunctionWithBody(decl.initializer)) {\n declaration = decl.initializer;\n break;\n }\n }\n }\n }\n\n if (declaration) {\n middlewareDeclarations.push({\n node: declaration,\n name: middlewareFnName,\n });\n }\n }\n\n // Also check if the call expression argument itself is an arrow function\n // e.g., middleware((ctx) => {...})\n // But skip if the function name is 'validate'\n if (\n ts.isIdentifier(arg.expression) &&\n arg.expression.text === 'validate'\n ) {\n continue;\n }\n const firstArg = arg.arguments[0];\n if (isFunctionWithBody(firstArg)) {\n middlewareDeclarations.push({ node: firstArg });\n }\n }\n }\n\n const props = selector.body.expression.properties.filter(\n ts.isPropertyAssignment,\n );\n\n const sourceFile = node.getSourceFile();\n\n typeDeriver.setTrace({\n file: sourceFile.fileName,\n operation: `${method.toUpperCase()} ${path}`,\n });\n\n const responses: ResponseItem[] = [];\n\n // Analyze all middlewares for potential responses\n for (const middleware of middlewareDeclarations) {\n for (const { token, node } of returnTokens(middleware.node, typeChecker)) {\n const items = responseAnalyzer(middleware.node as any, token, node);\n if (middleware.name) {\n for (const item of items) {\n item.middlewareName = middleware.name;\n }\n }\n responses.push(...items);\n }\n }\n\n // Analyze the main handler for responses\n for (const { token, node } of returnTokens(handler, typeChecker)) {\n responses.push(...responseAnalyzer(handler, token, node));\n }\n\n paths.addPath(\n operationName,\n path,\n method,\n contentType\n ? ts.isStringLiteral(contentType)\n ? contentType.text\n : undefined\n : undefined,\n toSelectors(props),\n responses,\n sourceFile.fileName,\n {\n ...metadata,\n security: security\n ? middlewareSecurity(node.arguments.slice(1, -1), security, typeChecker)\n : undefined,\n },\n );\n\n function moveOn() {\n ts.forEachChild(node, (node) =>\n visit(node, responseAnalyzer, paths, typeChecker, typeDeriver, security),\n );\n }\n}\n\nfunction toSelectors(props: ts.PropertyAssignment[]) {\n const selectors: Selector[] = [];\n for (const prop of props) {\n if (!ts.isObjectLiteralExpression(prop.initializer)) {\n continue;\n }\n const name = prop.name.getText();\n const select = prop.initializer.properties\n .filter(ts.isPropertyAssignment)\n .find((prop) => prop.name.getText() === 'select');\n if (!select) {\n console.warn(\n `\\u26a0 No select found in ${name}\\n at ${nodeLocation(prop) ?? 'unknown'}`,\n );\n continue;\n }\n const against = prop.initializer.properties\n .filter(ts.isPropertyAssignment)\n .find((prop) => prop.name.getText() === 'against');\n if (!against) {\n console.warn(\n `\\u26a0 No against found in ${name}\\n at ${nodeLocation(prop) ?? 'unknown'}`,\n );\n continue;\n }\n const [, source, selectText] = select.initializer.getText().split('.');\n selectors.push({\n name: selectText,\n against: against.initializer.getText(),\n source: source as SemanticSource,\n });\n }\n return selectors;\n}\n\nexport async function analyze(\n tsconfigPath: string,\n config: {\n /**\n * Additional code to inject before resolving zod schemas\n */\n imports?: InjectImport[];\n typesMap?: Record<string, string>;\n responseAnalyzer: ResponseAnalyzerFn | NaunceResponseAnalyzer;\n onOperation?: OnOperation;\n securitySchemes?: Record<string, OpenAPISecuritySchemeObject>;\n middlewareSecurity?: readonly MiddlewareSecurityRule[];\n },\n) {\n logger(`Parsing tsconfig`);\n const program = getProgram(tsconfigPath);\n logger(`Program created`);\n const typeChecker = program.getTypeChecker();\n\n logger(`Type checker created`);\n const typeDeriver = new TypeDeriver(typeChecker, config.typesMap);\n const paths = new Paths({\n imports: config.imports ?? [],\n onOperation: config.onOperation,\n });\n const security: SecurityAnalysis | undefined = config.middlewareSecurity && {\n rules: config.middlewareSecurity,\n matched: new Set(),\n };\n\n for (const sourceFile of program.getSourceFiles()) {\n logger(`Analyzing ${sourceFile.fileName}`);\n if (!sourceFile.isDeclarationFile) {\n logger(`Visiting ${sourceFile.fileName}`);\n visit(\n sourceFile,\n (handler, token, node) => {\n const responseAnalyzer = config.responseAnalyzer;\n if (typeof responseAnalyzer !== 'function') {\n const naunce =\n responseAnalyzer[token] || responseAnalyzer['default'];\n if (!naunce) {\n throw new Error(`No response analyzer for token ${token}`);\n }\n return naunce(handler, typeDeriver, node);\n }\n return responseAnalyzer(handler, typeDeriver);\n },\n paths,\n typeChecker,\n typeDeriver,\n security,\n );\n }\n }\n\n for (const rule of security?.rules ?? []) {\n if (!security?.matched.has(rule)) {\n console.warn(\n `\u26A0 Security middleware ${rule.middleware.import} from ${rule.middleware.from} matched no route\\n routes it protects are documented as public`,\n );\n }\n }\n\n const components: OpenAPIComponentsObject = {\n schemas: {\n ...paths.getSharedSchemas(),\n ...Object.entries(typeDeriver.collector).reduce(\n (acc, [key, value]) => ({ ...acc, [key]: toSchema(value) }),\n {},\n ),\n },\n securitySchemes: config.securitySchemes,\n };\n\n return {\n paths: await paths.getPaths(),\n tags: paths.getTags(),\n components,\n };\n}\n\nexport type Serialized = ReturnType<typeof analyze>;\n\nfunction isFunctionWithBody(\n node: ts.Node | ts.Declaration | undefined,\n): node is ts.FunctionLikeDeclaration & { body: ts.Block | ts.Expression } {\n if (!node) {\n return false;\n }\n return (\n (ts.isFunctionDeclaration(node) ||\n ts.isFunctionExpression(node) ||\n ts.isArrowFunction(node) ||\n ts.isMethodDeclaration(node) ||\n ts.isConstructorDeclaration(node) ||\n ts.isGetAccessor(node) ||\n ts.isSetAccessor(node)) &&\n !!node.body\n );\n}\n", "import ts from 'typescript';\n\nimport type {\n NaunceResponseAnalyzer,\n ResponseItem,\n TypeDeriver,\n} from '@sdk-it/core';\n\nconst handlerVisitor: (\n on: (\n node: ts.Node | undefined,\n statusCode: ts.Node | undefined,\n headers: ts.Node | undefined,\n contentType: string,\n ) => void,\n) => ts.Visitor = (callback) => {\n return (node: ts.Node) => {\n if (ts.isReturnStatement(node) && node.expression) {\n if (\n ts.isCallExpression(node.expression) &&\n ts.isPropertyAccessExpression(node.expression.expression)\n ) {\n const propAccess = node.expression.expression;\n if (\n ts.isIdentifier(propAccess.expression) &&\n propAccess.expression.text === 'output'\n ) {\n let contentType = 'application/json';\n const callerMethod = propAccess.name.text;\n const [body, statusCode, headers] = node.expression.arguments;\n if (callerMethod === 'attachment') {\n contentType = 'application/octet-stream';\n }\n if (!body) {\n contentType = 'empty';\n }\n callback(body, statusCode, headers, contentType);\n }\n }\n }\n return ts.forEachChild(node, handlerVisitor(callback));\n };\n};\n\nfunction toResponses(\n handler: ts.ArrowFunction | ts.FunctionExpression,\n deriver: TypeDeriver,\n) {\n const responsesList: ResponseItem[] = [];\n const visit = handlerVisitor((node, statusCode, headers, contentType) => {\n responsesList.push({\n headers: headers ? Object.keys(deriver.serializeNode(headers)) : [],\n contentType,\n statusCode: statusCode ? resolveStatusCode(statusCode) : '200',\n response: node ? deriver.serializeNode(node) : undefined,\n });\n });\n visit(handler.body);\n return responsesList;\n}\n\nfunction resolveStatusCode(node: ts.Node) {\n if (ts.isNumericLiteral(node)) {\n return node.text;\n }\n throw new Error(`Could not resolve status code`);\n}\n\nexport function defaultResponseAnalyzer(\n handler: ts.ArrowFunction | ts.FunctionExpression,\n deriver: TypeDeriver,\n) {\n try {\n return toResponses(handler, deriver);\n } catch (error) {\n console.error('Error analyzing response\\n', handler.getText());\n throw error;\n }\n}\n\nexport const responseAnalyzer: NaunceResponseAnalyzer = {\n 'throw.new.ProblemDetailsException': (handler, deriver, node) => {\n if (ts.isNewExpression(node)) {\n const [problem] = node.arguments ?? [];\n if (!ts.isObjectLiteralExpression(problem)) {\n return [];\n }\n const properties = problem.properties.reduce<Record<string, string>>(\n (acc, prop) => {\n if (ts.isPropertyAssignment(prop)) {\n const key = prop.name.getText();\n if (ts.isLiteralExpression(prop.initializer)) {\n acc[key] = prop.initializer.text;\n } else {\n acc[key] = prop.initializer.getText();\n }\n }\n return acc;\n },\n {},\n );\n return [\n {\n contentType: 'application/problem+json',\n headers: [],\n statusCode: properties.status,\n response: deriver.serializeNode(problem),\n },\n ];\n }\n return [];\n },\n default: defaultResponseAnalyzer,\n};\n\nexport default responseAnalyzer;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["node", "tag", "responseAnalyzer", "prop", "ts", "visit"]
|
|
7
7
|
}
|
package/dist/lib/generic.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SecurityRequirementObject } from 'openapi3-ts/oas31';
|
|
2
2
|
import ts from 'typescript';
|
|
3
|
-
import { type InjectImport, type NaunceResponseAnalyzer, type OnOperation, type ResponseAnalyzerFn } from '@sdk-it/core';
|
|
3
|
+
import { type InjectImport, type NaunceResponseAnalyzer, type OnOperation, type OpenAPIComponentsObject, type OpenAPISecuritySchemeObject, type ResponseAnalyzerFn } from '@sdk-it/core';
|
|
4
|
+
export interface MiddlewareSecurityRule {
|
|
5
|
+
middleware: {
|
|
6
|
+
import: string;
|
|
7
|
+
from: string;
|
|
8
|
+
};
|
|
9
|
+
security: SecurityRequirementObject[];
|
|
10
|
+
values?: {
|
|
11
|
+
scheme: string;
|
|
12
|
+
argument: number;
|
|
13
|
+
mode: 'any' | 'all';
|
|
14
|
+
};
|
|
15
|
+
}
|
|
4
16
|
export declare const returnTokens: (node: ts.Node, typeChecker?: ts.TypeChecker, options?: {
|
|
5
17
|
consider3rdParty?: boolean;
|
|
6
18
|
maxDepth?: number;
|
|
@@ -16,10 +28,12 @@ export declare function analyze(tsconfigPath: string, config: {
|
|
|
16
28
|
typesMap?: Record<string, string>;
|
|
17
29
|
responseAnalyzer: ResponseAnalyzerFn | NaunceResponseAnalyzer;
|
|
18
30
|
onOperation?: OnOperation;
|
|
31
|
+
securitySchemes?: Record<string, OpenAPISecuritySchemeObject>;
|
|
32
|
+
middlewareSecurity?: readonly MiddlewareSecurityRule[];
|
|
19
33
|
}): Promise<{
|
|
20
34
|
paths: import("openapi3-ts/oas31").PathsObject;
|
|
21
35
|
tags: string[];
|
|
22
|
-
components:
|
|
36
|
+
components: OpenAPIComponentsObject;
|
|
23
37
|
}>;
|
|
24
38
|
export type Serialized = ReturnType<typeof analyze>;
|
|
25
39
|
//# sourceMappingURL=generic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/lib/generic.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/lib/generic.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAEhC,KAAK,kBAAkB,EAUxB,MAAM,cAAc,CAAC;AAsCtB,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;KACrB,CAAC;CACH;AA+OD,eAAO,MAAM,YAAY,GACvB,MAAM,EAAE,CAAC,IAAI,EACb,cAAc,EAAE,CAAC,WAAW,EAC5B,UAAU;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;WAEpC,MAAM;UAAQ,EAAE,CAAC,UAAU;GAsFnD,CAAC;AAgSF,wBAAsB,OAAO,CAC3B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE;IACN;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,gBAAgB,EAAE,kBAAkB,GAAG,sBAAsB,CAAC;IAC9D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACxD;;;;GAoEF;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdk-it/generic",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"!**/*.test.*"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@sdk-it/core": "0.46.
|
|
25
|
+
"@sdk-it/core": "0.46.4",
|
|
26
26
|
"stringcase": "^4.3.1",
|
|
27
27
|
"debug": "^4.4.0",
|
|
28
28
|
"openapi3-ts": "^4.4.0"
|