@hediet/linkrpc-cli 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -133,16 +133,48 @@ LINKRPC_ENDPOINT=unix:/run/hub/hub.sock?token=abc linkrpc ls
133
133
  | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
134
134
  | `ls` | Explore the directory referral graph and its services/interfaces. Supports exact and prefix filters, regexp `--search`, `--format pretty\|json\|jsonl`, `--watch`, `--dump <file>`, and `--dump-patches <file\|->`. |
135
135
  | `defaults` | Print the preset service/interface (wraps `hubrpc.defaults::get`). |
136
+ | `contract export --output <file\|-> [--service <id>] [--max-depth <n>]` | Export a complete reusable static contract from live reflection. |
136
137
  | `schema show <interfaceId>[@hash]` | Print an interface schema (wraps `hubrpc.schemas::get`). `--method <name>`, `--json`. |
137
138
  | `schema hash <schema.json>` | Compute `computeInterfaceHash` for a local schema without connecting. |
138
139
  | `schema check-compat <interfaceId> <local.json>` | Compare a live interface schema with a local schema. |
139
140
  | `codegen --input <bundle> --interface <id> --name <name> --output <file> [--preserve-wire-schema] [--check]` | Generate a TypeScript interface definition from a validated offline static bundle. |
141
+ | `codegen --input <contract> --output <file> [--names <json>] [--check]` | Generate all reusable interface definitions and typed root/service/default/bare bindings. |
140
142
 
141
- `codegen` uses the same `{ interfaceSchemas, services, defaultInterface }`
143
+ `codegen` uses the same `{ interfaceSchemas, services?, defaultInterface?, bareInterfaces? }`
142
144
  bundle parser and interface-hash verification as static reflection. `--check`
143
145
  compares the generated source with the output file and exits unsuccessfully if
144
146
  the file is missing or stale; it never overwrites in check mode.
145
147
 
148
+ ```sh
149
+ linkrpc --endpoint-cmd-stdio "node server.js" contract export --output contract.json
150
+ linkrpc codegen --input contract.json --output generated.ts
151
+ linkrpc codegen --input contract.json --output generated.ts --check
152
+ # Existing per-interface generation remains available:
153
+ linkrpc codegen --input contract.json --interface demo.echo --name echoInterface \
154
+ --output echo.ts --preserve-wire-schema
155
+ ```
156
+
157
+ Omit both `--interface` and `--name` for whole-contract generation. Every interface
158
+ is emitted once, with canonical wire schemas preserved, and each binding refers
159
+ to that definition. For `demo.echo`, default names include `demoEchoInterface`,
160
+ `demoEchoRoot`, `demoEchoDefault`, and (for bare prefix `Runtime.`) `runtimeBare`.
161
+ A service `worker` produces `workerDemoEchoService`. If names collide, supply a
162
+ local `--names` JSON file containing `interfaceNames` (`"id@hash": "exportName"`)
163
+ and/or `bindingNames` (`"root:id@hash"`, `"service:serviceId:id@hash"`,
164
+ `"default"`, or `"bare:prefix"` keys). Unknown keys and colliding overrides fail.
165
+
166
+ Contract export queries directory interfaces, `defaults.get`, and
167
+ `defaults.listBindings`; it fails explicitly if discovery is inaccessible,
168
+ truncated or depth-limited, default metadata is incomplete, or schemas cannot be
169
+ retrieved and hash-verified. It writes the output only after successful completion.
170
+ `--service` selects a reflection scope; default/bare bindings describe that scope's
171
+ preset routes, not additional service-qualified foreign methods. No default or
172
+ bare bindings are guessed from directory listings.
173
+
174
+ `ls --dump` remains the diagnostic graph format, including directory provenance.
175
+ It is not a static contract and is not an input to `contract export`. Export a
176
+ contract directly from the endpoint for offline generation instead.
177
+
146
178
  `hub ls --format json` waits for finite exploration and prints the final state.
