@kithinji/pod 1.0.23 → 1.0.24

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.
package/dist/main.js CHANGED
@@ -928,79 +928,144 @@ async function expandMacros(source, filePath, projectRoot = process.cwd()) {
928
928
  import * as path4 from "path";
929
929
  import { parseSync } from "@swc/core";
930
930
  function generateController(filePath, code) {
931
- const ast = parseSync(code, {
932
- syntax: "typescript",
933
- tsx: filePath.endsWith("x"),
934
- decorators: true
935
- });
936
- const serviceInfo = extractServiceInfo(ast);
937
- if (!serviceInfo || !serviceInfo.hasInjectable) return null;
938
- return generateControllerCode(serviceInfo, filePath);
931
+ try {
932
+ const ast = parseSync(code, {
933
+ syntax: "typescript",
934
+ tsx: filePath.endsWith("x") || filePath.endsWith(".tsx"),
935
+ decorators: true
936
+ });
937
+ const serviceInfo = extractServiceInfo(ast, filePath);
938
+ if (!serviceInfo || !serviceInfo.hasInjectable) {
939
+ return null;
940
+ }
941
+ validateServiceInfo(serviceInfo, filePath);
942
+ return generateControllerCode(serviceInfo, filePath);
943
+ } catch (error) {
944
+ if (error.type) {
945
+ throw error;
946
+ }
947
+ throw {
948
+ type: "parse",
949
+ message: `Failed to parse TypeScript file: ${error.message}`,
950
+ filePath,
951
+ details: error
952
+ };
953
+ }
939
954
  }
940
- function extractServiceInfo(ast) {
955
+ function extractServiceInfo(ast, filePath) {
941
956
  let serviceClass = null;
942
957
  let hasInjectable = false;
943
958
  const importMap = {};
944
- for (const item of ast.body) {
945
- if (item.type === "ImportDeclaration") {
946
- const decl = item;
947
- const source = decl.source.value;
948
- decl.specifiers.forEach((spec) => {
949
- if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") {
950
- importMap[spec.local.value] = source;
959
+ try {
960
+ for (const item of ast.body) {
961
+ if (item.type === "ImportDeclaration") {
962
+ const decl = item;
963
+ const source = decl.source.value;
964
+ decl.specifiers.forEach((spec) => {
965
+ if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") {
966
+ importMap[spec.local.value] = source;
967
+ }
968
+ });
969
+ }
970
+ if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
971
+ const classDecl = item.declaration;
972
+ if (hasInjectableDecorator(classDecl.decorators)) {
973
+ serviceClass = classDecl;
974
+ hasInjectable = true;
951
975
  }
952
- });
953
- }
954
- if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
955
- const classDecl = item.declaration;
956
- if (hasInjectableDecorator(classDecl.decorators)) {
957
- serviceClass = classDecl;
958
- hasInjectable = true;
959
976
  }
960
977
  }
978
+ if (!serviceClass?.identifier) {
979
+ return null;
980
+ }
981
+ return {
982
+ className: serviceClass.identifier.value,
983
+ methods: extractMethods(serviceClass, filePath),
984
+ hasInjectable,
985
+ importMap
986
+ };
987
+ } catch (error) {
988
+ throw {
989
+ type: "parse",
990
+ message: `Failed to extract service info: ${error.message}`,
991
+ filePath,
992
+ details: error
993
+ };
961
994
  }
962
- if (!serviceClass || !serviceClass.identifier) return null;
963
- return {
964
- className: serviceClass.identifier.value,
965
- methods: extractMethods(serviceClass),
966
- hasInjectable,
967
- importMap
968
- };
969
995
  }
970
996
  function hasInjectableDecorator(decorators) {
971
- return decorators?.some((d) => {
997
+ if (!decorators) return false;
998
+ return decorators.some((d) => {
972
999
  const expr = d.expression;
973
1000
  return expr.type === "Identifier" && expr.value === "Injectable" || expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Injectable";
974
- }) ?? false;
1001
+ });
975
1002
  }
976
- function extractMethods(classDecl) {
1003
+ function extractMethods(classDecl, filePath) {
977
1004
  const methods = [];
1005
+ const className = classDecl.identifier?.value || "UnknownClass";
978
1006
  for (const member of classDecl.body) {
979
- if (member.type === "ClassMethod" && member.accessibility === "public") {
980
- const method = member;
981
- const methodName = method.key.type === "Identifier" ? method.key.value : "";
982
- if (!methodName) continue;
983
- if (!method.function.async) {
984
- throw new Error(
985
- `Server action ${classDecl.identifier.value}.${methodName} must be async.`
986
- );
987
- }
988
- const { paramSchemas, returnSchema } = extractSignature(
989
- method.function.decorators,
990
- method.function.params.length
991
- );
992
- methods.push({
993
- name: methodName,
994
- params: extractMethodParams(method.function.params),
995
- returnType: extractReturnType(method.function.returnType),
996
- isAsync: true,
997
- paramSchemas,
998
- returnSchema
999
- });
1007
+ if (!isPublicMethod(member)) continue;
1008
+ const method = member;
1009
+ const methodName = getMethodName(method);
1010
+ if (!methodName) continue;
1011
+ const returnTypeInfo = analyzeReturnType(method);
1012
+ if (!returnTypeInfo.isObservable && !method.function.async) {
1013
+ throw {
1014
+ type: "validation",
1015
+ message: `Method ${className}.${methodName} must be async or return an Observable`,
1016
+ filePath,
1017
+ details: { className, methodName }
1018
+ };
1000
1019
  }
1020
+ const { paramSchemas, returnSchema } = extractSignature(
1021
+ method.function.decorators,
1022
+ method.function.params.length
1023
+ );
1024
+ methods.push({
1025
+ name: methodName,
1026
+ params: extractMethodParams(method.function.params),
1027
+ returnType: returnTypeInfo.type,
1028
+ isAsync: method.function.async,
1029
+ isObservable: returnTypeInfo.isObservable,
1030
+ paramSchemas,
1031
+ returnSchema
1032
+ });
1001
1033
  }
1002
1034
  return methods;
1003
1035
  }
1036
+ function isPublicMethod(member) {
1037
+ return member.type === "ClassMethod" && (member.accessibility === "public" || !member.accessibility);
1038
+ }
1039
+ function getMethodName(method) {
1040
+ if (method.key.type === "Identifier") {
1041
+ return method.key.value;
1042
+ }
1043
+ return null;
1044
+ }
1045
+ function analyzeReturnType(method) {
1046
+ const returnType = method.function.returnType?.typeAnnotation;
1047
+ if (!returnType) {
1048
+ return { type: "any", isObservable: false };
1049
+ }
1050
+ if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Observable") {
1051
+ const innerType = returnType.typeParams?.params[0];
1052
+ return {
1053
+ type: innerType ? stringifyType(innerType) : "any",
1054
+ isObservable: true
1055
+ };
1056
+ }
1057
+ if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Promise") {
1058
+ const innerType = returnType.typeParams?.params[0];
1059
+ return {
1060
+ type: innerType ? stringifyType(innerType) : "any",
1061
+ isObservable: false
1062
+ };
1063
+ }
1064
+ return {
1065
+ type: stringifyType(returnType),
1066
+ isObservable: false
1067
+ };
1068
+ }
1004
1069
  function extractSignature(decorators, paramCount) {
1005
1070
  if (!decorators) return { paramSchemas: [] };
1006
1071
  for (const decorator of decorators) {
@@ -1023,9 +1088,14 @@ function extractSignature(decorators, paramCount) {
1023
1088
  return { paramSchemas: [] };
1024
1089
  }
1025
1090
  function stringifyExpression(expr) {
1026
- if (expr.type === "Identifier") return expr.value;
1091
+ if (!expr) return "any";
1092
+ if (expr.type === "Identifier") {
1093
+ return expr.value;
1094
+ }
1027
1095
  if (expr.type === "MemberExpression") {
1028
- return `${stringifyExpression(expr.object)}.${expr.property.value || ""}`;
1096
+ const object = stringifyExpression(expr.object);
1097
+ const property = expr.property.value || stringifyExpression(expr.property);
1098
+ return `${object}.${property}`;
1029
1099
  }
1030
1100
  if (expr.type === "CallExpression") {
1031
1101
  const args = expr.arguments.map((a) => stringifyExpression(a.expression)).join(", ");
@@ -1036,6 +1106,13 @@ function stringifyExpression(expr) {
1036
1106
  function extractMethodParams(params) {
1037
1107
  return params.map((p) => {
1038
1108
  const pat = p.pat;
1109
+ if (pat.type !== "Identifier") {
1110
+ return {
1111
+ name: "param",
1112
+ type: "any",
1113
+ decorators: []
1114
+ };
1115
+ }
1039
1116
  return {
1040
1117
  name: pat.value,
1041
1118
  type: pat.typeAnnotation ? stringifyType(pat.typeAnnotation.typeAnnotation) : "any",
@@ -1043,97 +1120,191 @@ function extractMethodParams(params) {
1043
1120
  };
1044
1121
  });
1045
1122
  }
1046
- function extractReturnType(node) {
1047
- if (!node?.typeAnnotation) return "any";
1048
- const type = node.typeAnnotation;
1049
- if (type.type === "TsTypeReference" && type.typeName.value === "Promise") {
1050
- return stringifyType(type.typeParams?.params[0]);
1051
- }
1052
- return stringifyType(type);
1053
- }
1054
1123
  function stringifyType(node) {
1055
1124
  if (!node) return "any";
1056
1125
  switch (node.type) {
1057
1126
  case "TsKeywordType":
1058
1127
  return node.kind;
1059
1128
  case "TsTypeReference":
1129
+ if (node.typeName.type !== "Identifier") return "any";
1060
1130
  const base = node.typeName.value;
1061
- const args = node.typeParams ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
1131
+ const args = node.typeParams?.params ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
1062
1132
  return base + args;
1063
1133
  case "TsArrayType":
1064
1134
  return `${stringifyType(node.elemType)}[]`;
1135
+ case "TsUnionType":
1136
+ return node.types.map(stringifyType).join(" | ");
1137
+ case "TsIntersectionType":
1138
+ return node.types.map(stringifyType).join(" & ");
1065
1139
  default:
1066
1140
  return "any";
1067
1141
  }
1068
1142
  }
1143
+ function validateServiceInfo(serviceInfo, filePath) {
1144
+ if (!serviceInfo.className) {
1145
+ throw {
1146
+ type: "validation",
1147
+ message: "Service class must have a valid name",
1148
+ filePath
1149
+ };
1150
+ }
1151
+ if (serviceInfo.methods.length === 0) {
1152
+ console.warn(
1153
+ `Warning: Service ${serviceInfo.className} has no public methods`
1154
+ );
1155
+ }
1156
+ serviceInfo.methods.forEach((method) => {
1157
+ if (method.params.length > 0 && method.paramSchemas.length === 0) {
1158
+ console.warn(
1159
+ `Warning: Method ${serviceInfo.className}.${method.name} has parameters but no @Signature validation`
1160
+ );
1161
+ }
1162
+ });
1163
+ }
1069
1164
  function generateControllerCode(serviceInfo, filePath) {
1070
1165
  const serviceName = serviceInfo.className;
1071
- const controllerName = serviceName.replace(/Service$/, "AutoController");
1072
- const serviceImportPath = `./${path4.basename(filePath).replace(/\.ts$/, "")}`;
1166
+ const controllerName = serviceName.replace(/Service$/, "GenController");
1167
+ const serviceImportPath = getImportPath(filePath);
1168
+ const controllerPath = serviceNameToPath(serviceName);
1169
+ const imports = generateImports(serviceInfo, serviceName, serviceImportPath);
1170
+ const methods = generateMethods(serviceInfo);
1171
+ const serviceInstance = toInstanceName(serviceName);
1172
+ return `${imports}
1173
+
1174
+ @Controller("/${controllerPath}", {
1175
+ providedIn: "root",
1176
+ })
1177
+ export class ${controllerName} {
1178
+ constructor(private readonly ${serviceInstance}: ${serviceName}) {}
1179
+
1180
+ ${methods}
1181
+ }`;
1182
+ }
1183
+ function getImportPath(filePath) {
1184
+ const basename3 = path4.basename(filePath);
1185
+ return `./${basename3.replace(/\.tsx?$/, "")}`;
1186
+ }
1187
+ function serviceNameToPath(serviceName) {
1188
+ return serviceName.replace(/Service$/, "").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1189
+ }
1190
+ function toInstanceName(className) {
1191
+ return className.charAt(0).toLowerCase() + className.slice(1);
1192
+ }
1193
+ function generateImports(serviceInfo, serviceName, serviceImportPath) {
1073
1194
  const importGroups = /* @__PURE__ */ new Map();
1074
1195
  const registerIdentifier = (id) => {
1075
1196
  const source = serviceInfo.importMap[id] || serviceImportPath;
1076
- if (!importGroups.has(source)) importGroups.set(source, /* @__PURE__ */ new Set());
1197
+ if (!importGroups.has(source)) {
1198
+ importGroups.set(source, /* @__PURE__ */ new Set());
1199
+ }
1077
1200
  importGroups.get(source).add(id);
1078
1201
  };
1079
1202
  serviceInfo.methods.forEach((m) => {
1080
1203
  [...m.paramSchemas, m.returnSchema].filter(Boolean).forEach((s) => {
1081
1204
  const matches = s.match(/[A-Z][a-zA-Z0-9]*/g);
1082
1205
  matches?.forEach(registerIdentifier);
1083
- if (s.includes("z.")) registerIdentifier("z");
1206
+ if (s.includes("z.")) {
1207
+ registerIdentifier("z");
1208
+ }
1084
1209
  });
1085
1210
  });
1086
- let importStrings = `import { Controller, Post, Get, Body } from "@kithinji/orca";
1211
+ const hasPost = serviceInfo.methods.some(
1212
+ (m) => !m.isObservable && m.params.length > 0
1213
+ );
1214
+ const hasGet = serviceInfo.methods.some(
1215
+ (m) => !m.isObservable && m.params.length === 0
1216
+ );
1217
+ const hasSse = serviceInfo.methods.some((m) => m.isObservable);
1218
+ const hasSseWithParams = serviceInfo.methods.some(
1219
+ (m) => m.isObservable && m.params.length > 0
1220
+ );
1221
+ const decorators = ["Controller"];
1222
+ if (hasPost) decorators.push("Post");
1223
+ if (hasGet) decorators.push("Get");
1224
+ if (hasPost) decorators.push("Body");
1225
+ if (hasSse) decorators.push("Sse");
1226
+ if (hasSseWithParams) decorators.push("Query");
1227
+ let importStrings = `import { ${decorators.join(
1228
+ ", "
1229
+ )} } from "@kithinji/orca";
1087
1230
  `;
1088
1231
  importGroups.forEach((ids, source) => {
1089
1232
  const filteredIds = Array.from(ids).filter((id) => id !== serviceName);
1090
1233
  if (filteredIds.length > 0) {
1091
- importStrings += `
1092
- import { ${filteredIds.join(
1234
+ importStrings += `import { ${filteredIds.join(
1093
1235
  ", "
1094
- )} } from "${source}";`;
1095
- }
1096
- });
1097
- const methods = serviceInfo.methods.map((m) => {
1098
- const hasParams = m.params.length > 0;
1099
- const bodyParam = hasParams ? `@Body() body: any` : "";
1100
- let body = "";
1101
- if (hasParams) {
1102
- if (m.paramSchemas.length > 0) {
1103
- body += ` const b = typeof body === 'object' && body !== null ? body : {};
1104
- `;
1105
- m.params.forEach((p, i) => {
1106
- body += ` const ${p.name} = ${m.paramSchemas[i]}.parse(b.${p.name});
1107
- `;
1108
- });
1109
- } else {
1110
- body += ` const { ${m.params.map((p) => p.name).join(", ")} } = body;
1236
+ )} } from "${source}";
1111
1237
  `;
1112
- }
1113
- }
1114
- const callArgs = m.params.map((p) => p.name).join(", ");
1115
- const serviceCall = `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}.${m.name}(${callArgs})`;
1116
- if (m.returnSchema) {
1117
- body += ` const res = await ${serviceCall};
1118
- return ${m.returnSchema}.parse(res);`;
1119
- } else {
1120
- body += ` return ${serviceCall};`;
1121
1238
  }
1122
- return ` @${hasParams ? "Post" : "Get"}("${m.name}")
1123
- async ${m.name}(${bodyParam}): Promise<${m.returnType}> {
1239
+ });
1240
+ return importStrings;
1241
+ }
1242
+ function generateMethods(serviceInfo) {
1243
+ return serviceInfo.methods.map((m) => generateMethod(m, serviceInfo.className)).join("\n\n");
1244
+ }
1245
+ function generateMethod(method, serviceName) {
1246
+ const hasParams = method.params.length > 0;
1247
+ const serviceInstance = toInstanceName(serviceName);
1248
+ if (method.isObservable) {
1249
+ const queryParams = hasParams ? method.params.map((p) => `@Query('${p.name}') ${p.name}: ${p.type}`).join(", ") : "";
1250
+ const body2 = generateMethodBody(method, serviceInstance, false);
1251
+ return ` @Sse("${method.name}")
1252
+ ${method.name}(${queryParams}): Observable<${method.returnType}> {
1253
+ ${body2}
1254
+ }`;
1255
+ }
1256
+ const decorator = hasParams ? "Post" : "Get";
1257
+ const bodyParam = hasParams ? `@Body() body: any` : "";
1258
+ const body = generateMethodBody(method, serviceInstance, true);
1259
+ return ` @${decorator}("${method.name}")
1260
+ async ${method.name}(${bodyParam}): Promise<${method.returnType}> {
1124
1261
  ${body}
1125
1262
  }`;
1126
- }).join("\n\n");
1127
- return `${importStrings}
1128
-
1129
- @Controller("/${serviceName}", {
1130
- providedIn: "root",
1131
- })
1132
- export class ${controllerName} {
1133
- constructor(private readonly ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}: ${serviceName}) {}
1134
-
1135
- ${methods}
1136
- }`;
1263
+ }
1264
+ function generateMethodBody(method, serviceInstance, isAsync) {
1265
+ const lines = [];
1266
+ const hasParams = method.params.length > 0;
1267
+ if (hasParams && method.isObservable && method.paramSchemas.length > 0) {
1268
+ method.params.forEach((p, i) => {
1269
+ lines.push(
1270
+ ` const validated${capitalize(p.name)} = ${method.paramSchemas[i]}.parse(${p.name});`
1271
+ );
1272
+ });
1273
+ }
1274
+ if (hasParams && !method.isObservable) {
1275
+ if (method.paramSchemas.length > 0) {
1276
+ lines.push(
1277
+ ` const b = typeof body === 'object' && body !== null ? body : {};`
1278
+ );
1279
+ method.params.forEach((p, i) => {
1280
+ lines.push(
1281
+ ` const ${p.name} = ${method.paramSchemas[i]}.parse(b.${p.name});`
1282
+ );
1283
+ });
1284
+ } else {
1285
+ const paramNames = method.params.map((p) => p.name).join(", ");
1286
+ lines.push(` const { ${paramNames} } = body || {};`);
1287
+ }
1288
+ }
1289
+ let callArgs;
1290
+ if (hasParams && method.isObservable && method.paramSchemas.length > 0) {
1291
+ callArgs = method.params.map((p) => `validated${capitalize(p.name)}`).join(", ");
1292
+ } else {
1293
+ callArgs = method.params.map((p) => p.name).join(", ");
1294
+ }
1295
+ const serviceCall = `${serviceInstance}.${method.name}(${callArgs})`;
1296
+ if (method.returnSchema && isAsync) {
1297
+ lines.push(` const res = await this.${serviceCall};`);
1298
+ lines.push(` return ${method.returnSchema}.parse(res);`);
1299
+ } else if (isAsync) {
1300
+ lines.push(` return this.${serviceCall};`);
1301
+ } else {
1302
+ lines.push(` return this.${serviceCall};`);
1303
+ }
1304
+ return lines.join("\n");
1305
+ }
1306
+ function capitalize(str) {
1307
+ return str.charAt(0).toUpperCase() + str.slice(1);
1137
1308
  }
1138
1309
 
1139
1310
  // src/plugins/generators/tsx_server_stub.ts
@@ -2378,7 +2549,7 @@ function extractMethods2(classDecl) {
2378
2549
  continue;
2379
2550
  }
2380
2551
  const params = extractMethodParams2(method.function.params || []);
2381
- const returnType = extractReturnType2(method.function.returnType);
2552
+ const returnType = extractReturnType(method.function.returnType);
2382
2553
  const isAsync = method.function.async || false;
2383
2554
  methods.push({
2384
2555
  name: methodName,
@@ -2407,7 +2578,7 @@ function extractMethodParams2(params) {
2407
2578
  }
2408
2579
  return result;
2409
2580
  }
2410
- function extractReturnType2(returnType) {
2581
+ function extractReturnType(returnType) {
2411
2582
  if (!returnType || !returnType.typeAnnotation) {
2412
2583
  return "any";
2413
2584
  }
@@ -2526,37 +2697,66 @@ ${decoratorsStr}export class ${className} extends _OrcaComponent {
2526
2697
  }`;
2527
2698
  }
2528
2699
 
2529
- // src/plugins/generators/generate_rsc.ts
2700
+ // src/plugins/generators/generate_rpc.ts
2530
2701
  import { parseSync as parseSync4 } from "@swc/core";
2531
- function generateRscStub(filePath, code) {
2532
- const ast = parseSync4(code, {
2533
- syntax: "typescript",
2534
- tsx: filePath.endsWith("x"),
2535
- decorators: true
2536
- });
2537
- const serviceInfo = extractServiceInfo2(ast);
2538
- return generateStubCode2(serviceInfo);
2702
+ function generateRpcStub(filePath, code) {
2703
+ try {
2704
+ const ast = parseSync4(code, {
2705
+ syntax: "typescript",
2706
+ tsx: filePath.endsWith("x") || filePath.endsWith(".tsx"),
2707
+ decorators: true
2708
+ });
2709
+ const serviceInfo = extractServiceInfo2(ast, filePath);
2710
+ validateServiceInfo2(serviceInfo, filePath);
2711
+ return generateStubCode2(serviceInfo);
2712
+ } catch (error) {
2713
+ if (error.type) {
2714
+ throw error;
2715
+ }
2716
+ throw {
2717
+ type: "parse",
2718
+ message: `Failed to parse TypeScript file: ${error.message}`,
2719
+ filePath,
2720
+ details: error
2721
+ };
2722
+ }
2539
2723
  }
2540
- function extractServiceInfo2(ast) {
2724
+ function extractServiceInfo2(ast, filePath) {
2541
2725
  let serviceClass = null;
2542
- for (const item of ast.body) {
2543
- if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
2544
- const classDecl = item.declaration;
2545
- if (hasInjectableDecorator2(classDecl.decorators)) {
2546
- serviceClass = classDecl;
2547
- break;
2726
+ try {
2727
+ for (const item of ast.body) {
2728
+ if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
2729
+ const classDecl = item.declaration;
2730
+ if (hasInjectableDecorator2(classDecl.decorators)) {
2731
+ serviceClass = classDecl;
2732
+ break;
2733
+ }
2548
2734
  }
2549
2735
  }
2736
+ if (!serviceClass?.identifier) {
2737
+ throw {
2738
+ type: "validation",
2739
+ message: "No exported class with @Injectable decorator found",
2740
+ filePath
2741
+ };
2742
+ }
2743
+ const className = serviceClass.identifier.value;
2744
+ const methods = extractMethods3(serviceClass, className, filePath);
2745
+ return {
2746
+ className,
2747
+ methods
2748
+ };
2749
+ } catch (error) {
2750
+ if (error.type) {
2751
+ throw error;
2752
+ }
2753
+ throw {
2754
+ type: "parse",
2755
+ message: `Failed to extract service info: ${error.message}`,
2756
+ filePath,
2757
+ details: error
2758
+ };
2550
2759
  }
2551
- if (!serviceClass || !serviceClass.identifier) {
2552
- throw new Error("Service class is undefined");
2553
- }
2554
- const className = serviceClass.identifier.value;
2555
- const methods = extractMethods3(serviceClass);
2556
- return {
2557
- className,
2558
- methods
2559
- };
2560
2760
  }
2561
2761
  function hasInjectableDecorator2(decorators) {
2562
2762
  if (!decorators) return false;
@@ -2573,65 +2773,81 @@ function hasInjectableDecorator2(decorators) {
2573
2773
  return false;
2574
2774
  });
2575
2775
  }
2576
- function extractMethods3(classDecl) {
2776
+ function extractMethods3(classDecl, className, filePath) {
2577
2777
  const methods = [];
2578
2778
  for (const member of classDecl.body) {
2579
- if (member.type === "ClassMethod" && member.accessibility === "public") {
2580
- const method = member;
2581
- const methodName = method.key.type === "Identifier" ? method.key.value : "";
2582
- if (!methodName) {
2583
- continue;
2584
- }
2585
- if (!method.function.async) {
2586
- throw new Error(
2587
- `Server action ${classDecl.identifier.value}.${methodName} must be async.`
2588
- );
2589
- }
2590
- const params = extractMethodParams3(method.function.params || []);
2591
- const returnType = extractReturnType3(method.function.returnType);
2592
- const isAsync = method.function.async || false;
2593
- methods.push({
2594
- name: methodName,
2595
- params,
2596
- returnType,
2597
- isAsync
2598
- });
2779
+ if (!isPublicMethod2(member)) continue;
2780
+ const method = member;
2781
+ const methodName = getMethodName2(method);
2782
+ if (!methodName) continue;
2783
+ const returnTypeInfo = analyzeReturnType2(method);
2784
+ if (!returnTypeInfo.isObservable && !method.function.async) {
2785
+ throw {
2786
+ type: "validation",
2787
+ message: `Method ${className}.${methodName} must be async or return an Observable`,
2788
+ filePath,
2789
+ details: { className, methodName }
2790
+ };
2599
2791
  }
2792
+ const params = extractMethodParams3(method.function.params || []);
2793
+ methods.push({
2794
+ name: methodName,
2795
+ params,
2796
+ returnType: returnTypeInfo.type,
2797
+ isAsync: method.function.async,
2798
+ isObservable: returnTypeInfo.isObservable
2799
+ });
2600
2800
  }
2601
2801
  return methods;
2602
2802
  }
2803
+ function isPublicMethod2(member) {
2804
+ return member.type === "ClassMethod" && (member.accessibility === "public" || !member.accessibility);
2805
+ }
2806
+ function getMethodName2(method) {
2807
+ if (method.key.type === "Identifier") {
2808
+ return method.key.value;
2809
+ }
2810
+ return null;
2811
+ }
2812
+ function analyzeReturnType2(method) {
2813
+ const returnType = method.function.returnType?.typeAnnotation;
2814
+ if (!returnType) {
2815
+ return { type: "any", isObservable: false };
2816
+ }
2817
+ if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Observable") {
2818
+ const innerType = returnType.typeParams?.params[0];
2819
+ return {
2820
+ type: innerType ? stringifyType4(innerType) : "any",
2821
+ isObservable: true
2822
+ };
2823
+ }
2824
+ if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Promise") {
2825
+ const innerType = returnType.typeParams?.params[0];
2826
+ return {
2827
+ type: innerType ? stringifyType4(innerType) : "any",
2828
+ isObservable: false
2829
+ };
2830
+ }
2831
+ return {
2832
+ type: stringifyType4(returnType),
2833
+ isObservable: false
2834
+ };
2835
+ }
2603
2836
  function extractMethodParams3(params) {
2604
2837
  const result = [];
2605
2838
  for (const param of params) {
2606
- if (param.type === "Parameter") {
2607
- const pat = param.pat;
2608
- if (pat.type === "Identifier") {
2609
- const name = pat.value;
2610
- const type = pat.typeAnnotation?.typeAnnotation ? stringifyType4(pat.typeAnnotation.typeAnnotation) : "any";
2611
- result.push({
2612
- name,
2613
- type
2614
- });
2615
- }
2839
+ const pat = param.pat;
2840
+ if (pat.type === "Identifier") {
2841
+ const name = pat.value;
2842
+ const type = pat.typeAnnotation?.typeAnnotation ? stringifyType4(pat.typeAnnotation.typeAnnotation) : "any";
2843
+ result.push({
2844
+ name,
2845
+ type
2846
+ });
2616
2847
  }
2617
2848
  }
2618
2849
  return result;
2619
2850
  }
2620
- function extractReturnType3(returnType) {
2621
- if (!returnType || !returnType.typeAnnotation) {
2622
- return "any";
2623
- }
2624
- const type = returnType.typeAnnotation;
2625
- if (type.type === "TsTypeReference") {
2626
- const typeName = type.typeName;
2627
- if (typeName.type === "Identifier" && typeName.value === "Promise") {
2628
- if (type.typeParams && type.typeParams.params.length > 0) {
2629
- return stringifyType4(type.typeParams.params[0]);
2630
- }
2631
- }
2632
- }
2633
- return stringifyType4(type);
2634
- }
2635
2851
  function stringifyType4(typeNode) {
2636
2852
  if (!typeNode) return "any";
2637
2853
  switch (typeNode.type) {
@@ -2640,7 +2856,7 @@ function stringifyType4(typeNode) {
2640
2856
  case "TsTypeReference":
2641
2857
  if (typeNode.typeName.type === "Identifier") {
2642
2858
  const baseName = typeNode.typeName.value;
2643
- if (typeNode.typeParams && typeNode.typeParams.params.length > 0) {
2859
+ if (typeNode.typeParams?.params.length) {
2644
2860
  const params = typeNode.typeParams.params.map(stringifyType4).join(", ");
2645
2861
  return `${baseName}<${params}>`;
2646
2862
  }
@@ -2667,18 +2883,87 @@ function stringifyType4(typeNode) {
2667
2883
  return "any";
2668
2884
  }
2669
2885
  }
2886
+ function validateServiceInfo2(serviceInfo, filePath) {
2887
+ if (!serviceInfo.className) {
2888
+ throw {
2889
+ type: "validation",
2890
+ message: "Service class must have a valid name",
2891
+ filePath
2892
+ };
2893
+ }
2894
+ if (serviceInfo.methods.length === 0) {
2895
+ console.warn(
2896
+ `Warning: Service ${serviceInfo.className} has no public methods`
2897
+ );
2898
+ }
2899
+ }
2670
2900
  function generateStubCode2(serviceInfo) {
2671
2901
  const className = serviceInfo.className;
2672
- const methods = serviceInfo.methods.map((method) => {
2673
- const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2674
- const paramNames = method.params.map((p) => p.name).join(", ");
2675
- const asyncKeyword = method.isAsync ? "async " : "";
2676
- const returnType = method.isAsync ? `Promise<${method.returnType}>` : method.returnType;
2677
- const hasParams = method.params.length > 0;
2678
- const bodyParam = hasParams ? `{ ${paramNames} }` : "{}";
2679
- if (!hasParams) {
2680
- return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
2681
- const response = await fetch(\`/${className}/${method.name}\`, {
2902
+ const basePath = serviceNameToPath2(className);
2903
+ const methods = serviceInfo.methods.map((method) => generateMethod2(method, basePath)).join("\n\n");
2904
+ const hasObservable = serviceInfo.methods.some((m) => m.isObservable);
2905
+ const observableImport = hasObservable ? `import { Observable } from "@kithinji/orca";
2906
+ ` : "";
2907
+ return `${observableImport}import { Injectable } from "@kithinji/orca";
2908
+
2909
+ @Injectable()
2910
+ export class ${className} {
2911
+ ${methods}
2912
+ }`;
2913
+ }
2914
+ function serviceNameToPath2(serviceName) {
2915
+ return serviceName.replace(/Service$/, "").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
2916
+ }
2917
+ function generateMethod2(method, basePath) {
2918
+ if (method.isObservable) {
2919
+ return generateSseMethod(method, basePath);
2920
+ }
2921
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2922
+ const hasParams = method.params.length > 0;
2923
+ if (!hasParams) {
2924
+ return generateGetMethod(method, basePath);
2925
+ }
2926
+ return generatePostMethod(method, basePath, params);
2927
+ }
2928
+ function generateSseMethod(method, basePath) {
2929
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2930
+ const hasParams = method.params.length > 0;
2931
+ let urlBuilder;
2932
+ if (hasParams) {
2933
+ const queryParams = method.params.map((p) => `${p.name}=\${encodeURIComponent(${p.name})}`).join("&");
2934
+ urlBuilder = `\`/${basePath}/${method.name}?${queryParams}\``;
2935
+ } else {
2936
+ urlBuilder = `\`/${basePath}/${method.name}\``;
2937
+ }
2938
+ return ` ${method.name}(${params}): Observable<${method.returnType}> {
2939
+ return new Observable((observer) => {
2940
+ const eventSource = new EventSource(${urlBuilder});
2941
+
2942
+ eventSource.onmessage = (event) => {
2943
+ try {
2944
+ const data = JSON.parse(event.data);
2945
+ observer.next(data);
2946
+ } catch (error) {
2947
+ observer.error?.(error);
2948
+ }
2949
+ };
2950
+
2951
+ eventSource.onerror = (error) => {
2952
+ observer.error?.(error);
2953
+ eventSource.close();
2954
+ };
2955
+
2956
+ return () => {
2957
+ eventSource.close();
2958
+ };
2959
+ });
2960
+ }`;
2961
+ }
2962
+ function generateGetMethod(method, basePath) {
2963
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2964
+ const returnType = `Promise<${method.returnType}>`;
2965
+ return ` async ${method.name}(${params}): ${returnType} {
2966
+ const response = await fetch(\`/${basePath}/${method.name}\`, {
2682
2967
  method: 'GET',
2683
2968
  headers: {
2684
2969
  'Content-Type': 'application/json',
@@ -2691,14 +2976,17 @@ function generateStubCode2(serviceInfo) {
2691
2976
 
2692
2977
  return response.json();
2693
2978
  }`;
2694
- }
2695
- return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
2696
- const response = await fetch(\`/${className}/${method.name}\`, {
2979
+ }
2980
+ function generatePostMethod(method, basePath, params) {
2981
+ const paramNames = method.params.map((p) => p.name).join(", ");
2982
+ const returnType = `Promise<${method.returnType}>`;
2983
+ return ` async ${method.name}(${params}): ${returnType} {
2984
+ const response = await fetch(\`/${basePath}/${method.name}\`, {
2697
2985
  method: 'POST',
2698
2986
  headers: {
2699
2987
  'Content-Type': 'application/json',
2700
2988
  },
2701
- body: JSON.stringify(${bodyParam}),
2989
+ body: JSON.stringify({ ${paramNames} }),
2702
2990
  });
2703
2991
 
2704
2992
  if (!response.ok) {
@@ -2707,13 +2995,6 @@ function generateStubCode2(serviceInfo) {
2707
2995
 
2708
2996
  return response.json();
2709
2997
  }`;
2710
- }).join("\n\n");
2711
- return `import { Injectable } from "@kithinji/orca";
2712
-
2713
- @Injectable()
2714
- export class ${className} {
2715
- ${methods}
2716
- }`;
2717
2998
  }
2718
2999
 
2719
3000
  // src/plugins/my.ts
@@ -2830,8 +3111,8 @@ var ClientBuildTransformer = class {
2830
3111
  const scSource = generateServerComponent(path15, source);
2831
3112
  return swcTransform(scSource, path15);
2832
3113
  }
2833
- async transformPublicFileRsc(node, source, path15) {
2834
- const stubSource = generateRscStub(path15, source);
3114
+ async transformPublicFileRpc(node, source, path15) {
3115
+ const stubSource = generateRpcStub(path15, source);
2835
3116
  return swcTransform(stubSource, path15);
2836
3117
  }
2837
3118
  async transformSharedCode(source, path15) {
@@ -2853,7 +3134,7 @@ var ClientBuildTransformer = class {
2853
3134
  }
2854
3135
  }
2855
3136
  if (directive === "public") {
2856
- return this.transformPublicFileRsc(node, source, path15);
3137
+ return this.transformPublicFileRpc(node, source, path15);
2857
3138
  }
2858
3139
  if (directive === null) {
2859
3140
  return this.transformSharedCode(source, path15);
@@ -3484,7 +3765,7 @@ Processing dependencies for "${name}": [${component.dependencies.join(
3484
3765
  }
3485
3766
  }
3486
3767
  function updateModuleWithComponent(moduleContent, componentName) {
3487
- const className = capitalize(componentName);
3768
+ const className = capitalize2(componentName);
3488
3769
  const importPath = `./component/${componentName}.component`;
3489
3770
  const sourceFile = ts2.createSourceFile(
3490
3771
  "component.module.ts",
@@ -3640,7 +3921,7 @@ function ensureComponentModuleImported(appModuleContent) {
3640
3921
  function ensureInImportsArray(content, sourceFile) {
3641
3922
  return addToDecoratorArray(content, sourceFile, "imports", "ComponentModule");
3642
3923
  }
3643
- function capitalize(str) {
3924
+ function capitalize2(str) {
3644
3925
  return str.charAt(0).toUpperCase() + str.slice(1);
3645
3926
  }
3646
3927
  function createModule() {
@@ -5578,7 +5859,7 @@ Deployment Failed: ${err.message}`));
5578
5859
  // src/main.ts
5579
5860
  import chalk2 from "chalk";
5580
5861
  var program = new Command();
5581
- program.name("pod").description("Pod cli tool").version("1.0.23");
5862
+ program.name("pod").description("Pod cli tool").version("1.0.24");
5582
5863
  program.command("new <name>").description("Start a new Orca Project").action(async (name) => {
5583
5864
  await addNew(name);
5584
5865
  const appDir = path14.resolve(process.cwd());