@seam-rpc/server 4.0.0 → 4.1.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.
- package/dist/bin/gen-client.d.ts +8 -0
- package/dist/bin/gen-client.js +114 -153
- package/dist/bin/gen-config.d.ts +1 -0
- package/dist/bin/gen-config.js +5 -3
- package/dist/bin/generate.d.ts +8 -0
- package/dist/bin/generate.js +135 -0
- package/dist/bin/index.d.ts +6 -0
- package/dist/bin/index.js +7 -7
- package/dist/bin/init.d.ts +1 -0
- package/dist/bin/init.js +34 -0
- package/package.json +2 -2
- package/dist/validation.js +0 -54
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function genClient(): Promise<void>;
|
|
2
|
+
interface GenerateOptions {
|
|
3
|
+
tsPath: string;
|
|
4
|
+
jsPath: string;
|
|
5
|
+
outputPath: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function generateClientFile({ tsPath, jsPath, outputPath, }: GenerateOptions): Promise<string>;
|
|
8
|
+
export {};
|
package/dist/bin/gen-client.js
CHANGED
|
@@ -2,192 +2,153 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fg from "fast-glob";
|
|
4
4
|
import ts from "typescript";
|
|
5
|
-
import * as z from "zod";
|
|
6
5
|
import { zodToTs, createAuxiliaryTypeStore, printNode } from "zod-to-ts";
|
|
7
6
|
import { existsSync, writeFileSync } from "fs";
|
|
8
7
|
export async function genClient() {
|
|
9
8
|
const args = process.argv;
|
|
10
|
-
|
|
9
|
+
const config = loadConfig(args);
|
|
10
|
+
if (!config)
|
|
11
|
+
return;
|
|
12
|
+
const sourceFiles = await fg(config.source);
|
|
13
|
+
const compiledFolder = path.resolve(config.compiledFolder);
|
|
14
|
+
const outputPath = path.resolve(config.outputFolder);
|
|
15
|
+
const rootPath = path.resolve(".");
|
|
16
|
+
function removeRootPath(path) {
|
|
17
|
+
return "." + path.slice(rootPath.length);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const outputFiles = [];
|
|
21
|
+
for (let i = 0; i < sourceFiles.length; i++) {
|
|
22
|
+
const sourceFile = sourceFiles[i];
|
|
23
|
+
const path = await generateClientFile({ tsPath: sourceFile, jsPath: compiledFolder, outputPath });
|
|
24
|
+
const symb = i < sourceFiles.length - 1 ? " ├╴" : " └╴";
|
|
25
|
+
outputFiles.push(symb + removeRootPath(path));
|
|
26
|
+
}
|
|
27
|
+
console.log("\x1b[32m\x1b[1m%s\x1b[0m\n\x1b[94m%s\x1b[0m", `✅ Successfully generated client files at ${config.outputFolder}`, `${outputFiles.join("\n")}`);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
console.error("❌ Failed to generate client file:", err.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function loadConfig(args) {
|
|
11
35
|
if (args.length == 3) {
|
|
12
36
|
// Use config
|
|
13
37
|
// Check if config exists
|
|
14
|
-
if (!fs.existsSync("./seam-rpc.config.json"))
|
|
15
|
-
|
|
38
|
+
if (!fs.existsSync("./seam-rpc.config.json")) {
|
|
39
|
+
console.error("\x1b[31mCommand arguments omitted and no config file found.\x1b[0m\n"
|
|
16
40
|
+ "Either define a config file with \x1b[36mseam-rpc gen-config\x1b[0m or generate the client files using \x1b[36mseam-rpc gen-client <input-files> <output-folder> [global-types-file]\x1b[0m.");
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
17
43
|
// Load config from config file
|
|
18
|
-
|
|
44
|
+
return JSON.parse(fs.readFileSync("./seam-rpc.config.json", "utf-8"));
|
|
19
45
|
}
|
|
20
46
|
else if (args.length == 5 || args.length == 6) {
|
|
21
47
|
// Use command args
|
|
22
48
|
// Load config from command args
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
return {
|
|
50
|
+
source: args[3],
|
|
51
|
+
compiledFolder: args[4],
|
|
52
|
+
outputFolder: args[5]
|
|
26
53
|
};
|
|
27
54
|
}
|
|
28
55
|
else {
|
|
29
56
|
// Invalid command usage
|
|
30
|
-
|
|
57
|
+
console.error("Usage: seam-rpc gen-client <input-files> <output-folder>");
|
|
58
|
+
return null;
|
|
31
59
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
}
|
|
61
|
+
// Extracts comments from TS file.
|
|
62
|
+
function getProcedureComments(filePath) {
|
|
63
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
64
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
|
65
|
+
const comments = {};
|
|
66
|
+
function visit(node) {
|
|
67
|
+
if (ts.isVariableStatement(node)) {
|
|
68
|
+
const comment = getFullComment(node, sourceText);
|
|
69
|
+
for (const decl of node.declarationList.declarations) {
|
|
70
|
+
if (ts.isIdentifier(decl.name))
|
|
71
|
+
comments[decl.name.text] = comment;
|
|
72
|
+
}
|
|
41
73
|
}
|
|
42
|
-
|
|
74
|
+
ts.forEachChild(node, visit);
|
|
43
75
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
visit(sourceFile);
|
|
77
|
+
return comments;
|
|
78
|
+
}
|
|
79
|
+
function getFullComment(node, sourceText) {
|
|
80
|
+
const ranges = ts.getLeadingCommentRanges(sourceText, node.pos);
|
|
81
|
+
if (!ranges)
|
|
82
|
+
return "";
|
|
83
|
+
for (const range of ranges) {
|
|
84
|
+
const comment = sourceText.slice(range.pos, range.end);
|
|
85
|
+
// Only keep JSDoc-style comments
|
|
86
|
+
if (comment.startsWith("/**")) {
|
|
87
|
+
return comment;
|
|
88
|
+
}
|
|
47
89
|
}
|
|
90
|
+
return "";
|
|
48
91
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// comments: string;
|
|
54
|
-
// }
|
|
55
|
-
// export function getProcedureInfo(filePath: string): ProcedureInfo[] {
|
|
56
|
-
// const fullPath = path.resolve(filePath);
|
|
57
|
-
// const sourceText = fs.readFileSync(fullPath, "utf8");
|
|
58
|
-
// const sourceFile = ts.createSourceFile(
|
|
59
|
-
// fullPath,
|
|
60
|
-
// sourceText,
|
|
61
|
-
// ts.ScriptTarget.Latest,
|
|
62
|
-
// true,
|
|
63
|
-
// ts.ScriptKind.TS
|
|
64
|
-
// );
|
|
65
|
-
// const procedures: ProcedureInfo[] = [];
|
|
66
|
-
// function visit(node: ts.Node) {
|
|
67
|
-
// // Look for const assignments
|
|
68
|
-
// if (ts.isVariableStatement(node)) {
|
|
69
|
-
// node.declarationList.declarations.forEach((decl) => {
|
|
70
|
-
// if (ts.isIdentifier(decl.name) && decl.initializer) {
|
|
71
|
-
// let varName = decl.name.text;
|
|
72
|
-
// let inputType = "";
|
|
73
|
-
// let outputType = "";
|
|
74
|
-
// let comments = "";
|
|
75
|
-
// const jsDocs = ts.getJSDocCommentsAndTags(node); // node = VariableStatement
|
|
76
|
-
// // Get JSDoc comments
|
|
77
|
-
// if (jsDocs.length > 0) {
|
|
78
|
-
// comments = jsDocs.map(doc => doc.getText()).join("\n");
|
|
79
|
-
// }
|
|
80
|
-
// // Check for chain calls: .input(...).output(...)
|
|
81
|
-
// if (ts.isCallExpression(decl.initializer) || ts.isPropertyAccessExpression(decl.initializer)) {
|
|
82
|
-
// function extractChain(expr: ts.Expression) {
|
|
83
|
-
// if (ts.isCallExpression(expr)) {
|
|
84
|
-
// if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
85
|
-
// const propName = expr.expression.name.text;
|
|
86
|
-
// if (propName === "input" && expr.arguments.length > 0) {
|
|
87
|
-
// inputType = expr.arguments[0].getText();
|
|
88
|
-
// } else if (propName === "output" && expr.arguments.length > 0) {
|
|
89
|
-
// outputType = expr.arguments[0].getText();
|
|
90
|
-
// }
|
|
91
|
-
// extractChain(expr.expression.expression);
|
|
92
|
-
// }
|
|
93
|
-
// } else if (ts.isPropertyAccessExpression(expr)) {
|
|
94
|
-
// extractChain(expr.expression);
|
|
95
|
-
// }
|
|
96
|
-
// }
|
|
97
|
-
// extractChain(decl.initializer);
|
|
98
|
-
// }
|
|
99
|
-
// if (inputType || outputType || comments) {
|
|
100
|
-
// procedures.push({ name: varName, inputType, outputType, comments });
|
|
101
|
-
// }
|
|
102
|
-
// }
|
|
103
|
-
// });
|
|
104
|
-
// }
|
|
105
|
-
// ts.forEachChild(node, visit);
|
|
106
|
-
// }
|
|
107
|
-
// visit(sourceFile);
|
|
108
|
-
// return procedures;
|
|
109
|
-
// }
|
|
110
|
-
function removeRootPath(path, rootPath) {
|
|
111
|
-
return "." + path.slice(rootPath.length);
|
|
92
|
+
function convert(schema) {
|
|
93
|
+
const store = createAuxiliaryTypeStore();
|
|
94
|
+
const { node } = zodToTs(schema, { auxiliaryTypeStore: store });
|
|
95
|
+
return printNode(node);
|
|
112
96
|
}
|
|
113
|
-
async function generateClientFile(
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
97
|
+
export async function generateClientFile({ tsPath, jsPath, outputPath, }) {
|
|
98
|
+
const tsFile = path.resolve(process.cwd(), tsPath);
|
|
99
|
+
const tsFileName = path.basename(tsFile).slice(0, -path.extname(tsFile).length);
|
|
100
|
+
const jsFile = path.resolve(process.cwd(), path.join(jsPath, tsFileName + ".js"));
|
|
101
|
+
if (!existsSync(tsFile)) {
|
|
102
|
+
throw new Error(`TS file not found: ${tsFile}`);
|
|
118
103
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
104
|
+
if (!existsSync(jsFile)) {
|
|
105
|
+
throw new Error(`JS file not found: ${jsFile}`);
|
|
106
|
+
}
|
|
107
|
+
const mod = await import("file://" + jsFile);
|
|
108
|
+
const procedures = mod.default;
|
|
109
|
+
const routerName = path.basename(jsFile, path.extname(jsFile));
|
|
110
|
+
const comments = getProcedureComments(tsFile);
|
|
111
|
+
let output = `/**
|
|
112
|
+
* +===================================+
|
|
113
|
+
* | File auto-generated by SeamRPC. |
|
|
114
|
+
* | --- DO NOT EDIT --- |
|
|
115
|
+
* +===================================+
|
|
116
|
+
*/
|
|
123
117
|
|
|
124
|
-
import { callApi } from
|
|
118
|
+
import { callApi } from "@seam-rpc/client";
|
|
125
119
|
|
|
126
120
|
`;
|
|
127
|
-
// Parse the source file using TypeScript API
|
|
128
|
-
const program = ts.createProgram([sourceFile], {});
|
|
129
|
-
const tsFile = program.getSourceFile(sourceFile);
|
|
130
|
-
// Helper to get JSDoc comment for a variable
|
|
131
|
-
function getJsDocComment(node) {
|
|
132
|
-
const jsDocs = node.jsDoc;
|
|
133
|
-
if (!jsDocs || jsDocs.length === 0)
|
|
134
|
-
return "";
|
|
135
|
-
return jsDocs.map(doc => doc.comment).filter(Boolean).join("\n");
|
|
136
|
-
}
|
|
137
121
|
const functions = [];
|
|
138
|
-
for (const [
|
|
122
|
+
for (const [name, proc] of Object.entries(procedures)) {
|
|
139
123
|
// Input
|
|
140
|
-
|
|
124
|
+
let inputType = "void";
|
|
125
|
+
let hasInput = false;
|
|
141
126
|
if (proc._def.input) {
|
|
142
|
-
|
|
143
|
-
|
|
127
|
+
const fields = [];
|
|
128
|
+
for (const [key, schema] of Object.entries(proc._def.input)) {
|
|
129
|
+
const isOptional = schema.isOptional?.() ?? false;
|
|
130
|
+
const tsType = convert(schema);
|
|
131
|
+
fields.push(`${key}${isOptional ? "?" : ""}: ${tsType}`);
|
|
132
|
+
}
|
|
133
|
+
if (fields.length > 0) {
|
|
134
|
+
inputType = `{ ${fields.join("; ")} }`;
|
|
135
|
+
hasInput = true;
|
|
144
136
|
}
|
|
145
137
|
}
|
|
146
|
-
// Output
|
|
147
138
|
const returnType = proc._def.output ? convert(proc._def.output) : "void";
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
node.declarationList.declarations.forEach(decl => {
|
|
158
|
-
console.log(decl.name.getText(), procName);
|
|
159
|
-
if (decl.name.getText() === procName) {
|
|
160
|
-
const doc = getJsDocComment(decl);
|
|
161
|
-
if (doc)
|
|
162
|
-
comments = `/** ${doc} */\n`;
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
const func = `${comments}export function ${procName}(${params}): Promise<${returnType}> { ${body} }`;
|
|
139
|
+
const params = hasInput ? `input: ${inputType}` : "";
|
|
140
|
+
const args = [`"${routerName}"`, `"${name}"`];
|
|
141
|
+
if (hasInput)
|
|
142
|
+
args.push("input");
|
|
143
|
+
const call = `callApi(${args.join(", ")})`;
|
|
144
|
+
const comment = comments[name] ? comments[name] + "\n" : "";
|
|
145
|
+
const func = `${comment}export function ${name}(${params}): Promise<${returnType}> {
|
|
146
|
+
return ${call};
|
|
147
|
+
}`;
|
|
168
148
|
functions.push(func);
|
|
169
149
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
function convert(schema) {
|
|
175
|
-
const auxiliaryTypeStore = createAuxiliaryTypeStore();
|
|
176
|
-
const { node } = zodToTs(schema, { auxiliaryTypeStore });
|
|
177
|
-
return printNode(node);
|
|
150
|
+
output += functions.join("\n\n");
|
|
151
|
+
const outFile = path.join(outputPath, `${routerName}.ts`);
|
|
152
|
+
writeFileSync(outFile, output, "utf-8");
|
|
153
|
+
return outFile;
|
|
178
154
|
}
|
|
179
|
-
// function zodToTs(schema: z.ZodType): { value: string, definition?: string } {
|
|
180
|
-
// if (schema instanceof z.ZodString) {
|
|
181
|
-
// return { value: "string" };
|
|
182
|
-
// }
|
|
183
|
-
// if (schema instanceof z.ZodNumber) {
|
|
184
|
-
// return { value: "number" };
|
|
185
|
-
// }
|
|
186
|
-
// if (schema instanceof z.ZodArray) {
|
|
187
|
-
// return { value: zodToTs(schema.def.type) + "[]" };
|
|
188
|
-
// }
|
|
189
|
-
// if (schema instanceof z.ZodOptional) {
|
|
190
|
-
// return {value: };
|
|
191
|
-
// }
|
|
192
|
-
// return { value: "any" };
|
|
193
|
-
// }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function genConfig(): Promise<void>;
|
package/dist/bin/gen-config.js
CHANGED
|
@@ -2,10 +2,11 @@ import fs from "fs";
|
|
|
2
2
|
import readline from "readline";
|
|
3
3
|
export async function genConfig() {
|
|
4
4
|
const args = process.argv;
|
|
5
|
-
let
|
|
5
|
+
let source = "./src/api/*";
|
|
6
|
+
let compiledFolder = "./dist/api";
|
|
6
7
|
let outputFolder = "../client/src/api";
|
|
7
8
|
if (args.length == 5) {
|
|
8
|
-
|
|
9
|
+
source = args[3];
|
|
9
10
|
outputFolder = args[4];
|
|
10
11
|
}
|
|
11
12
|
else if (args.length > 3) {
|
|
@@ -30,7 +31,8 @@ export async function genConfig() {
|
|
|
30
31
|
}
|
|
31
32
|
function generateConfig() {
|
|
32
33
|
const config = {
|
|
33
|
-
|
|
34
|
+
source,
|
|
35
|
+
compiledFolder,
|
|
34
36
|
outputFolder
|
|
35
37
|
};
|
|
36
38
|
try {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function generateClient(): Promise<void>;
|
|
2
|
+
interface GenerateOptions {
|
|
3
|
+
tsPath: string;
|
|
4
|
+
jsPath: string;
|
|
5
|
+
outputPath: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function generateClientFile({ tsPath, jsPath, outputPath, }: GenerateOptions): Promise<string>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { zodToTs, createAuxiliaryTypeStore, printNode } from "zod-to-ts";
|
|
6
|
+
import { existsSync, writeFileSync } from "fs";
|
|
7
|
+
export async function generateClient() {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
if (!config)
|
|
10
|
+
return;
|
|
11
|
+
const sourceFiles = await fg(config.source);
|
|
12
|
+
const compiledFolder = path.resolve(config.compiledFolder);
|
|
13
|
+
const outputPath = path.resolve(config.outputFolder);
|
|
14
|
+
const rootPath = path.resolve(".");
|
|
15
|
+
function removeRootPath(path) {
|
|
16
|
+
return "." + path.slice(rootPath.length);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const outputFiles = [];
|
|
20
|
+
for (let i = 0; i < sourceFiles.length; i++) {
|
|
21
|
+
const sourceFile = sourceFiles[i];
|
|
22
|
+
const path = await generateClientFile({ tsPath: sourceFile, jsPath: compiledFolder, outputPath });
|
|
23
|
+
const symb = i < sourceFiles.length - 1 ? " ├╴" : " └╴";
|
|
24
|
+
outputFiles.push(symb + removeRootPath(path));
|
|
25
|
+
}
|
|
26
|
+
console.log("\x1b[32m\x1b[1m%s\x1b[0m\n\x1b[94m%s\x1b[0m", `✅ Successfully generated client files at ${config.outputFolder}`, `${outputFiles.join("\n")}`);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error("❌ Failed to generate client file:", err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function loadConfig() {
|
|
34
|
+
// Check if config exists
|
|
35
|
+
if (!fs.existsSync("./seam-rpc.config.json")) {
|
|
36
|
+
console.error("\x1b[31mNo config file found.\x1b[0m\n Create a config file with \x1b[36msrpc init\x1b[0m.");
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Load config from config file
|
|
40
|
+
return JSON.parse(fs.readFileSync("./seam-rpc.config.json", "utf-8"));
|
|
41
|
+
}
|
|
42
|
+
// Extracts comments from TS file.
|
|
43
|
+
function getProcedureComments(filePath) {
|
|
44
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
45
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
|
46
|
+
const comments = {};
|
|
47
|
+
function visit(node) {
|
|
48
|
+
if (ts.isVariableStatement(node)) {
|
|
49
|
+
const comment = getFullComment(node, sourceText);
|
|
50
|
+
for (const decl of node.declarationList.declarations) {
|
|
51
|
+
if (ts.isIdentifier(decl.name))
|
|
52
|
+
comments[decl.name.text] = comment;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
ts.forEachChild(node, visit);
|
|
56
|
+
}
|
|
57
|
+
visit(sourceFile);
|
|
58
|
+
return comments;
|
|
59
|
+
}
|
|
60
|
+
function getFullComment(node, sourceText) {
|
|
61
|
+
const ranges = ts.getLeadingCommentRanges(sourceText, node.pos);
|
|
62
|
+
if (!ranges)
|
|
63
|
+
return "";
|
|
64
|
+
for (const range of ranges) {
|
|
65
|
+
const comment = sourceText.slice(range.pos, range.end);
|
|
66
|
+
// Only keep JSDoc-style comments
|
|
67
|
+
if (comment.startsWith("/**")) {
|
|
68
|
+
return comment;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
function convert(schema) {
|
|
74
|
+
const store = createAuxiliaryTypeStore();
|
|
75
|
+
const { node } = zodToTs(schema, { auxiliaryTypeStore: store });
|
|
76
|
+
return printNode(node);
|
|
77
|
+
}
|
|
78
|
+
export async function generateClientFile({ tsPath, jsPath, outputPath, }) {
|
|
79
|
+
const tsFile = path.resolve(process.cwd(), tsPath);
|
|
80
|
+
const tsFileName = path.basename(tsFile).slice(0, -path.extname(tsFile).length);
|
|
81
|
+
const jsFile = path.resolve(process.cwd(), path.join(jsPath, tsFileName + ".js"));
|
|
82
|
+
if (!existsSync(tsFile)) {
|
|
83
|
+
throw new Error(`TS file not found: ${tsFile}`);
|
|
84
|
+
}
|
|
85
|
+
if (!existsSync(jsFile)) {
|
|
86
|
+
throw new Error(`JS file not found: ${jsFile}`);
|
|
87
|
+
}
|
|
88
|
+
const mod = await import("file://" + jsFile);
|
|
89
|
+
const procedures = mod.default;
|
|
90
|
+
const routerName = path.basename(jsFile, path.extname(jsFile));
|
|
91
|
+
const comments = getProcedureComments(tsFile);
|
|
92
|
+
let output = `/**
|
|
93
|
+
* +===================================+
|
|
94
|
+
* | File auto-generated by SeamRPC. |
|
|
95
|
+
* | --- DO NOT EDIT --- |
|
|
96
|
+
* +===================================+
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
import { callApi } from "@seam-rpc/client";
|
|
100
|
+
|
|
101
|
+
`;
|
|
102
|
+
const functions = [];
|
|
103
|
+
for (const [name, proc] of Object.entries(procedures)) {
|
|
104
|
+
// Input
|
|
105
|
+
let inputType = "void";
|
|
106
|
+
let hasInput = false;
|
|
107
|
+
if (proc._def.input) {
|
|
108
|
+
const fields = [];
|
|
109
|
+
for (const [key, schema] of Object.entries(proc._def.input)) {
|
|
110
|
+
const isOptional = schema.isOptional?.() ?? false;
|
|
111
|
+
const tsType = convert(schema);
|
|
112
|
+
fields.push(`${key}${isOptional ? "?" : ""}: ${tsType}`);
|
|
113
|
+
}
|
|
114
|
+
if (fields.length > 0) {
|
|
115
|
+
inputType = `{ ${fields.join("; ")} }`;
|
|
116
|
+
hasInput = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const returnType = proc._def.output ? convert(proc._def.output) : "void";
|
|
120
|
+
const params = hasInput ? `input: ${inputType}` : "";
|
|
121
|
+
const args = [`"${routerName}"`, `"${name}"`];
|
|
122
|
+
if (hasInput)
|
|
123
|
+
args.push("input");
|
|
124
|
+
const call = `callApi(${args.join(", ")})`;
|
|
125
|
+
const comment = comments[name] ? comments[name] + "\n" : "";
|
|
126
|
+
const func = `${comment}export function ${name}(${params}): Promise<${returnType}> {
|
|
127
|
+
return ${call};
|
|
128
|
+
}`;
|
|
129
|
+
functions.push(func);
|
|
130
|
+
}
|
|
131
|
+
output += functions.join("\n\n");
|
|
132
|
+
const outFile = path.join(outputPath, `${routerName}.ts`);
|
|
133
|
+
writeFileSync(outFile, output, "utf-8");
|
|
134
|
+
return outFile;
|
|
135
|
+
}
|
package/dist/bin/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { generateClient } from "./generate.js";
|
|
3
|
+
import { initConfig } from "./init.js";
|
|
4
4
|
main();
|
|
5
5
|
function main() {
|
|
6
6
|
const args = process.argv;
|
|
7
7
|
switch (args[2]) {
|
|
8
|
-
case "
|
|
9
|
-
|
|
8
|
+
case "generate":
|
|
9
|
+
generateClient();
|
|
10
10
|
break;
|
|
11
|
-
case "
|
|
12
|
-
|
|
11
|
+
case "init":
|
|
12
|
+
initConfig();
|
|
13
13
|
break;
|
|
14
14
|
default:
|
|
15
|
-
console.log("Commands:\n-
|
|
15
|
+
console.log("Commands:\n- \x1b[36msrpc generate\x1b[0m - Generate client files as defined in the config.\n- \x1b[36msrpc init\x1b[0m - Create config file.");
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initConfig(): Promise<void>;
|
package/dist/bin/init.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
export async function initConfig() {
|
|
4
|
+
let source = "./src/api/*";
|
|
5
|
+
let compiledFolder = "./dist/api";
|
|
6
|
+
let outputFolder = "../client/src/api";
|
|
7
|
+
if (fs.existsSync("./seam-rpc.config.json")) {
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
});
|
|
12
|
+
rl.question("Config file already exists. Do you want to overwrite it? [Y/n] ", answer => {
|
|
13
|
+
if (answer && answer.toLowerCase() != "y" && answer.toLowerCase() != "yes") {
|
|
14
|
+
console.log("Operation canceled.");
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
rl.close();
|
|
18
|
+
createConfig();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
createConfig();
|
|
23
|
+
}
|
|
24
|
+
function createConfig() {
|
|
25
|
+
const config = { source, compiledFolder, outputFolder };
|
|
26
|
+
try {
|
|
27
|
+
fs.writeFileSync("./seam-rpc.config.json", JSON.stringify(config, null, 4), "utf-8");
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.log("\x1b[31mFailed to create config file ./seam-rpc.config.json\x1b[0m\n" + e);
|
|
31
|
+
}
|
|
32
|
+
console.log(`\x1b[32mSuccessfully created config file ./seam-rpc.config.json\x1b[0m`);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
CHANGED
package/dist/validation.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
export class ValString {
|
|
2
|
-
constructor(value) {
|
|
3
|
-
this._isValid = true;
|
|
4
|
-
this._value = value;
|
|
5
|
-
}
|
|
6
|
-
get isValid() {
|
|
7
|
-
return this._isValid;
|
|
8
|
-
}
|
|
9
|
-
min(value) {
|
|
10
|
-
if (this._isValid)
|
|
11
|
-
this._isValid = this._value.length >= value;
|
|
12
|
-
return this;
|
|
13
|
-
}
|
|
14
|
-
max(value) {
|
|
15
|
-
if (this._isValid)
|
|
16
|
-
this._isValid = this._value.length <= value;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
length(value) {
|
|
20
|
-
if (this._isValid)
|
|
21
|
-
this._isValid = this._value.length == value;
|
|
22
|
-
return this;
|
|
23
|
-
}
|
|
24
|
-
startsWith(value) {
|
|
25
|
-
if (this._isValid)
|
|
26
|
-
this._isValid = this._value.startsWith(value);
|
|
27
|
-
return this;
|
|
28
|
-
}
|
|
29
|
-
endsWith(value) {
|
|
30
|
-
if (this._isValid)
|
|
31
|
-
this._isValid = this._value.endsWith(value);
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
includes(value) {
|
|
35
|
-
if (this._isValid)
|
|
36
|
-
this._isValid = this._value.includes(value);
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
regex(regExpr) {
|
|
40
|
-
// if (this._isValid)
|
|
41
|
-
// this._isValid = this._value.startsWith(value);
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
email() {
|
|
45
|
-
return this;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function createUser(name, age, data) {
|
|
49
|
-
name.min(3).max(50);
|
|
50
|
-
age.gte(1).lt(150);
|
|
51
|
-
data.description?.max(250);
|
|
52
|
-
}
|
|
53
|
-
function validate(data) {
|
|
54
|
-
}
|