@kithinji/pod 1.0.0

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 (98) hide show
  1. package/build.js +22 -0
  2. package/dist/main.js +4464 -0
  3. package/dist/main.js.map +7 -0
  4. package/dist/types/add/component/component.d.ts +5 -0
  5. package/dist/types/add/component/component.d.ts.map +1 -0
  6. package/dist/types/add/component/index.d.ts +2 -0
  7. package/dist/types/add/component/index.d.ts.map +1 -0
  8. package/dist/types/add/index.d.ts +4 -0
  9. package/dist/types/add/index.d.ts.map +1 -0
  10. package/dist/types/add/module/index.d.ts +2 -0
  11. package/dist/types/add/module/index.d.ts.map +1 -0
  12. package/dist/types/add/module/module.d.ts +3 -0
  13. package/dist/types/add/module/module.d.ts.map +1 -0
  14. package/dist/types/add/new/index.d.ts +2 -0
  15. package/dist/types/add/new/index.d.ts.map +1 -0
  16. package/dist/types/config/config.d.ts +18 -0
  17. package/dist/types/config/config.d.ts.map +1 -0
  18. package/dist/types/config/index.d.ts +2 -0
  19. package/dist/types/config/index.d.ts.map +1 -0
  20. package/dist/types/dev/index.d.ts +2 -0
  21. package/dist/types/dev/index.d.ts.map +1 -0
  22. package/dist/types/dev/project.d.ts +9 -0
  23. package/dist/types/dev/project.d.ts.map +1 -0
  24. package/dist/types/dev/server.d.ts +2 -0
  25. package/dist/types/dev/server.d.ts.map +1 -0
  26. package/dist/types/docker/docker.d.ts +2 -0
  27. package/dist/types/docker/docker.d.ts.map +1 -0
  28. package/dist/types/docker/index.d.ts +2 -0
  29. package/dist/types/docker/index.d.ts.map +1 -0
  30. package/dist/types/macros/expand_macros.d.ts +48 -0
  31. package/dist/types/macros/expand_macros.d.ts.map +1 -0
  32. package/dist/types/macros/index.d.ts +3 -0
  33. package/dist/types/macros/index.d.ts.map +1 -0
  34. package/dist/types/macros/macro_executer.d.ts +12 -0
  35. package/dist/types/macros/macro_executer.d.ts.map +1 -0
  36. package/dist/types/main.d.ts +13 -0
  37. package/dist/types/main.d.ts.map +1 -0
  38. package/dist/types/plugins/analyzers/graph.d.ts +25 -0
  39. package/dist/types/plugins/analyzers/graph.d.ts.map +1 -0
  40. package/dist/types/plugins/css/index.d.ts +7 -0
  41. package/dist/types/plugins/css/index.d.ts.map +1 -0
  42. package/dist/types/plugins/generators/generate_controller.d.ts +2 -0
  43. package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -0
  44. package/dist/types/plugins/generators/generate_rsc.d.ts +2 -0
  45. package/dist/types/plugins/generators/generate_rsc.d.ts.map +1 -0
  46. package/dist/types/plugins/generators/generate_server_component.d.ts +2 -0
  47. package/dist/types/plugins/generators/generate_server_component.d.ts.map +1 -0
  48. package/dist/types/plugins/generators/tsx_server_stub.d.ts +2 -0
  49. package/dist/types/plugins/generators/tsx_server_stub.d.ts.map +1 -0
  50. package/dist/types/plugins/index.d.ts +4 -0
  51. package/dist/types/plugins/index.d.ts.map +1 -0
  52. package/dist/types/plugins/my.d.ts +10 -0
  53. package/dist/types/plugins/my.d.ts.map +1 -0
  54. package/dist/types/plugins/transformers/j2d.d.ts +11 -0
  55. package/dist/types/plugins/transformers/j2d.d.ts.map +1 -0
  56. package/dist/types/store/index.d.ts +2 -0
  57. package/dist/types/store/index.d.ts.map +1 -0
  58. package/dist/types/store/store.d.ts +14 -0
  59. package/dist/types/store/store.d.ts.map +1 -0
  60. package/dist/types/utils/cases.d.ts +4 -0
  61. package/dist/types/utils/cases.d.ts.map +1 -0
  62. package/dist/types/utils/create.d.ts +12 -0
  63. package/dist/types/utils/create.d.ts.map +1 -0
  64. package/dist/types/utils/index.d.ts +3 -0
  65. package/dist/types/utils/index.d.ts.map +1 -0
  66. package/package.json +44 -0
  67. package/src/add/component/component.ts +496 -0
  68. package/src/add/component/index.ts +1 -0
  69. package/src/add/index.ts +3 -0
  70. package/src/add/module/index.ts +1 -0
  71. package/src/add/module/module.ts +521 -0
  72. package/src/add/new/index.ts +135 -0
  73. package/src/config/config.ts +141 -0
  74. package/src/config/index.ts +1 -0
  75. package/src/dev/index.ts +1 -0
  76. package/src/dev/project.ts +45 -0
  77. package/src/dev/server.ts +190 -0
  78. package/src/docker/docker.ts +452 -0
  79. package/src/docker/index.ts +1 -0
  80. package/src/macros/expand_macros.ts +791 -0
  81. package/src/macros/index.ts +2 -0
  82. package/src/macros/macro_executer.ts +189 -0
  83. package/src/main.ts +95 -0
  84. package/src/plugins/analyzers/graph.ts +291 -0
  85. package/src/plugins/css/index.ts +25 -0
  86. package/src/plugins/generators/generate_controller.ts +308 -0
  87. package/src/plugins/generators/generate_rsc.ts +274 -0
  88. package/src/plugins/generators/generate_server_component.ts +279 -0
  89. package/src/plugins/generators/tsx_server_stub.ts +295 -0
  90. package/src/plugins/index.ts +3 -0
  91. package/src/plugins/my.ts +274 -0
  92. package/src/plugins/transformers/j2d.ts +1014 -0
  93. package/src/store/index.ts +1 -0
  94. package/src/store/store.ts +44 -0
  95. package/src/utils/cases.ts +15 -0
  96. package/src/utils/create.ts +26 -0
  97. package/src/utils/index.ts +2 -0
  98. package/tsconfig.json +27 -0
