svelte-reflector 1.3.2 → 1.3.3

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.
@@ -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
  }
@@ -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
  }
package/dist/main.d.ts CHANGED
@@ -28,6 +28,35 @@ export declare class Reflector {
28
28
  typeImports: TypeImports;
29
29
  apiImport: string;
30
30
  });
31
+ /**
32
+ * Promotes inline object request-body schemas to named components.
33
+ * Without this, `MethodBodyAnalyzer` falls back to `schema.type` ("object")
34
+ * because inline bodies aren't referenced by a `$ref`, and the resulting
35
+ * body type name collides with the JS primitive — so the generated module
36
+ * imports a non-existent `{ object }` class and the schema file is never
37
+ * emitted (resolveTransitiveDeps finds nothing in schemaMap).
38
+ */
39
+ private extractInlineBodies;
40
+ private promoteInlineBody;
41
+ /**
42
+ * Promotes inline response `data` schemas to named components.
43
+ * Responses in this API follow a `{success, data, message}` envelope where
44
+ * `data` is either declared directly or overlaid via `allOf`. When `data` is
45
+ * an inline object/array (no `$ref`), the response analyzer can only
46
+ * extract the raw `schema.type` ("object"/"array"), which isn't a valid
47
+ * class name. Promoting `data` to a named component gives the analyzer a
48
+ * proper `$ref` to resolve.
49
+ */
50
+ private extractInlineResponses;
51
+ private promoteInlineResponse;
52
+ private findSuccessResponse;
53
+ /**
54
+ * Locates the inline `data` sub-schema inside a response envelope, whether
55
+ * it lives at `schema.properties.data` or inside an `allOf` entry. Returns
56
+ * a handle that lets the caller swap the inline schema for a `$ref`.
57
+ */
58
+ private findInlineDataHolder;
59
+ private hasExtractableContent;
31
60
  private getSchemas;
32
61
  private getModules;
33
62
  build(): Promise<{}>;
package/dist/main.js CHANGED
@@ -1,7 +1,7 @@
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";
@@ -28,18 +28,154 @@ export class Reflector {
28
28
  const { components, paths, fieldConfigs, typeImports, apiImport } = params;
29
29
  this.apiImport = apiImport;
30
30
  this.typeImports = typeImports;
31
- // Limpa estado global entre execuções
31
+ // Clear global state between runs
32
32
  enumTypes.clear();
33
33
  mockedParams.clear();
34
34
  this.clearSrc();
35
35
  this.components = components;
36
36
  this.paths = paths;
37
+ this.extractInlineBodies();
38
+ this.extractInlineResponses();
37
39
  this.files = [];
38
40
  this.modules = this.getModules();
39
41
  const { propertiesNames, schemas } = this.getSchemas({ fieldConfigs });
40
42
  this.propertiesNames = propertiesNames;
41
43
  this.schemas = schemas;
42
44
  }
