@pyrpc/client 0.6.0 → 0.6.2

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
@@ -12,18 +12,17 @@ pnpm add @pyrpc/client
12
12
  bun add @pyrpc/client
13
13
  ```
14
14
 
15
- On install, `@pyrpc/types` runs a `postinstall` script that prompts you to choose a **distribution mode**:
15
+ On install, `@pyrpc/client` 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
18
  - **Server** — types are fetched from a running server at `npx pyrpc sync` time.
19
19
 
20
- CI / non-TTY environments skip the prompt silently.
20
+ CI / non-TTY environments skip the prompt silently. Re-run with `npx pyrpc sync` to generate types later.
21
21
 
22
22
  ## Usage
23
23
 
24
24
  ```typescript
25
- import { createClient } from "@pyrpc/client";
26
- import type { Types } from "@pyrpc/types";
25
+ import { createClient, type Types } from "@pyrpc/client";
27
26
 
28
27
  const client = createClient<Types>({
29
28
  baseUrl: "https://api.example.com",
package/cli.js CHANGED
@@ -1,194 +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 };
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 \u2014 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 { createClient, type Types } from "@pyrpc/client"');
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
@@ -24,23 +24,22 @@ interface ClientOptions {
24
24
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
25
25
  }
26
26
 
27
- declare class PyRPCClient {
28
- private url;
29
- private options;
30
- constructor(options?: ClientOptions);
31
- /**
32
- * Internal method to perform the fetch request.
33
- */
34
- private request;
35
- /**
36
- * Creates a proxy that allows calling remote procedures as if they were local methods.
37
- */
38
- get rpc(): any;
39
- }
40
27
  /**
41
- * Modern factory API for pyRPC.
28
+ * Creates a typed pyRPC client.
29
+ *
30
+ * Pass your generated `Types` interface as the generic parameter
31
+ * to get full type safety and auto-complete for all RPC procedures.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createClient } from "@pyrpc/client"
36
+ * import type { Types } from "@pyrpc/types"
37
+ *
38
+ * const api = createClient<Types>({ baseUrl: "http://localhost:8000" })
39
+ * const user = await api.get_user("John")
40
+ * ```
42
41
  */
43
- declare function createClient<TTypes = any>(options?: ClientOptions): TTypes;
42
+ declare function createClient<T = any>(options?: ClientOptions): T;
44
43
 
45
44
  declare class PyRPCError extends Error {
46
45
  readonly code: number;
@@ -48,4 +47,4 @@ declare class PyRPCError extends Error {
48
47
  constructor(code: number, message: string, data?: any);
49
48
  }
50
49
 
51
- export { type ClientOptions, PyRPCClient, PyRPCError, type RpcRequest, type RpcResponse, createClient };
50
+ export { type ClientOptions, PyRPCError, type RpcRequest, type RpcResponse, createClient };
package/dist/index.d.ts CHANGED
@@ -24,23 +24,22 @@ interface ClientOptions {
24
24
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
25
25
  }
26
26
 
27
- declare class PyRPCClient {
28
- private url;
29
- private options;
30
- constructor(options?: ClientOptions);
31
- /**
32
- * Internal method to perform the fetch request.
33
- */
34
- private request;
35
- /**
36
- * Creates a proxy that allows calling remote procedures as if they were local methods.
37
- */
38
- get rpc(): any;
39
- }
40
27
  /**
41
- * Modern factory API for pyRPC.
28
+ * Creates a typed pyRPC client.
29
+ *
30
+ * Pass your generated `Types` interface as the generic parameter
31
+ * to get full type safety and auto-complete for all RPC procedures.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createClient } from "@pyrpc/client"
36
+ * import type { Types } from "@pyrpc/types"
37
+ *
38
+ * const api = createClient<Types>({ baseUrl: "http://localhost:8000" })
39
+ * const user = await api.get_user("John")
40
+ * ```
42
41
  */
43
- declare function createClient<TTypes = any>(options?: ClientOptions): TTypes;
42
+ declare function createClient<T = any>(options?: ClientOptions): T;
44
43
 
45
44
  declare class PyRPCError extends Error {
46
45
  readonly code: number;
@@ -48,4 +47,4 @@ declare class PyRPCError extends Error {
48
47
  constructor(code: number, message: string, data?: any);
49
48
  }
50
49
 
51
- export { type ClientOptions, PyRPCClient, PyRPCError, type RpcRequest, type RpcResponse, createClient };
50
+ export { type ClientOptions, PyRPCError, type RpcRequest, type RpcResponse, createClient };
package/dist/index.js CHANGED
@@ -20,7 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- PyRPCClient: () => PyRPCClient,
24
23
  PyRPCError: () => PyRPCError,
25
24
  createClient: () => createClient
26
25
  });
@@ -67,9 +66,6 @@ var PyRPCClient = class {
67
66
  this.url = clean.replace(/\/rpc$/i, "") + "/rpc";
68
67
  this.options = options;
69
68
  }
70
- /**
71
- * Internal method to perform the fetch request.
72
- */
73
69
  async request(method, params) {
74
70
  const id = Math.random().toString(36).substring(7);
75
71
  const body = { id, method, params };
@@ -95,41 +91,21 @@ var PyRPCClient = class {
95
91
  }
96
92
  return data.result;
97
93
  }
98
- /**
99
- * Creates a proxy that allows calling remote procedures as if they were local methods.
100
- */
101
- get rpc() {
102
- return new Proxy({}, {
103
- get: (_, method) => {
104
- return (...args) => {
105
- const params = args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : args;
106
- return this.request(method, params);
107
- };
108
- }
109
- });
110
- }
111
94
  };
