svelte-reflector 1.3.2 → 1.3.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.
@@ -0,0 +1,23 @@
1
+ import type { Method } from "../../method.js";
2
+ import type { ModuleImports } from "../module/ModuleImports.js";
3
+ import type { ModuleClassBuilder } from "../module/ModuleClassBuilder.js";
4
+ export interface ApiEndpointBlock {
5
+ paramCode: string;
6
+ classCode: string;
7
+ schemaEntries: Set<string>;
8
+ }
9
+ export declare class ApiClassBuilder {
10
+ private readonly imports;
11
+ private readonly methodGenerator;
12
+ private readonly paramProcessor;
13
+ constructor(params: {
14
+ imports: ModuleImports;
15
+ classBuilder: ModuleClassBuilder;
16
+ });
17
+ build(params: {
18
+ method: Method;
19
+ }): ApiEndpointBlock | null;
20
+ private buildStateProperties;
21
+ private buildResetLines;
22
+ private shouldSkipMethod;
23
+ }
@@ -0,0 +1,125 @@
1
+ import { capitalizeFirstLetter, createDangerMessage, treatByUppercase } from "../../helpers/helpers.js";
2
+ import { ApiMethodGenerator } from "./ApiMethodGenerator.js";
3
+ import { ApiParamProcessor } from "./ApiParamProcessor.js";
4
+ export class ApiClassBuilder {
5
+ imports;
6
+ methodGenerator = new ApiMethodGenerator();
7
+ paramProcessor;
8
+ constructor(params) {
9
+ this.imports = params.imports;
10
+ this.paramProcessor = new ApiParamProcessor({
11
+ imports: params.imports,
12
+ classBuilder: params.classBuilder,
13
+ });
14
+ }
15
+ build(params) {
16
+ const { method } = params;
17
+ if (this.shouldSkipMethod(method))
18
+ return null;
19
+ const { request, headers, cookies, paths, querys } = method;
20
+ const { bodyType, responseType, attributeType, isPrimitiveResponse } = request;
21
+ // Process per-endpoint params
22
+ const processedParams = this.paramProcessor.process({
23
+ methodName: capitalizeFirstLetter(method.name),
24
+ querys,
25
+ paths,
26
+ headers,
27
+ cookies,
28
+ });
29
+ // Build state properties
30
+ const stateProps = this.buildStateProperties(method);
31
+ // Build the call method
32
+ const callMethod = this.methodGenerator.generate(method);
33
+ // Build reset method
34
+ const resetLines = this.buildResetLines(method, processedParams.paramReset);
35
+ // Collect schema entries
36
+ const schemaEntries = new Set();
37
+ if (bodyType) {
38
+ schemaEntries.add(bodyType);
39
+ }
40
+ if (responseType && responseType !== "response" && !isPrimitiveResponse) {
41
+ schemaEntries.add(`type ${responseType}Interface`);
42
+ schemaEntries.add(responseType);
43
+ }
44
+ // Handle form imports
45
+ if (attributeType === "form" && bodyType) {
46
+ this.imports.addReflectorImport("isFormValid");
47
+ }
48
+ // Handle list imports
49
+ if (attributeType === "list") {
50
+ this.imports.addReflectorImport("genericArrayBundler");
51
+ }
52
+ const className = capitalizeFirstLetter(method.name);
53
+ const classCode = `
54
+ export class ${className} {
55
+ ${stateProps.join(";")}
56
+ ${processedParams.paramAttributes.map((a) => `${a};`).join("\n ")}
57
+
58
+ ${callMethod}
59
+
60
+ reset() {
61
+ ${resetLines.join(";")}
62
+ }
63
+ }
64
+ `;
65
+ const paramCode = processedParams.paramClasses.join("\n");
66
+ return { paramCode, classCode, schemaEntries };
67
+ }
68
+ buildStateProperties(method) {
69
+ const { attributeType, responseType, bodyType, isPrimitiveResponse } = method.request;
70
+ const props = ["loading = $state<boolean>(false)"];
71
+ if (attributeType === "form" && bodyType) {
72
+ // For form endpoints, the form IS the data
73
+ props.push(`form = new ${bodyType}()`);
74
+ }
75
+ else if (attributeType === "list") {
76
+ props.push(`data = $state<${responseType}['data']>([])`);
77
+ props.push("totalPages = $state<number>(1)");
78
+ }
79
+ else if (attributeType === "entity" && responseType && !isPrimitiveResponse) {
80
+ props.push(`data = $state<${responseType} | undefined>()`);
81
+ }
82
+ else {
83
+ props.push("data = $state<unknown>(undefined)");
84
+ }
85
+ return props;
86
+ }
87
+ buildResetLines(method, paramReset) {
88
+ const { attributeType, bodyType } = method.request;
89
+ const lines = [];
90
+ if (attributeType === "form" && bodyType) {
91
+ lines.push(`this.form = new ${bodyType}()`);
92
+ }
93
+ else if (attributeType === "list") {
94
+ lines.push("this.data = []");
95
+ lines.push("this.totalPages = 1");
96
+ }
97
+ else {
98
+ lines.push("this.data = undefined");
99
+ }
100
+ lines.push(...paramReset);
101
+ return lines;
102
+ }
103
+ shouldSkipMethod(method) {
104
+ const { bodyType, responseType, attributeType } = method.request;
105
+ if (bodyType === "string") {
106
+ createDangerMessage(`Method ${method.name} was skipped because it has an invalid body.`);
107
+ return true;
108
+ }
109
+ const isNullResponse = !responseType || responseType === "null";
110
+ if ((attributeType === "entity" || attributeType === "list") && isNullResponse) {
111
+ createDangerMessage(`Method ${method.name} was skipped because it has a null response.`);
112
+ return true;
113
+ }
114
+ const endpointParams = [...method.endpoint.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).filter(Boolean);
115
+ if (endpointParams.length > 0) {
116
+ const declaredPaths = new Set(method.paths.map((p) => p.name));
117
+ const undeclared = endpointParams.filter((p) => !declaredPaths.has(p));
118
+ if (undeclared.length > 0) {
119
+ createDangerMessage(`Method ${method.name} was skipped because it has undeclared path params: ${undeclared.join(", ")}`);
120
+ return true;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ }
@@ -0,0 +1,12 @@
1
+ import type { ModuleImports } from "../module/ModuleImports.js";
2
+ import type { ApiEndpointBlock } from "./ApiClassBuilder.js";
3
+ export declare class ApiFileBuilder {
4
+ private readonly imports;
5
+ constructor(params: {
6
+ imports: ModuleImports;
7
+ });
8
+ build(params: {
9
+ endpointBlocks: ApiEndpointBlock[];
10
+ classImports: string;
11
+ }): string;
12
+ }
@@ -0,0 +1,24 @@
1
+ export class ApiFileBuilder {
2
+ imports;
3
+ constructor(params) {
4
+ this.imports = params.imports;
5
+ }
6
+ build(params) {
7
+ const { endpointBlocks, classImports } = params;
8
+ const reflectorImports = this.imports.buildReflectorImportsLine();
9
+ const enumImports = this.imports.buildEnumImportsLine();
10
+ const mockedImports = this.imports.buildMockedImportsLine();
11
+ const sections = endpointBlocks.map((block) => {
12
+ return `${block.paramCode}\n${block.classCode}`;
13
+ });
14
+ return `
15
+ ${this.imports.getImportsArray().join(";")}
16
+ ${reflectorImports}
17
+ ${mockedImports}
18
+ ${enumImports}
19
+ ${classImports}
20
+
21
+ ${sections.join("\n\n")}
22
+ `;
23
+ }
24
+ }
@@ -0,0 +1,17 @@
1
+ import type { Method } from "../../method.js";
2
+ export declare class ApiMethodGenerator {
3
+ private readonly endpointBuilder;
4
+ generate(method: Method): string;
5
+ private buildProps;
6
+ private buildPathsInfo;
7
+ private buildApiCall;
8
+ private buildListCall;
9
+ private buildEntityCall;
10
+ private buildFormCall;
11
+ private buildDeleteCall;
12
+ private buildParamsType;
13
+ private buildDescription;
14
+ private buildMethodReturn;
15
+ private joinProps;
16
+ private buildQuerys;
17
+ }
@@ -0,0 +1,176 @@
1
+ import { MethodEndpointBuilder } from "../method/MethodEndpointBuilder.js";
2
+ export class ApiMethodGenerator {
3
+ endpointBuilder = new MethodEndpointBuilder();
4
+ generate(method) {
5
+ const description = this.buildDescription(method);
6
+ const endpoint = this.endpointBuilder.build(method.endpoint, method.paths);
7
+ const apiCall = this.buildApiCall(method);
8
+ const props = this.buildProps(method);
9
+ const pathsInfo = this.buildPathsInfo(method);
10
+ const paramsType = this.buildParamsType(method, pathsInfo);
11
+ return `
12
+ ${description}
13
+ async call(params?: ${paramsType}) {
14
+
15
+ const behavior = params?.behavior ?? new Behavior();
16
+ const { onError, onSuccess } = behavior;
17
+
18
+ this.loading = true;
19
+ ${props}
20
+ const endpoint = ${endpoint}
21
+
22
+ ${apiCall.outside}
23
+
24
+ try {
25
+ ${apiCall.inside}
26
+ await onSuccess?.(response);
27
+
28
+ return ${this.buildMethodReturn(method)};
29
+ } catch (e) {
30
+ let parsedError: ApiErrorResponse;
31
+ try {
32
+ parsedError = JSON.parse((e as Error).message) as ApiErrorResponse;
33
+ } catch {
34
+ parsedError = { error: 'unknown', message: (e as Error).message ?? String(e) };
35
+ }
36
+ return await onError?.(parsedError);
37
+ } finally {
38
+ this.loading = false;
39
+ }
40
+ }
41
+ `;
42
+ }
43
+ buildProps(method) {
44
+ const { querys, paths, cookies } = method;
45
+ const lines = [];
46
+ if (querys.length > 0) {
47
+ lines.push(`const { ${querys.map((x) => x.name).join(", ")} } = this.querys.bundle()`);
48
+ }
49
+ if (paths.length > 0) {
50
+ lines.push(`const { ${paths.map((x) => x.name).join(", ")} } = params?.paths ?? this.paths`);
51
+ }
52
+ if (cookies.length > 0) {
53
+ lines.push(`const cookies = this.cookies`);
54
+ }
55
+ return lines.join("\n");
56
+ }
57
+ buildPathsInfo(method) {
58
+ const { paths } = method;
59
+ if (paths.length === 0)
60
+ return undefined;
61
+ return `{ ${paths
62
+ .map((p) => {
63
+ const type = p.rawType ?? p.type;
64
+ return `${p.name}: ${type}`;
65
+ })
66
+ .join("; ")} }`;
67
+ }
68
+ buildApiCall(method) {
69
+ const { attributeType, apiType, responseType, bodyType, hasEnumResponse } = method.request;
70
+ const responseTypeStr = hasEnumResponse && responseType ? responseType : method.responseTypeInterface;
71
+ if (attributeType === "list") {
72
+ return this.buildListCall(method, responseTypeStr);
73
+ }
74
+ if (attributeType === "entity") {
75
+ return this.buildEntityCall(method, responseTypeStr);
76
+ }
77
+ if (apiType === "post" || apiType === "put" || apiType === "patch") {
78
+ return this.buildFormCall(method, responseTypeStr, bodyType);
79
+ }
80
+ if (apiType === "delete") {
81
+ return this.buildDeleteCall(responseTypeStr);
82
+ }
83
+ return { inside: "", outside: "" };
84
+ }
85
+ buildListCall(method, responseType) {
86
+ const querys = this.joinProps(method.querys);
87
+ const inside = `
88
+ const response = await api.get<${responseType}, unknown>({
89
+ endpoint,
90
+ queryData: { ${querys} }
91
+ })
92
+ this.data = ${method.request.responseType}.from(response.data);
93
+ this.totalPages = response.totalPages;
94
+ `;
95
+ return { inside, outside: "" };
96
+ }
97
+ buildEntityCall(method, responseType) {
98
+ const rType = method.request.responseType;
99
+ const isPrimitive = method.request.isPrimitiveResponse;
100
+ const querys = this.buildQuerys(method.querys);
101
+ const buildedThisResponseType = rType && !isPrimitive
102
+ ? `this.data = new ${rType}({ data: response })`
103
+ : "";
104
+ const inside = `
105
+ const response = await api.get<${responseType}, unknown>({
106
+ endpoint,
107
+ ${querys}
108
+ })
109
+
110
+ ${buildedThisResponseType}
111
+ `;
112
+ return { inside, outside: "" };
113
+ }
114
+ buildFormCall(method, responseType, bodyType) {
115
+ const { apiType } = method.request;
116
+ const hasHeaders = method.headers.length > 0;
117
+ const hasData = !!bodyType;
118
+ const outside = [];
119
+ if (hasData) {
120
+ outside.push(`const data = this.form.bundle()`);
121
+ }
122
+ if (hasHeaders) {
123
+ outside.push(`const headers = this.headers.bundle()`);
124
+ }
125
+ const inside = `
126
+ const response = await api.${apiType}<${responseType}>({
127
+ endpoint,
128
+ ${hasData ? "data," : ""}
129
+ ${hasHeaders ? "headers," : ""}
130
+ })
131
+ `;
132
+ return { inside, outside: outside.join("\n") };
133
+ }
134
+ buildDeleteCall(responseType) {
135
+ const inside = `
136
+ const response = await api.delete<${responseType}, unknown>({
137
+ endpoint,
138
+ })
139
+ `;
140
+ return { inside, outside: "" };
141
+ }
142
+ buildParamsType(method, paramsPaths) {
143
+ const responseType = method.responseTypeInterface;
144
+ if (paramsPaths) {
145
+ return `ApiCallParams<${responseType}, ${paramsPaths}>`;
146
+ }
147
+ return `ApiCallParams<${responseType}>`;
148
+ }
149
+ buildDescription(method) {
150
+ return `/** ${method.description ?? ""} */`;
151
+ }
152
+ buildMethodReturn(method) {
153
+ const { attributeType, responseType, hasEnumResponse, isPrimitiveResponse } = method.request;
154
+ if (attributeType === "list") {
155
+ return "this.data";
156
+ }
157
+ if (!responseType) {
158
+ return "null";
159
+ }
160
+ if (hasEnumResponse) {
161
+ return "response.data";
162
+ }
163
+ if (isPrimitiveResponse) {
164
+ return "response";
165
+ }
166
+ return `new ${responseType}({ data: response })`;
167
+ }
168
+ joinProps(props) {
169
+ return props.map((x) => x.name).join(",");
170
+ }
171
+ buildQuerys(querys) {
172
+ if (querys.length === 0)
173
+ return "";
174
+ return `queryData: {${querys.map((q) => q.name).join(",")}}`;
175
+ }
176
+ }
@@ -0,0 +1,24 @@
1
+ import type { AttributeProp } from "../../types/types.js";
2
+ import type { PrimitiveProp } from "../../props/primitive.property.js";
3
+ import type { ModuleImports } from "../module/ModuleImports.js";
4
+ import type { ModuleClassBuilder } from "../module/ModuleClassBuilder.js";
5
+ export interface ApiProcessedParams {
6
+ paramClasses: string[];
7
+ paramAttributes: string[];
8
+ paramReset: string[];
9
+ }
10
+ export declare class ApiParamProcessor {
11
+ private readonly imports;
12
+ private readonly classBuilder;
13
+ constructor(params: {
14
+ imports: ModuleImports;
15
+ classBuilder: ModuleClassBuilder;
16
+ });
17
+ process(params: {
18
+ methodName: string;
19
+ querys: AttributeProp[];
20
+ paths: PrimitiveProp[];
21
+ headers: PrimitiveProp[];
22
+ cookies: PrimitiveProp[];
23
+ }): ApiProcessedParams;
24
+ }
@@ -0,0 +1,36 @@
1
+ import { capitalizeFirstLetter } from "../../helpers/helpers.js";
2
+ export class ApiParamProcessor {
3
+ imports;
4
+ classBuilder;
5
+ constructor(params) {
6
+ this.imports = params.imports;
7
+ this.classBuilder = params.classBuilder;
8
+ }
9
+ process(params) {
10
+ const { methodName, cookies, headers, paths, querys } = params;
11
+ const paramClasses = [];
12
+ const paramAttributes = [];
13
+ const paramReset = [];
14
+ const processParam = (paramData) => {
15
+ const { paramType, attrName, props } = paramData;
16
+ const className = `${capitalizeFirstLetter(methodName)}${capitalizeFirstLetter(paramType)}`;
17
+ paramClasses.push(this.classBuilder.buildClassProps({ props, name: paramType, className }));
18
+ paramAttributes.push(`${attrName} = new ${className}()`);
19
+ paramReset.push(`this.${attrName} = new ${className}()`);
20
+ };
21
+ const argEntries = [
22
+ { attrName: "querys", props: querys },
23
+ { attrName: "headers", props: headers },
24
+ { attrName: "paths", props: paths },
25
+ { attrName: "cookies", props: cookies },
26
+ ];
27
+ for (const { attrName, props } of argEntries) {
28
+ if (!props.length)
29
+ continue;
30
+ this.imports.addReflectorImport("QueryBuilder");
31
+ const paramType = capitalizeFirstLetter(attrName);
32
+ processParam({ paramType, attrName, props });
33
+ }
34
+ return { paramClasses, paramAttributes, paramReset };
35
+ }
36
+ }
@@ -4,6 +4,10 @@ export { ModuleParamProcessor, type ProcessedParams } from "./module/ModuleParam
4
4
  export { ModuleClassBuilder } from "./module/ModuleClassBuilder.js";
5
5
  export { ModuleConstructorBuilder, type Form } from "./module/ModuleConstructorBuilder.js";
6
6
  export { ModuleFileBuilder, type FileBuildParams } from "./module/ModuleFileBuilder.js";
7
+ export { ApiFileBuilder } from "./api/ApiFileBuilder.js";
8
+ export { ApiClassBuilder, type ApiEndpointBlock } from "./api/ApiClassBuilder.js";
9
+ export { ApiMethodGenerator } from "./api/ApiMethodGenerator.js";
10
+ export { ApiParamProcessor, type ApiProcessedParams } from "./api/ApiParamProcessor.js";
7
11
  export { Method } from "./method/Method.js";
8
12
  export { MethodBuilder } from "./method/MethodBuilder.js";
9
13
  export { MethodApiTypeAnalyzer } from "./method/MethodApiTypeAnalyzer.js";
@@ -5,6 +5,11 @@ export { ModuleParamProcessor } from "./module/ModuleParamProcessor.js";
5
5
  export { ModuleClassBuilder } from "./module/ModuleClassBuilder.js";
6
6
  export { ModuleConstructorBuilder } from "./module/ModuleConstructorBuilder.js";
7
7
  export { ModuleFileBuilder } from "./module/ModuleFileBuilder.js";
8
+ // Api-related exports
9
+ export { ApiFileBuilder } from "./api/ApiFileBuilder.js";
10
+ export { ApiClassBuilder } from "./api/ApiClassBuilder.js";
11
+ export { ApiMethodGenerator } from "./api/ApiMethodGenerator.js";
12
+ export { ApiParamProcessor } from "./api/ApiParamProcessor.js";
8
13
  // Method-related exports
9
14
  export { Method } from "./method/Method.js";
10
15
  export { MethodBuilder } from "./method/MethodBuilder.js";
@@ -11,6 +11,7 @@ export interface MethodAnalyzers {
11
11
  apiType: ApiType;
12
12
  parameters: any[];
13
13
  hasEnumResponse: boolean;
14
+ isPrimitiveResponse: boolean;
14
15
  };
15
16
  props: {
16
17
  paths: PrimitiveProp[];
@@ -24,10 +24,11 @@ export class MethodBuilder {
24
24
  apiType,
25
25
  parameters: this.requestAnalyzer.paths,
26
26
  hasEnumResponse: this.responseAnalyzer.hasEnumResponse,
27
+ isPrimitiveResponse: this.responseAnalyzer.isPrimitiveResponse,
27
28
  },
28
29
  props,
29
30
  };
30
- const responseTypeInterface = this.buildResponseTypeInterface(analyzers.request.responseType);
31
+ const responseTypeInterface = this.buildResponseTypeInterface(analyzers.request.responseType, analyzers.request.isPrimitiveResponse);
31
32
  return new Method({
32
33
  name,
33
34
  endpoint: operation.endpoint,
@@ -44,7 +45,11 @@ export class MethodBuilder {
44
45
  return "listAll";
45
46
  return extracted ?? this.apiTypeAnalyzer.analyze(operation).apiType;
46
47
  }
47
- buildResponseTypeInterface(responseType) {
48
- return responseType ? `${responseType}Interface` : "null";
48
+ buildResponseTypeInterface(responseType, isPrimitiveResponse) {
49
+ if (!responseType)
50
+ return "null";
51
+ if (isPrimitiveResponse)
52
+ return responseType;
53
+ return `${responseType}Interface`;
49
54
  }
50
55
  }
@@ -2,7 +2,9 @@ import type { ResponsesObject } from "../../types/open-api-spec.interface.js";
2
2
  export declare class MethodResponseAnalyzer {
3
3
  responseType: string | null;
4
4
  hasEnumResponse: boolean;
5
+ isPrimitiveResponse: boolean;
5
6
  analyze(responses: ResponsesObject): void;
7
+ private normalizePrimitive;
6
8
  private isRef;
7
9
  private componentName;
8
10
  private getFromContent;
@@ -1,6 +1,8 @@
1
+ const PRIMITIVE_RESPONSE_TYPES = new Set(["string", "number", "boolean", "integer", "any", "object", "array"]);
1
2
  export class MethodResponseAnalyzer {
2
3
  responseType = null;
3
4
  hasEnumResponse = false;
5
+ isPrimitiveResponse = false;
4
6
  analyze(responses) {
5
7
  for (const response of Object.values(responses)) {
6
8
  if (!response || this.isRef(response))
@@ -8,11 +10,21 @@ export class MethodResponseAnalyzer {
8
10
  const schemaOrType = this.getFromContent(response.content);
9
11
  const type = this.typeFromSchemaOrType(schemaOrType);
10
12
  if (type !== undefined) {
11
- this.responseType = type;
13
+ this.isPrimitiveResponse = PRIMITIVE_RESPONSE_TYPES.has(type);
14
+ this.responseType = this.normalizePrimitive(type);
12
15
  break;
13
16
  }
14
17
  }
15
18
  }
19
+ normalizePrimitive(type) {
20
+ if (type === "integer")
21
+ return "number";
22
+ if (type === "array")
23
+ return "unknown[]";
24
+ if (type === "object")
25
+ return "unknown";
26
+ return type;
27
+ }
16
28
  isRef(v) {
17
29
  return !!v && typeof v === "object" && "$ref" in v;
18
30
  }
@@ -46,11 +58,14 @@ export class MethodResponseAnalyzer {
46
58
  this.hasEnumResponse = true;
47
59
  return this.extractEnumType(schema);
48
60
  }
49
- const allOfFirst = schema.allOf?.[0];
50
- if (allOfFirst && !this.isRef(allOfFirst)) {
51
- const t = this.typeFromProperties(allOfFirst.properties);
52
- if (t !== undefined)
53
- return t;
61
+ if (schema.allOf) {
62
+ for (const entry of schema.allOf) {
63
+ if (this.isRef(entry))
64
+ continue;
65
+ const t = this.typeFromProperties(entry.properties);
66
+ if (t !== undefined)
67
+ return t;
68
+ }
54
69
  }
55
70
  if (schema.type === "array" && schema.items) {
56
71
  const items = schema.items;
@@ -31,9 +31,10 @@ export class MethodApiCallBuilder {
31
31
  }
32
32
  buildEntityCall(method, responseType) {
33
33
  const rType = method.analyzers.request.responseType;
34
+ const isPrimitive = method.analyzers.request.isPrimitiveResponse;
34
35
  const querys = this.buildQuerys(method.analyzers.props.querys);
35
36
  const entityName = treatByUppercase(rType ?? "");
36
- const buildedThisResponseType = rType
37
+ const buildedThisResponseType = rType && !isPrimitive
37
38
  ? `this.${entityName} = new ${method.analyzers.request.responseType}({ data: response })`
38
39
  : "";
39
40
  const inside = `
@@ -62,7 +62,7 @@ export class MethodGenerator {
62
62
  return `/** ${method.description ?? ""} */`;
63
63
  }
64
64
  buildMethodReturn(method) {
65
- const { attributeType, responseType, hasEnumResponse } = method.analyzers.request;
65
+ const { attributeType, responseType, hasEnumResponse, isPrimitiveResponse } = method.analyzers.request;
66
66
  if (attributeType === "list") {
67
67
  return "this.list";
68
68
  }
@@ -72,6 +72,9 @@ export class MethodGenerator {
72
72
  if (hasEnumResponse) {
73
73
  return "response.data";
74
74
  }
75
+ if (isPrimitiveResponse) {
76
+ return "response";
77
+ }
75
78
  return `new ${responseType}({ data: response })`;
76
79
  }
77
80
  }
@@ -8,5 +8,6 @@ export declare class ModuleClassBuilder {
8
8
  buildClassProps(params: {
9
9
  props: AttributeProp[];
10
10
  name: ParamType;
11
+ className?: string;
11
12
  }): string;
12
13
  }
@@ -5,7 +5,8 @@ export class ModuleClassBuilder {
5
5
  this.imports = params.imports;
6
6
  }
7
7
  buildClassProps(params) {
8
- const { name, props } = params;
8
+ const { name, props, className } = params;
9
+ const outputName = className ?? name;
9
10
  const bundle = [];
10
11
  const attributes = [];
11
12
  if (name === "Paths") {
@@ -18,7 +19,7 @@ export class ModuleClassBuilder {
18
19
  });
19
20
  this.imports.addPageStateImport();
20
21
  return `
21
- class ${name} {
22
+ class ${outputName} {
22
23
  ${attributes.join(";")}
23
24
  }
24
25
  `;
@@ -66,7 +67,7 @@ export class ModuleClassBuilder {
66
67
  }
67
68
  `;
68
69
  return `
69
- class ${name} {
70
+ class ${outputName} {
70
71
  ${attributes.join(";")}
71
72
 
72
73
  ${constructorBuild}
@@ -100,7 +101,7 @@ export class ModuleClassBuilder {
100
101
  `
101
102
  : "";
102
103
  return `
103
- class ${name} {
104
+ class ${outputName} {
104
105
  ${attributes.join(";")}
105
106
 
106
107
  ${bundleBuild}
@@ -16,6 +16,7 @@ export declare class ModuleImports {
16
16
  hasEnumImports(): boolean;
17
17
  private hasMockedImports;
18
18
  getMockedImports(): string[];
19
+ private readonly typeOnlyImports;
19
20
  buildReflectorImportsLine(): string;
20
21
  buildEnumImportsLine(): string;
21
22
  buildMockedImportsLine(): string;
@@ -50,11 +50,16 @@ export class ModuleImports {
50
50
  getMockedImports() {
51
51
  return Array.from(this.mockedImports);
52
52
  }
53
+ typeOnlyImports = new Set(["ApiCallParams"]);
53
54
  buildReflectorImportsLine() {
54
55
  const imports = this.getReflectorImportsArray();
55
- const regularImports = imports.filter(i => i !== "setQueryGroup");
56
+ const regularImports = imports.filter(i => i !== "setQueryGroup" && !this.typeOnlyImports.has(i));
57
+ const typeImports = imports.filter(i => this.typeOnlyImports.has(i));
56
58
  const hasSetQueryGroup = imports.includes("setQueryGroup");
57
59
  let result = `import { ${regularImports.join(", ")}, type ApiErrorResponse`;
60
+ for (const t of typeImports) {
61
+ result += `, type ${t}`;
62
+ }
58
63
  if (hasSetQueryGroup) {
59
64
  result += `, setQueryGroup`;
60
65
  }
@@ -7,21 +7,21 @@ export class ModuleMethodProcessor {
7
7
  shouldSkipMethod(method) {
8
8
  const { bodyType, responseType, attributeType } = method.request;
9
9
  if (bodyType === "string") {
10
- createDangerMessage(`Metodo ${method.name} foi ignorado por possuir um body inválido.`);
10
+ createDangerMessage(`Method ${method.name} was skipped because it has an invalid body.`);
11
11
  return true;
12
12
  }
13
13
  const isNullResponse = !responseType || responseType === "null";
14
14
  if ((attributeType === "entity" || attributeType === "list") && isNullResponse) {
15
- createDangerMessage(`Metodo ${method.name} foi ignorado por possuir uma resposta nula.`);
15
+ createDangerMessage(`Method ${method.name} was skipped because it has a null response.`);
16
16
  return true;
17
17
  }
18
- // Verifica se todos os path params do endpoint estão declarados como parâmetros
18
+ // Check that all endpoint path params are declared as parameters
19
19
  const endpointParams = [...method.endpoint.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).filter(Boolean);
20
20
  if (endpointParams.length > 0) {
21
21
  const declaredPaths = new Set(method.paths.map((p) => p.name));
22
22
  const undeclared = endpointParams.filter((p) => !declaredPaths.has(p));
23
23
  if (undeclared.length > 0) {
24
- createDangerMessage(`Metodo ${method.name} foi ignorado por possuir path params não declarados: ${undeclared.join(", ")}`);
24
+ createDangerMessage(`Method ${method.name} was skipped because it has undeclared path params: ${undeclared.join(", ")}`);
25
25
  return true;
26
26
  }
27
27
  }
@@ -42,7 +42,7 @@ export class ModuleMethodProcessor {
42
42
  const cookieMap = new Map();
43
43
  for (const method of methods) {
44
44
  const { request, headers, cookies, paths, querys } = method;
45
- const { bodyType, responseType, attributeType } = request;
45
+ const { bodyType, responseType, attributeType, isPrimitiveResponse } = request;
46
46
  headers.forEach((h) => headerMap.set(h.name, h));
47
47
  cookies.forEach((c) => cookieMap.set(c.name, c));
48
48
  paths.forEach((p) => pathMap.set(p.name, p));
@@ -57,7 +57,7 @@ export class ModuleMethodProcessor {
57
57
  this.imports.addReflectorImport("isFormValid");
58
58
  }
59
59
  buildedMethods.push(method.build());
60
- if (attributeType === "entity") {
60
+ if (attributeType === "entity" && !isPrimitiveResponse) {
61
61
  const entityName = treatByUppercase(method.request.responseType ?? "");
62
62
  methodsAttributes.add(`${entityName} = $state<${responseType} | undefined>()`);
63
63
  methodsInit.add(`this.clear${capitalizeFirstLetter(entityName)}()`);
@@ -74,7 +74,7 @@ export class ModuleMethodProcessor {
74
74
  if (bodyType) {
75
75
  entries.add(bodyType);
76
76
  }
77
- if (responseType && responseType !== "response") {
77
+ if (responseType && responseType !== "response" && !isPrimitiveResponse) {
78
78
  entries.add(`type ${responseType}Interface`);
79
79
  entries.add(responseType);
80
80
  }
@@ -84,12 +84,16 @@ export async function reflector(manual = false) {
84
84
  }
85
85
  // Lê reflector.json do projeto consumidor (opcional)
86
86
  let apiImport = "$lib/api";
87
+ let experimentalFeatures = false;
87
88
  try {
88
89
  const reflectorJsonPath = path.resolve(process.cwd(), "reflector.json");
89
90
  const reflectorJson = JSON.parse(fs.readFileSync(reflectorJsonPath, "utf8"));
90
91
  if (reflectorJson.api) {
91
92
  apiImport = reflectorJson.api;
92
93
  }
94
+ if (reflectorJson.experimentalFeatures === true) {
95
+ experimentalFeatures = true;
96
+ }
93
97
  }
94
98
  catch {
95
99
  // reflector.json não encontrado ou inválido — usa o padrão
@@ -99,7 +103,7 @@ export async function reflector(manual = false) {
99
103
  console.warn("[reflector] OpenAPI sem components; abortando.");
100
104
  return breakReflector();
101
105
  }
102
- const r = new Reflector({ components, paths, fieldConfigs, typeImports, apiImport });
106
+ const r = new Reflector({ components, paths, fieldConfigs, typeImports, apiImport, experimentalFeatures });
103
107
  await r.build();
104
108
  await r.localSave(data);
105
109
  return breakReflector();
package/dist/main.d.ts CHANGED
@@ -21,13 +21,44 @@ export declare class Reflector {
21
21
  schemas: Schema[];
22
22
  modules: Module[];
23
23
  readonly apiImport: string;
24
+ readonly experimentalFeatures: boolean;
24
25
  constructor(params: {
25
26
  components: ComponentsObject;
26
27
  paths: PathsObject;
27
28
  fieldConfigs: FieldConfigs;
28
29
  typeImports: TypeImports;
29
30
  apiImport: string;
31
+ experimentalFeatures?: boolean;
30
32
  });
33
+ /**
34
+ * Promotes inline object request-body schemas to named components.
35
+ * Without this, `MethodBodyAnalyzer` falls back to `schema.type` ("object")
36
+ * because inline bodies aren't referenced by a `$ref`, and the resulting
37
+ * body type name collides with the JS primitive — so the generated module
38
+ * imports a non-existent `{ object }` class and the schema file is never
39
+ * emitted (resolveTransitiveDeps finds nothing in schemaMap).
40
+ */
41
+ private extractInlineBodies;
42
+ private promoteInlineBody;
43
+ /**
44
+ * Promotes inline response `data` schemas to named components.
45
+ * Responses in this API follow a `{success, data, message}` envelope where
46
+ * `data` is either declared directly or overlaid via `allOf`. When `data` is
47
+ * an inline object/array (no `$ref`), the response analyzer can only
48
+ * extract the raw `schema.type` ("object"/"array"), which isn't a valid
49
+ * class name. Promoting `data` to a named component gives the analyzer a
50
+ * proper `$ref` to resolve.
51
+ */
52
+ private extractInlineResponses;
53
+ private promoteInlineResponse;
54
+ private findSuccessResponse;
55
+ /**
56
+ * Locates the inline `data` sub-schema inside a response envelope, whether
57
+ * it lives at `schema.properties.data` or inside an `allOf` entry. Returns
58
+ * a handle that lets the caller swap the inline schema for a `$ref`.
59
+ */
60
+ private findInlineDataHolder;
61
+ private hasExtractableContent;
31
62
  private getSchemas;
32
63
  private getModules;
33
64
  build(): Promise<{}>;
package/dist/main.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import * as path from "node:path";
2
2
  import * as fs from "node:fs";
3
3
  import { Source } from "./file.js";
4
- import { getEndpoint, isReferenceObject, splitByUppercase } from "./helpers/helpers.js";
4
+ import { capitalizeFirstLetter, getEndpoint, isReferenceObject, splitByUppercase } from "./helpers/helpers.js";
5
5
  import { Schema } from "./schema.js";
6
6
  import { Module } from "./module.js";
7
7
  import { generatedDir } from "./vars.global.js";
8
8
  import { ReflectorFile } from "./reflector.js";
9
+ import * as process from "node:process";
9
10
  export const enumTypes = new Map();
10
11
  export const mockedParams = new Set();
11
12
  export class Reflector {
@@ -24,22 +25,160 @@ export class Reflector {
24
25
  schemas;
25
26
  modules;
26
27
  apiImport;
28
+ experimentalFeatures;
27
29
  constructor(params) {
28
- const { components, paths, fieldConfigs, typeImports, apiImport } = params;
30
+ const { components, paths, fieldConfigs, typeImports, apiImport, experimentalFeatures } = params;
29
31
  this.apiImport = apiImport;
32
+ this.experimentalFeatures = experimentalFeatures ?? false;
30
33
  this.typeImports = typeImports;
31
- // Limpa estado global entre execuções
34
+ // Clear global state between runs
32
35
  enumTypes.clear();
33
36
  mockedParams.clear();
34
37
  this.clearSrc();
35
38
  this.components = components;
36
39
  this.paths = paths;
40
+ this.extractInlineBodies();
41
+ this.extractInlineResponses();
37
42
  this.files = [];
38
43
  this.modules = this.getModules();
39
44
  const { propertiesNames, schemas } = this.getSchemas({ fieldConfigs });
40
45
  this.propertiesNames = propertiesNames;
41
46
  this.schemas = schemas;
42
47
  }
48
+ /**
49
+ * Promotes inline object request-body schemas to named components.
50
+ * Without this, `MethodBodyAnalyzer` falls back to `schema.type` ("object")
51
+ * because inline bodies aren't referenced by a `$ref`, and the resulting
52
+ * body type name collides with the JS primitive — so the generated module
53
+ * imports a non-existent `{ object }` class and the schema file is never
54
+ * emitted (resolveTransitiveDeps finds nothing in schemaMap).
55
+ */
56
+ extractInlineBodies() {
57
+ this.components.schemas ??= {};
58
+ const schemas = this.components.schemas;
59
+ const usedNames = new Set(Object.keys(schemas));
60
+ const httpMethods = ["get", "put", "post", "delete", "patch", "options", "head", "trace"];
61
+ for (const [pathStr, pathItem] of Object.entries(this.paths)) {
62
+ if (!pathItem)
63
+ continue;
64
+ for (const httpMethod of httpMethods) {
65
+ this.promoteInlineBody(pathItem[httpMethod], httpMethod, pathStr, schemas, usedNames);
66
+ }
67
+ }
68
+ }
69
+ promoteInlineBody(operation, httpMethod, pathStr, schemas, usedNames) {
70
+ if (!operation?.requestBody || "$ref" in operation.requestBody)
71
+ return;
72
+ const firstEntry = Object.values(operation.requestBody.content ?? {})[0];
73
+ const schema = firstEntry?.schema;
74
+ if (!schema || "$ref" in schema || !schema.properties)
75
+ return;
76
+ const base = operation.operationId
77
+ ? capitalizeFirstLetter(operation.operationId)
78
+ : capitalizeFirstLetter(httpMethod) + pathStr.replaceAll(/[^a-zA-Z0-9]/g, "");
79
+ let name = `${base}Body`;
80
+ let suffix = 2;
81
+ while (usedNames.has(name)) {
82
+ name = `${base}Body${suffix++}`;
83
+ }
84
+ usedNames.add(name);
85
+ schemas[name] = schema;
86
+ firstEntry.schema = { $ref: `#/components/schemas/${name}` };
87
+ }
88
+ /**
89
+ * Promotes inline response `data` schemas to named components.
90
+ * Responses in this API follow a `{success, data, message}` envelope where
91
+ * `data` is either declared directly or overlaid via `allOf`. When `data` is
92
+ * an inline object/array (no `$ref`), the response analyzer can only
93
+ * extract the raw `schema.type` ("object"/"array"), which isn't a valid
94
+ * class name. Promoting `data` to a named component gives the analyzer a
95
+ * proper `$ref` to resolve.
96
+ */
97
+ extractInlineResponses() {
98
+ this.components.schemas ??= {};
99
+ const schemas = this.components.schemas;
100
+ const usedNames = new Set(Object.keys(schemas));
101
+ const httpMethods = ["get", "put", "post", "delete", "patch", "options", "head", "trace"];
102
+ for (const [pathStr, pathItem] of Object.entries(this.paths)) {
103
+ if (!pathItem)
104
+ continue;
105
+ for (const httpMethod of httpMethods) {
106
+ this.promoteInlineResponse(pathItem[httpMethod], httpMethod, pathStr, schemas, usedNames);
107
+ }
108
+ }
109
+ }
110
+ promoteInlineResponse(operation, httpMethod, pathStr, schemas, usedNames) {
111
+ if (!operation?.responses)
112
+ return;
113
+ const successResponse = this.findSuccessResponse(operation.responses);
114
+ if (!successResponse || "$ref" in successResponse || !successResponse.content)
115
+ return;
116
+ const firstEntry = Object.values(successResponse.content)[0];
117
+ const schema = firstEntry?.schema;
118
+ if (!schema || "$ref" in schema)
119
+ return;
120
+ const dataHolder = this.findInlineDataHolder(schema);
121
+ if (!dataHolder)
122
+ return;
123
+ const base = operation.operationId
124
+ ? capitalizeFirstLetter(operation.operationId)
125
+ : capitalizeFirstLetter(httpMethod) + pathStr.replaceAll(/[^a-zA-Z0-9]/g, "");
126
+ let name = `${base}Response`;
127
+ let suffix = 2;
128
+ while (usedNames.has(name)) {
129
+ name = `${base}Response${suffix++}`;
130
+ }
131
+ usedNames.add(name);
132
+ schemas[name] = dataHolder.data;
133
+ dataHolder.replace({ $ref: `#/components/schemas/${name}` });
134
+ }
135
+ findSuccessResponse(responses) {
136
+ for (const [code, response] of Object.entries(responses)) {
137
+ if (code.startsWith("2"))
138
+ return response;
139
+ }
140
+ return undefined;
141
+ }
142
+ /**
143
+ * Locates the inline `data` sub-schema inside a response envelope, whether
144
+ * it lives at `schema.properties.data` or inside an `allOf` entry. Returns
145
+ * a handle that lets the caller swap the inline schema for a `$ref`.
146
+ */
147
+ findInlineDataHolder(schema) {
148
+ const holders = [];
149
+ if (schema.properties)
150
+ holders.push(schema);
151
+ if (schema.allOf) {
152
+ for (const entry of schema.allOf) {
153
+ if (!("$ref" in entry) && entry.properties) {
154
+ holders.push(entry);
155
+ }
156
+ }
157
+ }
158
+ for (const holder of holders) {
159
+ const data = holder.properties?.["data"];
160
+ if (!data || "$ref" in data)
161
+ continue;
162
+ if (!this.hasExtractableContent(data))
163
+ continue;
164
+ return {
165
+ data,
166
+ replace: (ref) => {
167
+ holder.properties["data"] = ref;
168
+ },
169
+ };
170
+ }
171
+ return null;
172
+ }
173
+ hasExtractableContent(schema) {
174
+ if (schema.properties)
175
+ return true;
176
+ if (schema.items)
177
+ return true;
178
+ if (schema.allOf?.length)
179
+ return true;
180
+ return false;
181
+ }
43
182
  getSchemas(params) {
44
183
  const { fieldConfigs } = params;
45
184
  const componentSchemas = this.components.schemas;
@@ -65,7 +204,7 @@ export class Reflector {
65
204
  for (const schema of schemas) {
66
205
  this.schemaMap.set(schema.name, schema);
67
206
  }
68
- console.log(`${schemas.length} schemas gerados com sucesso.`);
207
+ console.log(`${schemas.length} schemas generated successfully.`);
69
208
  return { schemas, propertiesNames };
70
209
  }
71
210
  getModules() {
@@ -73,8 +212,8 @@ export class Reflector {
73
212
  for (const [path, methods] of Object.entries(this.paths)) {
74
213
  const rawMethod = Object.values(methods)[0];
75
214
  const rawName = rawMethod.operationId?.split("_")[0] ?? "";
76
- const teste = splitByUppercase(rawName).filter((x) => x !== "Controller");
77
- const moduleName = teste.join("");
215
+ const nameParts = splitByUppercase(rawName).filter((x) => x !== "Controller");
216
+ const moduleName = nameParts.join("");
78
217
  const baseEndpoint = getEndpoint(path);
79
218
  const operations = Object.entries(methods).map(([apiMethod, attributes]) => {
80
219
  return {
@@ -101,6 +240,7 @@ export class Reflector {
101
240
  name,
102
241
  ...info,
103
242
  apiImport: this.apiImport,
243
+ experimentalFeatures: this.experimentalFeatures,
104
244
  });
105
245
  });
106
246
  return modules;
@@ -146,7 +286,7 @@ export class Reflector {
146
286
  export default mockedParams
147
287
  `;
148
288
  this.mockedParamsFile.changeData(mockedFile);
149
- // Salva todos os arquivos em paralelo, aguardando conclusão
289
+ // Save all files in parallel, awaiting completion
150
290
  await Promise.all([
151
291
  ...moduleSchemaFiles.map((f) => f.save()),
152
292
  this.typesSrc.save(),
@@ -154,6 +294,7 @@ export class Reflector {
154
294
  this.enumFile.save(),
155
295
  this.mockedParamsFile.save(),
156
296
  ...this.modules.filter((m) => m.methods.length > 0).map((m) => m.src.save()),
297
+ ...this.modules.filter((m) => m.methods.length > 0 && m.apiSrc).map((m) => m.apiSrc.save()),
157
298
  ]);
158
299
  return {};
159
300
  }
package/dist/method.d.ts CHANGED
@@ -15,7 +15,10 @@ export declare class Method {
15
15
  apiType: import("./types/types.js").ApiType;
16
16
  parameters: any[];
17
17
  hasEnumResponse: boolean;
18
+ isPrimitiveResponse: boolean;
18
19
  };
20
+ get analyzers(): import("./core/method/Method.js").MethodAnalyzers;
21
+ get responseTypeInterface(): string;
19
22
  get headers(): PrimitiveProp[];
20
23
  get cookies(): PrimitiveProp[];
21
24
  get paths(): PrimitiveProp[];
package/dist/method.js CHANGED
@@ -11,6 +11,12 @@ export class Method {
11
11
  get request() {
12
12
  return this.method.analyzers.request;
13
13
  }
14
+ get analyzers() {
15
+ return this.method.analyzers;
16
+ }
17
+ get responseTypeInterface() {
18
+ return this.method.responseTypeInterface;
19
+ }
14
20
  get headers() {
15
21
  return this.method.analyzers.props.headers;
16
22
  }
package/dist/module.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare class Module {
6
6
  readonly path: string;
7
7
  readonly moduleName: string;
8
8
  readonly src: Source;
9
+ readonly apiSrc: Source | null;
9
10
  readonly methods: Method[];
10
11
  /** Schema class names directly used by this module (for per-module schema generation) */
11
12
  readonly schemaClassNames: string[];
@@ -21,7 +22,10 @@ export declare class Module {
21
22
  operations: ReflectorOperation[];
22
23
  path: string;
23
24
  apiImport: string;
25
+ experimentalFeatures?: boolean;
24
26
  });
25
27
  private buildModuleData;
28
+ private buildApiFile;
29
+ private getApiPath;
26
30
  private getPath;
27
31
  }
package/dist/module.js CHANGED
@@ -4,12 +4,13 @@ import { Source } from "./file.js";
4
4
  import { capitalizeFirstLetter, toKebabCase } from "./helpers/helpers.js";
5
5
  import { Method } from "./method.js";
6
6
  import { generatedDir } from "./vars.global.js";
7
- import { ModuleImports, ModuleMethodProcessor, ModuleParamProcessor, ModuleClassBuilder, ModuleConstructorBuilder, ModuleFileBuilder, } from "./core/index.js";
7
+ import { ModuleImports, ModuleMethodProcessor, ModuleParamProcessor, ModuleClassBuilder, ModuleConstructorBuilder, ModuleFileBuilder, ApiFileBuilder, ApiClassBuilder, } from "./core/index.js";
8
8
  export class Module {
9
9
  name;
10
10
  path;
11
11
  moduleName;
12
12
  src;
13
+ apiSrc;
13
14
  methods;
14
15
  /** Schema class names directly used by this module (for per-module schema generation) */
15
16
  schemaClassNames;
@@ -20,7 +21,7 @@ export class Module {
20
21
  constructorBuilder;
21
22
  fileBuilder;
22
23
  constructor(params) {
23
- const { name, operations, moduleName, path: modulePath, apiImport } = params;
24
+ const { name, operations, moduleName, path: modulePath, apiImport, experimentalFeatures } = params;
24
25
  this.moduleName = moduleName;
25
26
  this.name = capitalizeFirstLetter(name);
26
27
  this.path = modulePath;
@@ -58,9 +59,11 @@ export class Module {
58
59
  data: this.fileBuilder.build({
59
60
  ...allBuilded,
60
61
  moduleConstructor,
61
- moduleName: this.moduleName,
62
+ moduleName: this.name,
62
63
  }),
63
64
  });
65
+ // Cria o arquivo Api (apenas com experimentalFeatures)
66
+ this.apiSrc = experimentalFeatures ? this.buildApiFile(apiImport) : null;
64
67
  }
65
68
  buildModuleData(processedMethods, processedParams) {
66
69
  const moduleAttributes = new Set().add("loading = $state<boolean>(false)");
@@ -102,6 +105,36 @@ export class Module {
102
105
  buildedMethods,
103
106
  };
104
107
  }
108
+ buildApiFile(apiImport) {
109
+ const apiImports = new ModuleImports(apiImport);
110
+ apiImports.addReflectorImport("ApiCallParams");
111
+ const apiClassBuilderDep = new ModuleClassBuilder({ imports: apiImports });
112
+ const apiClassBuilder = new ApiClassBuilder({ imports: apiImports, classBuilder: apiClassBuilderDep });
113
+ const apiFileBuilder = new ApiFileBuilder({ imports: apiImports });
114
+ const endpointBlocks = [];
115
+ const apiSchemaEntries = new Set();
116
+ for (const method of this.methods) {
117
+ const block = apiClassBuilder.build({ method });
118
+ if (!block)
119
+ continue;
120
+ endpointBlocks.push(block);
121
+ block.schemaEntries.forEach((e) => apiSchemaEntries.add(e));
122
+ }
123
+ const cleanEntries = Array.from(apiSchemaEntries).filter((x) => x !== "type any");
124
+ const kebabName = toKebabCase(this.name);
125
+ const classImports = cleanEntries.length > 0
126
+ ? `import { ${cleanEntries.join(", ")} } from './${kebabName}.schema.svelte';`
127
+ : "";
128
+ return new Source({
129
+ path: this.getApiPath(),
130
+ data: apiFileBuilder.build({ endpointBlocks, classImports }),
131
+ });
132
+ }
133
+ getApiPath() {
134
+ const kebabName = toKebabCase(this.name);
135
+ const inPath = path.join(generatedDir, "controllers", kebabName);
136
+ return path.join(inPath, `${kebabName}.api.svelte.ts`);
137
+ }
105
138
  getPath() {
106
139
  const kebabName = toKebabCase(this.name);
107
140
  const inPath = path.join(generatedDir, "controllers", kebabName);
@@ -22,7 +22,8 @@ export class PrimitiveProp {
22
22
  const { name, schemaObject, required, validator, customType, isParam, isNullable } = params;
23
23
  const { type: rawType } = schemaObject;
24
24
  this.isNullable = !!isNullable;
25
- const type = rawType ?? "string";
25
+ const normalizedRawType = rawType === "integer" ? "number" : rawType;
26
+ const type = normalizedRawType ?? "string";
26
27
  const { emptyExample, example } = this.getExampleAndFallback({ schemaObject, type, name });
27
28
  this.example = example;
28
29
  this.fallbackExample = emptyExample;
package/dist/reflector.js CHANGED
@@ -10,6 +10,10 @@ export class ReflectorFile {
10
10
  "type ValidatorResult = string | null",
11
11
  "type ValidatorFn<T> = (v: T) => ValidatorResult",
12
12
  "type BundleResult<T> = T extends { bundle: () => infer R } ? R : T;",
13
+ `export type ApiCallParams<TResponse, TPaths = void> =
14
+ TPaths extends void
15
+ ? { behavior?: Behavior<TResponse, ApiErrorResponse> }
16
+ : { behavior?: Behavior<TResponse, ApiErrorResponse>; paths?: TPaths }`,
13
17
  `type PartialBuildedInput<T> = {
14
18
  [K in Exclude<keyof T, 'bundle'>]?: BuildedInput<T[K]>;
15
19
  } & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Reflects zod types from openAPI schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "dotenv": "^16.4.5"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^24.12.0",
28
+ "@types/node": "^24.12.2",
29
29
  "typescript": "^5.9.3"
30
30
  },
31
31
  "peerDependencies": {