bunite-core 0.17.1 → 0.17.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/src/rpc/peer.ts CHANGED
@@ -1,50 +1,50 @@
1
1
  import {
2
- type CapDef,
3
- type Schema,
4
- type SchemaRoots,
2
+ type AlreadyExistsReason,
3
+ type FailedPreconditionReason,
4
+ type IpcCode,
5
+ IpcError,
6
+ type IpcStatus,
7
+ type ResourceExhaustedReason,
8
+ } from "./error";
9
+ import { frameworkTypeIdOf, RuntimeCap } from "./framework";
10
+ import {
11
+ type AnyCapDef,
12
+ type AnyCapToken,
13
+ type Attestation,
14
+ type CallCtx,
5
15
  type ClientOf,
16
+ type DisposalSpec,
17
+ type ExportedCap,
6
18
  type ImplOf,
7
19
  type ImplsOf,
8
- type CallCtx,
9
- type Attestation,
10
- type ExportedCap,
11
- type MethodDef,
12
- type AnyCapToken,
13
- type Stream as StreamType,
14
- type DisposalSpec,
15
20
  isCallDef,
16
- isStreamDef,
17
- isCapRef,
18
21
  isCapArray,
19
- isCapRecord,
20
22
  isCapDef,
23
+ isCapRecord,
24
+ isCapRef,
21
25
  isSchema,
26
+ isStreamDef,
27
+ type MethodDef,
28
+ type Schema,
29
+ type SchemaRoots,
30
+ type Stream as StreamType,
22
31
  } from "./schema";
23
- import { RuntimeCap, frameworkTypeIdOf } from "./framework";
24
32
  import {
25
- type Frame,
33
+ BOOTSTRAP_METHOD,
26
34
  type CallFrame,
27
- type ResultFrame,
28
- type StreamFrame,
29
- type CancelFrame,
30
- type DropFrame,
31
- type HelloFrame,
32
- type CapRevokedFrame,
33
35
  type CallMeta,
36
+ type CancelFrame,
34
37
  CapRef,
38
+ type CapRevokedFrame,
35
39
  DEFAULT_MAX_BYTES,
36
- PROTOCOL_VERSION,
40
+ type DropFrame,
37
41
  FRAMEWORK_NAME_PREFIX,
38
- BOOTSTRAP_METHOD,
42
+ type Frame,
43
+ type HelloFrame,
44
+ PROTOCOL_VERSION,
45
+ type ResultFrame,
46
+ type StreamFrame,
39
47
  } from "./wire";
40
- import {
41
- IpcError,
42
- type IpcStatus,
43
- type IpcCode,
44
- type FailedPreconditionReason,
45
- type ResourceExhaustedReason,
46
- type AlreadyExistsReason,
47
- } from "./error";
48
48
 
49
49
  export const USER_ROOTS_CAP_ID = 0;
50
50
  export const RUNTIME_CAP_ID = 1;
@@ -90,7 +90,7 @@ export function _setCallContextStorage(als: CallContextStorage | null): void {
90
90
  export interface CapTableEntry {
91
91
  capId: number;
92
92
  typeId: number;
93
- cap: CapDef<any, any> | null;
93
+ cap: AnyCapDef | null;
94
94
  impl: unknown;
95
95
  refCount: number;
96
96
  }
@@ -158,10 +158,16 @@ export class CapTable {
158
158
  }
159
159
  }
160
160
 
161
+ export interface CloseInfo {
162
+ code?: number;
163
+ reason?: string;
164
+ }
165
+
161
166
  export interface Transport {
162
167
  send(frame: Frame): void;
163
168
  setReceive(handler: (frame: Frame) => void): void;
164
169
  close(): void;
170
+ onClose?(handler: (info?: CloseInfo) => void): void;
165
171
  }
166
172
 
167
173
  export type Policy = (name: string, attestation: Attestation) => boolean | Promise<boolean>;
@@ -173,26 +179,60 @@ export interface ServeHandle extends Disposable {
173
179
  }
174
180
 
175
181
  export interface ConnectionEvents {
176
- bootstrap: { name: string; version?: string; attestation: Attestation; result: "ok" | "denied" | "not_found" | "version_mismatch" | "invalid_argument" | "resource_exhausted" | "internal"; capId?: number };
177
- call: { capId: number; capName?: string; method: string; callId: number; durationMs?: number; result: "ok" | "cancelled" | IpcCode };
178
- stream: { capId: number; capName?: string; method: string; callId: number; event: "start" | "end" | "cancel" | "error"; count?: number };
182
+ bootstrap: {
183
+ name: string;
184
+ version?: string;
185
+ attestation: Attestation;
186
+ result:
187
+ | "ok"
188
+ | "denied"
189
+ | "not_found"
190
+ | "version_mismatch"
191
+ | "invalid_argument"
192
+ | "resource_exhausted"
193
+ | "internal";
194
+ capId?: number;
195
+ };
196
+ call: {
197
+ capId: number;
198
+ capName?: string;
199
+ method: string;
200
+ callId: number;
201
+ durationMs?: number;
202
+ result: "ok" | "cancelled" | IpcCode;
203
+ };
204
+ stream: {
205
+ capId: number;
206
+ capName?: string;
207
+ method: string;
208
+ callId: number;
209
+ event: "start" | "end" | "cancel" | "error";
210
+ count?: number;
211
+ };
179
212
  revoke: { capIds: number[]; reason: "unserve" | "replace" };
180
213
  error: { phase: string; error: Error };
181
214
  }
182
215
 
183
216
  export interface Connection {
184
- bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
217
+ bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
185
218
  bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
186
- serve<C extends CapDef<any, any>>(cap: C, impl: ImplOf<C>, opts?: { ifExists?: IfExists }): ServeHandle;
187
- serveAll<R extends SchemaRoots>(schema: Schema<R>, impls: ImplsOf<R>, opts?: { ifExists?: IfExists }): ServeHandle;
188
- unserve(target: CapDef<any, any> | ServeHandle): void;
189
- replace<C extends CapDef<any, any>>(cap: C, impl: ImplOf<C>): void;
219
+ serve<C extends AnyCapDef>(cap: C, impl: ImplOf<C>, opts?: { ifExists?: IfExists }): ServeHandle;
220
+ serveAll<R extends SchemaRoots>(
221
+ schema: Schema<R>,
222
+ impls: ImplsOf<R>,
223
+ opts?: { ifExists?: IfExists },
224
+ ): ServeHandle;
225
+ unserve(target: AnyCapDef | ServeHandle): void;
226
+ replace<C extends AnyCapDef>(cap: C, impl: ImplOf<C>): void;
190
227
  runtime(): ClientOf<typeof RuntimeCap>;
191
228
  releaseRef(proxy: unknown): void;
192
- on<K extends keyof ConnectionEvents>(event: K, handler: (e: ConnectionEvents[K]) => void): () => void;
193
- onClose(handler: () => void): () => void;
229
+ on<K extends keyof ConnectionEvents>(
230
+ event: K,
231
+ handler: (e: ConnectionEvents[K]) => void,
232
+ ): () => void;
233
+ onClose(handler: (info?: CloseInfo) => void): () => void;
194
234
  /** Tear down the connection — rejects pending, fires onClose, closes transport. Reliable signal for application lifecycle. */
195
- shutdown(reason?: string): void;
235
+ shutdown(reason?: string, info?: CloseInfo): void;
196
236
  readonly closed: boolean;
197
237
  }