@@ -0,0 +1,308 @@
1
+ import * as path from "path";
2
+ import { parseSync } from "@swc/core";
3
+ import type {
4
+ ClassDeclaration,
5
+ Decorator,
6
+ Param,
7
+ ImportDeclaration,
8
+ } from "@swc/core";
9
+
10
+ interface ServiceMethod {
11
+ name: string;
12
+ params: MethodParam[];
13
+ returnType: string;
14
+ isAsync: boolean;
15
+ paramSchemas: string[];
16
+ returnSchema?: string;
17
+ }
18
+
19
+ interface MethodParam {
20
+ name: string;
21
+ type: string;
22
+ decorators: string[];
23
+ }
24
+
25
+ interface ServiceInfo {
26
+ className: string;
27
+ methods: ServiceMethod[];
28
+ hasInjectable: boolean;
29
+ importMap: Record<string, string>;
30
+ }
31
+
32
+ export function generateController(
33
+ filePath: string,
34
+ code: string
35
+ ): string | null {
36
+ const ast = parseSync(code, {
37
+ syntax: "typescript",
38
+ tsx: filePath.endsWith("x"),
39
+ decorators: true,
40
+ });
41
+
42
+ const serviceInfo = extractServiceInfo(ast);
43
+ if (!serviceInfo || !serviceInfo.hasInjectable) return null;
44
+
45
+ return generateControllerCode(serviceInfo, filePath);
46
+ }
47
+
48
+ function extractServiceInfo(ast: any): ServiceInfo | null {
49
+ let serviceClass: ClassDeclaration | null = null;
50
+ let hasInjectable = false;
51
+ const importMap: Record<string, string> = {};
52
+
53
+ for (const item of ast.body) {
54
+ if (item.type === "ImportDeclaration") {
55
+ const decl = item as ImportDeclaration;
56
+ const source = decl.source.value;
57
+ decl.specifiers.forEach((spec) => {
58
+ if (
59
+ spec.type === "ImportSpecifier" ||
60
+ spec.type === "ImportDefaultSpecifier" ||
61
+ spec.type === "ImportNamespaceSpecifier"
62
+ ) {
63
+ importMap[spec.local.value] = source;
64
+ }
65
+ });
66
+ }
67
+
68
+ if (
69
+ item.type === "ExportDeclaration" &&
70
+ item.declaration.type === "ClassDeclaration"
71
+ ) {
72
+ const classDecl = item.declaration as ClassDeclaration;
73
+ if (hasInjectableDecorator(classDecl.decorators)) {
74
+ serviceClass = classDecl;
75
+ hasInjectable = true;
76
+ }
77
+ }
78
+ }
79
+
80
+ if (!serviceClass || !serviceClass.identifier) return null;
81
+
82
+ return {
83
+ className: serviceClass.identifier.value,
84
+ methods: extractMethods(serviceClass),
85
+ hasInjectable,
86
+ importMap,
87
+ };
88
+ }
89
+
90
+ function hasInjectableDecorator(decorators?: Decorator[]): boolean {
91
+ return (
92
+ decorators?.some((d) => {
93
+ const expr = d.expression;
94
+ return (
95
+ (expr.type === "Identifier" && expr.value === "Injectable") ||
96
+ (expr.type === "CallExpression" &&
97
+ expr.callee.type === "Identifier" &&
98
+ expr.callee.value === "Injectable")
99
+ );
100
+ }) ?? false
101
+ );
102
+ }
103
+
104
+ function extractMethods(classDecl: ClassDeclaration): ServiceMethod[] {
105
+ const methods: ServiceMethod[] = [];
106
+
107
+ for (const member of classDecl.body) {
108
+ if (member.type === "ClassMethod" && member.accessibility === "public") {
109
+ const method = member as any;
110
+ const methodName =
111
+ method.key.type === "Identifier" ? method.key.value : "";
112
+ if (!methodName) continue;
113
+
114
+ if (!method.function.async) {
115
+ throw new Error(
116
+ `Server action ${classDecl.identifier.value}.${methodName} must be async.`
117
+ );
118
+ }
119
+
120
+ const { paramSchemas, returnSchema } = extractSignature(
121
+ method.function.decorators,
122
+ method.function.params.length
123
+ );
124
+
125
+ methods.push({
126
+ name: methodName,
127
+ params: extractMethodParams(method.function.params),
128
+ returnType: extractReturnType(method.function.returnType),
129
+ isAsync: true,
130
+ paramSchemas,
131
+ returnSchema,
132
+ });
133
+ }
134
+ }
135
+ return methods;
136
+ }
137
+
138
+ function extractSignature(
139
+ decorators: Decorator[] | undefined,
140
+ paramCount: number
141
+ ) {
142
+ if (!decorators) return { paramSchemas: [] };
143
+
144
+ for (const decorator of decorators) {
145
+ const expr = decorator.expression;
146
+ if (
147
+ expr.type === "CallExpression" &&
148
+ expr.callee.type === "Identifier" &&
149
+ expr.callee.value === "Signature"
150
+ ) {
151
+ const args = expr.arguments;
152
+ if (args.length === 0) return { paramSchemas: [] };
153
+
154
+ const schemaStrings = args.map((arg) =>
155
+ stringifyExpression(arg.expression)
156
+ );
157
+
158
+ if (args.length === 1) {
159
+ return { paramSchemas: [], returnSchema: schemaStrings[0] };
160
+ }
161
+
162
+ return {
163
+ paramSchemas: schemaStrings.slice(0, -1),
164
+ returnSchema: schemaStrings[schemaStrings.length - 1],
165
+ };
166
+ }
167
+ }
168
+ return { paramSchemas: [] };
169
+ }
170
+
171
+ function stringifyExpression(expr: any): string {
172
+ if (expr.type === "Identifier") return expr.value;
173
+ if (expr.type === "MemberExpression") {
174
+ return `${stringifyExpression(expr.object)}.${expr.property.value || ""}`;
175
+ }
176
+ if (expr.type === "CallExpression") {
177
+ const args = expr.arguments
178
+ .map((a: any) => stringifyExpression(a.expression))
179
+ .join(", ");
180
+ return `${stringifyExpression(expr.callee)}(${args})`;
181
+ }
182
+ return "any";
183
+ }
184
+
185
+ function extractMethodParams(params: Param[]): MethodParam[] {
186
+ return params.map((p) => {
187
+ const pat = (p as any).pat;
188
+ return {
189
+ name: pat.value,
190
+ type: pat.typeAnnotation
191
+ ? stringifyType(pat.typeAnnotation.typeAnnotation)
192
+ : "any",
193
+ decorators: [],
194
+ };
195
+ });
196
+ }
197
+
198
+ function extractReturnType(node: any): string {
199
+ if (!node?.typeAnnotation) return "any";
200
+ const type = node.typeAnnotation;
201
+ if (type.type === "TsTypeReference" && type.typeName.value === "Promise") {
202
+ return stringifyType(type.typeParams?.params[0]);
203
+ }
204
+ return stringifyType(type);
205
+ }
206
+
207
+ function stringifyType(node: any): string {
208
+ if (!node) return "any";
209
+ switch (node.type) {
210
+ case "TsKeywordType":
211
+ return node.kind;
212
+ case "TsTypeReference":
213
+ const base = node.typeName.value;
214
+ const args = node.typeParams
215
+ ? `<${node.typeParams.params.map(stringifyType).join(", ")}>`
216
+ : "";
217
+ return base + args;
218
+ case "TsArrayType":
219
+ return `${stringifyType(node.elemType)}[]`;
220
+ default:
221
+ return "any";
222
+ }
223
+ }
224
+
225
+ function generateControllerCode(
226
+ serviceInfo: ServiceInfo,
227
+ filePath: string
228
+ ): string {
229
+ const serviceName = serviceInfo.className;
230
+ const controllerName = serviceName.replace(/Service$/, "AutoController");
231
+ const serviceImportPath = `./${path.basename(filePath).replace(/\.ts$/, "")}`;
232
+
233
+ const importGroups = new Map<string, Set<string>>();
234
+
235
+ const registerIdentifier = (id: string) => {
236
+ const source = serviceInfo.importMap[id] || serviceImportPath;
237
+ if (!importGroups.has(source)) importGroups.set(source, new Set());
238
+ importGroups.get(source)!.add(id);
239
+ };
240
+
241
+ serviceInfo.methods.forEach((m) => {
242
+ [...m.paramSchemas, m.returnSchema].filter(Boolean).forEach((s) => {
243
+ const matches = s!.match(/[A-Z][a-zA-Z0-9]*/g);
244
+ matches?.forEach(registerIdentifier);
245
+ if (s!.includes("z.")) registerIdentifier("z");
246
+ });
247
+ });
248
+
249
+ let importStrings = `import { Controller, Post, Get, Body } from "@kithinji/orca";\n`;
250
+
251
+ importGroups.forEach((ids, source) => {
252
+ const filteredIds = Array.from(ids).filter((id) => id !== serviceName);
253
+ if (filteredIds.length > 0) {
254
+ importStrings += `\nimport { ${filteredIds.join(
255
+ ", "
256
+ )} } from "${source}";`;
257
+ }
258
+ });
259
+
260
+ const methods = serviceInfo.methods
261
+ .map((m) => {
262
+ const hasParams = m.params.length > 0;
263
+ const bodyParam = hasParams ? `@Body() body: any` : "";
264
+
265
+ let body = "";
266
+ if (hasParams) {
267
+ if (m.paramSchemas.length > 0) {
268
+ body += ` const b = typeof body === 'object' && body !== null ? body : {};\n`;
269
+ m.params.forEach((p, i) => {
270
+ body += ` const ${p.name} = ${m.paramSchemas[i]}.parse(b.${p.name});\n`;
271
+ });
272
+ } else {
273
+ body += ` const { ${m.params
274
+ .map((p) => p.name)
275
+ .join(", ")} } = body;\n`;
276
+ }
277
+ }
278
+
279
+ const callArgs = m.params.map((p) => p.name).join(", ");
280
+ const serviceCall = `this.${
281
+ serviceName.charAt(0).toLowerCase() + serviceName.slice(1)
282
+ }.${m.name}(${callArgs})`;
283
+
284
+ if (m.returnSchema) {
285
+ body += ` const res = await ${serviceCall};\n return ${m.returnSchema}.parse(res);`;
286
+ } else {
287
+ body += ` return ${serviceCall};`;
288
+ }
289
+
290
+ return ` @${hasParams ? "Post" : "Get"}("${m.name}")\n async ${
291
+ m.name
292
+ }(${bodyParam}): Promise<${m.returnType}> {\n${body}\n }`;
293
+ })
294
+ .join("\n\n");
295
+
296
+ return `${importStrings}
297
+
298
+ @Controller("/${serviceName}", {
299
+ providedIn: "root",
300
+ })
301
+ export class ${controllerName} {
302
+ constructor(private readonly ${
303
+ serviceName.charAt(0).toLowerCase() + serviceName.slice(1)
304
+ }: ${serviceName}) {}
305
+
306
+ ${methods}
307
+ }`;
308
+ }
@@ -0,0 +1,274 @@
1
+ import { parseSync } from "@swc/core";
2
+ import type { ClassDeclaration, Decorator } from "@swc/core";
3
+
4
+ interface ServiceMethod {
5
+ name: string;
6
+ params: MethodParam[];
7
+ returnType: string;
8
+ isAsync: boolean;
9
+ }
10
+
11
+ interface MethodParam {
12
+ name: string;
13
+ type: string;
14
+ }
15
+
16
+ interface ServiceInfo {
17
+ className: string;
18
+ methods: ServiceMethod[];
19
+ }
20
+
21
+ export function generateRscStub(filePath: string, code: string): string {
22
+ const ast = parseSync(code, {
23
+ syntax: "typescript",
24
+ tsx: filePath.endsWith("x"),
25
+ decorators: true,
26
+ });
27
+
28
+ const serviceInfo = extractServiceInfo(ast);
29
+
30
+ return generateStubCode(serviceInfo);
31
+ }
32
+
33
+ function extractServiceInfo(ast: any): ServiceInfo {
34
+ let serviceClass: ClassDeclaration | null = null;
35
+
36
+ for (const item of ast.body) {
37
+ if (
38
+ item.type === "ExportDeclaration" &&
39
+ item.declaration.type === "ClassDeclaration"
40
+ ) {
41
+ const classDecl = item.declaration as ClassDeclaration;
42
+
43
+ if (hasInjectableDecorator(classDecl.decorators)) {
44
+ serviceClass = classDecl;
45
+ break;
46
+ }
47
+ }
48
+ }
49
+
50
+ if (!serviceClass || !serviceClass.identifier) {
51
+ throw new Error("Service class is undefined");
52
+ }
53
+
54
+ const className = serviceClass.identifier.value;
55
+ const methods = extractMethods(serviceClass);
56
+
57
+ return {
58
+ className,
59
+ methods,
60
+ };
61
+ }
62
+
63
+ function hasInjectableDecorator(decorators?: Decorator[]): boolean {
64
+ if (!decorators) return false;
65
+
66
+ return decorators.some((decorator) => {
67
+ const expr = decorator.expression;
68
+
69
+ if (expr.type === "CallExpression") {
70
+ if (
71
+ expr.callee.type === "Identifier" &&
72
+ expr.callee.value === "Injectable"
73
+ ) {
74
+ return true;
75
+ }
76
+ }
77
+
78
+ if (expr.type === "Identifier" && expr.value === "Injectable") {
79
+ return true;
80
+ }
81
+
82
+ return false;
83
+ });
84
+ }
85
+
86
+ function extractMethods(classDecl: ClassDeclaration): ServiceMethod[] {
87
+ const methods: ServiceMethod[] = [];
88
+
89
+ for (const member of classDecl.body) {
90
+ if (member.type === "ClassMethod" && member.accessibility === "public") {
91
+ const method = member as any;
92
+
93
+ const methodName =
94
+ method.key.type === "Identifier" ? method.key.value : "";
95
+
96
+ if (!methodName) {
97
+ continue;
98
+ }
99
+
100
+ if (!method.function.async) {
101
+ throw new Error(
102
+ `Server action ${classDecl.identifier.value}.${methodName} must be async.`
103
+ );
104
+ }
105
+
106
+ const params = extractMethodParams(method.function.params || []);
107
+ const returnType = extractReturnType(method.function.returnType);
108
+ const isAsync = method.function.async || false;
109
+
110
+ methods.push({
111
+ name: methodName,
112
+ params,
113
+ returnType,
114
+ isAsync,
115
+ });
116
+ }
117
+ }
118
+
119
+ return methods;
120
+ }
121
+
122
+ function extractMethodParams(params: any[]): MethodParam[] {
123
+ const result: MethodParam[] = [];
124
+
125
+ for (const param of params) {
126
+ if (param.type === "Parameter") {
127
+ const pat = param.pat as any;
128
+
129
+ if (pat.type === "Identifier") {
130
+ const name = pat.value;
131
+ const type = pat.typeAnnotation?.typeAnnotation
132
+ ? stringifyType(pat.typeAnnotation.typeAnnotation)
133
+ : "any";
134
+
135
+ result.push({
136
+ name,
137
+ type,
138
+ });
139
+ }
140
+ }
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ function extractReturnType(returnType?: any): string {
147
+ if (!returnType || !returnType.typeAnnotation) {
148
+ return "any";
149
+ }
150
+
151
+ const type = returnType.typeAnnotation;
152
+
153
+ if (type.type === "TsTypeReference") {
154
+ const typeName = type.typeName;
155
+ if (typeName.type === "Identifier" && typeName.value === "Promise") {
156
+ if (type.typeParams && type.typeParams.params.length > 0) {
157
+ return stringifyType(type.typeParams.params[0]);
158
+ }
159
+ }
160
+ }
161
+
162
+ return stringifyType(type);
163
+ }
164
+
165
+ function stringifyType(typeNode: any): string {
166
+ if (!typeNode) return "any";
167
+
168
+ switch (typeNode.type) {
169
+ case "TsKeywordType":
170
+ return typeNode.kind;
171
+
172
+ case "TsTypeReference":
173
+ if (typeNode.typeName.type === "Identifier") {
174
+ const baseName = typeNode.typeName.value;
175
+ if (typeNode.typeParams && typeNode.typeParams.params.length > 0) {
176
+ const params = typeNode.typeParams.params
177
+ .map(stringifyType)
178
+ .join(", ");
179
+ return `${baseName}<${params}>`;
180
+ }
181
+ return baseName;
182
+ }
183
+ return "any";
184
+
185
+ case "TsArrayType":
186
+ return `${stringifyType(typeNode.elemType)}[]`;
187
+
188
+ case "TsUnionType":
189
+ return typeNode.types.map(stringifyType).join(" | ");
190
+
191
+ case "TsIntersectionType":
192
+ return typeNode.types.map(stringifyType).join(" & ");
193
+
194
+ case "TsTypeLiteral":
195
+ const props = typeNode.members
196
+ .map((member: any) => {
197
+ if (member.type === "TsPropertySignature") {
198
+ const key =
199
+ member.key.type === "Identifier" ? member.key.value : "";
200
+ const type = member.typeAnnotation
201
+ ? stringifyType(member.typeAnnotation.typeAnnotation)
202
+ : "any";
203
+ return `${key}: ${type}`;
204
+ }
205
+ return "";
206
+ })
207
+ .filter(Boolean);
208
+ return `{ ${props.join("; ")} }`;
209
+
210
+ default:
211
+ return "any";
212
+ }
213
+ }
214
+
215
+ function generateStubCode(serviceInfo: ServiceInfo): string {
216
+ const className = serviceInfo.className;
217
+
218
+ const methods = serviceInfo.methods
219
+ .map((method) => {
220
+ const params = method.params
221
+ .map((p) => `${p.name}: ${p.type}`)
222
+ .join(", ");
223
+ const paramNames = method.params.map((p) => p.name).join(", ");
224
+
225
+ const asyncKeyword = method.isAsync ? "async " : "";
226
+ const returnType = method.isAsync
227
+ ? `Promise<${method.returnType}>`
228
+ : method.returnType;
229
+
230
+ const hasParams = method.params.length > 0;
231
+ const bodyParam = hasParams ? `{ ${paramNames} }` : "{}";
232
+
233
+ if (!hasParams) {
234
+ return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
235
+ const response = await fetch(\`/${className}/${method.name}\`, {
236
+ method: 'GET',
237
+ headers: {
238
+ 'Content-Type': 'application/json',
239
+ }
240
+ });
241
+
242
+ if (!response.ok) {
243
+ throw new Error(\`HTTP error! status: \${response.status}\`);
244
+ }
245
+
246
+ return response.json();
247
+ }`;
248
+ }
249
+
250
+ return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
251
+ const response = await fetch(\`/${className}/${method.name}\`, {
252
+ method: 'POST',
253
+ headers: {
254
+ 'Content-Type': 'application/json',
255
+ },
256
+ body: JSON.stringify(${bodyParam}),
257
+ });
258
+
259
+ if (!response.ok) {
260
+ throw new Error(\`HTTP error! status: \${response.status}\`);
261
+ }
262
+
263
+ return response.json();
264
+ }`;
265
+ })
266
+ .join("\n\n");
267
+
268
+ return `import { Injectable } from "@kithinji/orca";
269
+
270
+ @Injectable()
271
+ export class ${className} {
272
+ ${methods}
273
+ }`;
274
+ }