svelte-reflector 1.3.3 → 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";
@@ -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
  }
@@ -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,12 +21,14 @@ 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
  });
31
33
  /**
32
34
  * Promotes inline object request-body schemas to named components.
package/dist/main.js CHANGED
@@ -6,6 +6,7 @@ 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,9 +25,11 @@ 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
34
  // Clear global state between runs
32
35
  enumTypes.clear();
@@ -237,6 +240,7 @@ export class Reflector {
237
240
  name,
238
241
  ...info,
239
242
  apiImport: this.apiImport,
243
+ experimentalFeatures: this.experimentalFeatures,
240
244
  });
241
245
  });
242
246
  return modules;
@@ -290,6 +294,7 @@ export class Reflector {
290
294
  this.enumFile.save(),
291
295
  this.mockedParamsFile.save(),
292
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()),
293
298
  ]);
294
299
  return {};
295
300
  }
package/dist/method.d.ts CHANGED
@@ -17,6 +17,8 @@ export declare class Method {
17
17
  hasEnumResponse: boolean;
18
18
  isPrimitiveResponse: boolean;
19
19
  };
20
+ get analyzers(): import("./core/method/Method.js").MethodAnalyzers;
21
+ get responseTypeInterface(): string;
20
22
  get headers(): PrimitiveProp[];
21
23
  get cookies(): PrimitiveProp[];
22
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;
@@ -61,6 +62,8 @@ export class Module {
61
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);
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.3",
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": {