@hediet/linkrpc-cli 0.0.3-next.20261006.1 → 0.0.3-next.20261006.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 +51 -1
- package/dist/chunks/{cli-yDtBqjCT.js → cli-JE4URhh3.js} +36 -379
- package/dist/chunks/cli-JE4URhh3.js.map +1 -0
- package/dist/chunks/{runApprovalUi-eLDf_5ZN.js → runApprovalUi-BFI84eck.js} +2 -2
- package/dist/chunks/{runApprovalUi-eLDf_5ZN.js.map → runApprovalUi-BFI84eck.js.map} +1 -1
- package/dist/chunks/{runComplete-CM1sMWnI.js → runComplete-DvUneaDU.js} +806 -454
- package/dist/chunks/runComplete-DvUneaDU.js.map +1 -0
- package/dist/chunks/{runUi-DF_HTMk9.js → runUi-BNCT8zwR.js} +3 -3
- package/dist/chunks/{runUi-DF_HTMk9.js.map → runUi-BNCT8zwR.js.map} +1 -1
- package/dist/hub.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/linkrpc.js +1 -1
- package/dist/rpc.js +1 -1
- package/package.json +5 -5
- package/dist/chunks/cli-yDtBqjCT.js.map +0 -1
- package/dist/chunks/runComplete-CM1sMWnI.js.map +0 -1
package/README.md
CHANGED
|
@@ -267,6 +267,17 @@ Every endpoint is a valid `new URL()` — safe in env vars and logs:
|
|
|
267
267
|
| `cmd:?command=…` / `cmd:?argv=…&argv=…` | spawn-as-server (`--endpoint-cmd`) |
|
|
268
268
|
| `cmd-stdio:?command=…` / `?argv=…&argv=…` | spawn over stdio (`--endpoint-cmd-stdio`) |
|
|
269
269
|
|
|
270
|
+
`cmd:` children can use `startEndpoint({ onConnection })` without parsing any
|
|
271
|
+
transport flags: the parent injects `LINKRPC_ENDPOINT` and `LINKRPC_TOKEN`.
|
|
272
|
+
The CLI accepts one authenticated callback connection and exposes the child's
|
|
273
|
+
interfaces directly. It does not wait for a hub service-namespace claim.
|
|
274
|
+
The existing root overlay still supplies namespace (`local`) and optional
|
|
275
|
+
provisioned-identity services for hub-aware children. Full routing-hub startup
|
|
276
|
+
remains a separate `connectViaLocalHub` helper.
|
|
277
|
+
Child stdout/stderr are diagnostics and appear on the CLI's stderr; parent stdout
|
|
278
|
+
remains reserved for command results. Startup fails if the child exits, cannot
|
|
279
|
+
spawn, or does not connect within 10 seconds.
|
|
280
|
+
|
|
270
281
|
The command payload is either a single verbatim string (`?command=node%20server.js`,
|
|
271
282
|
split by the OS shell) or repeated, structure-preserving `argv` params
|
|
272
283
|
(`?argv=node&argv=server.js`).
|
|
@@ -411,7 +422,7 @@ initial document:
|
|
|
411
422
|
| `call <method> [--param k=v]... [--params <json>\|-]` | Invoke a request. Method is form 1/2/3 (`send`, `iface::send`, `svc::iface::send`). Validates params against the live schema by default; pass `--no-validate` to skip. Use `@hash` on the method ref (e.g. `iface::send@a3f2b1`) to look up a pinned schema version for local validation. Reads stdin for `--params -`. |
|
|
412
423
|
| `notify <method> ...` | Fire-and-forget notification. Same flags as `call`. |
|
|
413
424
|
| `batch --call <method> ... [--notify <method> ...]` | Run multiple calls and notifications sequentially on one connection. `--params`, repeatable `--param`, and `--no-validate` apply to the preceding operation. Fails fast unless `--continue-on-error` is set. Results are one JSON array; stream chunks are labeled on stderr. |
|
|
414
|
-
| `connection create
|
|
425
|
+
| `connection create --timeout <duration\|inf> [--ttl <duration\|inf>] [--schema source]` | Open the configured remote once in a detached broker and print its authenticated local `unix:` / `npipe:` endpoint. Explicitly supply at least one of `--timeout` (transport inactivity) or `--ttl` (absolute lifetime). Omitted limits are `inf`. `--schema` overlays static LinkRPC reflection from a local JSON file or an HTTP(S) URL. |
|
|
415
426
|
| `connection status` | Read lifecycle and buffer status from the broker configured by the effective endpoint. |
|
|
416
427
|
| `connection notifications [--after n] [--wait 25s] [--follow]` | Read incoming remote notifications from the broker's bounded buffer. `--follow` emits one JSON object per line and can run alongside calls. |
|
|
417
428
|
| `connection destroy` | Stop the configured connection broker through its local overlay. |
|
|
@@ -490,6 +501,45 @@ hub batch \
|
|
|
490
501
|
|
|
491
502
|
### Persistent connections
|
|
492
503
|
|
|
504
|
+
Every `connection create` (including the legacy `connect` alias and context-owned
|
|
505
|
+
connections) requires an explicit `--timeout` or `--ttl`. Stored context limits
|
|
506
|
+
do not satisfy this requirement or supply omitted limits: each omitted limit is
|
|
507
|
+
`inf`. Both flags accept durations such as `250ms`, `30s`, `5min`, or `2h`, or the
|
|
508
|
+
literal `inf`. Finite limits must fit a Node timer (1 through 2147483647 ms).
|
|
509
|
+
Unlimited limits schedule no timer; status reports their `timeoutMs`/`ttlMs` as
|
|
510
|
+
the JSON string `"inf"`, while finite values remain millisecond numbers.
|
|
511
|
+
Broker startup has a separate 60-second readiness deadline; `inf` does not
|
|
512
|
+
disable startup error detection.
|
|
513
|
+
Command-environment endpoints also retain their existing 10-second deadline for
|
|
514
|
+
the spawned application to connect back. This is distinct from the transport
|
|
515
|
+
initialize handshake and the broker's lifetime limits. A slow application must
|
|
516
|
+
establish its endpoint before that deadline. Startup failures report the broker's
|
|
517
|
+
underlying error, including the last 16384 characters of command-environment child
|
|
518
|
+
stdout/stderr, rather than only the broker process's exit code.
|
|
519
|
+
|
|
520
|
+
Use a folder context to own the broker without environment overrides:
|
|
521
|
+
|
|
522
|
+
```sh
|
|
523
|
+
rpc --endpoint-cmd "node ./server.js" connection create --timeout inf --new-context .
|
|
524
|
+
rpc call example::ping --no-validate
|
|
525
|
+
rpc context remove --context .
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Only `connection create --new-context <selector>` assigns ownership. Removing
|
|
529
|
+
that context, replacing its endpoint/token/config, or unsetting the owned endpoint
|
|
530
|
+
or token stops the owned broker before saving the mutation. This also applies to
|
|
531
|
+
persisted `--context-set` overrides. Unrelated edits and temporary overrides do
|
|
532
|
+
not stop it. Saving an existing endpoint or deriving another context with
|
|
533
|
+
`--new-context` does not transfer or duplicate ownership.
|
|
534
|
+
|
|
535
|
+
Already-stopped local brokers permit cleanup. Authentication, RPC, permission,
|
|
536
|
+
and other shutdown errors fail explicitly and preserve the context for retry.
|
|
537
|
+
The shutdown RPC has a five-second response deadline.
|
|
538
|
+
If saving fails after shutdown, the old context remains available for cleanup.
|
|
539
|
+
Contexts created by older versions have no ownership metadata and are not
|
|
540
|
+
automatically stopped. An unlimited broker without an owning context must be
|
|
541
|
+
stopped explicitly with `connection destroy`.
|
|
542
|
+
|
|
493
543
|
`connection create` keeps one remote transport open in a detached process. It prints the
|
|
494
544
|
actual local socket endpoint; store that endpoint in `LINKRPC_ENDPOINT` for later
|
|
495
545
|
commands:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as fetchSchema$1, B as resolveTargetEndpoint, D as requestTopologyAccess, E as requestReflectionAccess, F as formatPrincipalSource, G as validateValueAgainstSchema, H as describeObjectParams, I as parsePrincipalSpec, L as connect, M as walkHub, O as setupSigning, P as logoutCliIdentity, R as connectViaRootOverlay, U as describeSchema, V as resolvedEndpointToConfig, W as explainValidation, X as mcpForwardInterface, Y as MethodRefWithOptHash, _ as parseConnectionLimit, a as resolveInvocationContext, c as ContextStore, d as getBrokerStatus, f as readBrokerNotifications, g as withStaticHubReflection, h as tunnelCommand, i as mutationReference, j as findMethodInSchema, k as fetchDefaults, l as formatContextReference, m as ConnectionBroker, n as loadStaticHubSchema, o as resolveInvocationEndpoint, p as stopConnectionBroker, q as mergeParams, r as resolveStaticHubSchemaSource, s as validationDefault, t as completeForLine, u as disconnectBroker, v as parseConnectionLimitMilliseconds, x as resolveCliInvocation, y as parseDuration, z as connectViaTransport } from "./runComplete-DvUneaDU.js";
|
|
2
2
|
import { a as derived, i as autorun, r as observableValue } from "./dist-BOcSU4-O.js";
|
|
3
3
|
import { ErrorCode, INTERFACE_TEMPLATES_EXTENSION, LinkRpcConnection, RpcError, computeInterfaceHash, connectTransports, createSchemaToZod, exportStaticHubSchema, generateTsContract, generateTsInterface, interfaceTemplateArgumentsEquivalent, interfaceTemplateMemberName, isAssignable, jcsCanonicalize } from "@hediet/linkrpc";
|
|
4
4
|
import { WebSocketTransport, connectNdjson, formatEndpointUri, isHubEndpoint, openWebSocket, parseEndpointUri, runInitializeHandshake, traceMessageTransport as traceMessageTransport$1 } from "@hediet/linkrpc/node";
|
|
@@ -11,7 +11,7 @@ import { spawnCommand } from "@hediet/linkrpc-hub/spawn";
|
|
|
11
11
|
import { tapTransport } from "@hediet/linkrpc-hub/hub/server/transit";
|
|
12
12
|
import * as net from "node:net";
|
|
13
13
|
import { mkdir, open, readFile, writeFile } from "node:fs/promises";
|
|
14
|
-
import { DEFAULT_WALK_DEPTH, HubDirectoryExplorer, fetchSchema, hubGrantedServiceIdInterface
|
|
14
|
+
import { DEFAULT_WALK_DEPTH, HubDirectoryExplorer, fetchSchema, hubGrantedServiceIdInterface } from "@hediet/linkrpc/hub/common";
|
|
15
15
|
import { Command, InvalidArgumentError } from "commander";
|
|
16
16
|
import { bootstrapApprovalCapability, createHubApprovalClient } from "@hediet/linkrpc-infra/approval";
|
|
17
17
|
import { connectionTokenBinderInterface } from "@hediet/linkrpc-hub/hub/server/connection-token-binder";
|
|
@@ -717,340 +717,6 @@ function getErrorMessage(error) {
|
|
|
717
717
|
return error instanceof Error ? error.message : String(error);
|
|
718
718
|
}
|
|
719
719
|
//#endregion
|
|
720
|
-
//#region src/commands/tunnel.ts
|
|
721
|
-
/**
|
|
722
|
-
* The wire method for the provenance claim front door. Mirrors
|
|
723
|
-
* `Hub.registerServiceIdNamespace`, which calls
|
|
724
|
-
* `connection.get(hubGrantedServiceIdInterface).register({ serviceId })`
|
|
725
|
-
* with no serviceId prefix — i.e. interface-form
|
|
726
|
-
* `hubGrantedServiceId::register`. The claimed serviceId must be
|
|
727
|
-
* within the connection's granted namespace.
|
|
728
|
-
*/
|
|
729
|
-
const CLAIM_METHOD$1 = `${hubGrantedServiceIdInterface.info.id}::register`;
|
|
730
|
-
const GET_CLAIM_NAMESPACE_METHOD = `${hubGrantedServiceIdInterface.info.id}::get`;
|
|
731
|
-
const GET_HUB_SERVICE_ID_METHOD = `${hubGrantedServiceIdInterface.info.id}::getHubServiceId`;
|
|
732
|
-
/**
|
|
733
|
-
* Run a transparent service tunnel until stopped.
|
|
734
|
-
*
|
|
735
|
-
* Claims `serviceId` on {@link TunnelOptions.local} via the hub claim
|
|
736
|
-
* interface, then forwards every inbound request/notification verbatim onto
|
|
737
|
-
* {@link TunnelOptions.remote} and pipes the response back. Forwarding is
|
|
738
|
-
* signature-preserving: the original caller's `$hubrpc` envelope rides inside
|
|
739
|
-
* `params`, so the remote send is issued unsigned (`signerOverride: null`) to
|
|
740
|
-
* avoid re-wrapping it. The streaming sub-protocol is bridged both ways, so
|
|
741
|
-
* long-running / streaming calls forward their in-flight stream messages and
|
|
742
|
-
* cancellation, not just the final response.
|
|
743
|
-
*
|
|
744
|
-
* When {@link TunnelOptions.bidi} is set, the mirror forwarder is also bound on
|
|
745
|
-
* {@link TunnelOptions.remote}, so every request/notification the endpoint
|
|
746
|
-
* emits is forwarded verbatim back to the hub.
|
|
747
|
-
*
|
|
748
|
-
* Resolves when {@link TunnelOptions.stop} resolves (or on `SIGINT`/`SIGTERM`).
|
|
749
|
-
*/
|
|
750
|
-
async function tunnelCommand(opts) {
|
|
751
|
-
const log = opts.log ?? ((line) => process.stderr.write(line + "\n"));
|
|
752
|
-
opts.local.setRequestHandler(createForwardingHandler(opts.remote));
|
|
753
|
-
if (opts.bidi) opts.remote.setRequestHandler(createForwardingHandler(opts.local));
|
|
754
|
-
await _claimServiceId(opts.local, opts.serviceId);
|
|
755
|
-
log(opts.bidi ? `tunnel: claimed '${opts.serviceId}' — forwarding requests to remote (bidirectional)` : `tunnel: claimed '${opts.serviceId}' — forwarding requests to remote`);
|
|
756
|
-
await (opts.stop ?? _untilSignalled$2());
|
|
757
|
-
log("tunnel: stopping");
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Build the inbound handler that forwards every request/notification received
|
|
761
|
-
* on one connection onto `to`, bridging the streaming sub-protocol both ways
|
|
762
|
-
* (see {@link _forwardRequest}). Used for the source→target direction and, in
|
|
763
|
-
* bidirectional mode, the mirror.
|
|
764
|
-
*/
|
|
765
|
-
function createForwardingHandler(to) {
|
|
766
|
-
return {
|
|
767
|
-
handleRequest: (call) => _forwardRequest(to, call),
|
|
768
|
-
handleNotification: (call) => _forwardNotification(to, call)
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
async function _claimServiceId(local, serviceId) {
|
|
772
|
-
const namespaceRes = await local.channel.sendRequest(GET_CLAIM_NAMESPACE_METHOD, {});
|
|
773
|
-
if (_isServiceIdUnder(serviceId, typeof namespaceRes === "object" && namespaceRes !== null && "grantedServiceIdNamespace" in namespaceRes && typeof namespaceRes.grantedServiceIdNamespace === "string" ? namespaceRes.grantedServiceIdNamespace : "")) {
|
|
774
|
-
await local.channel.sendRequest(CLAIM_METHOD$1, { serviceId });
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const hubIdRes = await local.channel.sendRequest(GET_HUB_SERVICE_ID_METHOD, {});
|
|
778
|
-
const hubServiceId = typeof hubIdRes === "object" && hubIdRes !== null && "hubServiceId" in hubIdRes && typeof hubIdRes.hubServiceId === "string" ? hubIdRes.hubServiceId : void 0;
|
|
779
|
-
if (!hubServiceId) throw new Error("tunnel: failed to resolve hub service id for out-of-namespace claim");
|
|
780
|
-
const registerMethod = `${hubServiceId}::${hubServiceIdRegistryInterface.info.id}::registerServiceId`;
|
|
781
|
-
await local.channel.sendRequest(registerMethod, { requestedPrefix: serviceId });
|
|
782
|
-
}
|
|
783
|
-
function _isServiceIdUnder(serviceId, namespace) {
|
|
784
|
-
if (namespace === "") return false;
|
|
785
|
-
return serviceId === namespace || serviceId.startsWith(`${namespace}/`);
|
|
786
|
-
}
|
|
787
|
-
/** Resolve on the next `SIGINT` / `SIGTERM`. */
|
|
788
|
-
function _untilSignalled$2() {
|
|
789
|
-
return new Promise((resolve) => {
|
|
790
|
-
const done = () => {
|
|
791
|
-
process.removeListener("SIGINT", done);
|
|
792
|
-
process.removeListener("SIGTERM", done);
|
|
793
|
-
resolve();
|
|
794
|
-
};
|
|
795
|
-
process.once("SIGINT", done);
|
|
796
|
-
process.once("SIGTERM", done);
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
async function _forwardRequest(remote, call) {
|
|
800
|
-
const rc = remote.channel.sendRequestWithStream(call.method, call.params, {
|
|
801
|
-
ctx: { signerOverride: null },
|
|
802
|
-
onStreamMessage: (payload) => {
|
|
803
|
-
call.stream.send(payload);
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
call.stream.onMessage((payload) => rc.send(payload));
|
|
807
|
-
const cancelRemote = () => rc.cancel(call.signal.reason?.message);
|
|
808
|
-
if (call.signal.aborted) cancelRemote();
|
|
809
|
-
else call.signal.addEventListener("abort", cancelRemote, { once: true });
|
|
810
|
-
try {
|
|
811
|
-
return { result: await rc.result };
|
|
812
|
-
} catch (err) {
|
|
813
|
-
if (err instanceof RpcError) return { error: {
|
|
814
|
-
code: err.code,
|
|
815
|
-
message: err.message,
|
|
816
|
-
data: err.data
|
|
817
|
-
} };
|
|
818
|
-
return { error: {
|
|
819
|
-
code: ErrorCode.internalError,
|
|
820
|
-
message: err.message
|
|
821
|
-
} };
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
function _forwardNotification(remote, call) {
|
|
825
|
-
remote.channel.sendNotification(call.method, call.params, { ctx: { signerOverride: null } }).catch(() => {});
|
|
826
|
-
}
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/commands/connectionBroker.ts
|
|
829
|
-
const BROKER_INTERFACE_ID = "hubrpc.connectionBroker";
|
|
830
|
-
const BROKER_STATUS_METHOD = `${BROKER_INTERFACE_ID}::status`;
|
|
831
|
-
const BROKER_READ_NOTIFICATIONS_METHOD = `${BROKER_INTERFACE_ID}::readNotifications`;
|
|
832
|
-
const BROKER_DISCONNECT_METHOD = `${BROKER_INTERFACE_ID}::disconnect`;
|
|
833
|
-
var ConnectionBroker = class {
|
|
834
|
-
_remote;
|
|
835
|
-
_options;
|
|
836
|
-
_now;
|
|
837
|
-
_notificationLimit;
|
|
838
|
-
_notifications = [];
|
|
839
|
-
_waiters = /* @__PURE__ */ new Set();
|
|
840
|
-
_remoteForwarder;
|
|
841
|
-
_staticReflection;
|
|
842
|
-
_startedAt;
|
|
843
|
-
_nextNotificationSequence = 1;
|
|
844
|
-
_locals = /* @__PURE__ */ new Set();
|
|
845
|
-
_timeoutTimer;
|
|
846
|
-
_ttlTimer;
|
|
847
|
-
_lastActivityAt;
|
|
848
|
-
_resolveStopped;
|
|
849
|
-
_disposed = false;
|
|
850
|
-
stopped = new Promise((resolve) => {
|
|
851
|
-
this._resolveStopped = resolve;
|
|
852
|
-
});
|
|
853
|
-
stoppedReason;
|
|
854
|
-
constructor(_remote, _options) {
|
|
855
|
-
this._remote = _remote;
|
|
856
|
-
this._options = _options;
|
|
857
|
-
this._now = _options.now ?? Date.now;
|
|
858
|
-
this._notificationLimit = _options.notificationLimit ?? 1e3;
|
|
859
|
-
this._startedAt = _options.startedAt;
|
|
860
|
-
this._lastActivityAt = _options.startedAt;
|
|
861
|
-
this._remoteForwarder = createForwardingHandler(_remote);
|
|
862
|
-
this._staticReflection = _options.staticHubSchema === void 0 ? void 0 : new StaticHubReflection(_options.staticHubSchema);
|
|
863
|
-
this._remote.setRequestHandler({
|
|
864
|
-
handleRequest: (call) => this._handleRemoteRequest(call),
|
|
865
|
-
handleNotification: (call) => this._bufferNotification(call)
|
|
866
|
-
});
|
|
867
|
-
this._resetTimeout();
|
|
868
|
-
this._ttlTimer = setTimeout(() => this._stop("ttl"), Math.max(0, this._options.ttlMs - (this._now() - this._startedAt)));
|
|
869
|
-
this._ttlTimer.unref?.();
|
|
870
|
-
}
|
|
871
|
-
attach(local) {
|
|
872
|
-
if (this._disposed) throw new Error("connection broker is stopped");
|
|
873
|
-
this._locals.add(local);
|
|
874
|
-
local.setRequestHandler({
|
|
875
|
-
handleRequest: (call) => this._handleLocalRequest(call),
|
|
876
|
-
handleNotification: (call) => this._handleLocalNotification(call)
|
|
877
|
-
});
|
|
878
|
-
this.recordActivity();
|
|
879
|
-
}
|
|
880
|
-
detach(local) {
|
|
881
|
-
if (!this._locals.delete(local)) return;
|
|
882
|
-
local.setRequestHandler(void 0);
|
|
883
|
-
}
|
|
884
|
-
recordActivity() {
|
|
885
|
-
if (this._disposed) return;
|
|
886
|
-
this._lastActivityAt = this._now();
|
|
887
|
-
this._resetTimeout();
|
|
888
|
-
}
|
|
889
|
-
dispose() {
|
|
890
|
-
this._stop("disposed");
|
|
891
|
-
}
|
|
892
|
-
async _handleLocalRequest(call) {
|
|
893
|
-
this.recordActivity();
|
|
894
|
-
switch (call.method) {
|
|
895
|
-
case BROKER_STATUS_METHOD: return { result: this._status() };
|
|
896
|
-
case BROKER_READ_NOTIFICATIONS_METHOD: return { result: await this._readNotifications(call.params, call.signal) };
|
|
897
|
-
case BROKER_DISCONNECT_METHOD:
|
|
898
|
-
setTimeout(() => this._stop("disconnect"), 0);
|
|
899
|
-
return { result: { disconnected: true } };
|
|
900
|
-
default:
|
|
901
|
-
if (call.method.startsWith(`hubrpc.connectionBroker::`)) return { error: {
|
|
902
|
-
code: ErrorCode.methodNotFound,
|
|
903
|
-
message: `Unknown connection broker method: ${call.method}`
|
|
904
|
-
} };
|
|
905
|
-
const reflected = this._staticReflection?.tryHandleRequest(call);
|
|
906
|
-
if (reflected !== void 0) return reflected;
|
|
907
|
-
return this._remoteForwarder.handleRequest(call);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
_handleLocalNotification(call) {
|
|
911
|
-
this.recordActivity();
|
|
912
|
-
if (call.method.startsWith(`hubrpc.connectionBroker::`)) return;
|
|
913
|
-
this._remoteForwarder.handleNotification(call);
|
|
914
|
-
}
|
|
915
|
-
_handleRemoteRequest(call) {
|
|
916
|
-
this.recordActivity();
|
|
917
|
-
if (this._locals.size !== 1) return Promise.resolve({ error: {
|
|
918
|
-
code: ErrorCode.peerDisconnected,
|
|
919
|
-
message: this._locals.size === 0 ? "No local broker client is attached" : "Cannot route a reverse request while multiple local broker clients are attached"
|
|
920
|
-
} });
|
|
921
|
-
const local = this._locals.values().next().value;
|
|
922
|
-
return createForwardingHandler(local).handleRequest(call);
|
|
923
|
-
}
|
|
924
|
-
_bufferNotification(call) {
|
|
925
|
-
this.recordActivity();
|
|
926
|
-
const notification = {
|
|
927
|
-
sequence: this._nextNotificationSequence++,
|
|
928
|
-
receivedAt: this._now(),
|
|
929
|
-
method: call.method,
|
|
930
|
-
...call.params === void 0 ? {} : { params: call.params }
|
|
931
|
-
};
|
|
932
|
-
this._notifications.push(notification);
|
|
933
|
-
if (this._notifications.length > this._notificationLimit) this._notifications.splice(0, this._notifications.length - this._notificationLimit);
|
|
934
|
-
this._settleReadyWaiters();
|
|
935
|
-
}
|
|
936
|
-
_status() {
|
|
937
|
-
return {
|
|
938
|
-
id: this._options.id,
|
|
939
|
-
mode: this._options.mode,
|
|
940
|
-
remoteEndpoint: this._options.remoteEndpoint,
|
|
941
|
-
startedAt: this._startedAt,
|
|
942
|
-
lastActivityAt: this._lastActivityAt,
|
|
943
|
-
timeoutMs: this._options.timeoutMs,
|
|
944
|
-
ttlMs: this._options.ttlMs,
|
|
945
|
-
notificationCount: this._notifications.length,
|
|
946
|
-
nextNotificationSequence: this._nextNotificationSequence,
|
|
947
|
-
localClientCount: this._locals.size
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
_readNotifications(params, signal) {
|
|
951
|
-
const parsed = parseReadParams(params);
|
|
952
|
-
const immediate = this._collectNotifications(parsed.after);
|
|
953
|
-
if (immediate.notifications.length > 0 || parsed.waitMs === 0) return Promise.resolve(immediate);
|
|
954
|
-
return new Promise((resolve) => {
|
|
955
|
-
const waiter = {
|
|
956
|
-
after: parsed.after,
|
|
957
|
-
resolve,
|
|
958
|
-
timer: setTimeout(() => this._settleWaiter(waiter), parsed.waitMs),
|
|
959
|
-
signal,
|
|
960
|
-
onAbort: () => this._settleWaiter(waiter)
|
|
961
|
-
};
|
|
962
|
-
waiter.timer.unref?.();
|
|
963
|
-
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
964
|
-
this._waiters.add(waiter);
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
_collectNotifications(after) {
|
|
968
|
-
const notifications = this._notifications.filter((item) => item.sequence > after);
|
|
969
|
-
return {
|
|
970
|
-
notifications,
|
|
971
|
-
next: notifications.at(-1)?.sequence ?? after,
|
|
972
|
-
droppedBefore: this._notifications[0]?.sequence ?? this._nextNotificationSequence
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
_settleReadyWaiters() {
|
|
976
|
-
for (const waiter of [...this._waiters]) if (this._notifications.some((item) => item.sequence > waiter.after)) this._settleWaiter(waiter);
|
|
977
|
-
}
|
|
978
|
-
_settleWaiter(waiter) {
|
|
979
|
-
if (!this._waiters.delete(waiter)) return;
|
|
980
|
-
clearTimeout(waiter.timer);
|
|
981
|
-
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
982
|
-
waiter.resolve(this._collectNotifications(waiter.after));
|
|
983
|
-
}
|
|
984
|
-
_resetTimeout() {
|
|
985
|
-
if (this._timeoutTimer !== void 0) clearTimeout(this._timeoutTimer);
|
|
986
|
-
this._timeoutTimer = setTimeout(() => this._stop("timeout"), this._options.timeoutMs);
|
|
987
|
-
this._timeoutTimer.unref?.();
|
|
988
|
-
}
|
|
989
|
-
_stop(reason) {
|
|
990
|
-
if (this._disposed) return;
|
|
991
|
-
this._disposed = true;
|
|
992
|
-
this.stoppedReason = reason;
|
|
993
|
-
if (this._timeoutTimer !== void 0) clearTimeout(this._timeoutTimer);
|
|
994
|
-
if (this._ttlTimer !== void 0) clearTimeout(this._ttlTimer);
|
|
995
|
-
for (const waiter of [...this._waiters]) this._settleWaiter(waiter);
|
|
996
|
-
for (const local of this._locals) local.close();
|
|
997
|
-
this._locals.clear();
|
|
998
|
-
this._remote.close();
|
|
999
|
-
this._resolveStopped(reason);
|
|
1000
|
-
this._options.onStop?.(reason);
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
function parseReadParams(params) {
|
|
1004
|
-
if (params === void 0) return {
|
|
1005
|
-
after: 0,
|
|
1006
|
-
waitMs: 0
|
|
1007
|
-
};
|
|
1008
|
-
if (typeof params !== "object" || params === null || Array.isArray(params)) throw new Error("readNotifications params must be an object");
|
|
1009
|
-
const after = "after" in params ? params.after : 0;
|
|
1010
|
-
const waitMs = "waitMs" in params ? params.waitMs : 0;
|
|
1011
|
-
if (typeof after !== "number" || !Number.isSafeInteger(after) || after < 0) throw new Error("readNotifications after must be a non-negative integer");
|
|
1012
|
-
if (typeof waitMs !== "number" || !Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > 3e4) throw new Error("readNotifications waitMs must be an integer between 0 and 30000");
|
|
1013
|
-
return {
|
|
1014
|
-
after,
|
|
1015
|
-
waitMs
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
//#endregion
|
|
1019
|
-
//#region src/commands/connectionBrokerClient.ts
|
|
1020
|
-
function getBrokerStatus(channel) {
|
|
1021
|
-
return channel.sendRequest(BROKER_STATUS_METHOD, {});
|
|
1022
|
-
}
|
|
1023
|
-
async function disconnectBroker(channel) {
|
|
1024
|
-
await channel.sendRequest(BROKER_DISCONNECT_METHOD, {});
|
|
1025
|
-
}
|
|
1026
|
-
async function readBrokerNotifications(channel, options = {}) {
|
|
1027
|
-
const value = await channel.sendRequest(BROKER_READ_NOTIFICATIONS_METHOD, {
|
|
1028
|
-
after: options.after ?? 0,
|
|
1029
|
-
waitMs: options.waitMs ?? 0
|
|
1030
|
-
});
|
|
1031
|
-
if (!isRecord(value) || !Array.isArray(value.notifications)) throw new Error("connection broker returned an invalid notification batch");
|
|
1032
|
-
const next = value.next;
|
|
1033
|
-
const droppedBefore = value.droppedBefore;
|
|
1034
|
-
if (typeof next !== "number" || !Number.isSafeInteger(next) || typeof droppedBefore !== "number" || !Number.isSafeInteger(droppedBefore)) throw new Error("connection broker returned invalid notification sequence metadata");
|
|
1035
|
-
return {
|
|
1036
|
-
notifications: value.notifications.map(parseNotification),
|
|
1037
|
-
next,
|
|
1038
|
-
droppedBefore
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
function parseNotification(value) {
|
|
1042
|
-
if (!isRecord(value) || typeof value.sequence !== "number" || !Number.isSafeInteger(value.sequence) || typeof value.receivedAt !== "number" || !Number.isSafeInteger(value.receivedAt) || typeof value.method !== "string") throw new Error("connection broker returned an invalid notification");
|
|
1043
|
-
return {
|
|
1044
|
-
sequence: value.sequence,
|
|
1045
|
-
receivedAt: value.receivedAt,
|
|
1046
|
-
method: value.method,
|
|
1047
|
-
..."params" in value ? { params: value.params } : {}
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
function isRecord(value) {
|
|
1051
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1052
|
-
}
|
|
1053
|
-
//#endregion
|
|
1054
720
|
//#region src/commands/connectionBrokerProcess.ts
|
|
1055
721
|
async function runConnectionBroker(options) {
|
|
1056
722
|
const startedAt = Date.now();
|
|
@@ -1139,7 +805,7 @@ async function spawnConnectionBroker(options) {
|
|
|
1139
805
|
throw new Error("connection broker child exposes no stdout");
|
|
1140
806
|
}
|
|
1141
807
|
try {
|
|
1142
|
-
const endpoint = await readReadyEndpoint(child, stdout, options.readyTimeoutMs ??
|
|
808
|
+
const endpoint = await readReadyEndpoint(child, stdout, options.readyTimeoutMs ?? 6e4);
|
|
1143
809
|
stdout.destroy();
|
|
1144
810
|
child.unref();
|
|
1145
811
|
return endpoint;
|
|
@@ -1162,6 +828,11 @@ function readReadyEndpoint(child, stdout, timeoutMs) {
|
|
|
1162
828
|
const endpoint = buffer.slice(0, newline).trim();
|
|
1163
829
|
cleanup();
|
|
1164
830
|
try {
|
|
831
|
+
if (endpoint.startsWith("{")) {
|
|
832
|
+
const response = JSON.parse(endpoint);
|
|
833
|
+
if (typeof response === "object" && response !== null && "error" in response && typeof response.error === "string") throw new Error(`connection broker failed to start: ${response.error}`);
|
|
834
|
+
throw new Error("connection broker returned an invalid startup response");
|
|
835
|
+
}
|
|
1165
836
|
const parsed = new URL(endpoint);
|
|
1166
837
|
if (parsed.protocol !== "unix:" && parsed.protocol !== "npipe:") throw new Error(`unexpected broker endpoint '${endpoint}'`);
|
|
1167
838
|
resolve(endpoint);
|
|
@@ -1181,31 +852,14 @@ function readReadyEndpoint(child, stdout, timeoutMs) {
|
|
|
1181
852
|
clearTimeout(timer);
|
|
1182
853
|
stdout.removeListener("data", onData);
|
|
1183
854
|
child.removeListener("error", onError);
|
|
1184
|
-
child.removeListener("
|
|
855
|
+
child.removeListener("close", onExit);
|
|
1185
856
|
};
|
|
1186
857
|
stdout.on("data", onData);
|
|
1187
858
|
child.once("error", onError);
|
|
1188
|
-
child.once("
|
|
859
|
+
child.once("close", onExit);
|
|
1189
860
|
});
|
|
1190
861
|
}
|
|
1191
862
|
//#endregion
|
|
1192
|
-
//#region src/duration.ts
|
|
1193
|
-
const DURATION_PATTERN = /^(\d+)(ms|s|m|min|h)$/;
|
|
1194
|
-
const UNIT_MS = {
|
|
1195
|
-
ms: 1,
|
|
1196
|
-
s: 1e3,
|
|
1197
|
-
m: 6e4,
|
|
1198
|
-
min: 6e4,
|
|
1199
|
-
h: 36e5
|
|
1200
|
-
};
|
|
1201
|
-
function parseDuration(value) {
|
|
1202
|
-
const match = DURATION_PATTERN.exec(value.trim());
|
|
1203
|
-
if (match === null) throw new Error(`invalid duration '${value}' (expected e.g. 250ms, 30s, 5min, or 2h)`);
|
|
1204
|
-
const durationMs = Number.parseInt(match[1], 10) * UNIT_MS[match[2]];
|
|
1205
|
-
if (!Number.isSafeInteger(durationMs) || durationMs <= 0) throw new Error(`invalid duration '${value}' (must be greater than zero)`);
|
|
1206
|
-
return durationMs;
|
|
1207
|
-
}
|
|
1208
|
-
//#endregion
|
|
1209
863
|
//#region src/commands/checkCompat.ts
|
|
1210
864
|
async function checkCompatCommand(channel, opts) {
|
|
1211
865
|
return diffSchemas(JSON.parse(readFileSync(opts.localPath, "utf8")), await fetchSchema$1(channel, opts.interfaceId, void 0));
|
|
@@ -6584,17 +6238,18 @@ Batch options:
|
|
|
6584
6238
|
}, { requestReflectionAccess: effectiveValidation(profile) !== "off" && plan.operations.some((operation) => !operation.noValidate) });
|
|
6585
6239
|
});
|
|
6586
6240
|
const connection = program.command("connection").description("Create, inspect, consume, and destroy persistent RPC connections.");
|
|
6587
|
-
connection.command("create").description("Create a detached persistent connection and print its local endpoint.").option("--timeout <duration>", "
|
|
6241
|
+
connection.command("create").description("Create a detached persistent connection and print its local endpoint.").option("--timeout <duration>", "transport inactivity limit (duration or inf; omitted: inf)").option("--ttl <duration>", "hard lifetime limit (duration or inf; omitted: inf)").option("--notification-limit <count>", "maximum buffered incoming notifications", (value) => parsePositiveInteger(value, "--notification-limit")).action(async (opts) => {
|
|
6242
|
+
if (opts.timeout === void 0 && opts.ttl === void 0) throw new Error("connection create requires an explicit --timeout or --ttl (use inf for unlimited)");
|
|
6588
6243
|
if (g_hubConfigPath !== void 0) throw new Error("connection create does not support --config; use an endpoint URI or command endpoint");
|
|
6589
6244
|
const schemaSource = g_invocation?.values.schema === void 0 ? void 0 : resolveStaticHubSchemaSource(g_invocation.values.schema);
|
|
6590
6245
|
if (schemaSource !== void 0) await loadStaticHubSchema(schemaSource);
|
|
6591
|
-
const timeout = opts.timeout ??
|
|
6592
|
-
const ttl = opts.ttl ??
|
|
6246
|
+
const timeout = opts.timeout ?? "inf";
|
|
6247
|
+
const ttl = opts.ttl ?? "inf";
|
|
6593
6248
|
const notificationLimit = opts.notificationLimit ?? g_invocation?.values.notificationLimit ?? 1e3;
|
|
6594
6249
|
const brokerEndpoint = await spawnConnectionBroker({
|
|
6595
6250
|
remote: needEndpoint(endpoint),
|
|
6596
|
-
timeoutMs:
|
|
6597
|
-
ttlMs:
|
|
6251
|
+
timeoutMs: parseConnectionLimit(timeout),
|
|
6252
|
+
ttlMs: parseConnectionLimit(ttl),
|
|
6598
6253
|
notificationLimit,
|
|
6599
6254
|
schemaSource
|
|
6600
6255
|
});
|
|
@@ -6659,14 +6314,14 @@ Batch options:
|
|
|
6659
6314
|
process.stdout.write(`Updated context ${formatContextReference(target)}.\n`);
|
|
6660
6315
|
if (result !== void 0) printContextValues(result.values, false);
|
|
6661
6316
|
});
|
|
6662
|
-
context.command("remove").description("
|
|
6317
|
+
context.command("remove").description("Stop any owned connection and remove the selected context from the global store.").action(async () => {
|
|
6663
6318
|
if (g_contextStore === void 0 || g_invocation === void 0) throw new Error("context resolver is unavailable");
|
|
6664
6319
|
const reference = g_invocation.selected.reference;
|
|
6665
6320
|
if (reference.kind === "empty" || reference.kind === "root" && explicitOption(program, "context") === void 0) throw new Error("select the context to remove explicitly with --context");
|
|
6666
6321
|
if (!await g_contextStore.remove(reference)) throw new Error(`context ${formatContextReference(reference)} does not exist`);
|
|
6667
6322
|
process.stdout.write(`Removed context ${formatContextReference(reference)}.\n`);
|
|
6668
6323
|
});
|
|
6669
|
-
program.command("_connection-broker").description("(internal) Run a persistent connection broker.").requiredOption("--remote-endpoint <uri>").requiredOption("--timeout-ms <milliseconds>", "",
|
|
6324
|
+
program.command("_connection-broker").description("(internal) Run a persistent connection broker.").requiredOption("--remote-endpoint <uri>").requiredOption("--timeout-ms <milliseconds-or-inf>", "", parseConnectionLimitMilliseconds).requiredOption("--ttl-ms <milliseconds-or-inf>", "", parseConnectionLimitMilliseconds).option("--notification-limit <count>", "", (value) => parsePositiveInteger(value, "--notification-limit"), 1e3).option("--schema <path-or-url>").action(async (opts) => {
|
|
6670
6325
|
const running = await runConnectionBroker({
|
|
6671
6326
|
remote: parseEndpointUri(opts.remoteEndpoint),
|
|
6672
6327
|
timeoutMs: opts.timeoutMs,
|
|
@@ -6919,7 +6574,7 @@ Batch options:
|
|
|
6919
6574
|
process.once("SIGINT", onSignal);
|
|
6920
6575
|
process.once("SIGTERM", onSignal);
|
|
6921
6576
|
try {
|
|
6922
|
-
const { runApprovalUi } = await import("./runApprovalUi-
|
|
6577
|
+
const { runApprovalUi } = await import("./runApprovalUi-BFI84eck.js");
|
|
6923
6578
|
await runApprovalUi({
|
|
6924
6579
|
client,
|
|
6925
6580
|
signal: stop.signal
|
|
@@ -6934,7 +6589,7 @@ Batch options:
|
|
|
6934
6589
|
registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
|
|
6935
6590
|
program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
|
|
6936
6591
|
const acquired = await acquireConnection(endpoint);
|
|
6937
|
-
const { runUi } = await import("./runUi-
|
|
6592
|
+
const { runUi } = await import("./runUi-BNCT8zwR.js");
|
|
6938
6593
|
try {
|
|
6939
6594
|
await runUi({
|
|
6940
6595
|
endpoint: acquired.ep,
|
|
@@ -7011,8 +6666,11 @@ function copyExplicit(command, source, target, optionName, contextName = optionN
|
|
|
7011
6666
|
function explicitOption(command, name) {
|
|
7012
6667
|
return command.getOptionValueSource(name) === "cli" ? command.opts()[name] : void 0;
|
|
7013
6668
|
}
|
|
7014
|
-
async function createContextFromInvocation(store, reference, values) {
|
|
7015
|
-
await store.replace(reference, values, {
|
|
6669
|
+
async function createContextFromInvocation(store, reference, values, ownedConnection) {
|
|
6670
|
+
await store.replace(reference, values, {
|
|
6671
|
+
createOnly: true,
|
|
6672
|
+
ownedConnection
|
|
6673
|
+
});
|
|
7016
6674
|
process.stderr.write(`linkrpc: created context ${formatContextReference(reference)}.\n`);
|
|
7017
6675
|
}
|
|
7018
6676
|
async function createConnectionContext(store, reference, values, brokerEndpoint) {
|
|
@@ -7021,7 +6679,7 @@ async function createConnectionContext(store, reference, values, brokerEndpoint)
|
|
|
7021
6679
|
await createContextFromInvocation(store, reference, {
|
|
7022
6680
|
...rest,
|
|
7023
6681
|
...connectionEndpoint
|
|
7024
|
-
});
|
|
6682
|
+
}, { endpoint: brokerEndpoint });
|
|
7025
6683
|
}
|
|
7026
6684
|
function splitEndpointToken(endpoint) {
|
|
7027
6685
|
const parsed = parseEndpointUri(endpoint);
|
|
@@ -7033,14 +6691,6 @@ function splitEndpointToken(endpoint) {
|
|
|
7033
6691
|
endpointToken: token
|
|
7034
6692
|
};
|
|
7035
6693
|
}
|
|
7036
|
-
async function stopConnectionBroker(endpoint) {
|
|
7037
|
-
const connection = await connect(parseEndpointUri(endpoint));
|
|
7038
|
-
try {
|
|
7039
|
-
await disconnectBroker(connection.channel);
|
|
7040
|
-
} finally {
|
|
7041
|
-
connection.close();
|
|
7042
|
-
}
|
|
7043
|
-
}
|
|
7044
6694
|
function mutableReference(reference) {
|
|
7045
6695
|
if (reference.kind === "empty") throw new Error(":empty is immutable and cannot be modified");
|
|
7046
6696
|
return reference;
|
|
@@ -7344,8 +6994,15 @@ function parseInterfaceRef(raw) {
|
|
|
7344
6994
|
};
|
|
7345
6995
|
}
|
|
7346
6996
|
function runCli(executableName) {
|
|
7347
|
-
main(process.argv.slice(2), executableName).catch((e) => {
|
|
7348
|
-
|
|
6997
|
+
main(process.argv.slice(2), executableName).catch(async (e) => {
|
|
6998
|
+
const message = formatCliError(e);
|
|
6999
|
+
process.stderr.write(`linkrpc: ${message}\n`);
|
|
7000
|
+
if (process.argv[2] === "_connection-broker") await new Promise((resolve, reject) => {
|
|
7001
|
+
process.stdout.write(JSON.stringify({ error: message }) + "\n", (error) => {
|
|
7002
|
+
if (error) reject(error);
|
|
7003
|
+
else resolve();
|
|
7004
|
+
});
|
|
7005
|
+
});
|
|
7349
7006
|
process.exit(1);
|
|
7350
7007
|
});
|
|
7351
7008
|
}
|
|
@@ -7367,4 +7024,4 @@ function safeJson(value) {
|
|
|
7367
7024
|
//#endregion
|
|
7368
7025
|
export { formatCommandLegend as a, safeTerminalText as c, interfaceTarget as d, matchesTarget as f, dispatchHostKey as i, resolveViewTarget as l, approvalDetailLines as m, views as n, hostCommands as o, targetId as p, ViewFrame as r, matchesKey as s, runCli as t, formatCondition as u };
|
|
7369
7026
|
|
|
7370
|
-
//# sourceMappingURL=cli-
|
|
7027
|
+
//# sourceMappingURL=cli-JE4URhh3.js.map
|