svelte-reflector 1.3.4 → 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.
Files changed (86) hide show
  1. package/dist/core/CodegenContext.d.ts +15 -0
  2. package/dist/core/CodegenContext.js +15 -0
  3. package/dist/core/Reflector.d.ts +25 -0
  4. package/dist/core/Reflector.js +66 -0
  5. package/dist/core/api/ApiClassBuilder.d.ts +2 -2
  6. package/dist/core/api/ApiClassBuilder.js +8 -27
  7. package/dist/core/config/ReflectorConfig.d.ts +17 -0
  8. package/dist/core/config/ReflectorConfig.js +9 -0
  9. package/dist/core/emit/RuntimeFilesEmitter.d.ts +13 -0
  10. package/dist/core/emit/RuntimeFilesEmitter.js +55 -0
  11. package/dist/core/generators/ApiCallStrategy.d.ts +9 -0
  12. package/dist/core/generators/ApiCallStrategy.js +34 -0
  13. package/dist/core/generators/CallMethodGenerator.d.ts +14 -0
  14. package/dist/core/generators/CallMethodGenerator.js +154 -0
  15. package/dist/core/generators/CallStrategy.d.ts +30 -0
  16. package/dist/core/generators/CallStrategy.js +1 -0
  17. package/dist/core/generators/ModuleCallStrategy.d.ts +9 -0
  18. package/dist/core/generators/ModuleCallStrategy.js +43 -0
  19. package/dist/core/index.d.ts +4 -1
  20. package/dist/core/index.js +4 -1
  21. package/dist/core/method/Method.d.ts +33 -8
  22. package/dist/core/method/Method.js +33 -1
  23. package/dist/core/method/MethodApiTypeAnalyzer.js +3 -0
  24. package/dist/core/method/MethodBuilder.d.ts +2 -1
  25. package/dist/core/method/MethodBuilder.js +2 -2
  26. package/dist/core/method/MethodNameDisambiguator.d.ts +38 -0
  27. package/dist/core/method/MethodNameDisambiguator.js +118 -0
  28. package/dist/core/method/MethodRequestAnalyzer.d.ts +2 -1
  29. package/dist/core/method/MethodRequestAnalyzer.js +5 -4
  30. package/dist/core/method/MethodValidator.d.ts +5 -0
  31. package/dist/core/method/MethodValidator.js +26 -0
  32. package/dist/core/method/generators/MethodGenerator.d.ts +2 -6
  33. package/dist/core/method/generators/MethodGenerator.js +5 -76
  34. package/dist/core/module/Module.d.ts +36 -0
  35. package/dist/core/module/Module.js +142 -0
  36. package/dist/core/module/ModuleClassBuilder.d.ts +3 -0
  37. package/dist/core/module/ModuleClassBuilder.js +3 -2
  38. package/dist/core/module/ModuleConstructorBuilder.d.ts +3 -0
  39. package/dist/core/module/ModuleConstructorBuilder.js +17 -13
  40. package/dist/core/module/ModuleFactory.d.ts +13 -0
  41. package/dist/core/module/ModuleFactory.js +44 -0
  42. package/dist/core/module/ModuleImports.d.ts +3 -1
  43. package/dist/core/module/ModuleImports.js +7 -5
  44. package/dist/core/module/ModuleMethodProcessor.d.ts +1 -2
  45. package/dist/core/module/ModuleMethodProcessor.js +10 -29
  46. package/dist/core/module/ModuleSchemaFileBuilder.d.ts +15 -0
  47. package/dist/core/module/ModuleSchemaFileBuilder.js +37 -0
  48. package/dist/core/openapi/InlineSchemaPromoter.d.ts +33 -0
  49. package/dist/core/openapi/InlineSchemaPromoter.js +140 -0
  50. package/dist/core/schema/ReflectorInterface.d.ts +14 -0
  51. package/dist/core/schema/ReflectorInterface.js +24 -0
  52. package/dist/core/schema/Schema.d.ts +30 -0
  53. package/dist/core/schema/Schema.js +64 -0
  54. package/dist/core/schema/SchemaClassRenderer.d.ts +16 -0
  55. package/dist/core/schema/SchemaClassRenderer.js +54 -0
  56. package/dist/core/schema/SchemaDependencyCollector.d.ts +17 -0
  57. package/dist/core/schema/SchemaDependencyCollector.js +33 -0
  58. package/dist/core/schema/SchemaPropertyClassifier.d.ts +19 -0
  59. package/dist/core/schema/SchemaPropertyClassifier.js +106 -0
  60. package/dist/core/schema/SchemaRegistry.d.ts +16 -0
  61. package/dist/core/schema/SchemaRegistry.js +51 -0
  62. package/dist/generate-doc.js +9 -76
  63. package/dist/helpers/codegen.d.ts +19 -0
  64. package/dist/helpers/codegen.js +38 -0
  65. package/dist/helpers/prop-name.d.ts +10 -0
  66. package/dist/helpers/prop-name.js +13 -0
  67. package/dist/loadTemplate.d.ts +2 -0
  68. package/dist/loadTemplate.js +8 -0
  69. package/dist/loaders/ConfigLoader.d.ts +20 -0
  70. package/dist/loaders/ConfigLoader.js +70 -0
  71. package/dist/loaders/OpenAPILoader.d.ts +10 -0
  72. package/dist/loaders/OpenAPILoader.js +31 -0
  73. package/dist/main.d.ts +11 -55
  74. package/dist/main.js +38 -327
  75. package/dist/module.d.ts +8 -3
  76. package/dist/module.js +16 -21
  77. package/dist/props/array.property.d.ts +3 -1
  78. package/dist/props/array.property.js +6 -9
  79. package/dist/props/enum.property.d.ts +2 -0
  80. package/dist/props/enum.property.js +6 -7
  81. package/dist/props/primitive.property.d.ts +0 -1
  82. package/dist/props/primitive.property.js +4 -9
  83. package/dist/runtime/reflector.svelte.ts +210 -0
  84. package/dist/schema.d.ts +2 -2
  85. package/dist/schema.js +36 -132
  86. package/package.json +7 -3
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Per-run codegen state. Replaces module-level mutable globals so two
3
+ * concurrent Reflector instances don't corrupt each other's output.
4
+ *
5
+ * - `enumTypes`: maps the joined enum literal string (e.g. `'a','b','c'`) to
6
+ * its generated type name. `EnumProp` writes here on first sighting and
7
+ * reads back the stable name on repeat sightings.
8
+ * - `mockedParams`: set of path-param names that need `$state` fallbacks in
9
+ * the generated `MockedParams` class. Written by `ModuleClassBuilder` when
10
+ * it sees path params.
11
+ */
12
+ export declare class CodegenContext {
13
+ readonly enumTypes: Map<string, string>;
14
+ readonly mockedParams: Set<string>;
15
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Per-run codegen state. Replaces module-level mutable globals so two
3
+ * concurrent Reflector instances don't corrupt each other's output.
4
+ *
5
+ * - `enumTypes`: maps the joined enum literal string (e.g. `'a','b','c'`) to
6
+ * its generated type name. `EnumProp` writes here on first sighting and
7
+ * reads back the stable name on repeat sightings.
8
+ * - `mockedParams`: set of path-param names that need `$state` fallbacks in
9
+ * the generated `MockedParams` class. Written by `ModuleClassBuilder` when
10
+ * it sees path params.
11
+ */
12
+ export class CodegenContext {
13
+ enumTypes = new Map();
14
+ mockedParams = new Set();
15
+ }
@@ -0,0 +1,25 @@
1
+ import { Module } from "./module/Module.js";
2
+ import { Schema } from "./schema/Schema.js";
3
+ import type { ComponentsObject, PathsObject } from "../types/open-api-spec.interface.js";
4
+ import type { FieldConfigs, TypeImports } from "../types/types.js";
5
+ import type { ReflectorConfig } from "./config/ReflectorConfig.js";
6
+ export declare class Reflector {
7
+ readonly schemas: Schema[];
8
+ readonly modules: Module[];
9
+ readonly propertiesNames: Set<string>;
10
+ private readonly registry;
11
+ private readonly typeImports;
12
+ private readonly config;
13
+ private readonly context;
14
+ private readonly srcDir;
15
+ constructor(params: {
16
+ components: ComponentsObject;
17
+ paths: PathsObject;
18
+ fieldConfigs: FieldConfigs;
19
+ typeImports: TypeImports;
20
+ apiImport: string;
21
+ experimentalFeatures?: boolean;
22
+ config?: Partial<ReflectorConfig>;
23
+ });
24
+ build(): Promise<{}>;
25
+ }
@@ -0,0 +1,66 @@
1
+ import * as path from "node:path";
2
+ import * as fs from "node:fs";
3
+ import * as process from "node:process";
4
+ import { Source } from "../file.js";
5
+ import { Module } from "./module/Module.js";
6
+ import { Schema } from "./schema/Schema.js";
7
+ import { CodegenContext } from "./CodegenContext.js";
8
+ import { InlineSchemaPromoter } from "./openapi/InlineSchemaPromoter.js";
9
+ import { SchemaRegistry } from "./schema/SchemaRegistry.js";
10
+ import { ModuleFactory } from "./module/ModuleFactory.js";
11
+ import { ModuleSchemaFileBuilder } from "./module/ModuleSchemaFileBuilder.js";
12
+ import { RuntimeFilesEmitter } from "./emit/RuntimeFilesEmitter.js";
13
+ import { resolveReflectorConfig } from "./config/ReflectorConfig.js";
14
+ import { generatedDir } from "../vars.global.js";
15
+ export class Reflector {
16
+ schemas;
17
+ modules;
18
+ propertiesNames;
19
+ registry;
20
+ typeImports;
21
+ config;
22
+ context = new CodegenContext();
23
+ srcDir = path.resolve(process.cwd(), `${generatedDir}/controllers`);
24
+ constructor(params) {
25
+ const { components, paths, fieldConfigs, typeImports, apiImport, experimentalFeatures, config } = params;
26
+ this.typeImports = typeImports;
27
+ this.config = resolveReflectorConfig(config);
28
+ InlineSchemaPromoter.promote(components, paths);
29
+ this.modules = ModuleFactory.build({
30
+ paths,
31
+ apiImport,
32
+ experimentalFeatures: experimentalFeatures ?? false,
33
+ context: this.context,
34
+ config: this.config,
35
+ });
36
+ this.registry = new SchemaRegistry({ components, fieldConfigs, context: this.context });
37
+ this.schemas = this.registry.schemas;
38
+ this.propertiesNames = this.registry.propertiesNames;
39
+ }
40
+ async build() {
41
+ fs.rmSync(this.srcDir, { recursive: true, force: true });
42
+ fs.mkdirSync(this.srcDir, { recursive: true });
43
+ const moduleSchemaFiles = [];
44
+ for (const module of this.modules) {
45
+ if (module.methods.length === 0 || module.schemaClassNames.length === 0)
46
+ continue;
47
+ const neededSchemas = this.registry.resolveTransitiveDeps(module.schemaClassNames);
48
+ if (neededSchemas.length === 0)
49
+ continue;
50
+ const data = ModuleSchemaFileBuilder.build({ schemas: neededSchemas, typeImports: this.typeImports, config: this.config });
51
+ const schemaFilePath = module.src.path.replace(".module.svelte.ts", ".schema.svelte.ts");
52
+ moduleSchemaFiles.push(new Source({ path: schemaFilePath, data }));
53
+ }
54
+ const runtimeFiles = RuntimeFilesEmitter.build({
55
+ propertiesNames: this.propertiesNames,
56
+ context: this.context,
57
+ });
58
+ await Promise.all([
59
+ ...moduleSchemaFiles.map((f) => f.save()),
60
+ ...runtimeFiles.map((f) => f.save()),
61
+ ...this.modules.filter((m) => m.methods.length > 0).map((m) => m.src.save()),
62
+ ...this.modules.filter((m) => m.methods.length > 0 && m.apiSrc).map((m) => m.apiSrc.save()),
63
+ ]);
64
+ return {};
65
+ }
66
+ }
@@ -1,4 +1,4 @@
1
- import type { Method } from "../../method.js";
1
+ import type { Method } from "../method/Method.js";
2
2
  import type { ModuleImports } from "../module/ModuleImports.js";
3
3
  import type { ModuleClassBuilder } from "../module/ModuleClassBuilder.js";
4
4
  export interface ApiEndpointBlock {
@@ -9,6 +9,7 @@ export interface ApiEndpointBlock {
9
9
  export declare class ApiClassBuilder {
10
10
  private readonly imports;
11
11
  private readonly methodGenerator;
12
+ private readonly callStrategy;
12
13
  private readonly paramProcessor;
13
14
  constructor(params: {
14
15
  imports: ModuleImports;
@@ -19,5 +20,4 @@ export declare class ApiClassBuilder {
19
20
  }): ApiEndpointBlock | null;
20
21
  private buildStateProperties;
21
22
  private buildResetLines;
22
- private shouldSkipMethod;
23
23
  }
@@ -1,9 +1,12 @@
1
- import { capitalizeFirstLetter, createDangerMessage, treatByUppercase } from "../../helpers/helpers.js";
2
- import { ApiMethodGenerator } from "./ApiMethodGenerator.js";
1
+ import { capitalizeFirstLetter } from "../../helpers/helpers.js";
2
+ import { MethodValidator } from "../method/MethodValidator.js";
3
+ import { CallMethodGenerator } from "../generators/CallMethodGenerator.js";
4
+ import { ApiCallStrategy } from "../generators/ApiCallStrategy.js";
3
5
  import { ApiParamProcessor } from "./ApiParamProcessor.js";
4
6
  export class ApiClassBuilder {
5
7
  imports;
6
- methodGenerator = new ApiMethodGenerator();
8
+ methodGenerator = new CallMethodGenerator();
9
+ callStrategy = new ApiCallStrategy();
7
10
  paramProcessor;
8
11
  constructor(params) {
9
12
  this.imports = params.imports;
@@ -14,7 +17,7 @@ export class ApiClassBuilder {
14
17
  }
15
18
  build(params) {
16
19
  const { method } = params;
17
- if (this.shouldSkipMethod(method))
20
+ if (MethodValidator.isSkippable(method))
18
21
  return null;
19
22
  const { request, headers, cookies, paths, querys } = method;
20
23
  const { bodyType, responseType, attributeType, isPrimitiveResponse } = request;
@@ -29,7 +32,7 @@ export class ApiClassBuilder {
29
32
  // Build state properties
30
33
  const stateProps = this.buildStateProperties(method);
31
34
  // Build the call method
32
- const callMethod = this.methodGenerator.generate(method);
35
+ const callMethod = this.methodGenerator.generate(method, this.callStrategy);
33
36
  // Build reset method
34
37
  const resetLines = this.buildResetLines(method, processedParams.paramReset);
35
38
  // Collect schema entries
@@ -100,26 +103,4 @@ export class ApiClassBuilder {
100
103
  lines.push(...paramReset);
101
104
  return lines;
102
105
  }
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
106
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Isolates the consumer's framework conventions (import aliases, env flag
3
+ * name) from the codegen core. Defaults reproduce the previous hard-coded
4
+ * SvelteKit paths so existing consumers don't need to change anything.
5
+ */
6
+ export interface ReflectorConfig {
7
+ /** Alias that resolves to the generated reflector folder (e.g. `$reflector`). */
8
+ reflectorAlias: string;
9
+ /** Full import path to the validators/sanitizers module. */
10
+ validatorsImport: string;
11
+ /** Module path for the environment flag (e.g. `$env/static/public`). */
12
+ environmentImport: string;
13
+ /** Name of the exported environment flag — values other than `DEV` are treated as prod. */
14
+ environmentFlag: string;
15
+ }
16
+ export declare const DEFAULT_REFLECTOR_CONFIG: ReflectorConfig;
17
+ export declare function resolveReflectorConfig(partial?: Partial<ReflectorConfig>): ReflectorConfig;
@@ -0,0 +1,9 @@
1
+ export const DEFAULT_REFLECTOR_CONFIG = {
2
+ reflectorAlias: "$reflector",
3
+ validatorsImport: "$lib/sanitizers/validateFormats",
4
+ environmentImport: "$env/static/public",
5
+ environmentFlag: "PUBLIC_ENVIRONMENT",
6
+ };
7
+ export function resolveReflectorConfig(partial) {
8
+ return { ...DEFAULT_REFLECTOR_CONFIG, ...(partial ?? {}) };
9
+ }
@@ -0,0 +1,13 @@
1
+ import { Source } from "../../file.js";
2
+ import type { CodegenContext } from "../CodegenContext.js";
3
+ /**
4
+ * Emits the shared runtime-support files that sit alongside the generated
5
+ * module files: the reflector runtime template, the FIELD_NAMES list, the
6
+ * enum unions, and the MockedParams class used by path-param stubs.
7
+ */
8
+ export declare class RuntimeFilesEmitter {
9
+ static build(params: {
10
+ propertiesNames: Set<string>;
11
+ context: CodegenContext;
12
+ }): Source[];
13
+ }
@@ -0,0 +1,55 @@
1
+ import * as path from "node:path";
2
+ import * as process from "node:process";
3
+ import { Source } from "../../file.js";
4
+ import { loadReflectorTemplate } from "../../loadTemplate.js";
5
+ import { generatedDir } from "../../vars.global.js";
6
+ import { dedent } from "../../helpers/codegen.js";
7
+ function generated(relPath) {
8
+ return path.resolve(process.cwd(), `${generatedDir}/${relPath}`);
9
+ }
10
+ /**
11
+ * Emits the shared runtime-support files that sit alongside the generated
12
+ * module files: the reflector runtime template, the FIELD_NAMES list, the
13
+ * enum unions, and the MockedParams class used by path-param stubs.
14
+ */
15
+ export class RuntimeFilesEmitter {
16
+ static build(params) {
17
+ const { propertiesNames, context } = params;
18
+ const typesSrc = new Source({
19
+ path: generated("reflector.svelte.ts"),
20
+ data: loadReflectorTemplate(),
21
+ });
22
+ const fieldLiterals = Array.from(propertiesNames).map((p) => `'${p}'`);
23
+ const fieldsFile = new Source({
24
+ path: generated("fields.ts"),
25
+ data: dedent `
26
+ export const FIELD_NAMES = [
27
+ ${fieldLiterals}
28
+ ] as const;
29
+ export type FieldName = (typeof FIELD_NAMES)[number]
30
+ `,
31
+ });
32
+ const enumss = Array.from(context.enumTypes)
33
+ .map(([types, key]) => `export const ${key} = [ ${types} ] as const; export type ${key} = typeof ${key}[number] `)
34
+ .join(";");
35
+ const enumFile = new Source({
36
+ path: generated("enums.ts"),
37
+ data: enumss,
38
+ });
39
+ const mockedFields = Array.from(context.mockedParams)
40
+ .map((paramName) => `${paramName} = $state<string | null>(null)`)
41
+ .join(";");
42
+ const mockedFile = new Source({
43
+ path: generated("mocked-params.svelte.ts"),
44
+ data: dedent `
45
+ class MockedParams {
46
+ ${mockedFields}
47
+ }
48
+
49
+ const mockedParams = new MockedParams()
50
+ export default mockedParams
51
+ `,
52
+ });
53
+ return [typesSrc, fieldsFile, enumFile, mockedFile];
54
+ }
55
+ }
@@ -0,0 +1,9 @@
1
+ import type { CallMethodInput, CallStrategy } from "./CallStrategy.js";
2
+ export declare class ApiCallStrategy implements CallStrategy {
3
+ listStateAccess(_method: CallMethodInput): string;
4
+ buildSignature(method: CallMethodInput): string;
5
+ entityStateAccess(_method: CallMethodInput): string;
6
+ formStateAccess(_method: CallMethodInput): string;
7
+ private buildParamsType;
8
+ private buildPathsInfo;
9
+ }
@@ -0,0 +1,34 @@
1
+ export class ApiCallStrategy {
2
+ listStateAccess(_method) {
3
+ return "this.data";
4
+ }
5
+ buildSignature(method) {
6
+ const paramsType = this.buildParamsType(method);
7
+ return `async call(params?: ${paramsType})`;
8
+ }
9
+ entityStateAccess(_method) {
10
+ return "this.data";
11
+ }
12
+ formStateAccess(_method) {
13
+ return "this.form";
14
+ }
15
+ buildParamsType(method) {
16
+ const responseType = method.responseTypeInterface;
17
+ const pathsBlock = this.buildPathsInfo(method);
18
+ if (pathsBlock) {
19
+ return `ApiCallParams<${responseType}, ${pathsBlock}>`;
20
+ }
21
+ return `ApiCallParams<${responseType}>`;
22
+ }
23
+ buildPathsInfo(method) {
24
+ const paths = method.analyzers.props.paths;
25
+ if (paths.length === 0)
26
+ return undefined;
27
+ return `{ ${paths
28
+ .map((p) => {
29
+ const type = p.rawType ?? p.type;
30
+ return `${p.name}: ${type}`;
31
+ })
32
+ .join("; ")} }`;
33
+ }
34
+ }
@@ -0,0 +1,14 @@
1
+ import type { CallMethodInput, CallStrategy } from "./CallStrategy.js";
2
+ export declare class CallMethodGenerator {
3
+ private readonly endpointBuilder;
4
+ generate(method: CallMethodInput, strategy: CallStrategy): string;
5
+ private buildProps;
6
+ private buildApiCall;
7
+ private buildListCall;
8
+ private buildEntityCall;
9
+ private buildFormCall;
10
+ private buildDeleteCall;
11
+ private buildMethodReturn;
12
+ private joinNames;
13
+ private buildQuerys;
14
+ }
@@ -0,0 +1,154 @@
1
+ import { MethodEndpointBuilder } from "../method/MethodEndpointBuilder.js";
2
+ export class CallMethodGenerator {
3
+ endpointBuilder = new MethodEndpointBuilder();
4
+ generate(method, strategy) {
5
+ const description = `/** ${method.description ?? ""} */`;
6
+ const endpoint = this.endpointBuilder.build(method.endpoint, method.analyzers.props.paths);
7
+ const props = this.buildProps(method);
8
+ const { inside, outside } = this.buildApiCall(method, strategy);
9
+ const methodReturn = this.buildMethodReturn(method, strategy);
10
+ const signature = strategy.buildSignature(method);
11
+ return `
12
+ ${description}
13
+ ${signature} {
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
+ ${outside}
23
+
24
+ try {
25
+ ${inside}
26
+ await onSuccess?.(response);
27
+
28
+ return ${methodReturn};
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.analyzers.props;
45
+ const lines = [];
46
+ if (querys.length > 0) {
47
+ lines.push(`const { ${this.joinNames(querys)} } = this.querys.bundle()`);
48
+ }
49
+ if (paths.length > 0) {
50
+ lines.push(`const { ${this.joinNames(paths)} } = 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
+ buildApiCall(method, strategy) {
58
+ const { attributeType, apiType, responseType, bodyType, hasEnumResponse } = method.analyzers.request;
59
+ const responseTypeStr = hasEnumResponse && responseType ? responseType : method.responseTypeInterface;
60
+ if (attributeType === "list") {
61
+ return this.buildListCall(method, responseTypeStr, strategy);
62
+ }
63
+ if (attributeType === "entity") {
64
+ return this.buildEntityCall(method, responseTypeStr, strategy);
65
+ }
66
+ if (apiType === "post" || apiType === "put" || apiType === "patch") {
67
+ return this.buildFormCall(method, responseTypeStr, bodyType, strategy);
68
+ }
69
+ if (apiType === "delete") {
70
+ return this.buildDeleteCall(method, responseTypeStr, bodyType, strategy);
71
+ }
72
+ return { inside: "", outside: "" };
73
+ }
74
+ buildListCall(method, responseType, strategy) {
75
+ const querys = this.joinNames(method.analyzers.props.querys);
76
+ const inside = `
77
+ const response = await api.get<${responseType}, unknown>({
78
+ endpoint,
79
+ queryData: { ${querys} }
80
+ })
81
+ ${strategy.listStateAccess(method)} = ${method.analyzers.request.responseType}.from(response.data);
82
+ this.totalPages = response.totalPages;
83
+ `;
84
+ return { inside, outside: "" };
85
+ }
86
+ buildEntityCall(method, responseType, strategy) {
87
+ const rType = method.analyzers.request.responseType;
88
+ const isPrimitive = method.analyzers.request.isPrimitiveResponse;
89
+ const querys = this.buildQuerys(method.analyzers.props.querys);
90
+ const assignment = rType && !isPrimitive
91
+ ? `${strategy.entityStateAccess(method)} = new ${rType}({ data: response })`
92
+ : "";
93
+ const inside = `
94
+ const response = await api.get<${responseType}, unknown>({
95
+ endpoint,
96
+ ${querys}
97
+ })
98
+
99
+ ${assignment}
100
+ `;
101
+ return { inside, outside: "" };
102
+ }
103
+ buildFormCall(method, responseType, bodyType, strategy) {
104
+ const { apiType } = method.analyzers.request;
105
+ const hasHeaders = method.analyzers.props.headers.length > 0;
106
+ const hasData = !!bodyType;
107
+ const outside = [];
108
+ if (hasData) {
109
+ outside.push(`const data = ${strategy.formStateAccess(method)}.bundle()`);
110
+ }
111
+ if (hasHeaders) {
112
+ outside.push(`const headers = this.headers.bundle()`);
113
+ }
114
+ const inside = `
115
+ const response = await api.${apiType}<${responseType}>({
116
+ endpoint,
117
+ ${hasData ? "data," : ""}
118
+ ${hasHeaders ? "headers," : ""}
119
+ })
120
+ `;
121
+ return { inside, outside: outside.join("\n") };
122
+ }
123
+ buildDeleteCall(method, responseType, bodyType, strategy) {
124
+ const hasData = !!bodyType;
125
+ const outside = hasData ? `const data = ${strategy.formStateAccess(method)}.bundle()` : "";
126
+ const inside = `
127
+ const response = await api.delete<${responseType}, unknown>({
128
+ endpoint,
129
+ ${hasData ? "data," : ""}
130
+ })
131
+ `;
132
+ return { inside, outside };
133
+ }
134
+ buildMethodReturn(method, strategy) {
135
+ const { attributeType, responseType, hasEnumResponse, isPrimitiveResponse } = method.analyzers.request;
136
+ if (attributeType === "list")
137
+ return strategy.listStateAccess(method);
138
+ if (!responseType)
139
+ return "null";
140
+ if (hasEnumResponse)
141
+ return "response.data";
142
+ if (isPrimitiveResponse)
143
+ return "response";
144
+ return `new ${responseType}({ data: response })`;
145
+ }
146
+ joinNames(props) {
147
+ return props.map((x) => x.name).join(", ");
148
+ }
149
+ buildQuerys(querys) {
150
+ if (querys.length === 0)
151
+ return "";
152
+ return `queryData: {${querys.map((q) => q.name).join(",")}}`;
153
+ }
154
+ }
@@ -0,0 +1,30 @@
1
+ import type { MethodAnalyzers } from "../method/Method.js";
2
+ /** Structural subset of `Method` that the generator and strategies actually read. */
3
+ export interface CallMethodInput {
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;
10
+ endpoint: string;
11
+ description: string | undefined;
12
+ responseTypeInterface: string;
13
+ analyzers: MethodAnalyzers;
14
+ }
15
+ /**
16
+ * Differences between the two flavors of generated `call` method
17
+ * (per-endpoint Api class vs Module `_methodName` protected method).
18
+ */
19
+ export interface CallStrategy {
20
+ /** Full method signature incl. params — e.g. `async call(params?: ...)` or `protected async _foo(params?: ...)` */
21
+ buildSignature(method: CallMethodInput): 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;
26
+ /** State field receiving the fetched entity */
27
+ entityStateAccess(method: CallMethodInput): string;
28
+ /** Form instance accessed to bundle body data */
29
+ formStateAccess(method: CallMethodInput): string;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { CallMethodInput, CallStrategy } from "./CallStrategy.js";
2
+ export declare class ModuleCallStrategy implements CallStrategy {
3
+ listStateAccess(method: CallMethodInput): string;
4
+ buildSignature(method: CallMethodInput): string;
5
+ entityStateAccess(method: CallMethodInput): string;
6
+ formStateAccess(method: CallMethodInput): string;
7
+ private buildParamsType;
8
+ private buildPathsBlock;
9
+ }
@@ -0,0 +1,43 @@
1
+ import { treatByUppercase } from "../../helpers/helpers.js";
2
+ export class ModuleCallStrategy {
3
+ listStateAccess(method) {
4
+ return `this.list${method.stateSuffix}`;
5
+ }
6
+ buildSignature(method) {
7
+ const paramsType = this.buildParamsType(method);
8
+ return `protected async _${method.name}(params?: ${paramsType})`;
9
+ }
10
+ entityStateAccess(method) {
11
+ const rType = method.analyzers.request.responseType ?? "";
12
+ return `this.${treatByUppercase(rType)}`;
13
+ }
14
+ formStateAccess(method) {
15
+ return `this.forms.${method.name}`;
16
+ }
17
+ buildParamsType(method) {
18
+ const behaviorType = `Behavior<${method.responseTypeInterface}, ApiErrorResponse>`;
19
+ const pathsBlock = this.buildPathsBlock(method);
20
+ if (pathsBlock) {
21
+ return `{
22
+ behavior?: ${behaviorType};${pathsBlock}
23
+ }`;
24
+ }
25
+ return `{
26
+ behavior?: ${behaviorType};
27
+ }`;
28
+ }
29
+ buildPathsBlock(method) {
30
+ const paths = method.analyzers.props.paths;
31
+ if (paths.length === 0)
32
+ return undefined;
33
+ return `
34
+ paths?: {
35
+ ${paths
36
+ .map((p) => {
37
+ const type = p.rawType ?? p.type;
38
+ return `${p.name}: ${type}`;
39
+ })
40
+ .join("\n")}
41
+ }`;
42
+ }
43
+ }
@@ -6,8 +6,11 @@ export { ModuleConstructorBuilder, type Form } from "./module/ModuleConstructorB
6
6
  export { ModuleFileBuilder, type FileBuildParams } from "./module/ModuleFileBuilder.js";
7
7
  export { ApiFileBuilder } from "./api/ApiFileBuilder.js";
8
8
  export { ApiClassBuilder, type ApiEndpointBlock } from "./api/ApiClassBuilder.js";
9
- export { ApiMethodGenerator } from "./api/ApiMethodGenerator.js";
10
9
  export { ApiParamProcessor, type ApiProcessedParams } from "./api/ApiParamProcessor.js";
10
+ export { CallMethodGenerator } from "./generators/CallMethodGenerator.js";
11
+ export { ApiCallStrategy } from "./generators/ApiCallStrategy.js";
12
+ export { ModuleCallStrategy } from "./generators/ModuleCallStrategy.js";
13
+ export type { CallStrategy } from "./generators/CallStrategy.js";
11
14
  export { Method } from "./method/Method.js";
12
15
  export { MethodBuilder } from "./method/MethodBuilder.js";
13
16
  export { MethodApiTypeAnalyzer } from "./method/MethodApiTypeAnalyzer.js";
@@ -8,8 +8,11 @@ export { ModuleFileBuilder } from "./module/ModuleFileBuilder.js";
8
8
  // Api-related exports
9
9
  export { ApiFileBuilder } from "./api/ApiFileBuilder.js";
10
10
  export { ApiClassBuilder } from "./api/ApiClassBuilder.js";
11
- export { ApiMethodGenerator } from "./api/ApiMethodGenerator.js";
12
11
  export { ApiParamProcessor } from "./api/ApiParamProcessor.js";
12
+ // Shared generators
13
+ export { CallMethodGenerator } from "./generators/CallMethodGenerator.js";
14
+ export { ApiCallStrategy } from "./generators/ApiCallStrategy.js";
15
+ export { ModuleCallStrategy } from "./generators/ModuleCallStrategy.js";
13
16
  // Method-related exports
14
17
  export { Method } from "./method/Method.js";
15
18
  export { MethodBuilder } from "./method/MethodBuilder.js";