@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 +493 -212
- package/dist/main.js.map +4 -4
- package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -1
- package/dist/types/plugins/generators/generate_rpc.d.ts +2 -0
- package/dist/types/plugins/generators/generate_rpc.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts +7 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
|
1091
|
+
if (!expr) return "any";
|
|
1092
|
+
if (expr.type === "Identifier") {
|
|
1093
|
+
return expr.value;
|
|
1094
|
+
}
|
|
1027
1095
|
if (expr.type === "MemberExpression") {
|
|
1028
|
-
|
|
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$/, "
|
|
1072
|
-
const serviceImportPath =
|
|
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))
|
|
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."))
|
|
1206
|
+
if (s.includes("z.")) {
|
|
1207
|
+
registerIdentifier("z");
|
|
1208
|
+
}
|
|
1084
1209
|
});
|
|
1085
1210
|
});
|
|
1086
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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 =
|
|
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
|
|
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/
|
|
2700
|
+
// src/plugins/generators/generate_rpc.ts
|
|
2530
2701
|
import { parseSync as parseSync4 } from "@swc/core";
|
|
2531
|
-
function
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
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
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
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
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
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
|
|
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
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
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
|
-
|
|
2696
|
-
|
|
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(${
|
|
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
|
|
2834
|
-
const stubSource =
|
|
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.
|
|
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 =
|
|
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
|
|
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.
|
|
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());
|