147
179
  `--format jsonl` emits progressive state while requests fan out: the first line
148
180
  is `{ "type": "snapshot", "revision": n, "value": ... }` and each later line
@@ -1,5 +1,5 @@
1
- import { A as connectViaRootOverlay, C as findMethodInSchema, D as formatPrincipalSource, E as logoutCliIdentity, F as describeSchema, H as mcpForwardInterface, I as explainValidation, L as validateValueAgainstSchema, M as resolveTargetEndpoint, N as resolvedEndpointToConfig, O as parsePrincipalSpec, P as describeObjectParams, S as fetchSchema$1, V as MethodRefWithOptHash, a as resolveStaticHubSchemaSource, b as setupSigning, c as resolveInvocationEndpoint, d as formatContextReference, i as loadStaticHubSchema, j as connectViaTransport, k as connect, l as validationDefault, n as StaticHubReflection, o as mutationReference, p as resolveCliInvocation, r as withStaticHubReflection, s as resolveInvocationContext, t as completeForLine, u as ContextStore, v as requestReflectionAccess, w as walkHub, x as fetchDefaults, y as requestTopologyAccess, z as mergeParams } from "./runComplete-BkQwzPbF.js";
2
- import { ErrorCode, LinkRpcConnection, RpcError, TransportPair, computeInterfaceHash, connectTransports, generateTsInterface, isAssignable, jcsCanonicalize } from "@hediet/linkrpc";
1
+ import { A as connectViaRootOverlay, C as findMethodInSchema, D as formatPrincipalSource, E as logoutCliIdentity, F as describeSchema, H as mcpForwardInterface, I as explainValidation, L as validateValueAgainstSchema, M as resolveTargetEndpoint, N as resolvedEndpointToConfig, O as parsePrincipalSpec, P as describeObjectParams, S as fetchSchema$1, V as MethodRefWithOptHash, a as resolveStaticHubSchemaSource, b as setupSigning, c as resolveInvocationEndpoint, d as formatContextReference, i as loadStaticHubSchema, j as connectViaTransport, k as connect, l as validationDefault, n as StaticHubReflection, o as mutationReference, p as resolveCliInvocation, r as withStaticHubReflection, s as resolveInvocationContext, t as completeForLine, u as ContextStore, v as requestReflectionAccess, w as walkHub, x as fetchDefaults, y as requestTopologyAccess, z as mergeParams } from "./runComplete-Du5wmOdB.js";
2
+ import { ErrorCode, LinkRpcConnection, RpcError, TransportPair, computeInterfaceHash, connectTransports, exportStaticHubSchema, generateTsContract, generateTsInterface, isAssignable, jcsCanonicalize } from "@hediet/linkrpc";
3
3
  import { WebSocketTransport, connectNdjson, formatEndpointUri, isHubEndpoint, openWebSocket, parseEndpointUri, runInitializeHandshake, traceMessageTransport as traceMessageTransport$1 } from "@hediet/linkrpc/node";
4
4
  import { SocketServer } from "@hediet/linkrpc-hub/hub/server/node";
5
5
  import { randomBytes, randomUUID } from "node:crypto";