198
238
 
@@ -244,14 +284,19 @@ function isExportedCap(v: unknown): v is ExportedCap<any> {
244
284
  return typeof v === "object" && v !== null && (v as any)[EXPORTED_CAP_BRAND] === true;
245
285
  }
246
286
 
247
- const proxyFinalizers = typeof FinalizationRegistry !== "undefined"
248
- ? new FinalizationRegistry<{ connRef: WeakRef<ConnectionImpl>; capId: number; dropped: () => boolean }>((held) => {
249
- if (held.dropped()) return;
250
- const conn = held.connRef.deref();
251
- if (!conn || conn.closed) return;
252
- conn._dropFromFinalizer(held.capId);
253
- })
254
- : ({ register: () => {} } as { register: (target: object, held: unknown) => void });
287
+ const proxyFinalizers =
288
+ typeof FinalizationRegistry !== "undefined"
289
+ ? new FinalizationRegistry<{
290
+ connRef: WeakRef<ConnectionImpl>;
291
+ capId: number;
292
+ dropped: () => boolean;
293
+ }>((held) => {
294
+ if (held.dropped()) return;
295
+ const conn = held.connRef.deref();
296
+ if (!conn || conn.closed) return;
297
+ conn._dropFromFinalizer(held.capId);
298
+ })
299
+ : ({ register: () => {} } as { register: (target: object, held: unknown) => void });
255
300
 
