vite-plugin-openapi-codegen 1.2.2 → 3.0.0
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/dist/cli.mjs +105 -8
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +105 -8
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -52,6 +52,40 @@ function renderOperationTypeAliases(typeAliases) {
|
|
|
52
52
|
if (typeAliases.length === 0) return "";
|
|
53
53
|
return `${typeAliases.map((alias) => `export type ${alias.typeName} = ${alias.definitionExpr};`).join("\n")}\n`;
|
|
54
54
|
}
|
|
55
|
+
function renderAccessPoliciesSource(entries, generatedHeader) {
|
|
56
|
+
if (entries.length === 0) return "";
|
|
57
|
+
const lines = [
|
|
58
|
+
...generatedHeader,
|
|
59
|
+
"",
|
|
60
|
+
"export type AccessPolicyKind = \"authenticated\" | \"internal\" | \"public\" | \"role\";",
|
|
61
|
+
"",
|
|
62
|
+
"export interface AccessPolicy {",
|
|
63
|
+
" kind: AccessPolicyKind;",
|
|
64
|
+
" roles?: readonly string[];",
|
|
65
|
+
"}",
|
|
66
|
+
"",
|
|
67
|
+
"export interface OperationAccessPolicy extends AccessPolicy {",
|
|
68
|
+
" apiPath: string;",
|
|
69
|
+
" method: string;",
|
|
70
|
+
" operationId: string;",
|
|
71
|
+
" path: string;",
|
|
72
|
+
"}",
|
|
73
|
+
"",
|
|
74
|
+
"export const accessPolicies = {"
|
|
75
|
+
];
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
lines.push(` ${entry.funcName}: {`);
|
|
78
|
+
lines.push(` apiPath: ${JSON.stringify(entry.apiPath)},`);
|
|
79
|
+
lines.push(` kind: ${JSON.stringify(entry.kind)},`);
|
|
80
|
+
lines.push(` method: ${JSON.stringify(entry.methodUpper)},`);
|
|
81
|
+
lines.push(` operationId: ${JSON.stringify(entry.operationId)},`);
|
|
82
|
+
lines.push(` path: ${JSON.stringify(entry.strippedPath)},`);
|
|
83
|
+
if (entry.roles.length > 0) lines.push(` roles: [${entry.roles.map((role) => JSON.stringify(role)).join(", ")}],`);
|
|
84
|
+
lines.push(" },");
|
|
85
|
+
}
|
|
86
|
+
lines.push("} as const satisfies Record<string, OperationAccessPolicy>;", "", "export type AccessPolicyKey = keyof typeof accessPolicies;", "");
|
|
87
|
+
return `${lines.join("\n")}`;
|
|
88
|
+
}
|
|
55
89
|
function printGeneratedFile(statements, generatedHeader) {
|
|
56
90
|
const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
57
91
|
const printed = AST_PRINTER.printFile(sourceFile).trim();
|
|
@@ -162,13 +196,29 @@ function createBodyRequestProperty(bodyChannel, bodyAssignment) {
|
|
|
162
196
|
return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
|
|
163
197
|
}
|
|
164
198
|
function createBuildSearchParamsFunction() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
199
|
+
const queryIdentifier = ts.factory.createIdentifier("query");
|
|
200
|
+
const searchParamsIdentifier = ts.factory.createIdentifier("searchParams");
|
|
201
|
+
const hasParamsIdentifier = ts.factory.createIdentifier("hasParams");
|
|
202
|
+
const keyIdentifier = ts.factory.createIdentifier("key");
|
|
203
|
+
const valueIdentifier = ts.factory.createIdentifier("value");
|
|
204
|
+
const itemIdentifier = ts.factory.createIdentifier("item");
|
|
205
|
+
const isNullishOrEmptyString = (identifier) => ts.factory.createBinaryExpression(ts.factory.createBinaryExpression(identifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken), ts.factory.createNull()), ts.factory.createToken(ts.SyntaxKind.BarBarToken), ts.factory.createBinaryExpression(identifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createStringLiteral("")));
|
|
206
|
+
const assignHasParamsTrue = () => ts.factory.createExpressionStatement(ts.factory.createAssignment(hasParamsIdentifier, ts.factory.createTrue()));
|
|
207
|
+
return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, queryIdentifier, void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
|
|
208
|
+
ts.factory.createIfStatement(ts.factory.createBinaryExpression(queryIdentifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
|
|
209
|
+
ts.factory.createVariableStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(searchParamsIdentifier, void 0, void 0, ts.factory.createNewExpression(ts.factory.createIdentifier("URLSearchParams"), void 0, []))], ts.NodeFlags.Const)),
|
|
210
|
+
ts.factory.createVariableStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(hasParamsIdentifier, void 0, void 0, ts.factory.createFalse())], ts.NodeFlags.Let)),
|
|
211
|
+
ts.factory.createForOfStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(ts.factory.createArrayBindingPattern([ts.factory.createBindingElement(void 0, void 0, keyIdentifier), ts.factory.createBindingElement(void 0, void 0, valueIdentifier)]))], ts.NodeFlags.Const), parseExpression("Object.entries(query)"), ts.factory.createBlock([
|
|
212
|
+
ts.factory.createIfStatement(isNullishOrEmptyString(valueIdentifier), ts.factory.createContinueStatement()),
|
|
213
|
+
ts.factory.createIfStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("Array"), "isArray"), void 0, [valueIdentifier]), ts.factory.createBlock([ts.factory.createForOfStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(itemIdentifier)], ts.NodeFlags.Const), valueIdentifier, ts.factory.createBlock([
|
|
214
|
+
ts.factory.createIfStatement(isNullishOrEmptyString(itemIdentifier), ts.factory.createContinueStatement()),
|
|
215
|
+
ts.factory.createExpressionStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(searchParamsIdentifier, "append"), void 0, [keyIdentifier, ts.factory.createCallExpression(ts.factory.createIdentifier("String"), void 0, [itemIdentifier])])),
|
|
216
|
+
assignHasParamsTrue()
|
|
217
|
+
], true)), ts.factory.createContinueStatement()], true)),
|
|
218
|
+
ts.factory.createExpressionStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(searchParamsIdentifier, "set"), void 0, [keyIdentifier, ts.factory.createCallExpression(ts.factory.createIdentifier("String"), void 0, [valueIdentifier])])),
|
|
219
|
+
assignHasParamsTrue()
|
|
220
|
+
], true)),
|
|
221
|
+
ts.factory.createReturnStatement(ts.factory.createConditionalExpression(hasParamsIdentifier, ts.factory.createToken(ts.SyntaxKind.QuestionToken), searchParamsIdentifier, ts.factory.createToken(ts.SyntaxKind.ColonToken), ts.factory.createIdentifier("undefined")))
|
|
172
222
|
], true));
|
|
173
223
|
}
|
|
174
224
|
function capitalize$1(value) {
|
|
@@ -486,6 +536,7 @@ function renderPrimitiveSchemaType(schema) {
|
|
|
486
536
|
const type = schema?.type;
|
|
487
537
|
if (!type) return "unknown";
|
|
488
538
|
if (Array.isArray(type)) return type.map((memberType) => mapPrimitiveType(memberType)).join(" | ");
|
|
539
|
+
if (type === "array") return `${renderPrimitiveSchemaType(schema.items)}[]`;
|
|
489
540
|
return mapPrimitiveType(type);
|
|
490
541
|
}
|
|
491
542
|
function allocateOperationTypeName(context, funcName, suffix) {
|
|
@@ -596,6 +647,48 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
596
647
|
}))
|
|
597
648
|
};
|
|
598
649
|
}
|
|
650
|
+
const ACCESS_POLICY_KINDS = new Set([
|
|
651
|
+
"authenticated",
|
|
652
|
+
"internal",
|
|
653
|
+
"public",
|
|
654
|
+
"role"
|
|
655
|
+
]);
|
|
656
|
+
function createAccessPolicyEntries(operations) {
|
|
657
|
+
return operations.flatMap((entry) => {
|
|
658
|
+
const accessPolicy = readAccessPolicyExtension(entry);
|
|
659
|
+
if (!accessPolicy) return [];
|
|
660
|
+
return [{
|
|
661
|
+
apiPath: entry.apiPath,
|
|
662
|
+
funcName: entry.funcName,
|
|
663
|
+
kind: accessPolicy.kind,
|
|
664
|
+
methodUpper: entry.method.toUpperCase(),
|
|
665
|
+
operationId: entry.operationId,
|
|
666
|
+
roles: accessPolicy.roles ?? [],
|
|
667
|
+
strippedPath: entry.strippedPath
|
|
668
|
+
}];
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
function readAccessPolicyExtension(entry) {
|
|
672
|
+
const accessPolicy = entry.operation["x-access"];
|
|
673
|
+
if (accessPolicy == null) return null;
|
|
674
|
+
if (!isRecord(accessPolicy)) throw new Error(`Operation "${entry.operationId}" has invalid x-access extension`);
|
|
675
|
+
const kind = accessPolicy.kind;
|
|
676
|
+
if (typeof kind !== "string" || !ACCESS_POLICY_KINDS.has(kind)) throw new Error(`Operation "${entry.operationId}" has invalid x-access kind`);
|
|
677
|
+
const rolesValue = accessPolicy.roles;
|
|
678
|
+
if (rolesValue == null) {
|
|
679
|
+
if (kind === "role") throw new Error(`Operation "${entry.operationId}" role access requires x-access.roles`);
|
|
680
|
+
return { kind };
|
|
681
|
+
}
|
|
682
|
+
if (!Array.isArray(rolesValue) || rolesValue.some((role) => typeof role !== "string")) throw new Error(`Operation "${entry.operationId}" has invalid x-access.roles`);
|
|
683
|
+
if (kind !== "role") throw new Error(`Operation "${entry.operationId}" non-role access must not include roles`);
|
|
684
|
+
return {
|
|
685
|
+
kind,
|
|
686
|
+
roles: rolesValue
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function isRecord(value) {
|
|
690
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
691
|
+
}
|
|
599
692
|
function resolveChannel(channel, useTypeAliases) {
|
|
600
693
|
if (!channel.typeRef) return channel;
|
|
601
694
|
return {
|
|
@@ -611,14 +704,17 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
611
704
|
const stripPrefix = options.stripPrefix ?? true;
|
|
612
705
|
const httpClient = resolveHttpClientConfig(options.httpClient);
|
|
613
706
|
const useTypeAliases = options.typeAliases ?? false;
|
|
614
|
-
const
|
|
707
|
+
const operations = preCollectedOperations ?? collectOperations(spec, pathPrefix, stripPrefix);
|
|
708
|
+
const clientModel = buildClientRenderModelFromOperations(operations, spec, {
|
|
615
709
|
json: httpClient.jsonFunction,
|
|
616
710
|
void: httpClient.voidFunction
|
|
617
711
|
});
|
|
618
712
|
if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
|
|
713
|
+
const accessPolicies = renderAccessPoliciesSource(createAccessPolicyEntries(operations), GENERATED_HEADER);
|
|
619
714
|
return {
|
|
620
715
|
api: renderApiSource(createApiEntries(clientModel.operations, useTypeAliases), GENERATED_HEADER),
|
|
621
716
|
client: renderClientSource(createClientRenderModel(clientModel, useTypeAliases), GENERATED_HEADER, httpClient),
|
|
717
|
+
...accessPolicies ? { accessPolicies } : {},
|
|
622
718
|
...useTypeAliases && clientModel.typeAliases.length > 0 ? { apiTypes: renderOperationTypeAliases(clientModel.typeAliases) } : {}
|
|
623
719
|
};
|
|
624
720
|
}
|
|
@@ -664,6 +760,7 @@ async function generateOpenAPIArtifacts(root, options) {
|
|
|
664
760
|
warnOnParameterLocationMismatch(operations);
|
|
665
761
|
await generateApiTypes(apiTypesSource, outputDir, options.typeAliases ?? false);
|
|
666
762
|
if (artifacts.apiTypes) writeFileSync(resolve(outputDir, "api-types.d.ts"), artifacts.apiTypes, { flag: "a" });
|
|
763
|
+
if (artifacts.accessPolicies) writeFileSync(resolve(outputDir, "access-policies.ts"), artifacts.accessPolicies);
|
|
667
764
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
668
765
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
669
766
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,9 @@ interface OpenAPIParameter {
|
|
|
9
9
|
name: string;
|
|
10
10
|
required?: boolean;
|
|
11
11
|
schema?: {
|
|
12
|
+
items?: {
|
|
13
|
+
type?: string | string[];
|
|
14
|
+
};
|
|
12
15
|
type?: string | string[];
|
|
13
16
|
};
|
|
14
17
|
}
|
|
@@ -23,15 +26,22 @@ interface OpenAPIResponse {
|
|
|
23
26
|
content?: Record<string, OpenAPIContent>;
|
|
24
27
|
description?: string;
|
|
25
28
|
}
|
|
29
|
+
type OpenAPIAccessKind = "authenticated" | "internal" | "public" | "role";
|
|
30
|
+
interface OpenAPIAccessExtension {
|
|
31
|
+
kind: OpenAPIAccessKind;
|
|
32
|
+
roles?: string[];
|
|
33
|
+
}
|
|
26
34
|
interface OpenAPIOperation {
|
|
27
35
|
operationId?: string;
|
|
28
36
|
parameters?: OpenAPIParameter[];
|
|
29
37
|
requestBody?: OpenAPIRequestBody;
|
|
30
38
|
responses?: Record<string, OpenAPIResponse>;
|
|
31
39
|
tags?: string[];
|
|
40
|
+
"x-access"?: OpenAPIAccessExtension;
|
|
32
41
|
}
|
|
33
42
|
type OpenAPIPathItem = Partial<Record<HttpMethod, OpenAPIOperation>>;
|
|
34
43
|
interface OpenAPISchema {
|
|
44
|
+
items?: unknown;
|
|
35
45
|
properties?: Record<string, unknown>;
|
|
36
46
|
required?: string[];
|
|
37
47
|
type?: string;
|
|
@@ -86,6 +96,7 @@ interface Options {
|
|
|
86
96
|
interface GeneratedArtifacts {
|
|
87
97
|
api: string;
|
|
88
98
|
apiTypes?: string;
|
|
99
|
+
accessPolicies?: string;
|
|
89
100
|
client: string;
|
|
90
101
|
}
|
|
91
102
|
declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "pathPrefix" | "stripPrefix" | "typeAliases">, preCollectedOperations?: OperationEntry[]): GeneratedArtifacts;
|
package/dist/index.mjs
CHANGED
|
@@ -50,6 +50,40 @@ function renderOperationTypeAliases(typeAliases) {
|
|
|
50
50
|
if (typeAliases.length === 0) return "";
|
|
51
51
|
return `${typeAliases.map((alias) => `export type ${alias.typeName} = ${alias.definitionExpr};`).join("\n")}\n`;
|
|
52
52
|
}
|
|
53
|
+
function renderAccessPoliciesSource(entries, generatedHeader) {
|
|
54
|
+
if (entries.length === 0) return "";
|
|
55
|
+
const lines = [
|
|
56
|
+
...generatedHeader,
|
|
57
|
+
"",
|
|
58
|
+
"export type AccessPolicyKind = \"authenticated\" | \"internal\" | \"public\" | \"role\";",
|
|
59
|
+
"",
|
|
60
|
+
"export interface AccessPolicy {",
|
|
61
|
+
" kind: AccessPolicyKind;",
|
|
62
|
+
" roles?: readonly string[];",
|
|
63
|
+
"}",
|
|
64
|
+
"",
|
|
65
|
+
"export interface OperationAccessPolicy extends AccessPolicy {",
|
|
66
|
+
" apiPath: string;",
|
|
67
|
+
" method: string;",
|
|
68
|
+
" operationId: string;",
|
|
69
|
+
" path: string;",
|
|
70
|
+
"}",
|
|
71
|
+
"",
|
|
72
|
+
"export const accessPolicies = {"
|
|
73
|
+
];
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
lines.push(` ${entry.funcName}: {`);
|
|
76
|
+
lines.push(` apiPath: ${JSON.stringify(entry.apiPath)},`);
|
|
77
|
+
lines.push(` kind: ${JSON.stringify(entry.kind)},`);
|
|
78
|
+
lines.push(` method: ${JSON.stringify(entry.methodUpper)},`);
|
|
79
|
+
lines.push(` operationId: ${JSON.stringify(entry.operationId)},`);
|
|
80
|
+
lines.push(` path: ${JSON.stringify(entry.strippedPath)},`);
|
|
81
|
+
if (entry.roles.length > 0) lines.push(` roles: [${entry.roles.map((role) => JSON.stringify(role)).join(", ")}],`);
|
|
82
|
+
lines.push(" },");
|
|
83
|
+
}
|
|
84
|
+
lines.push("} as const satisfies Record<string, OperationAccessPolicy>;", "", "export type AccessPolicyKey = keyof typeof accessPolicies;", "");
|
|
85
|
+
return `${lines.join("\n")}`;
|
|
86
|
+
}
|
|
53
87
|
function printGeneratedFile(statements, generatedHeader) {
|
|
54
88
|
const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
55
89
|
const printed = AST_PRINTER.printFile(sourceFile).trim();
|
|
@@ -160,13 +194,29 @@ function createBodyRequestProperty(bodyChannel, bodyAssignment) {
|
|
|
160
194
|
return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
|
|
161
195
|
}
|
|
162
196
|
function createBuildSearchParamsFunction() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
const queryIdentifier = ts.factory.createIdentifier("query");
|
|
198
|
+
const searchParamsIdentifier = ts.factory.createIdentifier("searchParams");
|
|
199
|
+
const hasParamsIdentifier = ts.factory.createIdentifier("hasParams");
|
|
200
|
+
const keyIdentifier = ts.factory.createIdentifier("key");
|
|
201
|
+
const valueIdentifier = ts.factory.createIdentifier("value");
|
|
202
|
+
const itemIdentifier = ts.factory.createIdentifier("item");
|
|
203
|
+
const isNullishOrEmptyString = (identifier) => ts.factory.createBinaryExpression(ts.factory.createBinaryExpression(identifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken), ts.factory.createNull()), ts.factory.createToken(ts.SyntaxKind.BarBarToken), ts.factory.createBinaryExpression(identifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createStringLiteral("")));
|
|
204
|
+
const assignHasParamsTrue = () => ts.factory.createExpressionStatement(ts.factory.createAssignment(hasParamsIdentifier, ts.factory.createTrue()));
|
|
205
|
+
return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, queryIdentifier, void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
|
|
206
|
+
ts.factory.createIfStatement(ts.factory.createBinaryExpression(queryIdentifier, ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
|
|
207
|
+
ts.factory.createVariableStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(searchParamsIdentifier, void 0, void 0, ts.factory.createNewExpression(ts.factory.createIdentifier("URLSearchParams"), void 0, []))], ts.NodeFlags.Const)),
|
|
208
|
+
ts.factory.createVariableStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(hasParamsIdentifier, void 0, void 0, ts.factory.createFalse())], ts.NodeFlags.Let)),
|
|
209
|
+
ts.factory.createForOfStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(ts.factory.createArrayBindingPattern([ts.factory.createBindingElement(void 0, void 0, keyIdentifier), ts.factory.createBindingElement(void 0, void 0, valueIdentifier)]))], ts.NodeFlags.Const), parseExpression("Object.entries(query)"), ts.factory.createBlock([
|
|
210
|
+
ts.factory.createIfStatement(isNullishOrEmptyString(valueIdentifier), ts.factory.createContinueStatement()),
|
|
211
|
+
ts.factory.createIfStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("Array"), "isArray"), void 0, [valueIdentifier]), ts.factory.createBlock([ts.factory.createForOfStatement(void 0, ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(itemIdentifier)], ts.NodeFlags.Const), valueIdentifier, ts.factory.createBlock([
|
|
212
|
+
ts.factory.createIfStatement(isNullishOrEmptyString(itemIdentifier), ts.factory.createContinueStatement()),
|
|
213
|
+
ts.factory.createExpressionStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(searchParamsIdentifier, "append"), void 0, [keyIdentifier, ts.factory.createCallExpression(ts.factory.createIdentifier("String"), void 0, [itemIdentifier])])),
|
|
214
|
+
assignHasParamsTrue()
|
|
215
|
+
], true)), ts.factory.createContinueStatement()], true)),
|
|
216
|
+
ts.factory.createExpressionStatement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(searchParamsIdentifier, "set"), void 0, [keyIdentifier, ts.factory.createCallExpression(ts.factory.createIdentifier("String"), void 0, [valueIdentifier])])),
|
|
217
|
+
assignHasParamsTrue()
|
|
218
|
+
], true)),
|
|
219
|
+
ts.factory.createReturnStatement(ts.factory.createConditionalExpression(hasParamsIdentifier, ts.factory.createToken(ts.SyntaxKind.QuestionToken), searchParamsIdentifier, ts.factory.createToken(ts.SyntaxKind.ColonToken), ts.factory.createIdentifier("undefined")))
|
|
170
220
|
], true));
|
|
171
221
|
}
|
|
172
222
|
function capitalize$1(value) {
|
|
@@ -484,6 +534,7 @@ function renderPrimitiveSchemaType(schema) {
|
|
|
484
534
|
const type = schema?.type;
|
|
485
535
|
if (!type) return "unknown";
|
|
486
536
|
if (Array.isArray(type)) return type.map((memberType) => mapPrimitiveType(memberType)).join(" | ");
|
|
537
|
+
if (type === "array") return `${renderPrimitiveSchemaType(schema.items)}[]`;
|
|
487
538
|
return mapPrimitiveType(type);
|
|
488
539
|
}
|
|
489
540
|
function allocateOperationTypeName(context, funcName, suffix) {
|
|
@@ -594,6 +645,48 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
594
645
|
}))
|
|
595
646
|
};
|
|
596
647
|
}
|
|
648
|
+
const ACCESS_POLICY_KINDS = new Set([
|
|
649
|
+
"authenticated",
|
|
650
|
+
"internal",
|
|
651
|
+
"public",
|
|
652
|
+
"role"
|
|
653
|
+
]);
|
|
654
|
+
function createAccessPolicyEntries(operations) {
|
|
655
|
+
return operations.flatMap((entry) => {
|
|
656
|
+
const accessPolicy = readAccessPolicyExtension(entry);
|
|
657
|
+
if (!accessPolicy) return [];
|
|
658
|
+
return [{
|
|
659
|
+
apiPath: entry.apiPath,
|
|
660
|
+
funcName: entry.funcName,
|
|
661
|
+
kind: accessPolicy.kind,
|
|
662
|
+
methodUpper: entry.method.toUpperCase(),
|
|
663
|
+
operationId: entry.operationId,
|
|
664
|
+
roles: accessPolicy.roles ?? [],
|
|
665
|
+
strippedPath: entry.strippedPath
|
|
666
|
+
}];
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function readAccessPolicyExtension(entry) {
|
|
670
|
+
const accessPolicy = entry.operation["x-access"];
|
|
671
|
+
if (accessPolicy == null) return null;
|
|
672
|
+
if (!isRecord(accessPolicy)) throw new Error(`Operation "${entry.operationId}" has invalid x-access extension`);
|
|
673
|
+
const kind = accessPolicy.kind;
|
|
674
|
+
if (typeof kind !== "string" || !ACCESS_POLICY_KINDS.has(kind)) throw new Error(`Operation "${entry.operationId}" has invalid x-access kind`);
|
|
675
|
+
const rolesValue = accessPolicy.roles;
|
|
676
|
+
if (rolesValue == null) {
|
|
677
|
+
if (kind === "role") throw new Error(`Operation "${entry.operationId}" role access requires x-access.roles`);
|
|
678
|
+
return { kind };
|
|
679
|
+
}
|
|
680
|
+
if (!Array.isArray(rolesValue) || rolesValue.some((role) => typeof role !== "string")) throw new Error(`Operation "${entry.operationId}" has invalid x-access.roles`);
|
|
681
|
+
if (kind !== "role") throw new Error(`Operation "${entry.operationId}" non-role access must not include roles`);
|
|
682
|
+
return {
|
|
683
|
+
kind,
|
|
684
|
+
roles: rolesValue
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function isRecord(value) {
|
|
688
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
689
|
+
}
|
|
597
690
|
function resolveChannel(channel, useTypeAliases) {
|
|
598
691
|
if (!channel.typeRef) return channel;
|
|
599
692
|
return {
|
|
@@ -609,14 +702,17 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
609
702
|
const stripPrefix = options.stripPrefix ?? true;
|
|
610
703
|
const httpClient = resolveHttpClientConfig(options.httpClient);
|
|
611
704
|
const useTypeAliases = options.typeAliases ?? false;
|
|
612
|
-
const
|
|
705
|
+
const operations = preCollectedOperations ?? collectOperations(spec, pathPrefix, stripPrefix);
|
|
706
|
+
const clientModel = buildClientRenderModelFromOperations(operations, spec, {
|
|
613
707
|
json: httpClient.jsonFunction,
|
|
614
708
|
void: httpClient.voidFunction
|
|
615
709
|
});
|
|
616
710
|
if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
|
|
711
|
+
const accessPolicies = renderAccessPoliciesSource(createAccessPolicyEntries(operations), GENERATED_HEADER);
|
|
617
712
|
return {
|
|
618
713
|
api: renderApiSource(createApiEntries(clientModel.operations, useTypeAliases), GENERATED_HEADER),
|
|
619
714
|
client: renderClientSource(createClientRenderModel(clientModel, useTypeAliases), GENERATED_HEADER, httpClient),
|
|
715
|
+
...accessPolicies ? { accessPolicies } : {},
|
|
620
716
|
...useTypeAliases && clientModel.typeAliases.length > 0 ? { apiTypes: renderOperationTypeAliases(clientModel.typeAliases) } : {}
|
|
621
717
|
};
|
|
622
718
|
}
|
|
@@ -662,6 +758,7 @@ async function generateOpenAPIArtifacts(root, options) {
|
|
|
662
758
|
warnOnParameterLocationMismatch(operations);
|
|
663
759
|
await generateApiTypes(apiTypesSource, outputDir, options.typeAliases ?? false);
|
|
664
760
|
if (artifacts.apiTypes) writeFileSync(resolve(outputDir, "api-types.d.ts"), artifacts.apiTypes, { flag: "a" });
|
|
761
|
+
if (artifacts.accessPolicies) writeFileSync(resolve(outputDir, "access-policies.ts"), artifacts.accessPolicies);
|
|
665
762
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
666
763
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
667
764
|
}
|