45
+ /**
46
+ * Promotes inline object request-body schemas to named components.
47
+ * Without this, `MethodBodyAnalyzer` falls back to `schema.type` ("object")
48
+ * because inline bodies aren't referenced by a `$ref`, and the resulting
49
+ * body type name collides with the JS primitive — so the generated module
50
+ * imports a non-existent `{ object }` class and the schema file is never
51
+ * emitted (resolveTransitiveDeps finds nothing in schemaMap).
52
+ */
53
+ extractInlineBodies() {
54
+ this.components.schemas ??= {};
55
+ const schemas = this.components.schemas;
56
+ const usedNames = new Set(Object.keys(schemas));
57
+ const httpMethods = ["get", "put", "post", "delete", "patch", "options", "head", "trace"];
58
+ for (const [pathStr, pathItem] of Object.entries(this.paths)) {
59
+ if (!pathItem)
60
+ continue;
61
+ for (const httpMethod of httpMethods) {
62
+ this.promoteInlineBody(pathItem[httpMethod], httpMethod, pathStr, schemas, usedNames);
63
+ }
64
+ }
65
+ }
66
+ promoteInlineBody(operation, httpMethod, pathStr, schemas, usedNames) {
67
+ if (!operation?.requestBody || "$ref" in operation.requestBody)
68
+ return;
69
+ const firstEntry = Object.values(operation.requestBody.content ?? {})[0];
70
+ const schema = firstEntry?.schema;
71
+ if (!schema || "$ref" in schema || !schema.properties)
72
+ return;
73
+ const base = operation.operationId
74
+ ? capitalizeFirstLetter(operation.operationId)
75
+ : capitalizeFirstLetter(httpMethod) + pathStr.replaceAll(/[^a-zA-Z0-9]/g, "");
76
+ let name = `${base}Body`;
77
+ let suffix = 2;
78
+ while (usedNames.has(name)) {
79
+ name = `${base}Body${suffix++}`;
80
+ }
81
+ usedNames.add(name);
82
+ schemas[name] = schema;
83
+ firstEntry.schema = { $ref: `#/components/schemas/${name}` };
84
+ }
85
+ /**
86
+ * Promotes inline response `data` schemas to named components.
87
+ * Responses in this API follow a `{success, data, message}` envelope where
88
+ * `data` is either declared directly or overlaid via `allOf`. When `data` is
89
+ * an inline object/array (no `$ref`), the response analyzer can only
90
+ * extract the raw `schema.type` ("object"/"array"), which isn't a valid
91
+ * class name. Promoting `data` to a named component gives the analyzer a
92
+ * proper `$ref` to resolve.
93
+ */
94
+ extractInlineResponses() {
95
+ this.components.schemas ??= {};
96
+ const schemas = this.components.schemas;
97
+ const usedNames = new Set(Object.keys(schemas));
98
+ const httpMethods = ["get", "put", "post", "delete", "patch", "options", "head", "trace"];
99
+ for (const [pathStr, pathItem] of Object.entries(this.paths)) {
100
+ if (!pathItem)
101
+ continue;
102
+ for (const httpMethod of httpMethods) {
103
+ this.promoteInlineResponse(pathItem[httpMethod], httpMethod, pathStr, schemas, usedNames);
104
+ }
105
+ }
106
+ }
107
+ promoteInlineResponse(operation, httpMethod, pathStr, schemas, usedNames) {
108
+ if (!operation?.responses)
109
+ return;
110
+ const successResponse = this.findSuccessResponse(operation.responses);
111
+ if (!successResponse || "$ref" in successResponse || !successResponse.content)
112
+ return;
113
+ const firstEntry = Object.values(successResponse.content)[0];
114
+ const schema = firstEntry?.schema;
115
+ if (!schema || "$ref" in schema)
116
+ return;
117
+ const dataHolder = this.findInlineDataHolder(schema);
118
+ if (!dataHolder)
119
+ return;
120
+ const base = operation.operationId
121
+ ? capitalizeFirstLetter(operation.operationId)
122
+ : capitalizeFirstLetter(httpMethod) + pathStr.replaceAll(/[^a-zA-Z0-9]/g, "");
123
+ let name = `${base}Response`;
124
+ let suffix = 2;
125
+ while (usedNames.has(name)) {
126
+ name = `${base}Response${suffix++}`;
127
+ }
128
+ usedNames.add(name);
129
+ schemas[name] = dataHolder.data;
130
+ dataHolder.replace({ $ref: `#/components/schemas/${name}` });
131
+ }
132
+ findSuccessResponse(responses) {
133
+ for (const [code, response] of Object.entries(responses)) {
134
+ if (code.startsWith("2"))
135
+ return response;
136
+ }
137
+ return undefined;
138
+ }
139
+ /**
140
+ * Locates the inline `data` sub-schema inside a response envelope, whether
141
+ * it lives at `schema.properties.data` or inside an `allOf` entry. Returns
142
+ * a handle that lets the caller swap the inline schema for a `$ref`.
143
+ */
144
+ findInlineDataHolder(schema) {
145
+ const holders = [];
146
+ if (schema.properties)
147
+ holders.push(schema);
148
+ if (schema.allOf) {
149
+ for (const entry of schema.allOf) {
150
+ if (!("$ref" in entry) && entry.properties) {
151
+ holders.push(entry);
152
+ }
153
+ }
154
+ }
155
+ for (const holder of holders) {
156
+ const data = holder.properties?.["data"];
157
+ if (!data || "$ref" in data)
158
+ continue;
159
+ if (!this.hasExtractableContent(data))
160
+ continue;
161
+ return {
162
+ data,
163
+ replace: (ref) => {
164
+ holder.properties["data"] = ref;
165
+ },
166
+ };
167
+ }
168
+ return null;
169
+ }
170
+ hasExtractableContent(schema) {
171
+ if (schema.properties)
172
+ return true;
173
+ if (schema.items)
174
+ return true;
175
+ if (schema.allOf?.length)
176
+ return true;
177
+ return false;
178
+ }
43
179
  getSchemas(params) {
44
180
  const { fieldConfigs } = params;
45
181
  const componentSchemas = this.components.schemas;
@@ -65,7 +201,7 @@ export class Reflector {
65
201
  for (const schema of schemas) {
66
202
  this.schemaMap.set(schema.name, schema);
67
203
  }
68
- console.log(`${schemas.length} schemas gerados com sucesso.`);
204
+ console.log(`${schemas.length} schemas generated successfully.`);
69
205
  return { schemas, propertiesNames };
70
206
  }
71
207
  getModules() {
@@ -73,8 +209,8 @@ export class Reflector {
73
209
  for (const [path, methods] of Object.entries(this.paths)) {
74
210
  const rawMethod = Object.values(methods)[0];
75
211
  const rawName = rawMethod.operationId?.split("_")[0] ?? "";
76
- const teste = splitByUppercase(rawName).filter((x) => x !== "Controller");
77
- const moduleName = teste.join("");
212
+ const nameParts = splitByUppercase(rawName).filter((x) => x !== "Controller");
213
+ const moduleName = nameParts.join("");
78
214
  const baseEndpoint = getEndpoint(path);
79
215
  const operations = Object.entries(methods).map(([apiMethod, attributes]) => {
80
216
  return {
@@ -146,7 +282,7 @@ export class Reflector {
146
282
  export default mockedParams
147
283
  `;
148
284
  this.mockedParamsFile.changeData(mockedFile);
149
- // Salva todos os arquivos em paralelo, aguardando conclusão
285
+ // Save all files in parallel, awaiting completion
150
286
  await Promise.all([
151
287
  ...moduleSchemaFiles.map((f) => f.save()),
152
288
  this.typesSrc.save(),
package/dist/method.d.ts CHANGED
@@ -15,6 +15,7 @@ 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
  };
19
20
  get headers(): PrimitiveProp[];
20
21
  get cookies(): PrimitiveProp[];
package/dist/module.js CHANGED
@@ -58,7 +58,7 @@ export class Module {
58
58
  data: this.fileBuilder.build({
59
59
  ...allBuilded,
60
60
  moduleConstructor,
61
- moduleName: this.moduleName,
61
+ moduleName: this.name,
62
62
  }),
63
63
  });
64
64
  }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Reflects zod types from openAPI schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",