256
301
  interface ServerStreamCtx {
257
302
  iter: AsyncIterator<unknown> | null;
@@ -274,7 +319,7 @@ interface ClientStreamCtx {
274
319
  }
275
320
 
276
321
  interface RegistryEntry {
277
- cap: CapDef<any, any>;
322
+ cap: AnyCapDef;
278
323
  impl: unknown;
279
324
  version?: string;
280
325
  }
@@ -286,15 +331,20 @@ class ConnectionImpl implements Connection {
286
331
  private readonly clientStreams = new Map<number, ClientStreamCtx>();
287
332
  private readonly serverStreams = new Map<number, ServerStreamCtx>();
288
333
  private readonly serverCallChildren = new Map<number, { parentId: number }>();
289
- private readonly serverActiveCalls = new Map<number, { ctrl: AbortController; capId: number; capName: string; method: string; startedAt: number }>();
334
+ private readonly serverActiveCalls = new Map<
335
+ number,
336
+ { ctrl: AbortController; capId: number; capName: string; method: string; startedAt: number }
337
+ >();
290
338
  /** Server-side: name → registry entry. */
291
339
  private readonly registry = new Map<string, RegistryEntry>();
292
340
  /** Server-side: name → instance cap-id (cached bootstrap result). */
293
341
  private readonly rootInstances = new Map<string, number>();
294
342
  /** Client-side: cap-ids that the server revoked via cap_revoked. */
295
343
  private readonly revokedCapIds = new Set<number>();
296
- private readonly closeHandlers = new Set<() => void>();
297
- private readonly observers: { [K in keyof ConnectionEvents]?: Set<(e: ConnectionEvents[K]) => void> } = {};
344
+ private readonly closeHandlers = new Set<(info?: CloseInfo) => void>();
345
+ private readonly observers: {
346
+ [K in keyof ConnectionEvents]?: Set<(e: ConnectionEvents[K]) => void>;
347
+ } = {};
298
348
  private nextCallId = 1;
299
349
  private remoteHello: HelloFrame | null = null;
300
350
  private readonly remoteReady: Promise<HelloFrame>;
@@ -344,6 +394,7 @@ class ConnectionImpl implements Connection {
344
394
  this.remoteReady.catch(() => {});
345
395
 
346
396
  this.transport.setReceive((frame) => this.handleFrame(frame));
397
+ this.transport.onClose?.((info) => this.shutdown(info?.reason || "transport_closed", info));
347
398
  this.transport.send({
348
399
  op: "hello",
349
400
  v: PROTOCOL_VERSION,
@@ -358,26 +409,35 @@ class ConnectionImpl implements Connection {
358
409
  return this.closed_;
359
410
  }
360
411
 
361
- onClose(handler: () => void): () => void {
412
+ onClose(handler: (info?: CloseInfo) => void): () => void {
362
413
  this.closeHandlers.add(handler);
363
414
  return () => this.closeHandlers.delete(handler);
364
415
  }
365
416
 
366
- on<K extends keyof ConnectionEvents>(event: K, handler: (e: ConnectionEvents[K]) => void): () => void {
417
+ on<K extends keyof ConnectionEvents>(
418
+ event: K,
419
+ handler: (e: ConnectionEvents[K]) => void,
420
+ ): () => void {
367
421
  let set = this.observers[event] as Set<(e: ConnectionEvents[K]) => void> | undefined;
368
422
  if (!set) {
369
423
  set = new Set();
370
424
  (this.observers as Record<string, Set<unknown>>)[event] = set as unknown as Set<unknown>;
371
425
  }
372
426
  set.add(handler);
373
- return () => { set!.delete(handler); };
427
+ return () => {
428
+ set!.delete(handler);
429
+ };
374
430
  }
375
431
 
376
432
  private emitObs<K extends keyof ConnectionEvents>(event: K, data: ConnectionEvents[K]): void {
377
433
  const set = this.observers[event] as Set<(e: ConnectionEvents[K]) => void> | undefined;
378
434
  if (!set || set.size === 0) return;
379
435
  for (const h of set) {
380
- try { h(data); } catch { /* swallow */ }
436
+ try {
437
+ h(data);
438
+ } catch {
439
+ /* swallow */
440
+ }
381
441
  }
382
442
  }
383
443
 
@@ -393,7 +453,7 @@ class ConnectionImpl implements Connection {
393
453
 
394
454
  // ---- serve / unserve / replace ----
395
455
 
396
- serve<C extends CapDef<any, any>>(cap: C, impl: ImplOf<C>, opts?: { ifExists?: IfExists }): ServeHandle {
456
+ serve<C extends AnyCapDef>(cap: C, impl: ImplOf<C>, opts?: { ifExists?: IfExists }): ServeHandle {
397
457
  this.assertNotFrameworkName(cap.name);
398
458
  const ifExists = opts?.ifExists ?? "throw";
399
459
  const existing = this.registry.get(cap.name);
@@ -425,7 +485,11 @@ class ConnectionImpl implements Connection {
425
485
  return handle;
426
486
  }
427
487
 
428
- serveAll<R extends SchemaRoots>(schema: Schema<R>, impls: ImplsOf<R>, opts?: { ifExists?: IfExists }): ServeHandle {
488
+ serveAll<R extends SchemaRoots>(
489
+ schema: Schema<R>,
490
+ impls: ImplsOf<R>,
491
+ opts?: { ifExists?: IfExists },
492
+ ): ServeHandle {
429
493
  const ifExists = opts?.ifExists ?? "throw";
430
494
  // Pre-validate everything that can fail across all modes before mutating any state (atomicity).
431
495
  for (const k of Object.keys(schema.roots)) {
@@ -461,7 +525,7 @@ class ConnectionImpl implements Connection {
461
525
  return this.makeHandle(names);
462
526
  }
463
527
 
464
- unserve(target: CapDef<any, any> | ServeHandle): void {
528
+ unserve(target: AnyCapDef | ServeHandle): void {
465
529
  const names = isCapDef(target) ? [target.name] : Array.from((target as ServeHandle).names);
466
530
  const revoked: number[] = [];
467
531
  for (const name of names) {
@@ -481,7 +545,7 @@ class ConnectionImpl implements Connection {
481
545
  }
482
546
  }
483
547
 
484
- replace<C extends CapDef<any, any>>(cap: C, impl: ImplOf<C>): void {
548
+ replace<C extends AnyCapDef>(cap: C, impl: ImplOf<C>): void {
485
549
  const entry = this.registry.get(cap.name);
486
550
  if (!entry) throw new IpcError({ code: "not_found", message: `cap "${cap.name}" not served` });
487
551
  if (entry.version !== cap.version) {
@@ -496,7 +560,10 @@ class ConnectionImpl implements Connection {
496
560
  const instId = this.rootInstances.get(cap.name);
497
561
  if (instId !== undefined) {
498
562
  const e = this.capTable.get(instId);
499
- if (e) { e.impl = impl; e.cap = cap; }
563
+ if (e) {
564
+ e.impl = impl;
565
+ e.cap = cap;
566
+ }
500
567
  }
501
568
  this.emitObs("revoke", { capIds: instId !== undefined ? [instId] : [], reason: "replace" });
502
569
  }
@@ -522,27 +589,33 @@ class ConnectionImpl implements Connection {
522
589
  return this.runtimeProxy;
523
590
  }
524
591
 
525
- bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
592
+ bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
526
593
  bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
527
- async bootstrap(target: CapDef<any, any> | Schema<any>): Promise<unknown> {
594
+ async bootstrap(target: AnyCapDef | Schema<any>): Promise<unknown> {
528
595
  if (isCapDef(target)) return this._bootstrapCap(target);
529
596
  if (isSchema(target)) return this._bootstrapSchema(target);
530
- throw new IpcError({ code: "invalid_argument", message: "bootstrap target must be CapDef or Schema" });
597
+ throw new IpcError({
598
+ code: "invalid_argument",
599
+ message: "bootstrap target must be CapDef or Schema",
600
+ });
531
601
  }
532
602
 
533
- private async _bootstrapCap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>> {
603
+ private async _bootstrapCap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>> {
534
604
  await this.remoteReady;
535
605
  const args: { name: string; version?: string } = { name: cap.name };
536
606
  if (cap.version != null) args.version = cap.version;
537
607
  const raw = await this.sendCallTyped(USER_ROOTS_CAP_ID, BOOTSTRAP_METHOD, args, undefined);
538
608
  if (!(raw instanceof CapRef)) {
539
- throw new IpcError({ code: "invalid_argument", message: "bootstrap did not return a CapRef" });
609
+ throw new IpcError({
610
+ code: "invalid_argument",
611
+ message: "bootstrap did not return a CapRef",
612
+ });
540
613
  }
541
614
  return this.makeCapProxy(cap, raw.capId) as ClientOf<C>;
542
615
  }
543
616
 
544
617
  private async _bootstrapSchema<R extends SchemaRoots>(
545
- schema: Schema<R>
618
+ schema: Schema<R>,
546
619
  ): Promise<{ [K in keyof R]: ClientOf<R[K]> }> {
547
620
  const keys = Object.keys(schema.roots) as (keyof R & string)[];
548
621
  const settled = await Promise.allSettled(keys.map((k) => this._bootstrapCap(schema.roots[k])));
@@ -551,14 +624,20 @@ class ConnectionImpl implements Connection {
551
624
  // Release server refCount on the roots that succeeded — otherwise their cap-table entries linger until connection close.
552
625
  for (const r of settled) {
553
626
  if (r.status === "fulfilled") {
554
- try { this.releaseRef(r.value); } catch { /* swallow */ }
627
+ try {
628
+ this.releaseRef(r.value);
629
+ } catch {
630
+ /* swallow */
631
+ }
555
632
  }
556
633
  }
557
634
  throw rejected.reason;
558
635
  }
559
636
  const out = {} as { [K in keyof R]: ClientOf<R[K]> };
560
637
  for (let i = 0; i < keys.length; i++) {
561
- (out as Record<string, unknown>)[keys[i]] = (settled[i] as PromiseFulfilledResult<unknown>).value;
638
+ (out as Record<string, unknown>)[keys[i]] = (
639
+ settled[i] as PromiseFulfilledResult<unknown>
640
+ ).value;
562
641
  }
563
642
  return out;
564
643
  }
@@ -601,10 +680,19 @@ class ConnectionImpl implements Connection {
601
680
  private handleUnknownFrame(frame: unknown): void {
602
681
  const id = (frame as { id?: unknown })?.id;
603
682
  if (typeof id === "number") {
604
- this.transport.send({ op: "result", id, ok: false, error: { code: "invalid_argument", message: "unknown opcode" } });
683
+ this.transport.send({
684
+ op: "result",
685
+ id,
686
+ ok: false,
687
+ error: { code: "invalid_argument", message: "unknown opcode" },
688
+ });
605
689
  return;
606
690
  }
607
- this.transport.send({ op: "goaway", reason: "invalid_argument", error: { code: "invalid_argument", message: "unknown opcode" } });
691
+ this.transport.send({
692
+ op: "goaway",
693
+ reason: "invalid_argument",
694
+ error: { code: "invalid_argument", message: "unknown opcode" },
695
+ });
608
696
  this.shutdown("invalid_argument");
609
697
  }
610
698
 
@@ -615,7 +703,7 @@ class ConnectionImpl implements Connection {
615
703
 
616
704
  private handleGoaway(frame: Extract<Frame, { op: "goaway" }>): void {
617
705
  this.rejectRemoteReady(
618
- new IpcError(frame.error ?? { code: "unavailable", message: frame.reason ?? "peer goaway" })
706
+ new IpcError(frame.error ?? { code: "unavailable", message: frame.reason ?? "peer goaway" }),
619
707
  );
620
708
  this.shutdown(frame.reason ?? "remote goaway");
621
709
  }
@@ -654,30 +742,58 @@ class ConnectionImpl implements Connection {
654
742
 
655
743
  const entry = this.capTable.get(frame.target.id);
656
744
  if (!entry) {
657
- this.emitObs("call", { capId: frame.target.id, method: frame.method, callId: frame.id, result: "not_found" });
745
+ this.emitObs("call", {
746
+ capId: frame.target.id,
747
+ method: frame.method,
748
+ callId: frame.id,
749
+ result: "not_found",
750
+ });
658
751
  return this.sendError(frame.id, "not_found", `cap-id ${frame.target.id} not found`);
659
752
  }
660
753
 
661
754
  const cap = entry.cap;
662
755
  if (!cap || !entry.impl) {
663
- this.emitObs("call", { capId: frame.target.id, method: frame.method, callId: frame.id, result: "not_found" });
756
+ this.emitObs("call", {
757
+ capId: frame.target.id,
758
+ method: frame.method,
759
+ callId: frame.id,
760
+ result: "not_found",
761
+ });
664
762
  return this.sendError(frame.id, "not_found", "cap has no impl");
665
763
  }
666
764
 
667
765
  const methodDef = cap.methods[frame.method] as MethodDef | undefined;
668
766
  if (!methodDef) {
669
- this.emitObs("call", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, result: "not_found" });
767
+ this.emitObs("call", {
768
+ capId: frame.target.id,
769
+ capName: cap.name,
770
+ method: frame.method,
771
+ callId: frame.id,
772
+ result: "not_found",
773
+ });
670
774
  return this.sendError(frame.id, "not_found", `method "${frame.method}" on cap "${cap.name}"`);
671
775
  }
672
776
  const impl = (entry.impl as Record<string, unknown>)[frame.method];
673
777
  if (typeof impl !== "function") {
674
- this.emitObs("call", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, result: "not_found" });
778
+ this.emitObs("call", {
779
+ capId: frame.target.id,
780
+ capName: cap.name,
781
+ method: frame.method,
782
+ callId: frame.id,
783
+ result: "not_found",
784
+ });
675
785
  return this.sendError(frame.id, "not_found", `method "${frame.method}" has no handler`);
676
786
  }
677
787
 
678
788
  // Bound inbound work — symmetric with sendCallTyped's outbound check.
679
789
  if (this.serverActiveCalls.size + this.serverStreams.size >= this.maxInFlightCalls) {
680
- this.emitObs("call", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, result: "resource_exhausted" });
790
+ this.emitObs("call", {
791
+ capId: frame.target.id,
792
+ capName: cap.name,
793
+ method: frame.method,
794
+ callId: frame.id,
795
+ result: "resource_exhausted",
796
+ });
681
797
  return this.sendError(
682
798
  frame.id,
683
799
  "resource_exhausted",
@@ -686,14 +802,23 @@ class ConnectionImpl implements Connection {
686
802
  );
687
803
  }
688
804
 
689
- await this.invokeServerMethod(frame, cap, methodDef, impl as (params: unknown, ctx: CallCtx) => unknown);
805
+ await this.invokeServerMethod(
806
+ frame,
807
+ cap,
808
+ methodDef,
809
+ impl as (params: unknown, ctx: CallCtx) => unknown,
810
+ );
690
811
  }
691
812
 
692
813
  private async handleBootstrap(frame: CallFrame): Promise<void> {
693
814
  const args = (frame.args ?? {}) as { name?: unknown; version?: unknown };
694
815
  const name = args.name;
695
816
  if (typeof name !== "string") {
696
- this.emitObs("bootstrap", { name: String(name), attestation: this.attestation, result: "invalid_argument" });
817
+ this.emitObs("bootstrap", {
818
+ name: String(name),
819
+ attestation: this.attestation,
820
+ result: "invalid_argument",
821
+ });
697
822
  return this.sendError(frame.id, "invalid_argument", "bootstrap requires {name: string}");
698
823
  }
699
824
  const clientVersion = args.version != null ? String(args.version) : undefined;
@@ -702,18 +827,28 @@ class ConnectionImpl implements Connection {
702
827
  // not via bootstrap). For user-facing bootstrap, only the registry is consulted.
703
828
  const entry = this.registry.get(name);
704
829
  if (!entry) {
705
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "not_found" });
830
+ this.emitObs("bootstrap", {
831
+ name,
832
+ version: clientVersion,
833
+ attestation: this.attestation,
834
+ result: "not_found",
835
+ });
706
836
  return this.sendError(frame.id, "not_found", `cap "${name}" not served`);
707
837
  }
708
838
 
709
839
  const serverVersion = entry.version;
710
840
  if (serverVersion != null && clientVersion != null && serverVersion !== clientVersion) {
711
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "version_mismatch" });
841
+ this.emitObs("bootstrap", {
842
+ name,
843
+ version: clientVersion,
844
+ attestation: this.attestation,
845
+ result: "version_mismatch",
846
+ });
712
847
  return this.sendError(
713
848
  frame.id,
714
849
  "failed_precondition",
715
850
  `version mismatch (server "${serverVersion}", client "${clientVersion}")`,
716
- { reason: "version_mismatch" as FailedPreconditionReason }
851
+ { reason: "version_mismatch" as FailedPreconditionReason },
717
852
  );
718
853
  }
719
854
 
@@ -722,24 +857,42 @@ class ConnectionImpl implements Connection {
722
857
  try {
723
858
  allowed = await this.policy(name, this.attestation);
724
859
  } catch (err) {
725
- this.emitObs("error", { phase: "policy", error: err instanceof Error ? err : new Error(String(err)) });
726
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "internal" });
860
+ this.emitObs("error", {
861
+ phase: "policy",
862
+ error: err instanceof Error ? err : new Error(String(err)),
863
+ });
864
+ this.emitObs("bootstrap", {
865
+ name,
866
+ version: clientVersion,
867
+ attestation: this.attestation,
868
+ result: "internal",
869
+ });
727
870
  return this.sendError(frame.id, "internal", "policy threw");
728
871
  }
729
872
  if (typeof allowed !== "boolean") {
730
873
  // Non-boolean return = programming bug; surface explicitly (not silent deny).
731
- this.emitObs("error", { phase: "policy", error: new Error(`policy must return boolean (got ${typeof allowed})`) });
732
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "internal" });
874
+ this.emitObs("error", {
875
+ phase: "policy",
876
+ error: new Error(`policy must return boolean (got ${typeof allowed})`),
877
+ });
878
+ this.emitObs("bootstrap", {
879
+ name,
880
+ version: clientVersion,
881
+ attestation: this.attestation,
882
+ result: "internal",
883
+ });
733
884
  return this.sendError(frame.id, "internal", "policy returned non-boolean");
734
885
  }
735
886
  if (!allowed) {
736
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "denied" });
737
- return this.sendError(
738
- frame.id,
739
- "failed_precondition",
740
- "policy denied",
741
- { reason: "unauthorized" as FailedPreconditionReason }
742
- );
887
+ this.emitObs("bootstrap", {
888
+ name,
889
+ version: clientVersion,
890
+ attestation: this.attestation,
891
+ result: "denied",
892
+ });
893
+ return this.sendError(frame.id, "failed_precondition", "policy denied", {
894
+ reason: "unauthorized" as FailedPreconditionReason,
895
+ });
743
896
  }
744
897
  }
745
898
 
@@ -766,21 +919,37 @@ class ConnectionImpl implements Connection {
766
919
  } catch (err) {
767
920
  if (err instanceof IpcError) {
768
921
  const result = err.code === "resource_exhausted" ? "resource_exhausted" : "internal";
769
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result });
770
- return this.sendError(frame.id, err.code, err.message, err.details as Record<string, unknown> | undefined);
922
+ this.emitObs("bootstrap", {
923
+ name,
924
+ version: clientVersion,
925
+ attestation: this.attestation,
926
+ result,
927
+ });
928
+ return this.sendError(
929
+ frame.id,
930
+ err.code,
931
+ err.message,
932
+ err.details as Record<string, unknown> | undefined,
933
+ );
771
934
  }
772
935
  throw err;
773
936
  }
774
937
  }
775
- this.emitObs("bootstrap", { name, version: clientVersion, attestation: this.attestation, result: "ok", capId });
938
+ this.emitObs("bootstrap", {
939
+ name,
940
+ version: clientVersion,
941
+ attestation: this.attestation,
942
+ result: "ok",
943
+ capId,
944
+ });
776
945
  this.transport.send({ op: "result", id: frame.id, ok: true, value: new CapRef(capId) });
777
946
  }
778
947
 
779
948
  private async invokeServerMethod(
780
949
  frame: CallFrame,
781
- cap: CapDef<any, any>,
950
+ cap: AnyCapDef,
782
951
  methodDef: MethodDef,
783
- impl: (params: unknown, ctx: CallCtx) => unknown
952
+ impl: (params: unknown, ctx: CallCtx) => unknown,
784
953
  ): Promise<void> {
785
954
  const ctx = this.makeCallCtx(frame);
786
955
 
@@ -789,13 +958,17 @@ class ConnectionImpl implements Connection {
789
958
  try {
790
959
  const runStream = () => impl(frame.args, ctx) as StreamType<unknown>;
791
960
  const als = callContextStorage;
792
- const scoped = als
793
- ? () => als.run({ callId: frame.id }, runStream)
794
- : runStream;
961
+ const scoped = als ? () => als.run({ callId: frame.id }, runStream) : runStream;
795
962
  const stream = scoped();
796
963
  this.runServerStream(frame.id, stream, ctx, hintBudget, cap, frame.method, frame.target.id);
797
964
  } catch (err) {
798
- this.emitObs("stream", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, event: "error" });
965
+ this.emitObs("stream", {
966
+ capId: frame.target.id,
967
+ capName: cap.name,
968
+ method: frame.method,
969
+ callId: frame.id,
970
+ event: "error",
971
+ });
799
972
  this.transport.send({ op: "stream", id: frame.id, ev: "error", error: toStatus(err) });
800
973
  }
801
974
  return;
@@ -814,22 +987,34 @@ class ConnectionImpl implements Connection {
814
987
  const encoder = makeReturnEncoder(methodDef.returns);
815
988
  const runImpl = () => Promise.resolve(impl(frame.args, ctx));
816
989
  const als = callContextStorage;
817
- const scoped = als
818
- ? () => als.run({ callId: frame.id }, runImpl)
819
- : runImpl;
990
+ const scoped = als ? () => als.run({ callId: frame.id }, runImpl) : runImpl;
820
991
  try {
821
992
  const result = await scoped();
822
993
  if (deadlineTimer) clearTimeout(deadlineTimer);
823
994
  if (!this.serverActiveCalls.delete(frame.id)) return;
824
995
  const encoded = encoder(result);
825
996
  this.transport.send({ op: "result", id: frame.id, ok: true, value: encoded });
826
- this.emitObs("call", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, durationMs: performance.now() - startedAt, result: "ok" });
997
+ this.emitObs("call", {
998
+ capId: frame.target.id,
999
+ capName: cap.name,
1000
+ method: frame.method,
1001
+ callId: frame.id,
1002
+ durationMs: performance.now() - startedAt,
1003
+ result: "ok",
1004
+ });
827
1005
  } catch (err) {
828
1006
  if (deadlineTimer) clearTimeout(deadlineTimer);
829
1007
  if (!this.serverActiveCalls.delete(frame.id)) return;
830
1008
  const status = toStatus(err);
831
1009
  this.sendErrorFromStatus(frame.id, status);
832
- this.emitObs("call", { capId: frame.target.id, capName: cap.name, method: frame.method, callId: frame.id, durationMs: performance.now() - startedAt, result: status.code });
1010
+ this.emitObs("call", {
1011
+ capId: frame.target.id,
1012
+ capName: cap.name,
1013
+ method: frame.method,
1014
+ callId: frame.id,
1015
+ durationMs: performance.now() - startedAt,
1016
+ result: status.code,
1017
+ });
833
1018
  }
834
1019
  return;
835
1020
  }
@@ -837,7 +1022,10 @@ class ConnectionImpl implements Connection {
837
1022
  this.sendError(frame.id, "not_found", "unknown method kind");
838
1023
  }
839
1024
 
840
- private armServerDeadline(frame: CallFrame, ctx: CallCtx): ReturnType<typeof setTimeout> | undefined {
1025
+ private armServerDeadline(
1026
+ frame: CallFrame,
1027
+ ctx: CallCtx,
1028
+ ): ReturnType<typeof setTimeout> | undefined {
841
1029
  const ms = frame.meta?.deadlineMs;
842
1030
  if (!ms) return;
843
1031
  return setTimeout(() => {
@@ -859,7 +1047,7 @@ class ConnectionImpl implements Connection {
859
1047
  return ctx;
860
1048
  }
861
1049
 
862
- private exportCap<C extends CapDef<any, any>>(capDef: C, impl: unknown): ExportedCap<C> {
1050
+ private exportCap<C extends AnyCapDef>(capDef: C, impl: unknown): ExportedCap<C> {
863
1051
  const typeId = frameworkTypeIdOf(capDef) ?? FIRST_USER_TYPE_ID;
864
1052
  const entry = this.capTable.allocate({
865
1053
  typeId,
@@ -880,7 +1068,7 @@ class ConnectionImpl implements Connection {
880
1068
  stream: StreamType<unknown>,
881
1069
  ctx: CallCtx,
882
1070
  initialCredit: number,
883
- cap: CapDef<any, any>,
1071
+ cap: AnyCapDef,
884
1072
  method: string,
885
1073
  capId: number,
886
1074
  ): void {
@@ -903,7 +1091,9 @@ class ConnectionImpl implements Connection {
903
1091
 
904
1092
  const waitForCredit = (): Promise<void> => {
905
1093
  if (slot.credit > 0 || slot.cancelled) return Promise.resolve();
906
- return new Promise<void>((resolve) => { slot.creditWaker = resolve; });
1094
+ return new Promise<void>((resolve) => {
1095
+ slot.creditWaker = resolve;
1096
+ });
907
1097
  };
908
1098
 
909
1099
  const drain = async () => {
@@ -922,11 +1112,25 @@ class ConnectionImpl implements Connection {
922
1112
  } catch (err) {
923
1113
  errored = true;
924
1114
  this.transport.send({ op: "stream", id: callId, ev: "error", error: toStatus(err) });
925
- this.emitObs("stream", { capId, capName: cap.name, method, callId, event: "error", count: slot.count });
1115
+ this.emitObs("stream", {
1116
+ capId,
1117
+ capName: cap.name,
1118
+ method,
1119
+ callId,
1120
+ event: "error",
1121
+ count: slot.count,
1122
+ });
926
1123
  } finally {
927
1124
  if (!errored) {
928
1125
  this.transport.send({ op: "stream", id: callId, ev: "end" });
929
- this.emitObs("stream", { capId, capName: cap.name, method, callId, event: slot.cancelled ? "cancel" : "end", count: slot.count });
1126
+ this.emitObs("stream", {
1127
+ capId,
1128
+ capName: cap.name,
1129
+ method,
1130
+ callId,
1131
+ event: slot.cancelled ? "cancel" : "end",
1132
+ count: slot.count,
1133
+ });
930
1134
  }
931
1135
  this.serverStreams.delete(callId);
932
1136
  }
@@ -996,7 +1200,9 @@ class ConnectionImpl implements Connection {
996
1200
 
997
1201
  releaseRef(proxy: unknown): void {
998
1202
  if (typeof proxy !== "object" || proxy === null) return;
999
- const meta = (proxy as Record<symbol, unknown>)[CAP_PROXY_META] as { capId: number; dropped: boolean } | undefined;
1203
+ const meta = (proxy as Record<symbol, unknown>)[CAP_PROXY_META] as
1204
+ | { capId: number; dropped: boolean }
1205
+ | undefined;
1000
1206
  if (!meta || meta.dropped) return;
1001
1207
  meta.dropped = true;
1002
1208
  this.sendDrop(meta.capId);
@@ -1040,7 +1246,10 @@ class ConnectionImpl implements Connection {
1040
1246
  const released = this.capTable.release(id, delta);
1041
1247
  if (released) {
1042
1248
  for (const [rootName, capId] of this.rootInstances) {
1043
- if (capId === id) { this.rootInstances.delete(rootName); break; }
1249
+ if (capId === id) {
1250
+ this.rootInstances.delete(rootName);
1251
+ break;
1252
+ }
1044
1253
  }
1045
1254
  }
1046
1255
  }
@@ -1068,28 +1277,38 @@ class ConnectionImpl implements Connection {
1068
1277
  meta?: CallMeta,
1069
1278
  capName?: string,
1070
1279
  ): Promise<unknown> {
1071
- if (this.closed_) return Promise.reject(new IpcError({ code: "unavailable", message: "connection closed" }));
1280
+ if (this.closed_)
1281
+ return Promise.reject(new IpcError({ code: "unavailable", message: "connection closed" }));
1072
1282
  if (this.revokedCapIds.has(capId)) {
1073
- return Promise.reject(new IpcError({
1074
- code: "failed_precondition",
1075
- message: "cap revoked",
1076
- details: { reason: "revoked" as FailedPreconditionReason },
1077
- }));
1283
+ return Promise.reject(
1284
+ new IpcError({
1285
+ code: "failed_precondition",
1286
+ message: "cap revoked",
1287
+ details: { reason: "revoked" as FailedPreconditionReason },
1288
+ }),
1289
+ );
1078
1290
  }
1079
1291
  if (this.pending.size >= this.maxInFlightCalls) {
1080
- return Promise.reject(new IpcError({
1081
- code: "resource_exhausted",
1082
- message: `in-flight calls limit ${this.maxInFlightCalls}`,
1083
- details: { reason: "max_concurrent_calls" as ResourceExhaustedReason },
1084
- }));
1292
+ return Promise.reject(
1293
+ new IpcError({
1294
+ code: "resource_exhausted",
1295
+ message: `in-flight calls limit ${this.maxInFlightCalls}`,
1296
+ details: { reason: "max_concurrent_calls" as ResourceExhaustedReason },
1297
+ }),
1298
+ );
1085
1299
  }
1086
1300
  const id = this.nextId();
1087
1301
  return new Promise((resolve, reject) => {
1088
1302
  const abort = new AbortController();
1089
1303
  const pending: PendingCall = {
1090
- resolve, reject, abort, decodeReturn,
1304
+ resolve,
1305
+ reject,
1306
+ abort,
1307
+ decodeReturn,
1091
1308
  startedAt: performance.now(),
1092
- capId, method, capName,
1309
+ capId,
1310
+ method,
1311
+ capName,
1093
1312
  kind: "call",
1094
1313
  };
1095
1314
  if (meta?.deadlineMs) {
@@ -1102,14 +1321,21 @@ class ConnectionImpl implements Connection {
1102
1321
  }
1103
1322
  this.pending.set(id, pending);
1104
1323
  let finalMeta = meta;
1105
- if ((finalMeta?.parentCallId === undefined) && callContextStorage) {
1324
+ if (finalMeta?.parentCallId === undefined && callContextStorage) {
1106
1325
  const scope = callContextStorage.getStore();
1107
1326
  if (scope) finalMeta = { ...(finalMeta ?? {}), parentCallId: scope.callId };
1108
1327
  }
1109
1328
  if (finalMeta?.parentCallId !== undefined) {
1110
1329
  this.serverCallChildren.set(id, { parentId: finalMeta.parentCallId });
1111
1330
  }
1112
- this.transport.send({ op: "call", id, target: { kind: "cap", id: capId }, method, args, meta: finalMeta });
1331
+ this.transport.send({
1332
+ op: "call",
1333
+ id,
1334
+ target: { kind: "cap", id: capId },
1335
+ method,
1336
+ args,
1337
+ meta: finalMeta,
1338
+ });
1113
1339
  });
1114
1340
  }
1115
1341
 
@@ -1121,17 +1347,29 @@ class ConnectionImpl implements Connection {
1121
1347
  capName?: string,
1122
1348
  ): StreamType<T> {
1123
1349
  if (this.closed_) {
1124
- const empty = makeClientStream<T>(capId, 0, () => {}, () => {});
1350
+ const empty = makeClientStream<T>(
1351
+ capId,
1352
+ 0,
1353
+ () => {},
1354
+ () => {},
1355
+ );
1125
1356
  empty.fail(new IpcError({ code: "unavailable", message: "connection closed" }));
1126
1357
  return empty.stream;
1127
1358
  }
1128
1359
  if (this.revokedCapIds.has(capId)) {
1129
- const empty = makeClientStream<T>(capId, 0, () => {}, () => {});
1130
- empty.fail(new IpcError({
1131
- code: "failed_precondition",
1132
- message: "cap revoked",
1133
- details: { reason: "revoked" as FailedPreconditionReason },
1134
- }));
1360
+ const empty = makeClientStream<T>(
1361
+ capId,
1362
+ 0,
1363
+ () => {},
1364
+ () => {},
1365
+ );
1366
+ empty.fail(
1367
+ new IpcError({
1368
+ code: "failed_precondition",
1369
+ message: "cap revoked",
1370
+ details: { reason: "revoked" as FailedPreconditionReason },
1371
+ }),
1372
+ );
1135
1373
  return empty.stream;
1136
1374
  }
1137
1375
  const id = this.nextId();
@@ -1151,7 +1389,7 @@ class ConnectionImpl implements Connection {
1151
1389
  return ctx.stream;
1152
1390
  }
1153
1391
 
1154
- makeCapProxy<C extends CapDef<any, any>>(capDef: C, capId: number): ClientOf<C> {
1392
+ makeCapProxy<C extends AnyCapDef>(capDef: C, capId: number): ClientOf<C> {
1155
1393
  const proxy: Record<string | symbol, unknown> = {};
1156
1394
  const meta = { capId, dropped: false };
1157
1395
  proxy[CAP_PROXY_META] = meta;
@@ -1164,26 +1402,33 @@ class ConnectionImpl implements Connection {
1164
1402
  for (const name of Object.keys(capDef.methods)) {
1165
1403
  const def = capDef.methods[name] as MethodDef;
1166
1404
  if (isStreamDef(def)) {
1167
- proxy[name] = (params?: unknown) => this.openClientStream(capId, name, params, undefined, capDef.name);
1405
+ proxy[name] = (params?: unknown) =>
1406
+ this.openClientStream(capId, name, params, undefined, capDef.name);
1168
1407
  } else if (isCallDef(def)) {
1169
1408
  const decoder = makeReturnDecoder(def.returns, (cd, cid) => this.makeCapProxy(cd, cid));
1170
- proxy[name] = (params?: unknown) => this.sendCallTyped(capId, name, params, decoder, undefined, capDef.name);
1409
+ proxy[name] = (params?: unknown) =>
1410
+ this.sendCallTyped(capId, name, params, decoder, undefined, capDef.name);
1171
1411
  }
1172
1412
  }
1173
1413
 
1174
1414
  const disposal = capDef.disposal as DisposalSpec | undefined;
1175
1415
  if (disposal) {
1176
- const conn = this;
1177
1416
  proxy[Symbol.dispose] = () => {
1178
1417
  try {
1179
1418
  const r = (proxy[disposal.method] as (() => unknown) | undefined)?.();
1180
1419
  if (r && typeof (r as Promise<unknown>).then === "function") {
1181
1420
  (r as Promise<unknown>).catch((err) =>
1182
- conn.emitObs("error", { phase: "dispose", error: err instanceof Error ? err : new Error(String(err)) })
1421
+ this.emitObs("error", {
1422
+ phase: "dispose",
1423
+ error: err instanceof Error ? err : new Error(String(err)),
1424
+ }),
1183
1425
  );
1184
1426
  }
1185
1427
  } catch (err) {
1186
- conn.emitObs("error", { phase: "dispose", error: err instanceof Error ? err : new Error(String(err)) });
1428
+ this.emitObs("error", {
1429
+ phase: "dispose",
1430
+ error: err instanceof Error ? err : new Error(String(err)),
1431
+ });
1187
1432
  }
1188
1433
  drop();
1189
1434
  };
@@ -1201,10 +1446,14 @@ class ConnectionImpl implements Connection {
1201
1446
  }
1202
1447
 
1203
1448
  _dropFromFinalizer(capId: number): void {
1204
- try { this.sendDrop(capId); } catch { /* swallow */ }
1449
+ try {
1450
+ this.sendDrop(capId);
1451
+ } catch {
1452
+ /* swallow */
1453
+ }
1205
1454
  }
1206
1455
 
1207
- shutdown(reason: string = "shutdown"): void {
1456
+ shutdown(reason: string = "shutdown", info?: CloseInfo): void {
1208
1457
  if (this.closed_) return;
1209
1458
  this.closed_ = true;
1210
1459
  for (const pending of this.pending.values()) {
@@ -1234,10 +1483,18 @@ class ConnectionImpl implements Connection {
1234
1483
  this.rejectRemoteReady(new IpcError({ code: "unavailable", message: reason }));
1235
1484
  }
1236
1485
  for (const fn of this.closeHandlers) {
1237
- try { fn(); } catch { /* swallow */ }
1486
+ try {
1487
+ fn(info);
1488
+ } catch {
1489
+ /* swallow */
1490
+ }
1238
1491
  }
1239
1492
  this.closeHandlers.clear();
1240
- try { this.transport.close(); } catch { /* swallow */ }
1493
+ try {
1494
+ this.transport.close();
1495
+ } catch {
1496
+ /* swallow */
1497
+ }
1241
1498
  }
1242
1499
 
1243
1500
  private invokeServerDisposal(entry: CapTableEntry): void {
@@ -1248,13 +1505,17 @@ class ConnectionImpl implements Connection {
1248
1505
  const impl = entry.impl as Record<string, unknown> | null;
1249
1506
  const fn = impl?.[disposal.method] as ((p: unknown, c?: unknown) => unknown) | undefined;
1250
1507
  if (!fn) return;
1251
- try { void fn.call(impl, undefined, undefined); } catch { /* swallow */ }
1508
+ try {
1509
+ void fn.call(impl, undefined, undefined);
1510
+ } catch {
1511
+ /* swallow */
1512
+ }
1252
1513
  }
1253
1514
  }
1254
1515
 
1255
1516
  function makeReturnEncoder(returns: AnyCapToken | undefined): (v: unknown) => unknown {
1256
1517
  if (!returns) return (v) => v;
1257
- const expectExportedCap = (v: unknown, expectedCap: CapDef<any, any>): CapRef => {
1518
+ const expectExportedCap = (v: unknown, expectedCap: AnyCapDef): CapRef => {
1258
1519
  if (!isExportedCap(v)) {
1259
1520
  throw new IpcError({
1260
1521
  code: "failed_precondition",
@@ -1275,7 +1536,10 @@ function makeReturnEncoder(returns: AnyCapToken | undefined): (v: unknown) => un
1275
1536
  if (isCapArray(returns)) {
1276
1537
  return (v) => {
1277
1538
  if (!Array.isArray(v)) {
1278
- throw new IpcError({ code: "invalid_argument", message: "expected ExportedCap[] for cap.array method" });
1539
+ throw new IpcError({
1540
+ code: "invalid_argument",
1541
+ message: "expected ExportedCap[] for cap.array method",
1542
+ });
1279
1543
  }
1280
1544
  return v.map((item) => expectExportedCap(item, returns.cap));
1281
1545
  };
@@ -1283,7 +1547,10 @@ function makeReturnEncoder(returns: AnyCapToken | undefined): (v: unknown) => un
1283
1547
  if (isCapRecord(returns)) {
1284
1548
  return (v) => {
1285
1549
  if (!v || typeof v !== "object") {
1286
- throw new IpcError({ code: "invalid_argument", message: "expected Record<string, ExportedCap> for cap.record method" });
1550
+ throw new IpcError({
1551
+ code: "invalid_argument",
1552
+ message: "expected Record<string, ExportedCap> for cap.record method",
1553
+ });
1287
1554
  }
1288
1555
  const out: Record<string, CapRef> = {};
1289
1556
  for (const k of Object.keys(v as object)) {
@@ -1296,14 +1563,15 @@ function makeReturnEncoder(returns: AnyCapToken | undefined): (v: unknown) => un
1296
1563
  }
1297
1564
 
1298
1565
  function makeReturnDecoder(
1299
- returns: { cap?: CapDef<any, any> } | undefined,
1300
- spawn: (cap: CapDef<any, any>, capId: number) => unknown
1566
+ returns: { cap?: AnyCapDef } | undefined,
1567
+ spawn: (cap: AnyCapDef, capId: number) => unknown,
1301
1568
  ): ReturnDecoder | undefined {
1302
1569
  if (!returns) return undefined;
1303
1570
  if (isCapRef(returns)) {
1304
1571
  const inner = returns.cap;
1305
1572
  return (raw) => {
1306
- if (!(raw instanceof CapRef)) throw new IpcError({ code: "invalid_argument", message: "expected CapRef" });
1573
+ if (!(raw instanceof CapRef))
1574
+ throw new IpcError({ code: "invalid_argument", message: "expected CapRef" });
1307
1575
  return spawn(inner, raw.capId);
1308
1576
  };
1309
1577
  }
@@ -1311,9 +1579,11 @@ function makeReturnDecoder(
1311
1579
  const inner = returns.cap;
1312
1580
  const disposal = inner.disposal as DisposalSpec | undefined;
1313
1581
  return (raw) => {
1314
- if (!Array.isArray(raw)) throw new IpcError({ code: "invalid_argument", message: "expected array" });
1582
+ if (!Array.isArray(raw))
1583
+ throw new IpcError({ code: "invalid_argument", message: "expected array" });
1315
1584
  const proxies = raw.map((item) => {
1316
- if (!(item instanceof CapRef)) throw new IpcError({ code: "invalid_argument", message: "expected CapRef in array" });
1585
+ if (!(item instanceof CapRef))
1586
+ throw new IpcError({ code: "invalid_argument", message: "expected CapRef in array" });
1317
1587
  return spawn(inner, item.capId);
1318
1588
  });
1319
1589
  attachArrayDisposal(proxies, disposal);
@@ -1323,11 +1593,13 @@ function makeReturnDecoder(
1323
1593
  if (isCapRecord(returns)) {
1324
1594
  const inner = returns.cap;
1325
1595
  return (raw) => {
1326
- if (!raw || typeof raw !== "object") throw new IpcError({ code: "invalid_argument", message: "expected record" });
1596
+ if (!raw || typeof raw !== "object")
1597
+ throw new IpcError({ code: "invalid_argument", message: "expected record" });
1327
1598
  const out: Record<string, unknown> = {};
1328
1599
  for (const k of Object.keys(raw as object)) {
1329
1600
  const item = (raw as Record<string, unknown>)[k];
1330
- if (!(item instanceof CapRef)) throw new IpcError({ code: "invalid_argument", message: "expected CapRef in record" });
1601
+ if (!(item instanceof CapRef))
1602
+ throw new IpcError({ code: "invalid_argument", message: "expected CapRef in record" });
1331
1603
  out[k] = spawn(inner, item.capId);
1332
1604
  }
1333
1605
  return out;
@@ -1364,7 +1636,7 @@ function makeClientStream<T>(
1364
1636
  capId: number,
1365
1637
  streamId: number,
1366
1638
  cancel: () => void,
1367
- sendCredit: (messages: number) => void
1639
+ sendCredit: (messages: number) => void,
1368
1640
  ): ClientStreamHandle<T> {
1369
1641
  const buffer: T[] = [];
1370
1642
  const waiters: Array<{ resolve(r: IteratorResult<T>): void; reject(e: Error): void }> = [];
@@ -1403,8 +1675,14 @@ function makeClientStream<T>(
1403
1675
  while (waiters.length > 0) {
1404
1676
  const w = waiters.shift()!;
1405
1677
  const r = pump();
1406
- if (r) { w.resolve(r); continue; }
1407
- if (failure) { w.reject(failure); continue; }
1678
+ if (r) {
1679
+ w.resolve(r);
1680
+ continue;
1681
+ }
1682
+ if (failure) {
1683
+ w.reject(failure);
1684
+ continue;
1685
+ }
1408
1686
  waiters.unshift(w);
1409
1687
  break;
1410
1688
  }