@hediet/linkrpc 0.0.1
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 +383 -0
- package/dist/chunks/_empty-crypto-Bi0tGx5K.js +8 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js +1126 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js.map +1 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js +526 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js.map +1 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts +79 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts.map +1 -0
- package/dist/chunks/hubFacade-CQflVkVC.js +85 -0
- package/dist/chunks/hubFacade-CQflVkVC.js.map +1 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts +101 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts.map +1 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts +3623 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts.map +1 -0
- package/dist/chunks/rolldown-runtime-4LSo1kEK.js +17 -0
- package/dist/chunks/src-D3NUIwyo.js +7795 -0
- package/dist/chunks/src-D3NUIwyo.js.map +1 -0
- package/dist/hub/client/index.d.ts +50 -0
- package/dist/hub/client/index.d.ts.map +1 -0
- package/dist/hub/client/index.js +97 -0
- package/dist/hub/client/index.js.map +1 -0
- package/dist/hub/common/index.d.ts +1189 -0
- package/dist/hub/common/index.d.ts.map +1 -0
- package/dist/hub/common/index.js +267 -0
- package/dist/hub/common/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/inspection/index.d.ts +66 -0
- package/dist/inspection/index.d.ts.map +1 -0
- package/dist/inspection/index.js +6 -0
- package/dist/node.d.ts +548 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +1087 -0
- package/dist/node.js.map +1 -0
- package/dist/web.d.ts +44 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +58 -0
- package/dist/web.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# @hediet/linkrpc
|
|
2
|
+
|
|
3
|
+
Typed, multiplexed RPC over a single bidirectional connection.
|
|
4
|
+
|
|
5
|
+
linkrpc is a **specialization of [JSON-RPC 2.0](https://www.jsonrpc.org/specification)**: every
|
|
6
|
+
message on the wire is a valid JSON-RPC message, and linkrpc adds just enough on top to let many
|
|
7
|
+
strongly-typed services share one connection — an addressing grammar, built-in reflection,
|
|
8
|
+
content-addressed interface identity, and optional layers for signing and capabilities.
|
|
9
|
+
|
|
10
|
+
## How linkrpc works
|
|
11
|
+
|
|
12
|
+
```mermaid
|
|
13
|
+
sequenceDiagram
|
|
14
|
+
actor App as Consumer application
|
|
15
|
+
participant Caller as Consumer LinkRPC node
|
|
16
|
+
participant Hub as Hub (optional intermediary)
|
|
17
|
+
participant Provider as Provider LinkRPC node
|
|
18
|
+
participant Service as Service handlers
|
|
19
|
+
|
|
20
|
+
Note over Caller,Provider: Reliable, ordered, bidirectional transport<br/>NDJSON, WebSocket, or stdio
|
|
21
|
+
Caller->>Hub: hubrpc.directory::list / hubrpc.schemas::get
|
|
22
|
+
Hub-->>Caller: Services, interface schemas, and hashes
|
|
23
|
+
|
|
24
|
+
opt The target call is capability-gated
|
|
25
|
+
Caller->>Hub: hubAccess::request
|
|
26
|
+
Hub-->>Caller: Scoped signed capabilities
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
App->>Caller: Typed call
|
|
30
|
+
Caller->>Hub: JSON-RPC request<br/>service::interface::member<br/>+ optional signature and capabilities
|
|
31
|
+
Hub->>Hub: If required, verify identity and capability<br/>Route and rewrite correlation id
|
|
32
|
+
Hub->>Provider: Forwarded request
|
|
33
|
+
Provider->>Service: Validate params and invoke member
|
|
34
|
+
|
|
35
|
+
opt Long-running or bidirectional call
|
|
36
|
+
Service-->>Provider: Typed stream payload
|
|
37
|
+
Provider-->>Hub: $stream::send(requestId)
|
|
38
|
+
Hub-->>Caller: $stream::send(rewritten requestId)
|
|
39
|
+
Caller-->>App: Progress or partial result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Service-->>Provider: Typed result or enumerated error
|
|
43
|
+
Provider-->>Hub: JSON-RPC response
|
|
44
|
+
Hub-->>Caller: Correlated response
|
|
45
|
+
Caller-->>App: Typed result
|
|
46
|
+
|
|
47
|
+
Note over Caller,Provider: Caller and provider are per-call roles.<br/>Either node may play both roles concurrently.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The **nodes** are the protocol participants: each is one endpoint of one bidirectional connection.
|
|
51
|
+
The **hub** is optional; on a direct connection the consumer node talks to the provider node without
|
|
52
|
+
the routing and access-broker steps. A **service** is a concrete provider of one or more typed
|
|
53
|
+
interfaces. Reflection exposes those interfaces at the connection boundary, while signatures identify
|
|
54
|
+
the caller and capabilities authorize specific calls. Optional metadata stays inside reserved
|
|
55
|
+
`$hubrpc`-prefixed `params` members, so peers that only implement the Core profile can still handle
|
|
56
|
+
ungated calls.
|
|
57
|
+
|
|
58
|
+
You describe an interface once with [zod](https://zod.dev) schemas. The same definition gives you a
|
|
59
|
+
fully-typed client *and* type-checked server handlers, and validates params and results at runtime.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// shared.ts
|
|
63
|
+
import { defineInterface, requestType, notificationType } from '@hediet/linkrpc';
|
|
64
|
+
import { z } from 'zod';
|
|
65
|
+
|
|
66
|
+
export const greeter = defineInterface(
|
|
67
|
+
{ id: 'test.greeter', description: 'Greets people.' },
|
|
68
|
+
{
|
|
69
|
+
hello: requestType(
|
|
70
|
+
z.object({ name: z.string() }),
|
|
71
|
+
z.object({ greeting: z.string() }),
|
|
72
|
+
),
|
|
73
|
+
shout: notificationType(z.object({ msg: z.string() })),
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Put the interface in `shared.ts`, then serve it over a WebSocket:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// server.ts
|
|
82
|
+
import { LinkRpcConnection } from '@hediet/linkrpc';
|
|
83
|
+
import { WebSocketServer } from '@hediet/linkrpc-hub/hub/server/node';
|
|
84
|
+
import { greeter } from './shared.js';
|
|
85
|
+
|
|
86
|
+
const token = process.env.LINKRPC_TOKEN;
|
|
87
|
+
if (!token) throw new Error('LINKRPC_TOKEN is required');
|
|
88
|
+
|
|
89
|
+
const listener = await WebSocketServer.start({
|
|
90
|
+
host: '127.0.0.1',
|
|
91
|
+
port: 7878,
|
|
92
|
+
isTokenAccepted: async candidate => candidate === token,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
listener.setConnectionHandler(transport => {
|
|
96
|
+
const connection = LinkRpcConnection.fromTransport(transport);
|
|
97
|
+
connection.register(greeter, {
|
|
98
|
+
hello: async ({ name }) => ({ greeting: `Hi, ${name}!` }),
|
|
99
|
+
shout: ({ msg }) => console.log('heard:', msg),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Connect from another process — the generated proxy remains fully typed:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// client.ts
|
|
108
|
+
import { LinkRpcConnection } from '@hediet/linkrpc';
|
|
109
|
+
import {
|
|
110
|
+
openWebSocket,
|
|
111
|
+
runInitializeHandshake,
|
|
112
|
+
WebSocketTransport,
|
|
113
|
+
} from '@hediet/linkrpc/node';
|
|
114
|
+
import { greeter } from './shared.js';
|
|
115
|
+
|
|
116
|
+
const token = process.env.LINKRPC_TOKEN;
|
|
117
|
+
if (!token) throw new Error('LINKRPC_TOKEN is required');
|
|
118
|
+
|
|
119
|
+
const socket = await openWebSocket('ws://127.0.0.1:7878');
|
|
120
|
+
const transport = new WebSocketTransport(socket);
|
|
121
|
+
await runInitializeHandshake(transport, {
|
|
122
|
+
kind: 'client',
|
|
123
|
+
token,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const client = LinkRpcConnection.fromTransport(transport);
|
|
127
|
+
const g = client.get(greeter); // typed proxy
|
|
128
|
+
await g.hello({ name: 'world' }); // → { greeting: 'Hi, world!' }
|
|
129
|
+
g.shout({ msg: 'boom' }); // fire-and-forget notification
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Pass the wrong shape and TypeScript stops you at compile time; if a bad value reaches the wire
|
|
133
|
+
anyway, the provider rejects it with a JSON-RPC `-32602 invalidParams`.
|
|
134
|
+
|
|
135
|
+
## Reflection — discoverability built in
|
|
136
|
+
|
|
137
|
+
A connection is self-describing. Call `enableReflection()` and it exposes three standard interfaces
|
|
138
|
+
backed by its live registry, so a peer (or the [`linkrpc` CLI](../linkrpc-cli)) can explore and call
|
|
139
|
+
it with **no prior knowledge** — list the services, fetch their schemas, generate a typed client at
|
|
140
|
+
runtime:
|
|
141
|
+
|
|
142
|
+
- **`hubrpc.directory`** — which services and interfaces this connection serves (each with its `id@hash`).
|
|
143
|
+
- **`hubrpc.schemas`** — the full schema for any advertised interface.
|
|
144
|
+
- **`hubrpc.defaults`** — the connection's preset service/interface, if any (`setPreset(iface)`).
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
connection.register(greeter, handlers);
|
|
148
|
+
connection.setPreset(greeter); // lets callers use the bare `hello` form
|
|
149
|
+
connection.enableReflection(); // directory / schemas / defaults, for free
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Because the served contract is observable from the boundary, generic tooling — explorers, the CLI,
|
|
153
|
+
conformance checkers — works against any endpoint without compiled-in knowledge of its interfaces.
|
|
154
|
+
|
|
155
|
+
## Streaming
|
|
156
|
+
|
|
157
|
+
Any request can carry in-flight, bidirectionally-correlated stream messages — input from the caller
|
|
158
|
+
*and* progress or partial results from the provider, on the same call. Declare a payload schema per
|
|
159
|
+
direction with `.withStream({ client, server })`:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const transcribe = defineInterface({ id: 'acme.voice' }, {
|
|
163
|
+
// client streams audio chunks; server streams partial transcripts; returns the final text.
|
|
164
|
+
session: requestType(z.object({ lang: z.string() }), z.object({ text: z.string() }))
|
|
165
|
+
.withStream({
|
|
166
|
+
client: z.object({ audio: z.string() }), // client → server
|
|
167
|
+
server: z.object({ partial: z.string() }), // server → client
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// provider: consume client messages, emit server messages, return the final result.
|
|
172
|
+
server.register(transcribe, {
|
|
173
|
+
session: async ({ lang }, _ctx, stream) => {
|
|
174
|
+
let text = '';
|
|
175
|
+
stream.onMessage(({ audio }) => { // client → server
|
|
176
|
+
text += decode(audio, lang);
|
|
177
|
+
stream.send({ partial: text }); // server → client
|
|
178
|
+
});
|
|
179
|
+
await untilSilence();
|
|
180
|
+
return { text };
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// caller: send input as it arrives, observe partials, await the final result.
|
|
185
|
+
const call = client.get(transcribe).session(
|
|
186
|
+
{ lang: 'en' },
|
|
187
|
+
{ onMessage: ({ partial }) => console.log('…', partial) }, // server → client
|
|
188
|
+
);
|
|
189
|
+
await call.send({ audio: chunk1 }); // client → server
|
|
190
|
+
await call.send({ audio: chunk2 });
|
|
191
|
+
const { text } = await call; // final result
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Declare only `server` for provider→caller progress, only `client` for caller→provider input, or
|
|
195
|
+
both for a full duplex session. Cancellation (`call.cancel()`), keepalive pings, and idle-timeout
|
|
196
|
+
handling are wired in for you; a handler observes cancellation via an `AbortSignal`.
|
|
197
|
+
|
|
198
|
+
## Identity & capabilities
|
|
199
|
+
|
|
200
|
+
On an untrusted link — a shared hub, a sandboxed extension — you often need to know *who* is calling
|
|
201
|
+
and *what they're allowed to do*. linkrpc layers both on top of the same JSON-RPC envelope, and both
|
|
202
|
+
are optional: a plain call needs neither.
|
|
203
|
+
|
|
204
|
+
**Signing (identity).** A call can be Ed25519-signed by a **principal**. The signature covers the
|
|
205
|
+
method, params, a freshness timestamp, and a one-time nonce, so a provider can authenticate the
|
|
206
|
+
caller and reject replays or tampered forwards. Principals are managed for you
|
|
207
|
+
(`createManagedPrincipal`, `loadOrCreateIdentity`); on Node, `connectToHub({ principal })` signs
|
|
208
|
+
outgoing calls transparently.
|
|
209
|
+
|
|
210
|
+
**Capabilities (authorization).** A **capability** is a signed grant from an issuer to an audience
|
|
211
|
+
listing exactly which calls are permitted — optionally narrowed to specific services, interfaces,
|
|
212
|
+
param values, or even one exact call. Capabilities delegate by chaining (each link can only narrow
|
|
213
|
+
the authority it received), and a provider runs the **gate** — verify identity → resolve the chain →
|
|
214
|
+
check permission — admitting the call only if some presented capability permits it. It's
|
|
215
|
+
fail-closed: with no permitting capability, a gated call is refused with `-32401 permissionRequired`.
|
|
216
|
+
|
|
217
|
+
Here is a complete capability-gated notes service. First create the identities and issue authority:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import {
|
|
221
|
+
defineInterface,
|
|
222
|
+
invoke,
|
|
223
|
+
issueCapability,
|
|
224
|
+
KeypairSigningIdentity,
|
|
225
|
+
prefix,
|
|
226
|
+
requestType,
|
|
227
|
+
TransportPair,
|
|
228
|
+
} from '@hediet/linkrpc';
|
|
229
|
+
import { object, string } from 'zod/mini';
|
|
230
|
+
|
|
231
|
+
const notes = defineInterface({ id: 'demo.notes' }, {
|
|
232
|
+
read: requestType(
|
|
233
|
+
object({ id: string() }),
|
|
234
|
+
object({ contents: string() }),
|
|
235
|
+
),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const adminId = await KeypairSigningIdentity.generateNew();
|
|
239
|
+
const appId = await KeypairSigningIdentity.generateNew();
|
|
240
|
+
const capability = await issueCapability(adminId, {
|
|
241
|
+
audience: appId.publicSigningIdentity,
|
|
242
|
+
permissions: [
|
|
243
|
+
invoke('notes', notes, 'read', {
|
|
244
|
+
id: prefix('public/'),
|
|
245
|
+
}),
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Capabilities are portable JSON, not callbacks or shared process state.
|
|
250
|
+
const capabilityJson = JSON.stringify(capability);
|
|
251
|
+
const transport = new TransportPair();
|
|
252
|
+
const notesById = new Map([
|
|
253
|
+
['public/roadmap', 'Ship it!'],
|
|
254
|
+
['private/payroll', 'Classified'],
|
|
255
|
+
]);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The server gates its incoming transport, so rejected calls never reach its handlers:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { LinkRpcConnection } from '@hediet/linkrpc';
|
|
262
|
+
import { withFullyQualifiedCallGate } from '@hediet/linkrpc-hub/hub/server';
|
|
263
|
+
|
|
264
|
+
const serverTransport = withFullyQualifiedCallGate(transport.b, {
|
|
265
|
+
requireCapability: true,
|
|
266
|
+
trustedRoots: [adminId.publicSigningIdentity],
|
|
267
|
+
});
|
|
268
|
+
const server = LinkRpcConnection.fromTransport(serverTransport);
|
|
269
|
+
let handlerCalls = 0;
|
|
270
|
+
|
|
271
|
+
server.register(notes, {
|
|
272
|
+
read: ({ id }) => {
|
|
273
|
+
handlerCalls++;
|
|
274
|
+
return { contents: notesById.get(id) ?? 'Not found' };
|
|
275
|
+
},
|
|
276
|
+
}, { serviceId: 'notes' });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The client parses the capability as ordinary JSON, then a signing channel attaches it to every call:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import {
|
|
283
|
+
LinkRpcConnection,
|
|
284
|
+
JsonRpcChannel,
|
|
285
|
+
Principal,
|
|
286
|
+
SigningSender,
|
|
287
|
+
type SignedCapability,
|
|
288
|
+
} from '@hediet/linkrpc';
|
|
289
|
+
|
|
290
|
+
const receivedCapability: SignedCapability = JSON.parse(capabilityJson);
|
|
291
|
+
const appPrincipal = await Principal.create(appId, [receivedCapability]);
|
|
292
|
+
const signedChannel = SigningSender.wrapChannel(
|
|
293
|
+
JsonRpcChannel.create(transport.a),
|
|
294
|
+
{ principal: appPrincipal },
|
|
295
|
+
);
|
|
296
|
+
const client = new LinkRpcConnection(signedChannel);
|
|
297
|
+
const notesClient = client.service('notes').get(notes);
|
|
298
|
+
|
|
299
|
+
await notesClient.read({ id: 'public/roadmap' }); // allowed
|
|
300
|
+
await notesClient.read({ id: 'private/payroll' })
|
|
301
|
+
.catch(error => console.log(error.code)); // -32401 permissionRequired
|
|
302
|
+
handlerCalls; // 1
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The service-scoped client emits `notes::demo.notes::read`, so the gate checks it. Root-addressed calls
|
|
306
|
+
terminate at the connection root and always bypass this forwarded-call gate.
|
|
307
|
+
|
|
308
|
+
This is what lets a [hub](../linkrpc-hub) broker calls between mutually-distrusting participants: it
|
|
309
|
+
hands each consumer a scoped capability and enforces it on every routed call.
|
|
310
|
+
|
|
311
|
+
## A layered protocol
|
|
312
|
+
|
|
313
|
+
linkrpc is built in additive layers. Lower layers stand alone; higher ones ride in reserved
|
|
314
|
+
`$hubrpc`-prefixed members that a peer who doesn't implement them treats as opaque. That's what lets
|
|
315
|
+
a minimal node and a fully-secured node interoperate on any call that needs no gated authority.
|
|
316
|
+
|
|
317
|
+
| Layer | What it adds |
|
|
318
|
+
|---|---|
|
|
319
|
+
| Messages | JSON-RPC envelope, `::` method grammar, error codes |
|
|
320
|
+
| Transport | framed whole-message channel + endpoint URIs |
|
|
321
|
+
| Interfaces | schema format, JSON Schema subset, the interface hash |
|
|
322
|
+
| Reflection | `hubrpc.directory` / `.schemas` / `.defaults` |
|
|
323
|
+
| Streaming *(optional)* | in-flight correlated stream messages |
|
|
324
|
+
| Identity *(optional)* | Ed25519-signed calls (principals, freshness + replay protection) |
|
|
325
|
+
| Capabilities *(optional)* | signed grants + the authorization gate (`permits`) |
|
|
326
|
+
|
|
327
|
+
The full normative protocol — every layer, wire field, and conformance rule — is specified in
|
|
328
|
+
the [LinkRPC specification](../../../spec) (start at [`00-overview.md`](../../../spec/00-overview.md)). The
|
|
329
|
+
hub model and design notes live in [`docs/`](./docs).
|
|
330
|
+
|
|
331
|
+
## Interface identity & schema tooling
|
|
332
|
+
|
|
333
|
+
Every interface has an identity of the form `test.greeter@<hash>`, where the hash is derived from
|
|
334
|
+
the *normalized* schema of its members. Two peers agree on an interface only when their contracts
|
|
335
|
+
are structurally identical, so a mismatch surfaces up front as a hash disagreement rather than a
|
|
336
|
+
decode error three calls later. The hashing is defined byte-for-byte (RFC 8785 JCS → SHA-256,
|
|
337
|
+
truncated) so independent implementations in any language agree. Because the schema is just data,
|
|
338
|
+
you get tooling for free:
|
|
339
|
+
|
|
340
|
+
- **`computeInterfaceHash(schema)`** — the stable `@hash` for a schema.
|
|
341
|
+
- **`isAssignable(a, b)`** — structural compatibility: is every value of interface `A` accepted by `B`?
|
|
342
|
+
- **`generateTsInterface(schema)`** — emit a `.ts` client/server typing from a schema fetched at runtime.
|
|
343
|
+
|
|
344
|
+
## Transports & entry points
|
|
345
|
+
|
|
346
|
+
The core (`@hediet/linkrpc`) is environment-agnostic. Platform transports live behind subpath
|
|
347
|
+
exports so browser bundles never pull in Node built-ins:
|
|
348
|
+
|
|
349
|
+
| Import | Provides |
|
|
350
|
+
|---|---|
|
|
351
|
+
| `@hediet/linkrpc` | interfaces, connection, schema/hash, identity, capabilities |
|
|
352
|
+
| `@hediet/linkrpc/node` | NDJSON sockets, WebSocket, stdio, `connectToHub`, endpoint-URI parsing |
|
|
353
|
+
| `@hediet/linkrpc/web` | `WindowMessageTransport` (iframe / web worker) |
|
|
354
|
+
| `@hediet/linkrpc/hub/common`, `/hub/client` | hub-facing interfaces and client helpers |
|
|
355
|
+
|
|
356
|
+
On Node, a single call resolves an endpoint from the environment and dials it:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
import { connectToHub } from '@hediet/linkrpc/node';
|
|
360
|
+
import { directoryInterface } from '@hediet/linkrpc';
|
|
361
|
+
|
|
362
|
+
// reads LINKRPC_ENDPOINT (unix:/npipe:/ws:/wss:/cmd:) and LINKRPC_TOKEN
|
|
363
|
+
const hub = await connectToHub();
|
|
364
|
+
const dir = hub.connection.get(directoryInterface);
|
|
365
|
+
console.log(await dir.list({}));
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Companion packages
|
|
369
|
+
|
|
370
|
+
| Package | Role |
|
|
371
|
+
|---|---|
|
|
372
|
+
| **`@hediet/linkrpc`** (this package) | the core library |
|
|
373
|
+
| [`@hediet/linkrpc-cli`](../linkrpc-cli) | generic CLI / terminal UI for any endpoint |
|
|
374
|
+
| [`@hediet/linkrpc-hub`](../linkrpc-hub) | standalone WebSocket hub that routes between participants |
|
|
375
|
+
|
|
376
|
+
## Development
|
|
377
|
+
|
|
378
|
+
```sh
|
|
379
|
+
pnpm --filter @hediet/linkrpc build
|
|
380
|
+
pnpm --filter @hediet/linkrpc test
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`zod@^4` is a peer dependency.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
//#region \0empty-crypto
|
|
6
|
+
const webcrypto = void 0;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { webcrypto };
|