@@ -1256,14 +1256,30 @@ function formatVerdict(v) {
1256
1256
  //#region src/commands/codegen.ts
1257
1257
  async function codegenCommand(options) {
1258
1258
  if (/^https?:\/\//i.test(options.input)) throw new Error("codegen --input must be a local offline bundle");
1259
- if (!/^[$A-Z_a-z][$\w]*$/.test(options.name)) throw new Error(`codegen --name must be a valid TypeScript identifier, got '${options.name}'`);
1260
- const matches = (await loadStaticHubSchema(options.input)).interfaceSchemas.filter((schema) => schema.id === options.interfaceId);
1261
- if (matches.length === 0) throw new Error(`Interface '${options.interfaceId}' was not found in bundle '${options.input}'`);
1262
- if (matches.length > 1) throw new Error(`Interface '${options.interfaceId}' is ambiguous in bundle '${options.input}' (${matches.length} schema versions)`);
1263
- const generated = generateTsInterface(matches[0], {
1264
- exportName: options.name,
1265
- preserveWireSchema: options.preserveWireSchema === true
1266
- });
1259
+ if (options.name !== void 0 && !/^[$A-Z_a-z][$\w]*$/.test(options.name)) throw new Error(`codegen --name must be a valid TypeScript identifier, got '${options.name}'`);
1260
+ const bundle = await loadStaticHubSchema(options.input);
1261
+ let generated;
1262
+ if (options.interfaceId === void 0) {
1263
+ if (options.name !== void 0) throw new Error("codegen --name requires --interface");
1264
+ let naming = {};
1265
+ if (options.names !== void 0) {
1266
+ const raw = JSON.parse(await readFile(options.names, "utf8"));
1267
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).some((key) => key !== "interfaceNames" && key !== "bindingNames")) throw new Error("codegen --names must contain only interfaceNames and bindingNames maps");
1268
+ for (const map of Object.values(raw)) if (map === null || typeof map !== "object" || Array.isArray(map) || Object.values(map).some((name) => typeof name !== "string")) throw new Error("codegen --names values must be string maps");
1269
+ naming = raw;
1270
+ }
1271
+ generated = generateTsContract(bundle, naming);
1272
+ } else {
1273
+ if (options.name === void 0) throw new Error("codegen --interface requires --name");
1274
+ if (options.names !== void 0) throw new Error("codegen --names is only supported for whole contracts");
1275
+ const matches = bundle.interfaceSchemas.filter((schema) => schema.id === options.interfaceId);
1276
+ if (matches.length === 0) throw new Error(`Interface '${options.interfaceId}' was not found in bundle '${options.input}'`);
1277
+ if (matches.length > 1) throw new Error(`Interface '${options.interfaceId}' is ambiguous in bundle '${options.input}' (${matches.length} schema versions)`);
1278
+ generated = generateTsInterface(matches[0], {
1279
+ exportName: options.name,
1280
+ preserveWireSchema: options.preserveWireSchema === true
1281
+ });
1282
+ }
1267
1283
  if (options.check === true) {
1268
1284
  let current;
1269
1285
  try {
@@ -1279,6 +1295,17 @@ async function codegenCommand(options) {
1279
1295
  await writeFile(options.output, generated, "utf8");
1280
1296
  }
1281
1297
  //#endregion
1298
+ //#region src/commands/contract.ts
1299
+ async function exportContractCommand(channel, options) {
1300
+ const document = await exportStaticHubSchema(channel, options);
1301
+ const text = `${JSON.stringify(document, null, 2)}\n`;
1302
+ if (options.output === "-") process.stdout.write(text);
1303
+ else {
1304
+ await mkdir(dirname(options.output), { recursive: true });
1305
+ await writeFile(options.output, text, "utf8");
1306
+ }
1307
+ }
1308
+ //#endregion
1282
1309
  //#region src/completions/shellScripts.ts
1283
1310
  const SUPPORTED = /* @__PURE__ */ new Set(["powershell"]);
1284
1311
  function isSupportedShell(name) {
@@ -5140,16 +5167,29 @@ async function main(rawArgv, executableName = "linkrpc") {
5140
5167
  process.stdout.write(out + "\n");
5141
5168
  });
5142
5169
  });
5143
- program.command("codegen").description("Generate a TypeScript interface definition from an offline schema bundle.").requiredOption("--input <bundle>", "local static hub schema bundle").requiredOption("--interface <id>", "interface id to generate").requiredOption("--name <name>", "exported TypeScript const name").requiredOption("--output <file>", "generated TypeScript output file").option("--preserve-wire-schema", "freeze the canonical input schema verbatim in the generated definition").option("--check", "fail if the output is missing or stale without overwriting it").action(async (opts) => {
5170
+ program.command("codegen").description("Generate reusable TypeScript interfaces and bindings from an offline contract.").requiredOption("--input <bundle>", "local static hub schema bundle").option("--interface <id>", "generate only this interface (requires --name)").option("--name <name>", "single-interface exported TypeScript const name").option("--names <file>", "whole-contract JSON interfaceNames/bindingNames overrides").requiredOption("--output <file>", "generated TypeScript output file").option("--preserve-wire-schema", "freeze the canonical input schema verbatim in the generated definition").option("--check", "fail if the output is missing or stale without overwriting it").action(async (opts) => {
5144
5171
  await codegenCommand({
5145
5172
  input: opts.input,
5146
5173
  interfaceId: opts.interface,
5147
5174
  name: opts.name,
5148
5175
  output: opts.output,
5149
5176
  preserveWireSchema: opts.preserveWireSchema === true,
5150
- check: opts.check === true
5177
+ check: opts.check === true,
5178
+ names: opts.names
5151
5179
  });
5152
5180
  });
5181
+ program.command("contract").description("Export reusable static interface and binding contracts.").command("export").requiredOption("--output <file>", "contract JSON destination, or - for stdout").option("--service <id>", "reflection scope (defaults to the endpoint root)").option("--max-depth <depth>", "maximum directory referral depth", (value) => {
5182
+ if (!/^\d+$/.test(value)) throw new InvalidArgumentError("depth must be a non-negative integer");
5183
+ return Number(value);
5184
+ }).action(async (opts) => {
5185
+ await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
5186
+ await exportContractCommand(channel, {
5187
+ output: opts.output,
5188
+ serviceId: opts.service,
5189
+ maxDepth: opts.maxDepth
5190
+ });
5191
+ }, { requestReflectionAccess: true });
5192
+ });
5153
5193
  const schema = program.command("schema").description("Inspect, hash, and compare LinkRPC interface schemas.");
5154
5194
  schema.command("show <interfaceRef>").description("Print an interface schema. `<interfaceRef>` is `id[@hash]`.").option("--method <name>", "show only this method").option("--service <id>", "route the lookup to this service (form-3); needed behind a hub").option("--json", "raw JSON output").action(async (interfaceRef, opts) => {
5155
5195
  const { id, hash } = parseInterfaceRef(interfaceRef);
@@ -5625,7 +5665,7 @@ Batch options:
5625
5665
  process.once("SIGINT", onSignal);
5626
5666
  process.once("SIGTERM", onSignal);
5627
5667
  try {
5628
- const { runApprovalUi } = await import("./runApprovalUi-BvJZdjKm.js");
5668
+ const { runApprovalUi } = await import("./runApprovalUi-CTyhiDpa.js");
5629
5669
  await runApprovalUi({
5630
5670
  client,
5631
5671
  signal: stop.signal
@@ -5639,7 +5679,7 @@ Batch options:
5639
5679
  }
5640
5680
  program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
5641
5681
  const acquired = await acquireConnection(endpoint);
5642
- const { runUi } = await import("./runUi-D-8LMU4F.js");
5682
+ const { runUi } = await import("./runUi-CuJr8_tY.js");
5643
5683
  try {
5644
5684
  await runUi({
5645
5685
  endpoint: acquired.ep,
@@ -6070,4 +6110,4 @@ function safeJson(value) {
6070
6110
  //#endregion
6071
6111
  export { observableValue as a, Disposable as c, ObservablePromise as i, approvalDetailLines as n, autorun as o, ObservableLazyPromise as r, derived as s, runCli as t };
6072
6112
 
6073
- //# sourceMappingURL=cli-D_-vlAa2.js.map
6113
+ //# sourceMappingURL=cli-FDKN1ydZ.js.map