@source-repo/rpc 3.0.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/LICENSE +21 -0
- package/README.md +1208 -0
- package/dist/Logging/ILogger.d.ts +7 -0
- package/dist/Logging/ILogger.d.ts.map +1 -0
- package/dist/Logging/ILogger.js +2 -0
- package/dist/Logging/ILogger.js.map +1 -0
- package/dist/NodeRpcServer.d.ts +55 -0
- package/dist/NodeRpcServer.d.ts.map +1 -0
- package/dist/NodeRpcServer.js +46 -0
- package/dist/NodeRpcServer.js.map +1 -0
- package/dist/RPC/Auth.d.ts +98 -0
- package/dist/RPC/Auth.d.ts.map +1 -0
- package/dist/RPC/Auth.js +14 -0
- package/dist/RPC/Auth.js.map +1 -0
- package/dist/RPC/Codec.d.ts +24 -0
- package/dist/RPC/Codec.d.ts.map +1 -0
- package/dist/RPC/Codec.js +21 -0
- package/dist/RPC/Codec.js.map +1 -0
- package/dist/RPC/Compatibility.d.ts +39 -0
- package/dist/RPC/Compatibility.d.ts.map +1 -0
- package/dist/RPC/Compatibility.js +195 -0
- package/dist/RPC/Compatibility.js.map +1 -0
- package/dist/RPC/Core.d.ts +188 -0
- package/dist/RPC/Core.d.ts.map +1 -0
- package/dist/RPC/Core.js +354 -0
- package/dist/RPC/Core.js.map +1 -0
- package/dist/RPC/Expose.d.ts +90 -0
- package/dist/RPC/Expose.d.ts.map +1 -0
- package/dist/RPC/Expose.js +110 -0
- package/dist/RPC/Expose.js.map +1 -0
- package/dist/RPC/Idempotency.d.ts +90 -0
- package/dist/RPC/Idempotency.d.ts.map +1 -0
- package/dist/RPC/Idempotency.js +46 -0
- package/dist/RPC/Idempotency.js.map +1 -0
- package/dist/RPC/Introspection.d.ts +92 -0
- package/dist/RPC/Introspection.d.ts.map +1 -0
- package/dist/RPC/Introspection.js +188 -0
- package/dist/RPC/Introspection.js.map +1 -0
- package/dist/RPC/Introspection.types.json +1093 -0
- package/dist/RPC/Messages.d.ts +108 -0
- package/dist/RPC/Messages.d.ts.map +1 -0
- package/dist/RPC/Messages.js +30 -0
- package/dist/RPC/Messages.js.map +1 -0
- package/dist/RPC/Rpc.d.ts +51 -0
- package/dist/RPC/Rpc.d.ts.map +1 -0
- package/dist/RPC/Rpc.js +54 -0
- package/dist/RPC/Rpc.js.map +1 -0
- package/dist/RPC/RpcClientHandler.d.ts +129 -0
- package/dist/RPC/RpcClientHandler.d.ts.map +1 -0
- package/dist/RPC/RpcClientHandler.js +267 -0
- package/dist/RPC/RpcClientHandler.js.map +1 -0
- package/dist/RPC/RpcServerHandler.d.ts +226 -0
- package/dist/RPC/RpcServerHandler.d.ts.map +1 -0
- package/dist/RPC/RpcServerHandler.js +676 -0
- package/dist/RPC/RpcServerHandler.js.map +1 -0
- package/dist/RPC/Schema.d.ts +150 -0
- package/dist/RPC/Schema.d.ts.map +1 -0
- package/dist/RPC/Schema.js +198 -0
- package/dist/RPC/Schema.js.map +1 -0
- package/dist/RPC/Signing.d.ts +118 -0
- package/dist/RPC/Signing.d.ts.map +1 -0
- package/dist/RPC/Signing.js +171 -0
- package/dist/RPC/Signing.js.map +1 -0
- package/dist/RPC/Tokens.d.ts +49 -0
- package/dist/RPC/Tokens.d.ts.map +1 -0
- package/dist/RPC/Tokens.js +45 -0
- package/dist/RPC/Tokens.js.map +1 -0
- package/dist/RpcClient.d.ts +91 -0
- package/dist/RpcClient.d.ts.map +1 -0
- package/dist/RpcClient.js +145 -0
- package/dist/RpcClient.js.map +1 -0
- package/dist/RpcServer.d.ts +191 -0
- package/dist/RpcServer.d.ts.map +1 -0
- package/dist/RpcServer.js +293 -0
- package/dist/RpcServer.js.map +1 -0
- package/dist/Transports/Mqtt5Frame.d.ts +107 -0
- package/dist/Transports/Mqtt5Frame.d.ts.map +1 -0
- package/dist/Transports/Mqtt5Frame.js +162 -0
- package/dist/Transports/Mqtt5Frame.js.map +1 -0
- package/dist/Transports/MqttTransport.d.ts +279 -0
- package/dist/Transports/MqttTransport.d.ts.map +1 -0
- package/dist/Transports/MqttTransport.js +843 -0
- package/dist/Transports/MqttTransport.js.map +1 -0
- package/dist/Transports/Presence.d.ts +68 -0
- package/dist/Transports/Presence.d.ts.map +1 -0
- package/dist/Transports/Presence.js +21 -0
- package/dist/Transports/Presence.js.map +1 -0
- package/dist/Transports/SocketIoClientTransport.d.ts +89 -0
- package/dist/Transports/SocketIoClientTransport.d.ts.map +1 -0
- package/dist/Transports/SocketIoClientTransport.js +280 -0
- package/dist/Transports/SocketIoClientTransport.js.map +1 -0
- package/dist/Transports/SocketIoServerTransport.d.ts +135 -0
- package/dist/Transports/SocketIoServerTransport.d.ts.map +1 -0
- package/dist/Transports/SocketIoServerTransport.js +505 -0
- package/dist/Transports/SocketIoServerTransport.js.map +1 -0
- package/dist/Utilities/Converters.d.ts +22 -0
- package/dist/Utilities/Converters.d.ts.map +1 -0
- package/dist/Utilities/Converters.js +58 -0
- package/dist/Utilities/Converters.js.map +1 -0
- package/dist/Utilities/Filter.d.ts +7 -0
- package/dist/Utilities/Filter.d.ts.map +1 -0
- package/dist/Utilities/Filter.js +14 -0
- package/dist/Utilities/Filter.js.map +1 -0
- package/dist/Utilities/ReadableName.d.ts +38 -0
- package/dist/Utilities/ReadableName.d.ts.map +1 -0
- package/dist/Utilities/ReadableName.js +58 -0
- package/dist/Utilities/ReadableName.js.map +1 -0
- package/dist/Utilities/Switch.d.ts +21 -0
- package/dist/Utilities/Switch.d.ts.map +1 -0
- package/dist/Utilities/Switch.js +70 -0
- package/dist/Utilities/Switch.js.map +1 -0
- package/dist/Utilities/TryCatch.d.ts +11 -0
- package/dist/Utilities/TryCatch.d.ts.map +1 -0
- package/dist/Utilities/TryCatch.js +13 -0
- package/dist/Utilities/TryCatch.js.map +1 -0
- package/dist/index-web.d.ts +25 -0
- package/dist/index-web.d.ts.map +1 -0
- package/dist/index-web.js +28 -0
- package/dist/index-web.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marking which methods of a class may be called remotely.
|
|
3
|
+
*
|
|
4
|
+
* exposeClassInstance walks the prototype chain and publishes every function it finds, so a helper
|
|
5
|
+
* a class never meant to offer becomes callable by anyone who can reach the transport. Marking the
|
|
6
|
+
* intended methods turns that into an allow-list.
|
|
7
|
+
*
|
|
8
|
+
* A class with no marks keeps the old behaviour, so the plain "just expose the class" style still
|
|
9
|
+
* works. Set requireExplicitExposure on RpcServer to make the marks compulsory instead.
|
|
10
|
+
*/
|
|
11
|
+
/** Marked method names per constructor. Subclasses accumulate their own plus the ones they inherit. */
|
|
12
|
+
const marked = new WeakMap();
|
|
13
|
+
/** Declared semantics per constructor and method name, for the methods that declare any. */
|
|
14
|
+
const semantics = new WeakMap();
|
|
15
|
+
const markOn = (constructor, method, declared) => {
|
|
16
|
+
let names = marked.get(constructor);
|
|
17
|
+
if (!names)
|
|
18
|
+
marked.set(constructor, (names = new Set()));
|
|
19
|
+
names.add(method);
|
|
20
|
+
if (!declared)
|
|
21
|
+
return;
|
|
22
|
+
let declarations = semantics.get(constructor);
|
|
23
|
+
if (!declarations)
|
|
24
|
+
semantics.set(constructor, (declarations = new Map()));
|
|
25
|
+
declarations.set(method, declared);
|
|
26
|
+
};
|
|
27
|
+
const mark = (context, options) => {
|
|
28
|
+
if (context.static)
|
|
29
|
+
throw new Error('@rpc: static methods cannot be exposed');
|
|
30
|
+
if (context.private)
|
|
31
|
+
throw new Error('@rpc: private methods cannot be exposed');
|
|
32
|
+
context.addInitializer(function () {
|
|
33
|
+
markOn(this.constructor, String(context.name), options.semantics);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
export function rpc(targetOrOptions, context) {
|
|
37
|
+
// Applied directly, the runtime hands a decorator its target and context; called as a factory,
|
|
38
|
+
// the one argument is the options object. A function in the first position is the giveaway.
|
|
39
|
+
if (typeof targetOrOptions === 'function' && context)
|
|
40
|
+
return mark(context, {});
|
|
41
|
+
const options = targetOrOptions;
|
|
42
|
+
return (_target, methodContext) => mark(methodContext, options);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Marks methods without decorators, for JavaScript callers or code that prefers not to use them.
|
|
46
|
+
* Names that are not functions on the prototype are rejected, since a typo would silently expose
|
|
47
|
+
* nothing.
|
|
48
|
+
*/
|
|
49
|
+
export const exposeMethods = (constructor, methods) => {
|
|
50
|
+
for (const method of methods) {
|
|
51
|
+
if (typeof constructor.prototype[method] !== 'function')
|
|
52
|
+
throw new Error(`exposeMethods: ${constructor.name}.${method} is not a method`);
|
|
53
|
+
markOn(constructor, method);
|
|
54
|
+
}
|
|
55
|
+
return constructor;
|
|
56
|
+
};
|
|
57
|
+
/** Namespace declared by a class, so the name is written once and read by both ends. */
|
|
58
|
+
const namespaces = new WeakMap();
|
|
59
|
+
/**
|
|
60
|
+
* Declares the name a class is exposed under, and optionally the version of its contract and how
|
|
61
|
+
* its calls may overlap.
|
|
62
|
+
*
|
|
63
|
+
* The exposure name only existed at the call site - `exposeClassInstance(instance, 'plant')` - so
|
|
64
|
+
* nothing reading the source could tell which namespace a class belongs to. Declaring it here lets
|
|
65
|
+
* the extraction CLI key a schema correctly, and lets exposeClassInstance take the name as read.
|
|
66
|
+
*
|
|
67
|
+
* ```typescript
|
|
68
|
+
* @rpcNamespace('plant', { version: '3', execution: 'serial' })
|
|
69
|
+
* class Plant { @rpc async writeSetpoint(value: number) { ... } }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export const rpcNamespace = (name, options = {}) => (target, _context) => {
|
|
73
|
+
namespaces.set(target, { name, version: options.version, execution: options.execution });
|
|
74
|
+
return target;
|
|
75
|
+
};
|
|
76
|
+
/** The namespace an instance's class declares, walking up so a subclass inherits it. */
|
|
77
|
+
export const declaredNamespace = (instance) => {
|
|
78
|
+
for (let ctor = instance.constructor; ctor; ctor = Object.getPrototypeOf(ctor)) {
|
|
79
|
+
const declared = namespaces.get(ctor);
|
|
80
|
+
if (declared)
|
|
81
|
+
return declared;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
/** The marked method names for an instance, or undefined when the class marks nothing. */
|
|
86
|
+
export const markedMethods = (instance) => {
|
|
87
|
+
const names = new Set();
|
|
88
|
+
// Walk the chain so a subclass inherits its parent's marks.
|
|
89
|
+
for (let ctor = instance.constructor; ctor; ctor = Object.getPrototypeOf(ctor)) {
|
|
90
|
+
for (const name of marked.get(ctor) ?? [])
|
|
91
|
+
names.add(name);
|
|
92
|
+
}
|
|
93
|
+
return names.size ? names : undefined;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* The semantics an instance's methods declare, walking the chain so a subclass inherits them.
|
|
97
|
+
*
|
|
98
|
+
* A subclass that redeclares wins, which is why the nearest constructor is consulted first: an
|
|
99
|
+
* override that turns a query into a command has to be able to say so.
|
|
100
|
+
*/
|
|
101
|
+
export const declaredSemantics = (instance) => {
|
|
102
|
+
const declarations = new Map();
|
|
103
|
+
for (let ctor = instance.constructor; ctor; ctor = Object.getPrototypeOf(ctor)) {
|
|
104
|
+
for (const [method, declared] of semantics.get(ctor) ?? [])
|
|
105
|
+
if (!declarations.has(method))
|
|
106
|
+
declarations.set(method, declared);
|
|
107
|
+
}
|
|
108
|
+
return declarations;
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=Expose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Expose.js","sourceRoot":"","sources":["../../src/RPC/Expose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { RpcErrorCode, RpcRemoteError } from './Messages.js';
|
|
2
|
+
/**
|
|
3
|
+
* Making a command run once when the network is willing to deliver it twice.
|
|
4
|
+
*
|
|
5
|
+
* MQTT at QoS 1 is at-least-once, and the RPC server already suppresses a redelivery from its own
|
|
6
|
+
* memory. That covers the ordinary case and none of the interesting one:
|
|
7
|
+
*
|
|
8
|
+
* 1. the server invokes a command;
|
|
9
|
+
* 2. the command changes something physical, or something on disk;
|
|
10
|
+
* 3. the server dies before it can publish the answer or remember it;
|
|
11
|
+
* 4. the broker redelivers the request to the restarted server, or to a replica;
|
|
12
|
+
* 5. it runs a second time.
|
|
13
|
+
*
|
|
14
|
+
* Nothing held in one process's memory can close that, because the memory is what died. A store
|
|
15
|
+
* that outlives the process can, and this is the seam for one. The library deliberately ships no
|
|
16
|
+
* database: what it owes you is the semantics and a clean place to put Redis, Postgres or a file.
|
|
17
|
+
*
|
|
18
|
+
* **Without a store, delivery and execution are at least once.** That is the honest statement, and
|
|
19
|
+
* it is true of every RPC system that does not have one - the difference is whether it is written
|
|
20
|
+
* down.
|
|
21
|
+
*/
|
|
22
|
+
/** Everything a store needs to recognise one command and to record what it did. */
|
|
23
|
+
export interface RpcInvocation {
|
|
24
|
+
/**
|
|
25
|
+
* Names the command rather than the attempt: two attempts at one command share it. Defaults to
|
|
26
|
+
* the request id, which makes a redelivered packet the same command, and a caller pressing the
|
|
27
|
+
* button again a different one - unless the caller says otherwise.
|
|
28
|
+
*/
|
|
29
|
+
idempotencyKey: string;
|
|
30
|
+
/** The attempt, which is unique per request even when the key is not. */
|
|
31
|
+
requestId: string;
|
|
32
|
+
/** `namespace.method`, so one store can serve a whole server without keys colliding. */
|
|
33
|
+
scope: string;
|
|
34
|
+
/** Peer that sent the request. */
|
|
35
|
+
source: string;
|
|
36
|
+
/** Milliseconds the caller said it would still wait, when it said. */
|
|
37
|
+
ttl?: number;
|
|
38
|
+
}
|
|
39
|
+
/** What a completed command answered, in a form that survives being written down and read back. */
|
|
40
|
+
export type StoredRpcOutcome = {
|
|
41
|
+
result: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
code: RpcErrorCode;
|
|
44
|
+
error?: RpcRemoteError;
|
|
45
|
+
};
|
|
46
|
+
export declare const isFailedOutcome: (outcome: StoredRpcOutcome) => outcome is {
|
|
47
|
+
code: RpcErrorCode;
|
|
48
|
+
error?: RpcRemoteError;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Claim the right to run a command, and record what it did.
|
|
52
|
+
*
|
|
53
|
+
* `begin` returns:
|
|
54
|
+
* - `'acquired'` — nobody else has this one; run it, then call `complete`.
|
|
55
|
+
* - `'in-progress'` — somebody else holds it. The caller is waiting on whoever holds it, so this
|
|
56
|
+
* request is dropped rather than answered: two answers to one request would be worse than one.
|
|
57
|
+
* - a `StoredRpcOutcome` — it already ran, and this is what it answered. Sent back without running
|
|
58
|
+
* anything, which is the entire point.
|
|
59
|
+
*
|
|
60
|
+
* A store that throws stops the call with `UnknownOutcome` rather than running it. Failing open
|
|
61
|
+
* would turn an unreachable store into exactly the double execution it was installed to prevent,
|
|
62
|
+
* and a command refused because its guard is down is a problem an operator can see.
|
|
63
|
+
*/
|
|
64
|
+
export interface RpcIdempotencyStore {
|
|
65
|
+
begin(invocation: RpcInvocation): Promise<'acquired' | 'in-progress' | StoredRpcOutcome>;
|
|
66
|
+
complete(invocation: RpcInvocation, outcome: StoredRpcOutcome): Promise<void>;
|
|
67
|
+
/** Release a claim that never completed, so a crashed attempt does not lock the key forever. */
|
|
68
|
+
abandon?(invocation: RpcInvocation): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A store in memory, for tests and for a single process that will not be restarted mid-command.
|
|
72
|
+
*
|
|
73
|
+
* Included so the wiring can be exercised and so the shape is obvious, **not** as an answer to the
|
|
74
|
+
* problem above: it dies with the process, which is precisely when the durable one would earn its
|
|
75
|
+
* keep. Reach for it in a test; reach for something on disk in a plant.
|
|
76
|
+
*/
|
|
77
|
+
export declare class MemoryIdempotencyStore implements RpcIdempotencyStore {
|
|
78
|
+
/** Bound on remembered outcomes, oldest evicted first, since the keys come off the wire. */
|
|
79
|
+
maxRemembered: number;
|
|
80
|
+
private outcomes;
|
|
81
|
+
private running;
|
|
82
|
+
constructor(
|
|
83
|
+
/** Bound on remembered outcomes, oldest evicted first, since the keys come off the wire. */
|
|
84
|
+
maxRemembered?: number);
|
|
85
|
+
private id;
|
|
86
|
+
begin(invocation: RpcInvocation): Promise<"acquired" | "in-progress" | StoredRpcOutcome>;
|
|
87
|
+
complete(invocation: RpcInvocation, outcome: StoredRpcOutcome): Promise<void>;
|
|
88
|
+
abandon(invocation: RpcInvocation): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=Idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Idempotency.d.ts","sourceRoot":"","sources":["../../src/RPC/Idempotency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEjE;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC1B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAA;IACtB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAA;IACjB,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAED,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,CAAC,EAAE,cAAc,CAAA;CAAE,CAAA;AAEnG,eAAO,MAAM,eAAe,YAAa,gBAAgB,KAAG,OAAO,IAAI;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,CAAC,EAAE,cAAc,CAAA;CAAuB,CAAA;AAE1I;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,GAAG,aAAa,GAAG,gBAAgB,CAAC,CAAA;IACxF,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,gGAAgG;IAChG,OAAO,CAAC,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,YAAW,mBAAmB;IAK1D,4FAA4F;IACrF,aAAa;IALxB,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,OAAO,CAAoB;IAEnC;IACI,4FAA4F;IACrF,aAAa,SAAO,EAC3B;IAEJ,OAAO,CAAC,EAAE;IAIJ,KAAK,CAAC,UAAU,EAAE,aAAa,0DAOpC;IAEK,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,iBASlE;IAEK,OAAO,CAAC,UAAU,EAAE,aAAa,iBAEtC;CACJ"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const isFailedOutcome = (outcome) => 'code' in outcome;
|
|
2
|
+
/**
|
|
3
|
+
* A store in memory, for tests and for a single process that will not be restarted mid-command.
|
|
4
|
+
*
|
|
5
|
+
* Included so the wiring can be exercised and so the shape is obvious, **not** as an answer to the
|
|
6
|
+
* problem above: it dies with the process, which is precisely when the durable one would earn its
|
|
7
|
+
* keep. Reach for it in a test; reach for something on disk in a plant.
|
|
8
|
+
*/
|
|
9
|
+
export class MemoryIdempotencyStore {
|
|
10
|
+
maxRemembered;
|
|
11
|
+
outcomes = new Map();
|
|
12
|
+
running = new Set();
|
|
13
|
+
constructor(
|
|
14
|
+
/** Bound on remembered outcomes, oldest evicted first, since the keys come off the wire. */
|
|
15
|
+
maxRemembered = 1000) {
|
|
16
|
+
this.maxRemembered = maxRemembered;
|
|
17
|
+
}
|
|
18
|
+
id(invocation) {
|
|
19
|
+
return `${invocation.scope}\u0000${invocation.idempotencyKey}`;
|
|
20
|
+
}
|
|
21
|
+
async begin(invocation) {
|
|
22
|
+
const id = this.id(invocation);
|
|
23
|
+
const done = this.outcomes.get(id);
|
|
24
|
+
if (done)
|
|
25
|
+
return done;
|
|
26
|
+
if (this.running.has(id))
|
|
27
|
+
return 'in-progress';
|
|
28
|
+
this.running.add(id);
|
|
29
|
+
return 'acquired';
|
|
30
|
+
}
|
|
31
|
+
async complete(invocation, outcome) {
|
|
32
|
+
const id = this.id(invocation);
|
|
33
|
+
this.running.delete(id);
|
|
34
|
+
this.outcomes.set(id, outcome);
|
|
35
|
+
while (this.outcomes.size > this.maxRemembered) {
|
|
36
|
+
const oldest = this.outcomes.keys().next();
|
|
37
|
+
if (oldest.done)
|
|
38
|
+
break;
|
|
39
|
+
this.outcomes.delete(oldest.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async abandon(invocation) {
|
|
43
|
+
this.running.delete(this.id(invocation));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=Idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Idempotency.js","sourceRoot":"","sources":["../../src/RPC/Idempotency.ts"],"names":[],"mappings":"AA4CA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAyB,EAA6D,EAAE,CAAC,MAAM,IAAI,OAAO,CAAA;AAuB1I;;;;;;GAMG;AACH,MAAM,OAAO,sBAAsB;IAMpB,aAAa;IALhB,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAA;IAC9C,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IAEnC;IACI,4FAA4F;IACrF,aAAa,GAAG,IAAI;6BAApB,aAAa;IACrB,CAAC;IAEI,EAAE,CAAC,UAAyB;QAChC,OAAO,GAAG,UAAU,CAAC,KAAK,SAAS,UAAU,CAAC,cAAc,EAAE,CAAA;IAClE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,UAAyB;QACjC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QACrB,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,aAAsB,CAAA;QACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACpB,OAAO,UAAmB,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,UAAyB,EAAE,OAAyB;QAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;QAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACvB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;YAC1C,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAK;YACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACtC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAyB;QACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAC5C,CAAC;CACJ"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { RpcServerHandler } from './RpcServerHandler.js';
|
|
2
|
+
import type { NamespaceSchema, RpcSchema, TypeNode } from './Schema.js';
|
|
3
|
+
import type { RpcMethodSemantics } from './Messages.js';
|
|
4
|
+
/**
|
|
5
|
+
* What a server can say about itself.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately msgrpc's own shape rather than a borrowed one. OpenAPI is HTTP-shaped and has no
|
|
8
|
+
* good way to describe a server pushing events; AsyncAPI models everything as a channel, which
|
|
9
|
+
* fights an RPC surface. Both would mean describing this system in someone else's concepts to
|
|
10
|
+
* satisfy a viewer we would then still want to replace.
|
|
11
|
+
*/
|
|
12
|
+
export interface DescribedMethod {
|
|
13
|
+
name: string;
|
|
14
|
+
/** Present when a schema describes this method; absent when nothing does. */
|
|
15
|
+
params?: TypeNode[];
|
|
16
|
+
/** Positionally matching `params`, when the schema carries them. */
|
|
17
|
+
paramNames?: string[];
|
|
18
|
+
rest?: TypeNode;
|
|
19
|
+
returns?: TypeNode;
|
|
20
|
+
/**
|
|
21
|
+
* What calling it does to the world, when the method says: `query`, `idempotent-command` or
|
|
22
|
+
* `non-repeatable-command`. Absent means it does not say, which a person reading a list of
|
|
23
|
+
* methods should treat as "ask before pressing this".
|
|
24
|
+
*/
|
|
25
|
+
semantics?: RpcMethodSemantics;
|
|
26
|
+
}
|
|
27
|
+
export interface DescribedEvent {
|
|
28
|
+
name: string;
|
|
29
|
+
params?: TypeNode[];
|
|
30
|
+
/** How many peers currently hold a subscription to it. */
|
|
31
|
+
subscribers: number;
|
|
32
|
+
}
|
|
33
|
+
export interface DescribedNamespace {
|
|
34
|
+
name: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
/** Class the exposed instance came from, which is usually what a person is looking for. */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** True when the instance was created at runtime through createRpcInstance. */
|
|
39
|
+
created: boolean;
|
|
40
|
+
/** True when the instance can emit events at all. */
|
|
41
|
+
emitter: boolean;
|
|
42
|
+
/** True when calls into this instance run one at a time rather than side by side. */
|
|
43
|
+
serialised?: boolean;
|
|
44
|
+
methods: DescribedMethod[];
|
|
45
|
+
events: DescribedEvent[];
|
|
46
|
+
}
|
|
47
|
+
export interface ServerDescription {
|
|
48
|
+
/** Name this server is addressed by. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Contract version of the schema as a whole, when one is loaded. */
|
|
51
|
+
version?: string;
|
|
52
|
+
/** True when arguments are being checked, which tells a caller how much to trust the types. */
|
|
53
|
+
validating: boolean;
|
|
54
|
+
namespaces: DescribedNamespace[];
|
|
55
|
+
/** Named types the described methods refer to. */
|
|
56
|
+
types?: {
|
|
57
|
+
[name: string]: TypeNode;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** What this namespace offers, ready to merge into whatever schema a server was given. */
|
|
61
|
+
export declare const introspectionSchema: {
|
|
62
|
+
namespace: NamespaceSchema;
|
|
63
|
+
types: {
|
|
64
|
+
[name: string]: TypeNode;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Adds the `msgrpc` namespace to a server's schema, so describe() is described like anything else -
|
|
69
|
+
* and so `validation: 'required'` does not refuse the one call a peer makes to find out what is
|
|
70
|
+
* here, which it did before this existed.
|
|
71
|
+
*
|
|
72
|
+
* A server given no schema still gets this one. That does not turn checking on: validation defaults
|
|
73
|
+
* from the schema the *caller* passed, so an undescribed server stays undescribed and only reports
|
|
74
|
+
* its own introspection honestly.
|
|
75
|
+
*
|
|
76
|
+
* A user schema already defining `msgrpc` wins untouched. It is the contract that server actually
|
|
77
|
+
* serves, and overwriting it would describe the server as something it is not.
|
|
78
|
+
*/
|
|
79
|
+
export declare const withIntrospection: (schema: RpcSchema | undefined) => RpcSchema;
|
|
80
|
+
/**
|
|
81
|
+
* Exposed under the namespace `msgrpc` when RpcServer is constructed with exposeIntrospection.
|
|
82
|
+
*
|
|
83
|
+
* Off by default, and it goes through the ordinary dispatch path, so authorize() sees it as a call
|
|
84
|
+
* on `msgrpc.describe` and can restrict it. Listing every class, method and live instance is
|
|
85
|
+
* reconnaissance, and on a plant network instance names tend to encode plant structure.
|
|
86
|
+
*/
|
|
87
|
+
export declare class Introspection {
|
|
88
|
+
private handler;
|
|
89
|
+
constructor(handler: RpcServerHandler);
|
|
90
|
+
describe(): Promise<ServerDescription>;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=Introspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Introspection.d.ts","sourceRoot":"","sources":["../../src/RPC/Introspection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAgB,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAKvD;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAA;IACnB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,OAAO,CAAC,EAAE,QAAQ,CAAA;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAA;CACjC;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAA;IACnB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAA;IAChB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAA;IAChB,qFAAqF;IACrF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,MAAM,EAAE,cAAc,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAC9B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+FAA+F;IAC/F,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,kBAAkB,EAAE,CAAA;IAChC,kDAAkD;IAClD,KAAK,CAAC,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;KAAE,CAAA;CACvC;AAsCD,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,EAAE;IAAE,SAAS,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;KAAE,CAAA;CASlG,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,WAAY,SAAS,GAAG,SAAS,KAAG,SAQjE,CAAA;AAED;;;;;;GAMG;AACH,qBACa,aAAa;IACV,OAAO,CAAC,OAAO;IAA3B,YAAoB,OAAO,EAAE,gBAAgB,EAAI;IAG3C,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAmD3C;CACJ"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import EventEmitter from 'events';
|
|
36
|
+
import { rpc, rpcNamespace } from './Expose.js';
|
|
37
|
+
// Extracted from this file by `npm run contract` in the CLI package and committed, so building
|
|
38
|
+
// msgrpc never needs the extractor that reads it. A test there asserts it still matches this source.
|
|
39
|
+
import extracted from './Introspection.types.json' with { type: 'json' };
|
|
40
|
+
/**
|
|
41
|
+
* The schema format has one type map shared by every namespace, so a library adding types to a
|
|
42
|
+
* user's schema has to stay out of their names - a plant defining its own `TypeNode` would
|
|
43
|
+
* otherwise find describe() described against it. Everything here moves under a prefix that no
|
|
44
|
+
* extracted type can collide with, since `.` is not part of an identifier.
|
|
45
|
+
*/
|
|
46
|
+
const PREFIX = 'msgrpc.';
|
|
47
|
+
const prefixRefs = (node) => {
|
|
48
|
+
switch (node.kind) {
|
|
49
|
+
case 'ref':
|
|
50
|
+
return { ...node, name: PREFIX + node.name };
|
|
51
|
+
case 'array':
|
|
52
|
+
return { ...node, items: prefixRefs(node.items) };
|
|
53
|
+
case 'record':
|
|
54
|
+
return { ...node, values: prefixRefs(node.values) };
|
|
55
|
+
case 'tuple':
|
|
56
|
+
return { ...node, items: node.items.map(prefixRefs) };
|
|
57
|
+
case 'union':
|
|
58
|
+
return { ...node, options: node.options.map(prefixRefs) };
|
|
59
|
+
case 'object':
|
|
60
|
+
return { ...node, fields: Object.fromEntries(Object.entries(node.fields).map(([name, field]) => [name, { ...field, type: prefixRefs(field.type) }])) };
|
|
61
|
+
default:
|
|
62
|
+
return node;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const prefixMethod = (method) => ({
|
|
66
|
+
...method,
|
|
67
|
+
params: method.params.map(prefixRefs),
|
|
68
|
+
...(method.rest ? { rest: prefixRefs(method.rest) } : {}),
|
|
69
|
+
...(method.returns ? { returns: prefixRefs(method.returns) } : {})
|
|
70
|
+
});
|
|
71
|
+
const source = extracted;
|
|
72
|
+
/** What this namespace offers, ready to merge into whatever schema a server was given. */
|
|
73
|
+
export const introspectionSchema = {
|
|
74
|
+
namespace: {
|
|
75
|
+
...source.namespaces.msgrpc,
|
|
76
|
+
methods: Object.fromEntries(Object.entries(source.namespaces.msgrpc.methods).map(([name, method]) => [name, prefixMethod(method)])),
|
|
77
|
+
...(source.namespaces.msgrpc.events
|
|
78
|
+
? { events: Object.fromEntries(Object.entries(source.namespaces.msgrpc.events).map(([name, event]) => [name, { params: event.params.map(prefixRefs) }])) }
|
|
79
|
+
: {})
|
|
80
|
+
},
|
|
81
|
+
types: Object.fromEntries(Object.entries(source.types ?? {}).map(([name, type]) => [PREFIX + name, prefixRefs(type)]))
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Adds the `msgrpc` namespace to a server's schema, so describe() is described like anything else -
|
|
85
|
+
* and so `validation: 'required'` does not refuse the one call a peer makes to find out what is
|
|
86
|
+
* here, which it did before this existed.
|
|
87
|
+
*
|
|
88
|
+
* A server given no schema still gets this one. That does not turn checking on: validation defaults
|
|
89
|
+
* from the schema the *caller* passed, so an undescribed server stays undescribed and only reports
|
|
90
|
+
* its own introspection honestly.
|
|
91
|
+
*
|
|
92
|
+
* A user schema already defining `msgrpc` wins untouched. It is the contract that server actually
|
|
93
|
+
* serves, and overwriting it would describe the server as something it is not.
|
|
94
|
+
*/
|
|
95
|
+
export const withIntrospection = (schema) => {
|
|
96
|
+
if (schema?.namespaces.msgrpc)
|
|
97
|
+
return schema;
|
|
98
|
+
return {
|
|
99
|
+
schema: 1,
|
|
100
|
+
...schema,
|
|
101
|
+
types: { ...schema?.types, ...introspectionSchema.types },
|
|
102
|
+
namespaces: { ...schema?.namespaces, msgrpc: introspectionSchema.namespace }
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Exposed under the namespace `msgrpc` when RpcServer is constructed with exposeIntrospection.
|
|
107
|
+
*
|
|
108
|
+
* Off by default, and it goes through the ordinary dispatch path, so authorize() sees it as a call
|
|
109
|
+
* on `msgrpc.describe` and can restrict it. Listing every class, method and live instance is
|
|
110
|
+
* reconnaissance, and on a plant network instance names tend to encode plant structure.
|
|
111
|
+
*/
|
|
112
|
+
let Introspection = (() => {
|
|
113
|
+
let _classDecorators = [rpcNamespace('msgrpc')];
|
|
114
|
+
let _classDescriptor;
|
|
115
|
+
let _classExtraInitializers = [];
|
|
116
|
+
let _classThis;
|
|
117
|
+
let _instanceExtraInitializers = [];
|
|
118
|
+
let _describe_decorators;
|
|
119
|
+
var Introspection = class {
|
|
120
|
+
static { _classThis = this; }
|
|
121
|
+
static {
|
|
122
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
123
|
+
_describe_decorators = [rpc];
|
|
124
|
+
__esDecorate(this, null, _describe_decorators, { kind: "method", name: "describe", static: false, private: false, access: { has: obj => "describe" in obj, get: obj => obj.describe }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
125
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
126
|
+
Introspection = _classThis = _classDescriptor.value;
|
|
127
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
128
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
129
|
+
}
|
|
130
|
+
handler = __runInitializers(this, _instanceExtraInitializers);
|
|
131
|
+
constructor(handler) {
|
|
132
|
+
this.handler = handler;
|
|
133
|
+
}
|
|
134
|
+
async describe() {
|
|
135
|
+
const manage = this.handler.manageRpc;
|
|
136
|
+
const schema = this.handler.schema;
|
|
137
|
+
const created = new Set(manage.createdInstances.keys());
|
|
138
|
+
const namespaces = Object.entries(manage.exposedNameSpaceInstances).map(([name, instance]) => {
|
|
139
|
+
const described = schema?.namespaces[name];
|
|
140
|
+
const methodNames = [...(manage.findNameSpaceMethodMap(name)?.keys() ?? [])].sort();
|
|
141
|
+
const methods = methodNames.map((method) => {
|
|
142
|
+
const signature = described?.methods[method];
|
|
143
|
+
const semantics = this.handler.semanticsOf({ path: name, method });
|
|
144
|
+
return {
|
|
145
|
+
name: method,
|
|
146
|
+
...(signature ? { params: signature.params, paramNames: signature.paramNames, rest: signature.rest, returns: signature.returns } : {}),
|
|
147
|
+
...(semantics ? { semantics } : {})
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
// Declared events plus any a peer is currently subscribed to, since a server without a
|
|
151
|
+
// schema still knows what has been subscribed.
|
|
152
|
+
const eventNames = new Set(Object.keys(described?.events ?? {}));
|
|
153
|
+
for (const proxy of this.handler.eventProxies.values())
|
|
154
|
+
if (proxy.instanceName === name)
|
|
155
|
+
eventNames.add(proxy.event);
|
|
156
|
+
const events = [...eventNames].sort().map((event) => ({
|
|
157
|
+
name: event,
|
|
158
|
+
...(described?.events?.[event] ? { params: described.events[event].params } : {}),
|
|
159
|
+
subscribers: [...this.handler.eventProxies.values()].filter((proxy) => proxy.instanceName === name && proxy.event === event).length
|
|
160
|
+
}));
|
|
161
|
+
const execution = manage.exposedExecution[name];
|
|
162
|
+
return {
|
|
163
|
+
name,
|
|
164
|
+
...(described?.version ? { version: described.version } : {}),
|
|
165
|
+
// Worth reporting because it changes what a caller should expect: on a serialised
|
|
166
|
+
// instance a slow method delays every other call into it, and that is a property of
|
|
167
|
+
// the server rather than of the network.
|
|
168
|
+
...(execution && execution !== 'parallel' ? { serialised: true } : {}),
|
|
169
|
+
className: instance.constructor?.name,
|
|
170
|
+
created: created.has(name),
|
|
171
|
+
emitter: instance instanceof EventEmitter,
|
|
172
|
+
methods,
|
|
173
|
+
events
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
name: this.handler.name,
|
|
178
|
+
...(schema?.version ? { version: schema.version } : {}),
|
|
179
|
+
validating: !!schema && this.handler.validation !== 'off',
|
|
180
|
+
namespaces: namespaces.sort((a, b) => a.name.localeCompare(b.name)),
|
|
181
|
+
...(schema?.types ? { types: schema.types } : {})
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return Introspection = _classThis;
|
|
186
|
+
})();
|
|
187
|
+
export { Introspection };
|
|
188
|
+
//# sourceMappingURL=Introspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Introspection.js","sourceRoot":"","sources":["../../src/RPC/Introspection.ts"],"names":[],"mappings":"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"}
|