95
+ function normalizeArgs(args) {
96
+ return args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : args;
97
+ }
112
98
  function createClient(options = {}) {
113
99
  const client = new PyRPCClient(options);
114
- return new Proxy(client, {
115
- get(target, prop, receiver) {
116
- if (prop === "rpc") {
117
- return new Proxy({}, {
118
- get(_, method) {
119
- throw new Error(
120
- `Use client.${String(method)}() instead of client.rpc.${String(method)}(). The .rpc prefix was removed for a cleaner API.`
121
- );
122
- }
123
- });
124
- }
125
- if (prop in target) return Reflect.get(target, prop, receiver);
126
- return target.rpc[prop];
100
+ return new Proxy({}, {
101
+ get(_target, prop) {
102
+ if (typeof prop !== "string") return void 0;
103
+ return (...args) => client.request(prop, normalizeArgs(args));
127
104
  }
128
105
  });
129
106
  }
130
107
  // Annotate the CommonJS export names for ESM import in node:
131
108
  0 && (module.exports = {
132
- PyRPCClient,
133
109
  PyRPCError,
134
110
  createClient
135
111
  });
package/dist/index.mjs CHANGED
@@ -39,9 +39,6 @@ var PyRPCClient = class {
39
39
  this.url = clean.replace(/\/rpc$/i, "") + "/rpc";
40
40
  this.options = options;
41
41
  }
42
- /**
43
- * Internal method to perform the fetch request.
44
- */
45
42
  async request(method, params) {
46
43
  const id = Math.random().toString(36).substring(7);
47
44
  const body = { id, method, params };
@@ -67,40 +64,20 @@ var PyRPCClient = class {
67
64
  }
68
65
  return data.result;
69
66
  }
70
- /**
71
- * Creates a proxy that allows calling remote procedures as if they were local methods.
72
- */
73
- get rpc() {
74
- return new Proxy({}, {
75
- get: (_, method) => {
76
- return (...args) => {
77
- const params = args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : args;
78
- return this.request(method, params);
79
- };
80
- }
81
- });
82
- }
83
67
  };
68
+ function normalizeArgs(args) {
69
+ return args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : args;
70
+ }
84
71
  function createClient(options = {}) {
85
72
  const client = new PyRPCClient(options);
86
- return new Proxy(client, {
87
- get(target, prop, receiver) {
88
- if (prop === "rpc") {
89
- return new Proxy({}, {
90
- get(_, method) {
91
- throw new Error(
92
- `Use client.${String(method)}() instead of client.rpc.${String(method)}(). The .rpc prefix was removed for a cleaner API.`
93
- );
94
- }
95
- });
96
- }
97
- if (prop in target) return Reflect.get(target, prop, receiver);
98
- return target.rpc[prop];
73
+ return new Proxy({}, {
74
+ get(_target, prop) {
75
+ if (typeof prop !== "string") return void 0;
76
+ return (...args) => client.request(prop, normalizeArgs(args));
99
77
  }
100
78
  });
101
79
  }
102
80
  export {
103
- PyRPCClient,
104
81
  PyRPCError,
105
82
  createClient
106
83
  };
package/package.json CHANGED
@@ -1,39 +1,41 @@
1
- {
2
- "name": "@pyrpc/client",
3
- "version": "0.6.0",
4
- "description": "Universal TypeScript client for pyRPC",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "bin": {
9
- "pyrpc": "./cli.js"
10
- },
11
- "files": [
12
- "dist",
13
- "cli.js"
14
- ],
15
- "scripts": {
16
- "build": "tsup src/index.ts --format cjs,esm --dts",
17
- "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
18
- "lint": "eslint src/**/*.ts",
19
- "test": "vitest run"
20
- },
21
- "keywords": [
22
- "rpc",
23
- "pyrpc",
24
- "typescript",
25
- "client",
26
- "api"
27
- ],
28
- "author": "",
29
- "license": "MIT",
30
- "dependencies": {
31
- "@pyrpc/types": "^0.3.0"
32
- },
33
- "devDependencies": {
34
- "@types/node": "^20.19.39",
35
- "tsup": "^8.5.1",
36
- "typescript": "^5.9.3",
37
- "vitest": "^3.0.0"
38
- }
39
- }
1
+ {
2
+ "name": "@pyrpc/client",
3
+ "version": "0.6.2",
4
+ "description": "Universal TypeScript client for pyRPC",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "pyrpc": "./cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "cli.js",
14
+ "postinstall.js"
15
+ ],
16
+ "scripts": {
17
+ "postinstall": "node postinstall.js",
18
+ "build": "tsup src/index.ts --format cjs,esm --dts",
19
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
20
+ "lint": "eslint src/**/*.ts",
21
+ "test": "vitest run"
22
+ },
23
+ "keywords": [
24
+ "rpc",
25
+ "pyrpc",
26
+ "typescript",
27
+ "client",
28
+ "api"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@pyrpc/types": "^0.6.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.19.39",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "^5.9.3",
39
+ "vitest": "^3.0.0"
40
+ }
41
+ }
package/postinstall.js ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ var fs = require('fs');
3
+ var path = require('path');
4
+ var readline = require('readline');
5
+ var http = require('http');
6
+ var https = require('https');
7
+
8
+ var TYPES_DIR;
9
+ try {
10
+ TYPES_DIR = path.dirname(require.resolve('@pyrpc/types/package.json'));
11
+ } catch (e) {
12
+ TYPES_DIR = path.join(__dirname, 'node_modules', '@pyrpc', 'types');
13
+ }
14
+
15
+ var TYPES_FILE = path.join(TYPES_DIR, 'src', 'index.ts');
16
+
17
+ function findProjectRoot() {
18
+ var dir = process.cwd();
19
+ while (true) {
20
+ var pkgPath = path.join(dir, 'package.json');
21
+ if (fs.existsSync(pkgPath) && !pkgPath.replace(/\\/g, '/').includes('/node_modules/')) {
22
+ return dir;
23
+ }
24
+ var parent = path.dirname(dir);
25
+ if (parent === dir) return process.cwd();
26
+ dir = parent;
27
+ }
28
+ }
29
+
30
+ function getConfigPath() {
31
+ return path.join(findProjectRoot(), 'pyrpc-client.json');
32
+ }
33
+
34
+ var TYPE_MAP = {
35
+ int: 'number',
36
+ float: 'number',
37
+ str: 'string',
38
+ bool: 'boolean',
39
+ None: 'null',
40
+ NoneType: 'null',
41
+ Any: 'any',
42
+ };
43
+
44
+ function toTs(t) {
45
+ if (!t || t === 'None') return 'void';
46
+ var m = t.match(/^<class\s+'([^'>]+)'>$/);
47
+ if (m) {
48
+ var n = m[1];
49
+ if (TYPE_MAP[n]) return TYPE_MAP[n];
50
+ if (n[0] >= 'A' && n[0] <= 'Z') return n;
51
+ return 'any';
52
+ }
53
+ var s = t.replace(/^typing\./, '');
54
+ var o = s.match(/^Optional\[(.+)\]$/);
55
+ if (o) return toTs(o[1]) + ' | null';
56
+ var l = s.match(/^(?:List|list)\[(.+)\]$/);
57
+ if (l) return toTs(l[1]) + '[]';
58
+ var d = s.match(/^(?:Dict|dict)\[([^,]+),\s*(.+)\]$/);
59
+ if (d) return 'Record<' + toTs(d[1]) + ', ' + toTs(d[2]) + '>';
60
+ var u = s.match(/^Union\[(.+)\]$/);
61
+ if (u) return u[1].split(',').map(function(p) { return toTs(p.trim()); }).join(' | ');
62
+ var tup = s.match(/^(?:Tuple|tuple)\[([^\]]+)\]$/);
63
+ if (tup) return '[' + tup[1].split(',').map(function(p) { return toTs(p.trim()); }).join(', ') + ']';
64
+ return 'any';
65
+ }
66
+
67
+ function generate(schemas) {
68
+ var lines = [];
69
+ lines.push('// Auto-generated by @pyrpc/types');
70
+ lines.push('// Schema fetched from: ' + (process.env.PYRPC_URL || 'http://localhost:8000'));
71
+ lines.push('');
72
+ var names = Object.keys(schemas);
73
+ for (var i = 0; i < names.length; i++) {
74
+ var name = names[i];
75
+ var schema = schemas[name];
76
+ lines.push('export interface ' + name + 'Params {');
77
+ var params = schema.parameters || [];
78
+ for (var j = 0; j < params.length; j++) {
79
+ var p = params[j];
80
+ lines.push(' ' + p.name + ': ' + toTs(p.type) + ';');
81
+ }
82
+ lines.push('}');
83
+ lines.push('');
84
+ lines.push('export interface ' + name + 'Result {');
85
+ lines.push(' data: ' + toTs(schema.return_type) + ';');
86
+ lines.push('}');
87
+ lines.push('');
88
+ }
89
+ lines.push('export interface Types {');
90
+ for (var k = 0; k < names.length; k++) {
91
+ lines.push(' ' + names[k] + ': { params: ' + names[k] + 'Params; result: ' + names[k] + 'Result };');
92
+ }
93
+ lines.push('}');
94
+ lines.push('');
95
+ return lines.join('\n');
96
+ }
97
+
98
+ function fetchSchema(url) {
99
+ var endpoint = url.replace(/\/+$/, '');
100
+ if (endpoint.indexOf('/rpc') !== endpoint.length - 4) {
101
+ endpoint += '/rpc';
102
+ }
103
+ var mod = endpoint.indexOf('https') === 0 ? https : http;
104
+ return new Promise(function(resolve, reject) {
105
+ mod.get(endpoint, function(res) {
106
+ var data = '';
107
+ res.on('data', function(c) { data += c; });
108
+ res.on('end', function() {
109
+ if (res.statusCode !== 200) return reject(new Error('HTTP ' + res.statusCode));
110
+ try { resolve(JSON.parse(data)); }
111
+ catch (e) { reject(e); }
112
+ });
113
+ }).on('error', reject);
114
+ });
115
+ }
116
+
117
+ function main() {
118
+ if (fs.existsSync(getConfigPath())) {
119
+ return Promise.resolve();
120
+ }
121
+
122
+ var isInteractive = process.stdout.isTTY;
123
+ var isCI = process.env.CI;
124
+
125
+ if (process.env.CI || !isInteractive) {
126
+ return Promise.resolve();
127
+ }
128
+
129
+ console.log('');
130
+ console.log(' \u2728 Welcome to pyRPC!');
131
+ console.log(' Let\'s set up your client configuration.');
132
+ console.log('');
133
+
134
+ var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
135
+ return new Promise(function(resolve) {
136
+ rl.question('How are types distributed to the client?\n 1) workspace (default) - server writes types directly to your project\n 2) server - client fetches types via HTTP\nEnter choice [1]: ', function(answer) {
137
+ rl.close();
138
+ resolve(answer.trim() === '2' ? 'server' : 'workspace');
139
+ });
140
+ }).then(function(distribution) {
141
+ if (distribution === 'workspace') {
142
+ var config = { distribution: 'workspace' };
143
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\n');
144
+ console.log(' \u2713 pyrpc-client.json created (workspace mode)');
145
+ console.log(' Your server\'s pyrpc dev command will write types directly.');
146
+ console.log(' Import: import { createClient, type Types } from "@pyrpc/client"');
147
+ return;
148
+ }
149
+
150
+ var rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
151
+ return new Promise(function(resolve) {
152
+ rl2.question('pyRPC backend URL (default: http://localhost:8000): ', function(answer) {
153
+ rl2.close();
154
+ resolve(answer.trim() || 'http://localhost:8000');
155
+ });
156
+ }).then(function(url) {
157
+ return fetchSchema(url).then(function(schemas) {
158
+ var code = generate(schemas);
159
+ fs.mkdirSync(path.dirname(TYPES_FILE), { recursive: true });
160
+ fs.writeFileSync(TYPES_FILE, code, 'utf-8');
161
+ console.log(' \u2713 @pyrpc/types: generated for ' + Object.keys(schemas).length + ' procedures');
162
+
163
+ var config = { distribution: 'server', server_url: url };
164
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\n');
165
+ console.log(' \u2713 pyrpc-client.json created (server mode)');
166
+ console.log(' Import: import { createClient, type Types } from "@pyrpc/client"');
167
+ }).catch(function(err) {
168
+ console.log(' \u2717 @pyrpc/types: could not connect (' + err.message + ')');
169
+ console.log(' Run later: npx pyrpc sync');
170
+
171
+ var config = { distribution: 'server', server_url: url };
172
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\n');
173
+ console.log(' \u2713 pyrpc-client.json created (server mode)');
174
+ });
175
+ });
176
+ });
177
+ }
178
+
179
+ if (require.main === module) {
180
+ main().catch(function(e) {});
181
+ }
182
+
183
+ module.exports = { main, toTs, generate, fetchSchema, findProjectRoot, getConfigPath, TYPES_FILE };