@source-repo/rpc-cli 3.0.0 → 3.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/README.md +571 -710
- package/dist/fake.d.ts +39 -0
- package/dist/fake.d.ts.map +1 -1
- package/dist/fake.js +26 -3
- package/dist/fake.js.map +1 -1
- package/dist/handlers.d.ts +29 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +204 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +18 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +181 -3
- package/dist/mcp.js.map +1 -1
- package/dist/packages.d.ts +28 -0
- package/dist/packages.d.ts.map +1 -0
- package/dist/packages.js +94 -0
- package/dist/packages.js.map +1 -0
- package/dist/scripts.d.ts +62 -0
- package/dist/scripts.d.ts.map +1 -0
- package/dist/scripts.js +193 -0
- package/dist/scripts.js.map +1 -0
- package/dist/web/app.js +7 -7
- package/dist/web/app.js.map +1 -1
- package/package.json +14 -2
package/dist/fake.d.ts
CHANGED
|
@@ -33,12 +33,51 @@ export interface FakeScript {
|
|
|
33
33
|
every: number;
|
|
34
34
|
params?: unknown[];
|
|
35
35
|
}[];
|
|
36
|
+
/**
|
|
37
|
+
* Starting values for `state`, which the handlers below share and keep between calls.
|
|
38
|
+
*
|
|
39
|
+
* This is what makes a fake a simulator rather than a recording: a pump that ramps toward the
|
|
40
|
+
* setpoint it was last given cannot be expressed by `returns`, because the answer depends on
|
|
41
|
+
* what was called before it.
|
|
42
|
+
*/
|
|
43
|
+
state?: {
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `plant.read` -> a JavaScript function, as source. It is called with the arguments the caller
|
|
48
|
+
* sent and shares the mutable `state` above.
|
|
49
|
+
*
|
|
50
|
+
* Needs `--allow-exec`: this is the script author's code running in this process. See handlers.ts.
|
|
51
|
+
*/
|
|
52
|
+
handlers?: {
|
|
53
|
+
[target: string]: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* A Python program answering calls, for a simulation with more arithmetic in it than a
|
|
57
|
+
* JavaScript one-liner wants to carry. Decorate functions with `@rpc('plant.read')`; the
|
|
58
|
+
* interpreter is started once and keeps its own state.
|
|
59
|
+
*
|
|
60
|
+
* Needs `--allow-exec`, and a `python3` on PATH.
|
|
61
|
+
*/
|
|
62
|
+
python?: {
|
|
63
|
+
program: string;
|
|
64
|
+
targets: string[];
|
|
65
|
+
interpreter?: string;
|
|
66
|
+
};
|
|
36
67
|
}
|
|
37
68
|
export interface FakeOptions extends NetworkOptions {
|
|
38
69
|
/** The contract to serve. Every namespace in it is exposed. */
|
|
39
70
|
schema: RpcSchema;
|
|
40
71
|
script?: FakeScript;
|
|
72
|
+
/**
|
|
73
|
+
* Permit `handlers` and `python`, which run code the script supplied. Off by default, and the
|
|
74
|
+
* container ships without it: the flag is the security boundary, so it has to be given
|
|
75
|
+
* deliberately rather than inherited from a config file somebody copied.
|
|
76
|
+
*/
|
|
77
|
+
allowExec?: boolean;
|
|
41
78
|
}
|
|
79
|
+
/** What a script asks for that only `--allow-exec` grants. Empty when it asks for neither. */
|
|
80
|
+
export declare const execUsedBy: (script?: FakeScript) => string[];
|
|
42
81
|
/** Whether something handed over as a contract is one, so the wrong object is refused kindly. */
|
|
43
82
|
export declare const looksLikeSchema: (value: unknown) => value is RpcSchema;
|
|
44
83
|
/**
|
package/dist/fake.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake.d.ts","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAwB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"fake.d.ts","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAwB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAGrE;;;;;;;;;;;;GAYG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,4FAA4F;IAC5F,OAAO,CAAC,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IACvC;;;;OAIG;IACH,KAAK,CAAC,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IACpC,8FAA8F;IAC9F,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,EAAE,CAAA;IAC9D;;;;;;OAMG;IACH,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;AAED,MAAM,WAAW,WAAY,SAAQ,cAAc;IAC/C,+DAA+D;IAC/D,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,8FAA8F;AAC9F,eAAO,MAAM,UAAU,YAAa,UAAU,aAC+E,CAAA;AAE7H,iGAAiG;AACjG,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,KAAK,IAAI,SAC0E,CAAA;AAcpI;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,SAAU,QAAQ,GAAG,SAAS,SAAS,SAAS,CAAC,OAAO,CAAC,SAAQ,GAAG,CAAC,MAAM,CAAC,qBAA0B,OA8D3H,CAAA;AAoCD,eAAO,MAAM,SAAS,YAAmB,WAAW;;;;EA6DnD,CAAA"}
|
package/dist/fake.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { RpcServer } from '@source-repo/rpc';
|
|
3
3
|
import { networkTransports } from './network.js';
|
|
4
|
+
import { javascriptRuntime, pythonRuntime } from './handlers.js';
|
|
5
|
+
/** What a script asks for that only `--allow-exec` grants. Empty when it asks for neither. */
|
|
6
|
+
export const execUsedBy = (script) => [...(script?.handlers && Object.keys(script.handlers).length ? ['handlers'] : []), ...(script?.python ? ['python'] : [])];
|
|
4
7
|
/** Whether something handed over as a contract is one, so the wrong object is refused kindly. */
|
|
5
8
|
export const looksLikeSchema = (value) => !!value && typeof value === 'object' && !!value.namespaces && typeof value.namespaces === 'object';
|
|
6
9
|
/** The code that means "never answer" rather than "answer with this error". */
|
|
@@ -95,13 +98,15 @@ export const sampleFor = (type, types, seen = new Set(), depth = 0) => {
|
|
|
95
98
|
*/
|
|
96
99
|
class Fake extends EventEmitter {
|
|
97
100
|
}
|
|
98
|
-
const buildNamespace = (name, namespace, types, script) => {
|
|
101
|
+
const buildNamespace = (name, namespace, types, script, runtimes = []) => {
|
|
99
102
|
// Named, so a console describing this peer says `Fake` where a real one names its class. That
|
|
100
103
|
// line is the only place the browser says out loud that it is not talking to a device.
|
|
101
104
|
const instance = new Fake();
|
|
102
105
|
for (const [method, described] of Object.entries(namespace.methods)) {
|
|
103
106
|
const target = `${name}.${method}`;
|
|
104
|
-
|
|
107
|
+
// The arguments are taken now, where before they were ignored: a handler is only worth
|
|
108
|
+
// having if it can see what it was called with.
|
|
109
|
+
instance[method] = async (...params) => {
|
|
105
110
|
const failure = script?.fails?.[target];
|
|
106
111
|
// Never answered rather than answered with a timeout: a caller has to see the call go
|
|
107
112
|
// unanswered for its own timeout to be what fires, which is the behaviour being staged.
|
|
@@ -109,6 +114,11 @@ const buildNamespace = (name, namespace, types, script) => {
|
|
|
109
114
|
return await new Promise(() => { });
|
|
110
115
|
if (failure)
|
|
111
116
|
throw Object.assign(new Error(`${target} is set to fail`), { code: failure });
|
|
117
|
+
// Before `returns`, because a handler is the more specific instruction: a script that
|
|
118
|
+
// carries both means the canned value for everything the handler does not answer.
|
|
119
|
+
const runtime = runtimes.find((candidate) => candidate.handles(target));
|
|
120
|
+
if (runtime)
|
|
121
|
+
return await runtime.call(target, params);
|
|
112
122
|
if (script?.returns && target in script.returns)
|
|
113
123
|
return script.returns[target];
|
|
114
124
|
return sampleFor(described.returns, types);
|
|
@@ -120,6 +130,17 @@ export const startFake = async (options) => {
|
|
|
120
130
|
const namespaces = Object.entries(options.schema.namespaces).filter(([name]) => name !== 'msgrpc');
|
|
121
131
|
if (!namespaces.length)
|
|
122
132
|
throw new Error('startFake: the contract describes no namespaces to serve');
|
|
133
|
+
// Refused here rather than ignored. A script whose handlers were silently dropped would serve
|
|
134
|
+
// generated values that look plausible and answer nothing the simulation was written to answer,
|
|
135
|
+
// which is worse than not starting.
|
|
136
|
+
const wantsExec = execUsedBy(options.script);
|
|
137
|
+
if (wantsExec.length && !options.allowExec)
|
|
138
|
+
throw new Error(`startFake: this script uses ${wantsExec.join(' and ')}, which run code it supplied. Pass --allow-exec to permit that; it is off by default and not enabled in the container.`);
|
|
139
|
+
const runtimes = [];
|
|
140
|
+
if (options.allowExec && options.script?.handlers && Object.keys(options.script.handlers).length)
|
|
141
|
+
runtimes.push(javascriptRuntime(options.script.handlers, options.script.state ?? {}));
|
|
142
|
+
if (options.allowExec && options.script?.python)
|
|
143
|
+
runtimes.push(await pythonRuntime(options.script.python.program, options.script.python.targets, options.script.python.interpreter));
|
|
123
144
|
// Built here rather than through connectNetwork, which makes a window onto the network; this
|
|
124
145
|
// one is a peer that serves. The schema is handed over so the fake refuses exactly what the real
|
|
125
146
|
// peer would - a stand-in that accepts calls the device rejects teaches the wrong lesson.
|
|
@@ -133,7 +154,7 @@ export const startFake = async (options) => {
|
|
|
133
154
|
});
|
|
134
155
|
const instances = new Map();
|
|
135
156
|
for (const [name, namespace] of namespaces) {
|
|
136
|
-
const instance = buildNamespace(name, namespace, options.schema.types, options.script);
|
|
157
|
+
const instance = buildNamespace(name, namespace, options.schema.types, options.script, runtimes);
|
|
137
158
|
network.exposeObject(instance, name);
|
|
138
159
|
instances.set(name, instance);
|
|
139
160
|
}
|
|
@@ -157,6 +178,8 @@ export const startFake = async (options) => {
|
|
|
157
178
|
close: async () => {
|
|
158
179
|
for (const timer of timers)
|
|
159
180
|
clearInterval(timer);
|
|
181
|
+
for (const runtime of runtimes)
|
|
182
|
+
await runtime.close();
|
|
160
183
|
await network.close();
|
|
161
184
|
}
|
|
162
185
|
};
|
package/dist/fake.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake.js","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,SAAS,EAAuD,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAE,iBAAiB,EAAuB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"fake.js","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface HandlerRuntime {
|
|
2
|
+
/** Whether this runtime answers for a `namespace.method`. */
|
|
3
|
+
handles: (target: string) => boolean;
|
|
4
|
+
/** Run it. Throwing an error carrying `code` selects the RPC error the caller sees. */
|
|
5
|
+
call: (target: string, params: unknown[]) => Promise<unknown>;
|
|
6
|
+
close: () => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* JavaScript method bodies over shared state.
|
|
10
|
+
*
|
|
11
|
+
* The functions are defined once, so a handler can close over whatever it likes, and *called* by a
|
|
12
|
+
* script with a timeout - the budget has to be on the call rather than on the definition, since a
|
|
13
|
+
* loop that never ends is written in the body and not at the top level.
|
|
14
|
+
*/
|
|
15
|
+
export declare const javascriptRuntime: (handlers: {
|
|
16
|
+
[target: string]: string;
|
|
17
|
+
}, state: {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}) => HandlerRuntime;
|
|
20
|
+
/**
|
|
21
|
+
* A Python program that answers calls, spoken to over stdio as newline-delimited JSON.
|
|
22
|
+
*
|
|
23
|
+
* One interpreter per fake, started once and kept, so state is whatever the program keeps in its own
|
|
24
|
+
* variables - which is the point, and the thing a JSON script cannot express. The protocol is
|
|
25
|
+
* deliberately tiny; the shim below is the whole of what the program has to satisfy.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PYTHON_SHIM = "\nimport sys, json\n_handlers = {}\n\ndef rpc(target):\n \"\"\"Decorate a function to answer 'namespace.method'.\"\"\"\n def register(fn):\n _handlers[target] = fn\n return fn\n return register\n\ndef _serve():\n for line in sys.stdin:\n line = line.strip()\n if not line:\n continue\n try:\n call = json.loads(line)\n except Exception as e:\n print(json.dumps({\"id\": None, \"error\": \"bad request: %s\" % e}), flush=True)\n continue\n fn = _handlers.get(call.get(\"target\"))\n if fn is None:\n print(json.dumps({\"id\": call.get(\"id\"), \"error\": \"no handler for %s\" % call.get(\"target\")}), flush=True)\n continue\n try:\n print(json.dumps({\"id\": call.get(\"id\"), \"result\": fn(*call.get(\"params\", []))}), flush=True)\n except Exception as e:\n print(json.dumps({\"id\": call.get(\"id\"), \"error\": str(e)}), flush=True)\n";
|
|
28
|
+
export declare const pythonRuntime: (program: string, targets: string[], interpreter?: string) => Promise<HandlerRuntime>;
|
|
29
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC3B,6DAA6D;IAC7D,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;IACpC,uFAAuF;IACvF,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7D,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,aAAc;IAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,KAAG,cAkD7G,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,u/BA6BvB,CAAA;AAED,eAAO,MAAM,aAAa,YAAmB,MAAM,WAAW,MAAM,EAAE,2BAA4B,OAAO,CAAC,cAAc,CA2EvH,CAAA"}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createContext, runInContext } from 'node:vm';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
/**
|
|
5
|
+
* Behaviour for a fake that the contract cannot describe: state.
|
|
6
|
+
*
|
|
7
|
+
* `returns` answers the same value every time and `emits` fires on a timer, which covers a screen
|
|
8
|
+
* that needs something to draw. What neither covers is a device that *reacts* - a pump that ramps
|
|
9
|
+
* toward the setpoint it was last given, a batch that will not start twice, a valve that reports
|
|
10
|
+
* closed until something opens it. Those are the behaviours an HMI is actually wrong about, and
|
|
11
|
+
* they need a variable and a method that can read what it was called with.
|
|
12
|
+
*
|
|
13
|
+
* So a script may supply the method bodies, in JavaScript or in Python. Both are **off unless
|
|
14
|
+
* `--allow-exec` says otherwise**, and the reason is worth stating rather than leaving implied:
|
|
15
|
+
*
|
|
16
|
+
* **This runs code the script author supplied, and the flag is the security boundary - not the
|
|
17
|
+
* runtime.** The JavaScript context has no `require`, no `process`, no filesystem and a per-call
|
|
18
|
+
* time budget, which stops a careless handler wedging the process; `node:vm` is documented as not
|
|
19
|
+
* being a security mechanism, and a determined script can get out of it. Python is a subprocess with
|
|
20
|
+
* the privileges of whoever started it and no confinement at all. Both are the right tool for a
|
|
21
|
+
* simulator on a development machine and the wrong thing to reach from a plant network, which is why
|
|
22
|
+
* the container ships without the flag.
|
|
23
|
+
*/
|
|
24
|
+
/** How long one handler may run before the call is failed rather than waited out. */
|
|
25
|
+
const CALL_BUDGET_MS = 200;
|
|
26
|
+
/** How long Python gets to answer, allowing for an interpreter doing real work on the first call. */
|
|
27
|
+
const PYTHON_BUDGET_MS = 5000;
|
|
28
|
+
/** Thrown for a handler that failed on its own terms, so the caller gets Exception and the reason. */
|
|
29
|
+
const handlerFailed = (target, reason) => Object.assign(new Error(`${target}: ${reason instanceof Error ? reason.message : String(reason)}`), { code: 'Exception' });
|
|
30
|
+
/**
|
|
31
|
+
* JavaScript method bodies over shared state.
|
|
32
|
+
*
|
|
33
|
+
* The functions are defined once, so a handler can close over whatever it likes, and *called* by a
|
|
34
|
+
* script with a timeout - the budget has to be on the call rather than on the definition, since a
|
|
35
|
+
* loop that never ends is written in the body and not at the top level.
|
|
36
|
+
*/
|
|
37
|
+
export const javascriptRuntime = (handlers, state) => {
|
|
38
|
+
// Deliberately spare. Everything a simulator needs to compute a reading, and nothing that
|
|
39
|
+
// reaches the machine it is running on.
|
|
40
|
+
const context = createContext({
|
|
41
|
+
state,
|
|
42
|
+
Math,
|
|
43
|
+
JSON,
|
|
44
|
+
Date,
|
|
45
|
+
Number,
|
|
46
|
+
String,
|
|
47
|
+
Boolean,
|
|
48
|
+
Array,
|
|
49
|
+
Object,
|
|
50
|
+
isNaN,
|
|
51
|
+
parseFloat,
|
|
52
|
+
parseInt,
|
|
53
|
+
__handlers: {},
|
|
54
|
+
__target: '',
|
|
55
|
+
__params: []
|
|
56
|
+
});
|
|
57
|
+
for (const [target, source] of Object.entries(handlers)) {
|
|
58
|
+
try {
|
|
59
|
+
// Wrapped in parentheses so `(v) => …` and `function (v) { … }` both read as expressions.
|
|
60
|
+
runInContext(`__handlers[${JSON.stringify(target)}] = (${source})`, context, { timeout: CALL_BUDGET_MS });
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
// At construction, so a typo is a startup failure rather than a call that fails later on
|
|
64
|
+
// a screen somebody is watching.
|
|
65
|
+
throw new Error(`handler for ${target} did not compile: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
66
|
+
}
|
|
67
|
+
if (typeof context.__handlers[target] !== 'function')
|
|
68
|
+
throw new Error(`handler for ${target} is not a function - give it \`(a, b) => …\` or \`function (a, b) { … }\``);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
handles: (target) => target in handlers,
|
|
72
|
+
call: async (target, params) => {
|
|
73
|
+
context.__target = target;
|
|
74
|
+
context.__params = params;
|
|
75
|
+
try {
|
|
76
|
+
// Awaited outside the context: a handler may be async, and the timeout only bounds
|
|
77
|
+
// the synchronous run. A promise that never settles is caught by the call timeout
|
|
78
|
+
// the caller already carries.
|
|
79
|
+
return await runInContext('__handlers[__target](...__params)', context, { timeout: CALL_BUDGET_MS });
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw handlerFailed(target, e);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
close: async () => undefined
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A Python program that answers calls, spoken to over stdio as newline-delimited JSON.
|
|
90
|
+
*
|
|
91
|
+
* One interpreter per fake, started once and kept, so state is whatever the program keeps in its own
|
|
92
|
+
* variables - which is the point, and the thing a JSON script cannot express. The protocol is
|
|
93
|
+
* deliberately tiny; the shim below is the whole of what the program has to satisfy.
|
|
94
|
+
*/
|
|
95
|
+
export const PYTHON_SHIM = `
|
|
96
|
+
import sys, json
|
|
97
|
+
_handlers = {}
|
|
98
|
+
|
|
99
|
+
def rpc(target):
|
|
100
|
+
"""Decorate a function to answer 'namespace.method'."""
|
|
101
|
+
def register(fn):
|
|
102
|
+
_handlers[target] = fn
|
|
103
|
+
return fn
|
|
104
|
+
return register
|
|
105
|
+
|
|
106
|
+
def _serve():
|
|
107
|
+
for line in sys.stdin:
|
|
108
|
+
line = line.strip()
|
|
109
|
+
if not line:
|
|
110
|
+
continue
|
|
111
|
+
try:
|
|
112
|
+
call = json.loads(line)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(json.dumps({"id": None, "error": "bad request: %s" % e}), flush=True)
|
|
115
|
+
continue
|
|
116
|
+
fn = _handlers.get(call.get("target"))
|
|
117
|
+
if fn is None:
|
|
118
|
+
print(json.dumps({"id": call.get("id"), "error": "no handler for %s" % call.get("target")}), flush=True)
|
|
119
|
+
continue
|
|
120
|
+
try:
|
|
121
|
+
print(json.dumps({"id": call.get("id"), "result": fn(*call.get("params", []))}), flush=True)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(json.dumps({"id": call.get("id"), "error": str(e)}), flush=True)
|
|
124
|
+
`;
|
|
125
|
+
export const pythonRuntime = async (program, targets, interpreter = 'python3') => {
|
|
126
|
+
let child;
|
|
127
|
+
try {
|
|
128
|
+
// `-u` because a buffered interpreter answers the first call when the second arrives.
|
|
129
|
+
child = spawn(interpreter, ['-u', '-c', `${PYTHON_SHIM}\n${program}\n_serve()\n`], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
throw new Error(`could not start ${interpreter}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
133
|
+
}
|
|
134
|
+
const pending = new Map();
|
|
135
|
+
let nextId = 1;
|
|
136
|
+
let died;
|
|
137
|
+
createInterface({ input: child.stdout }).on('line', (line) => {
|
|
138
|
+
if (!line.trim())
|
|
139
|
+
return;
|
|
140
|
+
let message;
|
|
141
|
+
try {
|
|
142
|
+
message = JSON.parse(line);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// A print() in the program rather than a reply. Passed through, since a simulator author
|
|
146
|
+
// debugging with print deserves to see it somewhere.
|
|
147
|
+
process.stderr.write(`source-rpc fake (python): ${line}\n`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const waiting = message.id !== undefined ? pending.get(message.id) : undefined;
|
|
151
|
+
if (!waiting)
|
|
152
|
+
return;
|
|
153
|
+
pending.delete(message.id);
|
|
154
|
+
if (message.error !== undefined)
|
|
155
|
+
waiting.reject(new Error(message.error));
|
|
156
|
+
else
|
|
157
|
+
waiting.resolve(message.result);
|
|
158
|
+
});
|
|
159
|
+
const stderr = [];
|
|
160
|
+
child.stderr.on('data', (chunk) => {
|
|
161
|
+
const text = chunk.toString();
|
|
162
|
+
stderr.push(text);
|
|
163
|
+
process.stderr.write(`source-rpc fake (python): ${text}`);
|
|
164
|
+
});
|
|
165
|
+
child.on('exit', (code) => {
|
|
166
|
+
// Everything still waiting is failed now rather than left to time out one by one, and the
|
|
167
|
+
// reason names the interpreter's own complaint - a traceback is the useful part.
|
|
168
|
+
died = new Error(`the python program exited (${code})${stderr.length ? `: ${stderr.join('').trim().split('\n').pop()}` : ''}`);
|
|
169
|
+
for (const [, waiting] of pending)
|
|
170
|
+
waiting.reject(died);
|
|
171
|
+
pending.clear();
|
|
172
|
+
});
|
|
173
|
+
child.on('error', (e) => {
|
|
174
|
+
died = new Error(`could not run ${interpreter}: ${e.message}. Is it installed and on PATH?`);
|
|
175
|
+
for (const [, waiting] of pending)
|
|
176
|
+
waiting.reject(died);
|
|
177
|
+
pending.clear();
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
handles: (target) => targets.includes(target),
|
|
181
|
+
call: async (target, params) => {
|
|
182
|
+
if (died)
|
|
183
|
+
throw handlerFailed(target, died);
|
|
184
|
+
const id = nextId++;
|
|
185
|
+
const answer = new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
|
|
186
|
+
child.stdin.write(`${JSON.stringify({ id, target, params })}\n`);
|
|
187
|
+
const budget = new Promise((_, reject) => setTimeout(() => {
|
|
188
|
+
pending.delete(id);
|
|
189
|
+
reject(new Error(`did not answer within ${PYTHON_BUDGET_MS} ms`));
|
|
190
|
+
}, PYTHON_BUDGET_MS).unref());
|
|
191
|
+
try {
|
|
192
|
+
return await Promise.race([answer, budget]);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
throw handlerFailed(target, e);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
close: async () => {
|
|
199
|
+
child.stdin.end();
|
|
200
|
+
child.kill();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -102,6 +102,11 @@ const usage = `source-rpc <command> [options]
|
|
|
102
102
|
unsafe by design: for a development bus, never a plant
|
|
103
103
|
--contracts <dir> let it save and load contracts here; without it those tools
|
|
104
104
|
are not offered at all
|
|
105
|
+
--allow-exec let start_fake take JavaScript or Python method bodies; without it
|
|
106
|
+
those fields are not offered at all. Development only
|
|
107
|
+
--scripts <dir> a directory of peers written as programs, which this can add to,
|
|
108
|
+
change, start and stop; without it those tools are not offered at
|
|
109
|
+
all. A script is a process with your privileges. Development only
|
|
105
110
|
stdio carries the protocol, so it is not for interactive use
|
|
106
111
|
|
|
107
112
|
serve
|
|
@@ -109,6 +114,9 @@ const usage = `source-rpc <command> [options]
|
|
|
109
114
|
--script <file> canned returns, deliberate failures and events on a timer
|
|
110
115
|
--fail <ns.method=Code> answer with that RPC error code, repeatable
|
|
111
116
|
Timeout is the special one: the call is never answered at all
|
|
117
|
+
--allow-exec let a script supply JavaScript or Python method bodies, so a fake
|
|
118
|
+
can hold state and react. Off by default: it runs code the script
|
|
119
|
+
supplied, on this machine. Development only
|
|
112
120
|
--broker / --hub / --prefix / --timeout / --name / --sign as above
|
|
113
121
|
|
|
114
122
|
record
|
|
@@ -352,6 +360,7 @@ const VALUE_FLAGS = new Set([
|
|
|
352
360
|
'--against',
|
|
353
361
|
'--speed',
|
|
354
362
|
'--contracts',
|
|
363
|
+
'--scripts',
|
|
355
364
|
'--rate',
|
|
356
365
|
'--concurrency',
|
|
357
366
|
'--idempotency-key',
|
|
@@ -451,11 +460,16 @@ const runFake = async (argv) => {
|
|
|
451
460
|
script = { ...script, fails: { ...script.fails, [pair.slice(0, equals)]: pair.slice(equals + 1) } };
|
|
452
461
|
}
|
|
453
462
|
const { signing: _keys, ...network } = resolveNetworkFlags(argv, 'serve', 'fake');
|
|
454
|
-
const
|
|
463
|
+
const allowExec = argv.includes('--allow-exec');
|
|
464
|
+
const running = await startFake({ ...network, schema, ...(Object.keys(script).length ? { script } : {}), ...(allowExec ? { allowExec } : {}) });
|
|
455
465
|
process.stdout.write(`source-rpc serve: ${network.name} answering ${running.namespaces.join(', ')} from ${contractPath}\n`);
|
|
456
466
|
// Anything calling this is talking to a stand-in. Worth one line, since a fake that is mistaken
|
|
457
467
|
// for the device is worse than no fake at all.
|
|
458
468
|
process.stderr.write('source-rpc serve: this is a fake. It answers from the contract, not from a device.\n');
|
|
469
|
+
// Said out loud for the same reason the broker says it relays for anyone: the flag is the whole
|
|
470
|
+
// of the protection, so the run that has it should be visibly different from the run that does not.
|
|
471
|
+
if (allowExec)
|
|
472
|
+
process.stderr.write('source-rpc serve: --allow-exec is on, so this fake runs code its script supplied. Development machines only.\n');
|
|
459
473
|
const stop = () => void running
|
|
460
474
|
.close()
|
|
461
475
|
.then(() => process.exit(0))
|
|
@@ -732,7 +746,9 @@ const runBroker = async (argv) => {
|
|
|
732
746
|
const runMcp = async (argv) => {
|
|
733
747
|
const { signing: _keys, ...network } = resolveNetworkFlags(argv, 'mcp', 'mcp');
|
|
734
748
|
const contracts = argument(argv, '--contracts', '');
|
|
735
|
-
const
|
|
749
|
+
const scriptsDir = argument(argv, '--scripts', '');
|
|
750
|
+
const running = await startMcp({ ...network, ...(contracts ? { contracts: resolve(contracts) } : {}), ...(argv.includes('--allow-exec') ? { allowExec: true } : {}),
|
|
751
|
+
...(scriptsDir ? { scripts: resolve(scriptsDir) } : {}) });
|
|
736
752
|
// Nothing is written to stdout here: it carries the protocol. See mcp.ts.
|
|
737
753
|
const stop = () => void running
|
|
738
754
|
.close()
|