svelte-reflector 1.3.5 → 1.3.6

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.
@@ -1,6 +1,6 @@
1
1
  import type { CallMethodInput, CallStrategy } from "./CallStrategy.js";
2
2
  export declare class ApiCallStrategy implements CallStrategy {
3
- readonly listStateAccess = "this.data";
3
+ listStateAccess(_method: CallMethodInput): string;
4
4
  buildSignature(method: CallMethodInput): string;
5
5
  entityStateAccess(_method: CallMethodInput): string;
6
6
  formStateAccess(_method: CallMethodInput): string;
@@ -1,5 +1,7 @@
1
1
  export class ApiCallStrategy {
2
- listStateAccess = "this.data";
2
+ listStateAccess(_method) {
3
+ return "this.data";
4
+ }
3
5
  buildSignature(method) {
4
6
  const paramsType = this.buildParamsType(method);
5
7
  return `async call(params?: ${paramsType})`;
@@ -78,7 +78,7 @@ export class CallMethodGenerator {
78
78
  endpoint,
79
79
  queryData: { ${querys} }
80
80
  })
81
- ${strategy.listStateAccess} = ${method.analyzers.request.responseType}.from(response.data);
81
+ ${strategy.listStateAccess(method)} = ${method.analyzers.request.responseType}.from(response.data);
82
82
  this.totalPages = response.totalPages;
83
83
  `;
84
84
  return { inside, outside: "" };
@@ -134,7 +134,7 @@ export class CallMethodGenerator {
134
134
  buildMethodReturn(method, strategy) {
135
135
  const { attributeType, responseType, hasEnumResponse, isPrimitiveResponse } = method.analyzers.request;
136
136
  if (attributeType === "list")
137
- return strategy.listStateAccess;
137
+ return strategy.listStateAccess(method);
138
138
  if (!responseType)
139
139
  return "null";
140
140
  if (hasEnumResponse)
@@ -2,6 +2,11 @@ import type { MethodAnalyzers } from "../method/Method.js";
2
2
  /** Structural subset of `Method` that the generator and strategies actually read. */
3
3
  export interface CallMethodInput {
4
4
  name: string;
5
+ /** Disambiguation suffix appended to the base name; empty when unique. */
6
+ nameSuffix: string;
7
+ /** Disambiguation suffix for the shared list state field; empty when
8
+ * the module has at most one list-typed method. */
9
+ stateSuffix: string;
5
10
  endpoint: string;
6
11
  description: string | undefined;
7
12
  responseTypeInterface: string;
@@ -14,8 +19,10 @@ export interface CallMethodInput {
14
19
  export interface CallStrategy {
15
20
  /** Full method signature incl. params — e.g. `async call(params?: ...)` or `protected async _foo(params?: ...)` */
16
21
  buildSignature(method: CallMethodInput): string;
17
- /** State field holding list results — e.g. `this.list` (module) or `this.data` (api) */
18
- readonly listStateAccess: string;
22
+ /** State field holding list results — e.g. `this.list` / `this.listControllers` (module) or `this.data` (api).
23
+ * Takes `method` so the module strategy can suffix the field when two list
24
+ * operations collide in the same controller. */
25
+ listStateAccess(method: CallMethodInput): string;
19
26
  /** State field receiving the fetched entity */
20
27
  entityStateAccess(method: CallMethodInput): string;
21
28
  /** Form instance accessed to bundle body data */
@@ -1,6 +1,6 @@
1
1
  import type { CallMethodInput, CallStrategy } from "./CallStrategy.js";
2
2
  export declare class ModuleCallStrategy implements CallStrategy {
3
- readonly listStateAccess = "this.list";
3
+ listStateAccess(method: CallMethodInput): string;
4
4
  buildSignature(method: CallMethodInput): string;
5
5
  entityStateAccess(method: CallMethodInput): string;
6
6
  formStateAccess(method: CallMethodInput): string;
@@ -1,6 +1,8 @@
1
1
  import { treatByUppercase } from "../../helpers/helpers.js";
2
2
  export class ModuleCallStrategy {
3
- listStateAccess = "this.list";
3
+ listStateAccess(method) {
4
+ return `this.list${method.stateSuffix}`;
5
+ }
4
6
  buildSignature(method) {
5
7
  const paramsType = this.buildParamsType(method);
6
8
  return `protected async _${method.name}(params?: ${paramsType})`;
@@ -22,7 +22,16 @@ export interface MethodAnalyzers {
22
22
  };
23
23
  }
24
24
  export declare class Method {
25
- readonly name: string;
25
+ name: string;
26
+ /** Disambiguation suffix appended to the base name when two methods in
27
+ * the same module collide (e.g. two `listAll`). Empty when unique. */
28
+ nameSuffix: string;
29
+ /** Disambiguation suffix for the shared list state field (`list`,
30
+ * `bundledList`, `clearList`). Set when the module has more than one
31
+ * `list`-typed method, even if the method names themselves are unique
32
+ * (e.g. `listAll` + `getMessages` both producing `list`). Empty when
33
+ * the module has at most one list method. */
34
+ stateSuffix: string;
26
35
  readonly endpoint: string;
27
36
  readonly apiType: ApiType;
28
37
  readonly attributeType: ReflectorRequestType;
@@ -2,6 +2,15 @@ import { MethodBuilder } from "./MethodBuilder.js";
2
2
  import { MethodGenerator } from "./generators/MethodGenerator.js";
3
3
  export class Method {
4
4
  name;
5
+ /** Disambiguation suffix appended to the base name when two methods in
6
+ * the same module collide (e.g. two `listAll`). Empty when unique. */
7
+ nameSuffix = "";
8
+ /** Disambiguation suffix for the shared list state field (`list`,
9
+ * `bundledList`, `clearList`). Set when the module has more than one
10
+ * `list`-typed method, even if the method names themselves are unique
11
+ * (e.g. `listAll` + `getMessages` both producing `list`). Empty when
12
+ * the module has at most one list method. */
13
+ stateSuffix = "";
5
14
  endpoint;
6
15
  apiType;
7
16
  attributeType;
@@ -0,0 +1,38 @@
1
+ import type { Method } from "./Method.js";
2
+ /**
3
+ * Two kinds of collision inside a single controller (module) produce invalid
4
+ * generated code. Both are fixed here by appending a suffix derived from the
5
+ * path's last non-parameterized segment.
6
+ *
7
+ * 1. Method-name collision (`nameSuffix`).
8
+ * Two operations with the same base name (e.g. two `listAll`, two
9
+ * `findOne`) would emit duplicate `_listAll` / `_findOne` methods and
10
+ * duplicate per-operation classes (`ListAll`, `FindOnePaths`, etc.).
11
+ *
12
+ * 2. List-state collision (`stateSuffix`).
13
+ * Any two list-typed operations in the same module emit `list = $state<…>`
14
+ * twice, even when their method names differ (e.g. `listAll` vs
15
+ * `getMessages`). The `list` field, `bundledList`, and `clearList()` must
16
+ * be suffixed per method so both are unique.
17
+ *
18
+ * Suffix derivation (same for both cases, so a single method can reuse one
19
+ * suffix for both):
20
+ * - Take the last non-`{param}` segment of the endpoint path.
21
+ * - Pluralize it when the path does NOT end with a `{param}`
22
+ * (collection endpoints: listAll, create).
23
+ * - Keep it singular when the path ends with a `{param}`
24
+ * (item endpoints: findOne, update, remove).
25
+ * - Camel-case the result to strip hyphens and capitalize.
26
+ *
27
+ * When a method has no collision of either kind, both suffixes stay empty
28
+ * and the output matches the pre-fix behavior — no churn for consumers of
29
+ * non-colliding controllers.
30
+ */
31
+ export declare class MethodNameDisambiguator {
32
+ static apply(methods: Method[]): void;
33
+ private static disambiguateNames;
34
+ private static disambiguateListStates;
35
+ private static ensureUnique;
36
+ private static deriveSuffix;
37
+ private static pluralize;
38
+ }
@@ -0,0 +1,118 @@
1
+ import { capitalizeFirstLetter, toCamelCase } from "../../helpers/helpers.js";
2
+ /**
3
+ * Two kinds of collision inside a single controller (module) produce invalid
4
+ * generated code. Both are fixed here by appending a suffix derived from the
5
+ * path's last non-parameterized segment.
6
+ *
7
+ * 1. Method-name collision (`nameSuffix`).
8
+ * Two operations with the same base name (e.g. two `listAll`, two
9
+ * `findOne`) would emit duplicate `_listAll` / `_findOne` methods and
10
+ * duplicate per-operation classes (`ListAll`, `FindOnePaths`, etc.).
11
+ *
12
+ * 2. List-state collision (`stateSuffix`).
13
+ * Any two list-typed operations in the same module emit `list = $state<…>`
14
+ * twice, even when their method names differ (e.g. `listAll` vs
15
+ * `getMessages`). The `list` field, `bundledList`, and `clearList()` must
16
+ * be suffixed per method so both are unique.
17
+ *
18
+ * Suffix derivation (same for both cases, so a single method can reuse one
19
+ * suffix for both):
20
+ * - Take the last non-`{param}` segment of the endpoint path.
21
+ * - Pluralize it when the path does NOT end with a `{param}`
22
+ * (collection endpoints: listAll, create).
23
+ * - Keep it singular when the path ends with a `{param}`
24
+ * (item endpoints: findOne, update, remove).
25
+ * - Camel-case the result to strip hyphens and capitalize.
26
+ *
27
+ * When a method has no collision of either kind, both suffixes stay empty
28
+ * and the output matches the pre-fix behavior — no churn for consumers of
29
+ * non-colliding controllers.
30
+ */
31
+ export class MethodNameDisambiguator {
32
+ static apply(methods) {
33
+ this.disambiguateNames(methods);
34
+ this.disambiguateListStates(methods);
35
+ }
36
+ static disambiguateNames(methods) {
37
+ const groups = new Map();
38
+ for (const m of methods) {
39
+ const bucket = groups.get(m.name);
40
+ if (bucket)
41
+ bucket.push(m);
42
+ else
43
+ groups.set(m.name, [m]);
44
+ }
45
+ for (const bucket of groups.values()) {
46
+ if (bucket.length < 2)
47
+ continue;
48
+ const used = new Set();
49
+ for (const method of bucket) {
50
+ const suffix = this.deriveSuffix(method.endpoint);
51
+ if (!suffix)
52
+ continue;
53
+ const finalSuffix = this.ensureUnique(suffix, used);
54
+ used.add(finalSuffix);
55
+ method.name = `${method.name}${finalSuffix}`;
56
+ method.nameSuffix = finalSuffix;
57
+ }
58
+ }
59
+ }
60
+ static disambiguateListStates(methods) {
61
+ const listMethods = methods.filter((m) => m.analyzers.request.attributeType === "list");
62
+ if (listMethods.length < 2)
63
+ return;
64
+ const used = new Set();
65
+ for (const method of listMethods) {
66
+ // If `disambiguateNames` already assigned a suffix for this method
67
+ // (two list-typed ops with the same name), reuse it so the method
68
+ // name and list state stay aligned (e.g. `_listAllPackages` ↔
69
+ // `listPackages`).
70
+ let suffix = method.nameSuffix || this.deriveSuffix(method.endpoint);
71
+ if (!suffix)
72
+ continue;
73
+ const finalSuffix = this.ensureUnique(suffix, used);
74
+ used.add(finalSuffix);
75
+ method.stateSuffix = finalSuffix;
76
+ }
77
+ }
78
+ static ensureUnique(suffix, used) {
79
+ if (!used.has(suffix))
80
+ return suffix;
81
+ let i = 2;
82
+ let candidate = `${suffix}${i}`;
83
+ while (used.has(candidate)) {
84
+ i += 1;
85
+ candidate = `${suffix}${i}`;
86
+ }
87
+ return candidate;
88
+ }
89
+ static deriveSuffix(rawPath) {
90
+ const segments = rawPath.split("/").filter(Boolean);
91
+ if (segments.length === 0)
92
+ return "";
93
+ const isParam = (s) => /^\{.+\}$/.test(s);
94
+ const last = segments[segments.length - 1];
95
+ let baseSegment = "";
96
+ for (let i = segments.length - 1; i >= 0; i--) {
97
+ const seg = segments[i];
98
+ if (!isParam(seg)) {
99
+ baseSegment = seg;
100
+ break;
101
+ }
102
+ }
103
+ if (!baseSegment)
104
+ return "";
105
+ const shouldPluralize = !isParam(last);
106
+ const word = shouldPluralize ? this.pluralize(baseSegment) : baseSegment;
107
+ return capitalizeFirstLetter(toCamelCase(word));
108
+ }
109
+ static pluralize(word) {
110
+ if (word.endsWith("s"))
111
+ return word;
112
+ if (/[^aeiou]y$/i.test(word))
113
+ return `${word.slice(0, -1)}ies`;
114
+ if (/(x|ch|sh)$/i.test(word))
115
+ return `${word}es`;
116
+ return `${word}s`;
117
+ }
118
+ }
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import { Source } from "../../file.js";
3
3
  import { capitalizeFirstLetter, toKebabCase } from "../../helpers/helpers.js";
4
4
  import { Method } from "../method/Method.js";
5
+ import { MethodNameDisambiguator } from "../method/MethodNameDisambiguator.js";
5
6
  import { generatedDir } from "../../vars.global.js";
6
7
  import { ModuleImports, ModuleMethodProcessor, ModuleParamProcessor, ModuleClassBuilder, ModuleConstructorBuilder, ModuleFileBuilder, ApiFileBuilder, ApiClassBuilder, } from "../index.js";
7
8
  export class Module {
@@ -42,6 +43,7 @@ export class Module {
42
43
  this.fileBuilder = new ModuleFileBuilder({ imports: this.imports });
43
44
  // Processa os métodos
44
45
  this.methods = operations.map((operation) => Method.fromOperation(operation, this.name, context));
46
+ MethodNameDisambiguator.apply(this.methods);
45
47
  const processedMethods = this.methodProcessor.process({ methods: this.methods });
46
48
  // Processa os parâmetros
47
49
  const processedParams = this.paramProcessor.process({
@@ -42,12 +42,15 @@ export class ModuleMethodProcessor {
42
42
  methodsClear.add(`protected clear${capitalizeFirstLetter(entityName)}() { this.${entityName} = undefined }`);
43
43
  }
44
44
  else if (attributeType === "list") {
45
+ const listField = `list${method.stateSuffix}`;
46
+ const bundledField = `bundled${capitalizeFirstLetter(listField)}`;
47
+ const clearMethod = `clear${capitalizeFirstLetter(listField)}`;
45
48
  methodsAttributes.add("totalPages = $state<number>(1)");
46
- methodsAttributes.add(`list = $state<${responseType}['data']>([])`);
49
+ methodsAttributes.add(`${listField} = $state<${responseType}['data']>([])`);
47
50
  this.imports.addReflectorImport("genericArrayBundler");
48
- methodsAttributes.add(`bundledList = $derived(genericArrayBundler(this.list))`);
49
- methodsInit.add("this.clearList()");
50
- methodsClear.add(`protected clearList() { this.list = [] }`);
51
+ methodsAttributes.add(`${bundledField} = $derived(genericArrayBundler(this.${listField}))`);
52
+ methodsInit.add(`this.${clearMethod}()`);
53
+ methodsClear.add(`protected ${clearMethod}() { this.${listField} = [] }`);
51
54
  }
52
55
  if (bodyType) {
53
56
  entries.add(bodyType);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "description": "Reflects zod types from openAPI schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",