@pyrpc/client 0.3.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pyrpc/client
2
2
 
3
- Universal TypeScript client for [pyRPC](https://pyrpc.dev). Type-safe RPC calls to your Python backend install, import, call.
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
- The postinstall script in `@pyrpc/types` will prompt for your server URL and generate typed contracts automatically.
15
+ On install, `@pyrpc/types` runs a `postinstall` script that prompts you to choose a **distribution mode**:
16
16
 
17
- For CI, set the `PYRPC_URL` environment variable:
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
- ```bash
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<TTypes>(options?)`
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
- Creates a proxy client that forwards method calls to the server.
73
+ - `baseUrl` - Server root URL (defaults to `window.location.origin` in browsers)
74
+ - `headers` - Static or async `HeadersInit`
58
75
 
59
- - `baseUrl` Server root URL (defaults to `window.location.origin` in browsers)
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/dist/index.d.mts CHANGED
@@ -25,7 +25,7 @@ interface ClientOptions {
25
25
  }
26
26
 
27
27
  declare class PyRPCClient {
28
- private baseUrl;
28
+ private url;
29
29
  private options;
30
30
  constructor(options?: ClientOptions);
31
31
  /**
@@ -40,7 +40,7 @@ declare class PyRPCClient {
40
40
  /**
41
41
  * Modern factory API for pyRPC.
42
42
  */
43
- declare function createClient<TTypes = any>(options?: ClientOptions): PyRPCClient & TTypes;
43
+ declare function createClient<TTypes = any>(options?: ClientOptions): TTypes;
44
44
 
45
45
  declare class PyRPCError extends Error {
46
46
  readonly code: number;
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ interface ClientOptions {
25
25
  }
26
26
 
27
27
  declare class PyRPCClient {
28
- private baseUrl;
28
+ private url;
29
29
  private options;
30
30
  constructor(options?: ClientOptions);
31
31
  /**
@@ -40,7 +40,7 @@ declare class PyRPCClient {
40
40
  /**
41
41
  * Modern factory API for pyRPC.
42
42
  */
43
- declare function createClient<TTypes = any>(options?: ClientOptions): PyRPCClient & TTypes;
43
+ declare function createClient<TTypes = any>(options?: ClientOptions): TTypes;
44
44
 
45
45
  declare class PyRPCError extends Error {
46
46
  readonly code: number;
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ createClient({
52
52
  })
53
53
  `;
54
54
  var PyRPCClient = class {
55
- baseUrl;
55
+ url;
56
56
  options;
57
57
  constructor(options = {}) {
58
58
  let baseUrl = options.baseUrl;
@@ -63,7 +63,8 @@ var PyRPCClient = class {
63
63
  throw new Error(NO_BASE_URL_ERROR);
64
64
  }
65
65
  }
66
- this.baseUrl = baseUrl.replace(/\/$/, "");
66
+ const clean = baseUrl.replace(/\/+$/, "");
67
+ this.url = clean.replace(/\/rpc$/i, "") + "/rpc";
67
68
  this.options = options;
68
69
  }
69
70
  /**
@@ -80,7 +81,7 @@ var PyRPCClient = class {
80
81
  userHeaders = typeof this.options.headers === "function" ? await this.options.headers() : this.options.headers;
81
82
  }
82
83
  const headers = { ...baseHeaders, ...Object.fromEntries(new Headers(userHeaders).entries()) };
83
- const response = await fetch(`${this.baseUrl}/rpc`, {
84
+ const response = await fetch(this.url, {
84
85
  method: "POST",
85
86
  headers,
86
87
  body: JSON.stringify(body)
package/dist/index.mjs CHANGED
@@ -24,7 +24,7 @@ createClient({
24
24
  })
25
25
  `;
26
26
  var PyRPCClient = class {
27
- baseUrl;
27
+ url;
28
28
  options;
29
29
  constructor(options = {}) {
30
30
  let baseUrl = options.baseUrl;
@@ -35,7 +35,8 @@ var PyRPCClient = class {
35
35
  throw new Error(NO_BASE_URL_ERROR);
36
36
  }
37
37
  }
38
- this.baseUrl = baseUrl.replace(/\/$/, "");
38
+ const clean = baseUrl.replace(/\/+$/, "");
39
+ this.url = clean.replace(/\/rpc$/i, "") + "/rpc";
39
40
  this.options = options;
40
41
  }
41
42
  /**
@@ -52,7 +53,7 @@ var PyRPCClient = class {
52
53
  userHeaders = typeof this.options.headers === "function" ? await this.options.headers() : this.options.headers;
53
54
  }
54
55
  const headers = { ...baseHeaders, ...Object.fromEntries(new Headers(userHeaders).entries()) };
55
- const response = await fetch(`${this.baseUrl}/rpc`, {
56
+ const response = await fetch(this.url, {
56
57
  method: "POST",
57
58
  headers,
58
59
  body: JSON.stringify(body)
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@pyrpc/client",
3
- "version": "0.3.2",
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",