@pyrpc/client 0.3.3 → 0.6.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/README.md +27 -11
- package/cli.js +194 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @pyrpc/client
|
|
2
2
|
|
|
3
|
-
Universal TypeScript client for [pyRPC](https://pyrpc.
|
|
3
|
+
Universal TypeScript client for [pyRPC](https://pyrpc.com). Type-safe RPC calls to your Python backend - install, import, call.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,13 +12,12 @@ pnpm add @pyrpc/client
|
|
|
12
12
|
bun add @pyrpc/client
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
On install, `@pyrpc/types` runs a `postinstall` script that prompts you to choose a **distribution mode**:
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- **Workspace** — types are written by the server-side `pyrpc dev` / `pyrpc codegen` commands.
|
|
18
|
+
- **Server** — types are fetched from a running server at `npx pyrpc sync` time.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
PYRPC_URL=https://api.example.com npm install @pyrpc/client
|
|
21
|
-
```
|
|
20
|
+
CI / non-TTY environments skip the prompt silently.
|
|
22
21
|
|
|
23
22
|
## Usage
|
|
24
23
|
|
|
@@ -31,7 +30,7 @@ const client = createClient<Types>({
|
|
|
31
30
|
});
|
|
32
31
|
|
|
33
32
|
const user = await client.get_user(1);
|
|
34
|
-
console.log(user.name);
|
|
33
|
+
console.log(user.name); // Fully typed - no manual type definitions needed
|
|
35
34
|
```
|
|
36
35
|
|
|
37
36
|
The proxy-based API lets you call any remote procedure as a local method. Parameters are passed positionally or as a single object for named arguments.
|
|
@@ -50,14 +49,31 @@ try {
|
|
|
50
49
|
}
|
|
51
50
|
```
|
|
52
51
|
|
|
52
|
+
## CLI — `npx pyrpc sync`
|
|
53
|
+
|
|
54
|
+
After install, you can sync types from a server distribution:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx pyrpc sync
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Reads `pyrpc-client.json` and behaves according to the configured distribution:
|
|
61
|
+
|
|
62
|
+
- **workspace** — prints "Nothing to sync — server writes types directly."
|
|
63
|
+
- **server** — fetches schema from the configured `server_url` and regenerates `@pyrpc/types/src/index.ts`.
|
|
64
|
+
|
|
65
|
+
Run `npx pyrpc --help` for options.
|
|
66
|
+
|
|
53
67
|
## API
|
|
54
68
|
|
|
55
|
-
### `createClient<
|
|
69
|
+
### `createClient<T>(options?)`
|
|
70
|
+
|
|
71
|
+
Creates a proxy client that forwards method calls to the server. The generic parameter `T` is your `Types` interface for full type safety.
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
- `baseUrl` - Server root URL (defaults to `window.location.origin` in browsers)
|
|
74
|
+
- `headers` - Static or async `HeadersInit`
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
- `headers` — Static or async `HeadersInit`
|
|
76
|
+
**Note:** There is no `.rpc` property. Call methods directly on the client object.
|
|
61
77
|
|
|
62
78
|
## Keywords
|
|
63
79
|
|
package/cli.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var fs = require('fs');
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var http = require('http');
|
|
5
|
+
var https = require('https');
|
|
6
|
+
|
|
7
|
+
var TYPES_OUTPUT;
|
|
8
|
+
try {
|
|
9
|
+
var typesDir = path.dirname(require.resolve('@pyrpc/types/package.json'));
|
|
10
|
+
TYPES_OUTPUT = path.join(typesDir, 'src', 'index.ts');
|
|
11
|
+
} catch (e) {
|
|
12
|
+
TYPES_OUTPUT = path.join(__dirname, 'node_modules', '@pyrpc', 'types', 'src', 'index.ts');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function findConfig() {
|
|
16
|
+
var dir = process.cwd();
|
|
17
|
+
while (true) {
|
|
18
|
+
var cfgPath = path.join(dir, 'pyrpc-client.json');
|
|
19
|
+
if (fs.existsSync(cfgPath)) {
|
|
20
|
+
return cfgPath;
|
|
21
|
+
}
|
|
22
|
+
var parent = path.dirname(dir);
|
|
23
|
+
if (parent === dir) return null;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var TYPE_MAP = {
|
|
29
|
+
int: 'number',
|
|
30
|
+
float: 'number',
|
|
31
|
+
str: 'string',
|
|
32
|
+
bool: 'boolean',
|
|
33
|
+
None: 'null',
|
|
34
|
+
NoneType: 'null',
|
|
35
|
+
Any: 'any',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function toTs(t) {
|
|
39
|
+
if (!t || t === 'None') return 'void';
|
|
40
|
+
var m = t.match(/^<class\s+'([^'>]+)'>$/);
|
|
41
|
+
if (m) {
|
|
42
|
+
var n = m[1];
|
|
43
|
+
if (TYPE_MAP[n]) return TYPE_MAP[n];
|
|
44
|
+
if (n[0] >= 'A' && n[0] <= 'Z') return n;
|
|
45
|
+
return 'any';
|
|
46
|
+
}
|
|
47
|
+
var s = t.replace(/^typing\./, '');
|
|
48
|
+
var o = s.match(/^Optional\[(.+)\]$/);
|
|
49
|
+
if (o) return toTs(o[1]) + ' | null';
|
|
50
|
+
var l = s.match(/^(?:List|list)\[(.+)\]$/);
|
|
51
|
+
if (l) return toTs(l[1]) + '[]';
|
|
52
|
+
var d = s.match(/^(?:Dict|dict)\[([^,]+),\s*(.+)\]$/);
|
|
53
|
+
if (d) return 'Record<' + toTs(d[1]) + ', ' + toTs(d[2]) + '>';
|
|
54
|
+
var u = s.match(/^Union\[(.+)\]$/);
|
|
55
|
+
if (u) return u[1].split(',').map(function(p) { return toTs(p.trim()); }).join(' | ');
|
|
56
|
+
var tup = s.match(/^(?:Tuple|tuple)\[([^\]]+)\]$/);
|
|
57
|
+
if (tup) return '[' + tup[1].split(',').map(function(p) { return toTs(p.trim()); }).join(', ') + ']';
|
|
58
|
+
return 'any';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function generate(schemas) {
|
|
62
|
+
var lines = [];
|
|
63
|
+
lines.push('// Auto-generated by @pyrpc/types');
|
|
64
|
+
lines.push('// Schema fetched from: ' + (process.env.PYRPC_URL || 'http://localhost:8000'));
|
|
65
|
+
lines.push('');
|
|
66
|
+
var names = Object.keys(schemas);
|
|
67
|
+
for (var i = 0; i < names.length; i++) {
|
|
68
|
+
var name = names[i];
|
|
69
|
+
var schema = schemas[name];
|
|
70
|
+
lines.push('export interface ' + name + 'Params {');
|
|
71
|
+
var params = schema.parameters || [];
|
|
72
|
+
for (var j = 0; j < params.length; j++) {
|
|
73
|
+
var p = params[j];
|
|
74
|
+
lines.push(' ' + p.name + ': ' + toTs(p.type) + ';');
|
|
75
|
+
}
|
|
76
|
+
lines.push('}');
|
|
77
|
+
lines.push('');
|
|
78
|
+
lines.push('export interface ' + name + 'Result {');
|
|
79
|
+
lines.push(' data: ' + toTs(schema.return_type) + ';');
|
|
80
|
+
lines.push('}');
|
|
81
|
+
lines.push('');
|
|
82
|
+
}
|
|
83
|
+
lines.push('export interface Types {');
|
|
84
|
+
for (var k = 0; k < names.length; k++) {
|
|
85
|
+
lines.push(' ' + names[k] + ': { params: ' + names[k] + 'Params; result: ' + names[k] + 'Result };');
|
|
86
|
+
}
|
|
87
|
+
lines.push('}');
|
|
88
|
+
lines.push('');
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function fetchSchema(url) {
|
|
93
|
+
var endpoint = url.replace(/\/+$/, '');
|
|
94
|
+
if (endpoint.indexOf('/rpc') !== endpoint.length - 4) {
|
|
95
|
+
endpoint += '/rpc';
|
|
96
|
+
}
|
|
97
|
+
var mod = endpoint.indexOf('https') === 0 ? https : http;
|
|
98
|
+
return new Promise(function(resolve, reject) {
|
|
99
|
+
mod.get(endpoint, function(res) {
|
|
100
|
+
var data = '';
|
|
101
|
+
res.on('data', function(c) { data += c; });
|
|
102
|
+
res.on('end', function() {
|
|
103
|
+
if (res.statusCode !== 200) return reject(new Error('HTTP ' + res.statusCode));
|
|
104
|
+
try { resolve(JSON.parse(data)); }
|
|
105
|
+
catch (e) { reject(e); }
|
|
106
|
+
});
|
|
107
|
+
}).on('error', reject);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printHelp() {
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(' pyRPC client CLI');
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log(' Usage:');
|
|
116
|
+
console.log(' npx pyrpc Sync types from server (default)');
|
|
117
|
+
console.log(' npx pyrpc sync Same as above');
|
|
118
|
+
console.log(' npx pyrpc --help Show this help');
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(' The sync command reads pyrpc-client.json from your project');
|
|
121
|
+
console.log(' root and fetches the latest RPC schema from the server.');
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log(' In workspace mode, the server writes types directly to your');
|
|
124
|
+
console.log(' project — no sync needed.');
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function main() {
|
|
129
|
+
var args = process.argv.slice(2);
|
|
130
|
+
|
|
131
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
132
|
+
printHelp();
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (args[0] && args[0] !== 'sync') {
|
|
137
|
+
console.log('Unknown command: ' + args[0]);
|
|
138
|
+
console.log('Usage: npx pyrpc sync');
|
|
139
|
+
return Promise.resolve();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var cfgPath = findConfig();
|
|
143
|
+
if (!cfgPath) {
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(' No pyrpc-client.json found.');
|
|
146
|
+
console.log(' Run npm install @pyrpc/client to set up,');
|
|
147
|
+
console.log(' or create pyrpc-client.json manually.');
|
|
148
|
+
console.log('');
|
|
149
|
+
return Promise.resolve();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
var config;
|
|
153
|
+
try {
|
|
154
|
+
config = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.log(' \u2717 Error reading ' + cfgPath);
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (config.distribution === 'workspace') {
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(' Nothing to sync \u2014 server writes types directly to your project.');
|
|
163
|
+
console.log(' Run pyrpc dev on your backend to regenerate types on change.');
|
|
164
|
+
console.log('');
|
|
165
|
+
return Promise.resolve();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (config.distribution === 'server') {
|
|
169
|
+
var url = config.server_url;
|
|
170
|
+
if (!url) {
|
|
171
|
+
console.log(' \u2717 pyrpc-client.json is missing server_url');
|
|
172
|
+
return Promise.resolve();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return fetchSchema(url).then(function(schemas) {
|
|
176
|
+
var code = generate(schemas);
|
|
177
|
+
fs.mkdirSync(path.dirname(TYPES_OUTPUT), { recursive: true });
|
|
178
|
+
fs.writeFileSync(TYPES_OUTPUT, code, 'utf-8');
|
|
179
|
+
console.log(' \u2713 Types synced: ' + Object.keys(schemas).length + ' procedures');
|
|
180
|
+
console.log(' Import: import type { Types } from "@pyrpc/types"');
|
|
181
|
+
}).catch(function(err) {
|
|
182
|
+
console.log(' \u2717 Could not fetch schema from ' + url + ' (' + err.message + ')');
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(' \u2717 Unknown distribution: ' + config.distribution);
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (require.main === module) {
|
|
191
|
+
main().catch(function(e) {});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { main, toTs, generate, fetchSchema, findConfig, printHelp };
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyrpc/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Universal TypeScript client for pyRPC",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"pyrpc": "./cli.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
|
-
"dist"
|
|
12
|
+
"dist",
|
|
13
|
+
"cli.js"
|
|
10
14
|
],
|
|
11
15
|
"scripts": {
|
|
12
16
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|