bunite-core 0.17.0 → 0.17.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.
@@ -1,12 +1,12 @@
1
1
  import {
2
+ type AnyCapDef,
3
+ type ClientOf,
4
+ type Connection,
2
5
  createConnection,
3
6
  createFrameTransport,
4
7
  createWebSocketPipe,
5
- type Connection,
6
- type CapDef,
7
8
  type Schema,
8
9
  type SchemaRoots,
9
- type ClientOf,
10
10
  type WebSocketLike,
11
11
  } from "./index";
12
12
 
@@ -22,8 +22,10 @@ export interface BuniteWebGlobal {
22
22
  declare global {
23
23
  interface Window {
24
24
  host?: {
25
- bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
26
- bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
25
+ bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
26
+ bootstrap<R extends SchemaRoots>(
27
+ schema: Schema<R>,
28
+ ): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
27
29
  runtime(): Promise<ClientOf<typeof import("./framework").RuntimeCap>>;
28
30
  releaseRef(proxy: unknown): Promise<void>;
29
31
  /** Full Connection for renderer-as-server (serve / serveAll / unserve / replace / on). */
@@ -62,7 +64,9 @@ function ensureWebConnection(path = "/rpc"): Promise<Connection> {
62
64
  ws.binaryType = "arraybuffer";
63
65
  await new Promise<void>((resolve, reject) => {
64
66
  ws.addEventListener("open", () => resolve(), { once: true });
65
- ws.addEventListener("error", () => reject(new Error("web RPC ws connect failed")), { once: true });
67
+ ws.addEventListener("error", () => reject(new Error("web RPC ws connect failed")), {
68
+ once: true,
69
+ });
66
70
  });
67
71
  const conn = createConnection({
68
72
  transport: createFrameTransport(createWebSocketPipe(ws as unknown as WebSocketLike)),
@@ -79,9 +83,11 @@ function ensureWebConnection(path = "/rpc"): Promise<Connection> {
79
83
  return attempt;
80
84
  }
81
85
 
82
- export function bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
83
- export function bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
84
- export async function bootstrap(target: CapDef<any, any> | Schema<any>): Promise<unknown> {
86
+ export function bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
87
+ export function bootstrap<R extends SchemaRoots>(
88
+ schema: Schema<R>,
89
+ ): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
90
+ export async function bootstrap(target: AnyCapDef | Schema<any>): Promise<unknown> {
85
91
  if (isNative()) {
86
92
  if (!window.host?.bootstrap) throw new Error("host preload not ready");
87
93
  return (window.host.bootstrap as (t: unknown) => Promise<unknown>)(target);
package/src/rpc/schema.ts CHANGED
@@ -10,31 +10,37 @@ declare const EXPORTED_CAP_BRAND: unique symbol;
10
10
 
11
11
  export type ReturnsKind = "type" | "cap" | "capArray" | "capRecord";
12
12
 
13
- export type CapRefToken<C extends CapDef<any, any>> = {
13
+ export type CapRefToken<C extends AnyCapDef> = {
14
14
  readonly [CAP_REF_TAG]: true;
15
15
  readonly cap: C;
16
16
  };
17
17
 
18
- export type CapArrayToken<C extends CapDef<any, any>> = {
18
+ export type CapArrayToken<C extends AnyCapDef> = {
19
19
  readonly [CAP_ARRAY_TAG]: true;
20
20
  readonly cap: C;
21
21
  };
22
22
 
23
- export type CapRecordToken<C extends CapDef<any, any>> = {
23
+ export type CapRecordToken<C extends AnyCapDef> = {
24
24
  readonly [CAP_RECORD_TAG]: true;
25
25
  readonly cap: C;
26
26
  };
27
27
 
28
- export type AnyCapToken<C extends CapDef<any, any> = CapDef<any, any>> =
28
+ export type AnyCapToken<C extends AnyCapDef = AnyCapDef> =
29
29
  | CapRefToken<C>
30
30
  | CapArrayToken<C>
31
31
  | CapRecordToken<C>;
32
32
 
33
- function _cap<C extends CapDef<any, any>>(c: C): CapRefToken<C> {
33
+ function _cap<C extends AnyCapDef>(c: C): CapRefToken<C> {
34
34
  return { [CAP_REF_TAG]: true, cap: c };
35
35
  }
36
- _cap.array = <C extends CapDef<any, any>>(c: C): CapArrayToken<C> => ({ [CAP_ARRAY_TAG]: true, cap: c });
37
- _cap.record = <C extends CapDef<any, any>>(c: C): CapRecordToken<C> => ({ [CAP_RECORD_TAG]: true, cap: c });
36
+ _cap.array = <C extends AnyCapDef>(c: C): CapArrayToken<C> => ({
37
+ [CAP_ARRAY_TAG]: true,
38
+ cap: c,
39
+ });
40
+ _cap.record = <C extends AnyCapDef>(c: C): CapRecordToken<C> => ({
41
+ [CAP_RECORD_TAG]: true,
42
+ cap: c,
43
+ });
38
44
  export const cap = _cap;
39
45
 
40
46
  export function isCapRef(v: unknown): v is CapRefToken<any> {
@@ -75,15 +81,18 @@ export function isStreamDef(v: unknown): v is StreamDef {
75
81
  return typeof v === "object" && v !== null && (v as any)[STREAM_TAG] === true;
76
82
  }
77
83
 
78
- export function call<P, C extends CapDef<any, any>>(
79
- opts: { returns: CapRefToken<C>; idempotent?: boolean }
80
- ): CallDef<P, CapRefToken<C>>;
81
- export function call<P, C extends CapDef<any, any>>(
82
- opts: { returns: CapArrayToken<C>; idempotent?: boolean }
83
- ): CallDef<P, CapArrayToken<C>>;
84
- export function call<P, C extends CapDef<any, any>>(
85
- opts: { returns: CapRecordToken<C>; idempotent?: boolean }
86
- ): CallDef<P, CapRecordToken<C>>;
84
+ export function call<P, C extends AnyCapDef>(opts: {
85
+ returns: CapRefToken<C>;
86
+ idempotent?: boolean;
87
+ }): CallDef<P, CapRefToken<C>>;
88
+ export function call<P, C extends AnyCapDef>(opts: {
89
+ returns: CapArrayToken<C>;
90
+ idempotent?: boolean;
91
+ }): CallDef<P, CapArrayToken<C>>;
92
+ export function call<P, C extends AnyCapDef>(opts: {
93
+ returns: CapRecordToken<C>;
94
+ idempotent?: boolean;
95
+ }): CallDef<P, CapRecordToken<C>>;
87
96
  export function call<P = void, R = void>(opts?: { idempotent?: boolean }): CallDef<P, R>;
88
97
  export function call(opts?: { idempotent?: boolean; returns?: AnyCapToken }): CallDef<any, any> {
89
98
  return {
@@ -93,7 +102,9 @@ export function call(opts?: { idempotent?: boolean; returns?: AnyCapToken }): Ca
93
102
  };
94
103
  }
95
104
 
96
- export function stream<P = void, Y = unknown>(opts?: { hint?: Record<string, unknown> }): StreamDef<P, Y> {
105
+ export function stream<P = void, Y = unknown>(opts?: {
106
+ hint?: Record<string, unknown>;
107
+ }): StreamDef<P, Y> {
97
108
  return { [STREAM_TAG]: true, hint: opts?.hint };
98
109
  }
99
110
 
@@ -104,7 +115,10 @@ export interface DisposalSpec<M extends MethodsRecord = MethodsRecord> {
104
115
 
105
116
  export type MethodsRecord = Record<string, MethodDef>;
106
117
 
107
- export interface CapDef<M extends MethodsRecord = MethodsRecord, D extends DisposalSpec<M> | undefined = undefined> {
118
+ export interface CapDef<
119
+ M extends MethodsRecord = MethodsRecord,
120
+ D extends DisposalSpec<M> | undefined = undefined,
121
+ > {
108
122
  readonly [CAP_TAG]: true;
109
123
  readonly name: string;
110
124
  readonly version?: string;
@@ -112,16 +126,17 @@ export interface CapDef<M extends MethodsRecord = MethodsRecord, D extends Dispo
112
126
  readonly disposal: D;
113
127
  }
114
128
 
129
+ export type AnyCapDef = CapDef<any, any>;
130
+
115
131
  export interface DefineCapOpts<M extends MethodsRecord, D extends DisposalSpec<M> | undefined> {
116
132
  version?: string | number;
117
133
  disposal?: D;
118
134
  }
119
135
 
120
- export function defineCap<M extends MethodsRecord, D extends DisposalSpec<M> | undefined = undefined>(
121
- name: string,
122
- methods: M,
123
- opts?: DefineCapOpts<M, D>
124
- ): CapDef<M, D> {
136
+ export function defineCap<
137
+ M extends MethodsRecord,
138
+ D extends DisposalSpec<M> | undefined = undefined,
139
+ >(name: string, methods: M, opts?: DefineCapOpts<M, D>): CapDef<M, D> {
125
140
  return {
126
141
  [CAP_TAG]: true,
127
142
  name,
@@ -136,7 +151,7 @@ export function isCapDef(v: unknown): v is CapDef {
136
151
  }
137
152
 
138
153
  // Schema = grouping sugar (Record<rootName, CapDef>). TS atomicity for serveAll.
139
- export type SchemaRoots = Record<string, CapDef<any, any>>;
154
+ export type SchemaRoots = Record<string, AnyCapDef>;
140
155
 
141
156
  export interface Schema<R extends SchemaRoots = SchemaRoots> {
142
157
  readonly [SCHEMA_TAG]: true;
@@ -155,7 +170,7 @@ export function isSchema(v: unknown): v is Schema {
155
170
  return typeof v === "object" && v !== null && (v as any)[SCHEMA_TAG] === true;
156
171
  }
157
172
 
158
- export interface ExportedCap<C extends CapDef<any, any>> {
173
+ export interface ExportedCap<C extends AnyCapDef> {
159
174
  readonly [EXPORTED_CAP_BRAND]: true;
160
175
  readonly cap: C;
161
176
  readonly capId: number;
@@ -168,7 +183,7 @@ export interface CallCtx {
168
183
  attestation: Attestation;
169
184
  signal: AbortSignal;
170
185
  deadline?: number;
171
- exportCap<C extends CapDef<any, any>>(capDef: C, impl: ImplOf<C>): ExportedCap<C>;
186
+ exportCap<C extends AnyCapDef>(capDef: C, impl: ImplOf<C>): ExportedCap<C>;
172
187
  }
173
188
 
174
189
  export interface Attestation {
@@ -184,45 +199,55 @@ export interface Attestation {
184
199
  type MaybePromise<T> = T | Promise<T>;
185
200
 
186
201
  type ServerReturn<R> =
187
- R extends CapRefToken<infer C> ? ExportedCap<C> :
188
- R extends CapArrayToken<infer C> ? ExportedCap<C>[] :
189
- R extends CapRecordToken<infer C> ? Record<string, ExportedCap<C>> :
190
- R;
202
+ R extends CapRefToken<infer C>
203
+ ? ExportedCap<C>
204
+ : R extends CapArrayToken<infer C>
205
+ ? ExportedCap<C>[]
206
+ : R extends CapRecordToken<infer C>
207
+ ? Record<string, ExportedCap<C>>
208
+ : R;
191
209
 
192
210
  export type ClientReturn<R> =
193
- R extends CapRefToken<infer C> ? ClientOf<C> :
194
- R extends CapArrayToken<infer C> ?
195
- C extends CapDef<any, infer D>
196
- ? [D] extends [DisposalSpec] ? ClientOf<C>[] & Disposable
197
- : ClientOf<C>[]
198
- : ClientOf<C>[] :
199
- R extends CapRecordToken<infer C> ? Record<string, ClientOf<C>> :
200
- R;
211
+ R extends CapRefToken<infer C>
212
+ ? ClientOf<C>
213
+ : R extends CapArrayToken<infer C>
214
+ ? C extends CapDef<any, infer D>
215
+ ? [D] extends [DisposalSpec]
216
+ ? ClientOf<C>[] & Disposable
217
+ : ClientOf<C>[]
218
+ : ClientOf<C>[]
219
+ : R extends CapRecordToken<infer C>
220
+ ? Record<string, ClientOf<C>>
221
+ : R;
201
222
 
202
223
  export type Stream<T> = AsyncIterable<T> & Disposable & { cancel(): void };
203
224
 
204
- export type ClientOf<T> = T extends CapDef<infer M, infer D>
205
- ? {
206
- [K in keyof M]: M[K] extends CallDef<infer P, infer R>
207
- ? [P] extends [void] ? () => Promise<ClientReturn<R>>
208
- : (params: P) => Promise<ClientReturn<R>>
209
- : M[K] extends StreamDef<infer P, infer Y>
210
- ? [P] extends [void] ? () => Stream<Y>
211
- : (params: P) => Stream<Y>
212
- : never;
213
- } & ([D] extends [DisposalSpec] ? Disposable : {})
214
- : never;
215
-
216
- export type ImplOf<T> = T extends CapDef<infer M, any>
217
- ? {
218
- [K in keyof M]: M[K] extends CallDef<infer P, infer R>
219
- ? (params: P, ctx: CallCtx) => MaybePromise<ServerReturn<R>>
220
- : M[K] extends StreamDef<infer P, infer Y>
221
- ? (params: P, ctx: CallCtx) => Stream<Y>
222
- : never;
223
- }
224
- : never;
225
-
226
- export function methodKeys(cap: CapDef<any, any>): string[] {
225
+ export type ClientOf<T> =
226
+ T extends CapDef<infer M, infer D>
227
+ ? {
228
+ [K in keyof M]: M[K] extends CallDef<infer P, infer R>
229
+ ? [P] extends [void]
230
+ ? () => Promise<ClientReturn<R>>
231
+ : (params: P) => Promise<ClientReturn<R>>
232
+ : M[K] extends StreamDef<infer P, infer Y>
233
+ ? [P] extends [void]
234
+ ? () => Stream<Y>
235
+ : (params: P) => Stream<Y>
236
+ : never;
237
+ } & ([D] extends [DisposalSpec] ? Disposable : unknown)
238
+ : never;
239
+
240
+ export type ImplOf<T> =
241
+ T extends CapDef<infer M, any>
242
+ ? {
243
+ [K in keyof M]: M[K] extends CallDef<infer P, infer R>
244
+ ? (params: P, ctx: CallCtx) => MaybePromise<ServerReturn<R>>
245
+ : M[K] extends StreamDef<infer P, infer Y>
246
+ ? (params: P, ctx: CallCtx) => Stream<Y>
247
+ : never;
248
+ }
249
+ : never;
250
+
251
+ export function methodKeys(cap: AnyCapDef): string[] {
227
252
  return Object.keys(cap.methods);
228
253
  }
package/src/rpc/stream.ts CHANGED
@@ -4,7 +4,10 @@ type Setup<T> = (emit: (chunk: T) => void, signal: AbortSignal) => void | (() =>
4
4
 
5
5
  class ServerStream<T> implements AsyncIterable<T>, Disposable {
6
6
  private readonly buffer: T[] = [];
7
- private readonly waiters: Array<{ resolve: (r: IteratorResult<T>) => void; reject: (e: unknown) => void }> = [];
7
+ private readonly waiters: Array<{
8
+ resolve: (r: IteratorResult<T>) => void;
9
+ reject: (e: unknown) => void;
10
+ }> = [];
8
11
  private readonly ctrl = new AbortController();
9
12
  private cleanup?: () => void;
10
13
  private ended = false;
@@ -36,7 +39,9 @@ class ServerStream<T> implements AsyncIterable<T>, Disposable {
36
39
  }
37
40
  if (this.failure) throw this.failure;
38
41
  if (this.ended) return { value: undefined as unknown as T, done: true };
39
- return new Promise<IteratorResult<T>>((resolve, reject) => this.waiters.push({ resolve, reject }));
42
+ return new Promise<IteratorResult<T>>((resolve, reject) =>
43
+ this.waiters.push({ resolve, reject }),
44
+ );
40
45
  },
41
46
  return: async (): Promise<IteratorResult<T>> => {
42
47
  this.dispose();
@@ -57,7 +62,11 @@ class ServerStream<T> implements AsyncIterable<T>, Disposable {
57
62
  if (this.ended) return;
58
63
  this.ended = true;
59
64
  this.ctrl.abort();
60
- try { this.cleanup?.(); } catch { /* swallow */ }
65
+ try {
66
+ this.cleanup?.();
67
+ } catch {
68
+ /* swallow */
69
+ }
61
70
  while (this.waiters.length > 0) {
62
71
  const w = this.waiters.shift()!;
63
72
  w.resolve({ value: undefined as unknown as T, done: true });
@@ -1,5 +1,5 @@
1
- import { type Frame, createCodec, isFrame } from "./wire";
2
1
  import type { Transport } from "./peer";
2
+ import { createCodec, type Frame, isFrame } from "./wire";
3
3
 
4
4
  export interface BytesPipe {
5
5
  send(bytes: Uint8Array): void;
@@ -48,21 +48,37 @@ export function createFrameTransport(pipe: BytesPipe, opts: FrameTransportOption
48
48
 
49
49
  export interface WebSocketLike {
50
50
  send(data: Uint8Array | ArrayBuffer): unknown;
51
- addEventListener(type: "message", listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void): void;
52
- removeEventListener?(type: "message", listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void): void;
51
+ addEventListener(
52
+ type: "message",
53
+ listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void,
54
+ ): void;
55
+ removeEventListener?(
56
+ type: "message",
57
+ listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void,
58
+ ): void;
53
59
  close?(): void;
54
60
  }
55
61
 
56
62
  export function createWebSocketPipe(ws: WebSocketLike): BytesPipe {
57
63
  if ("binaryType" in ws) {
58
- try { (ws as { binaryType?: string }).binaryType = "arraybuffer"; } catch { /* readonly in some envs */ }
64
+ try {
65
+ (ws as { binaryType?: string }).binaryType = "arraybuffer";
66
+ } catch {
67
+ /* readonly in some envs */
68
+ }
59
69
  }
60
70
  let handler: ((bytes: Uint8Array) => void) | undefined;
61
71
  const onMessage = (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => {
62
72
  if (!handler) return;
63
73
  const d = event.data;
64
- if (d instanceof Uint8Array) { handler(d); return; }
65
- if (d instanceof ArrayBuffer) { handler(new Uint8Array(d)); return; }
74
+ if (d instanceof Uint8Array) {
75
+ handler(d);
76
+ return;
77
+ }
78
+ if (d instanceof ArrayBuffer) {
79
+ handler(new Uint8Array(d));
80
+ return;
81
+ }
66
82
  if (typeof Blob !== "undefined" && d instanceof Blob) {
67
83
  void d.arrayBuffer().then((buf) => handler?.(new Uint8Array(buf)));
68
84
  return;
@@ -70,12 +86,15 @@ export function createWebSocketPipe(ws: WebSocketLike): BytesPipe {
70
86
  };
71
87
  ws.addEventListener("message", onMessage);
72
88
  return {
73
- send(bytes) { ws.send(bytes); },
74
- setReceive(h) { handler = h; },
89
+ send(bytes) {
90
+ ws.send(bytes);
91
+ },
92
+ setReceive(h) {
93
+ handler = h;
94
+ },
75
95
  close() {
76
96
  ws.removeEventListener?.("message", onMessage);
77
97
  ws.close?.();
78
98
  },
79
99
  };
80
100
  }
81
-
package/src/rpc/wire.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Packr, Unpackr, addExtension } from "msgpackr";
1
+ import { addExtension, Packr, Unpackr } from "msgpackr";
2
2
  import type { IpcStatus } from "./error";
3
3
 
4
4
  export type u32 = number;
@@ -75,28 +75,41 @@ export function isFrame(value: unknown): value is Frame {
75
75
  if (typeof op !== "string" || !OPS.has(op as Frame["op"])) return false;
76
76
  switch (op as Frame["op"]) {
77
77
  case "hello":
78
- return f.v === 1 && (f.mode === "native" || f.mode === "web")
79
- && Array.isArray(f.features) && typeof f.maxBytes === "number" && typeof f.origin === "string";
78
+ return (
79
+ f.v === 1 &&
80
+ (f.mode === "native" || f.mode === "web") &&
81
+ Array.isArray(f.features) &&
82
+ typeof f.maxBytes === "number" &&
83
+ typeof f.origin === "string"
84
+ );
80
85
  case "goaway":
81
86
  return f.reason === undefined || typeof f.reason === "string";
82
87
  case "call":
83
- return typeof f.id === "number" && typeof f.method === "string"
84
- && typeof f.target === "object" && f.target !== null
85
- && (f.target as { kind?: unknown }).kind === "cap"
86
- && typeof (f.target as { id?: unknown }).id === "number";
88
+ return (
89
+ typeof f.id === "number" &&
90
+ typeof f.method === "string" &&
91
+ typeof f.target === "object" &&
92
+ f.target !== null &&
93
+ (f.target as { kind?: unknown }).kind === "cap" &&
94
+ typeof (f.target as { id?: unknown }).id === "number"
95
+ );
87
96
  case "result":
88
97
  return typeof f.id === "number" && (f.ok === true || f.ok === false);
89
98
  case "cancel":
90
99
  return typeof f.id === "number";
91
100
  case "stream":
92
- return typeof f.id === "number" && typeof f.ev === "string"
93
- && (f.ev === "next" || f.ev === "credit" || f.ev === "end" || f.ev === "error");
101
+ return (
102
+ typeof f.id === "number" &&
103
+ typeof f.ev === "string" &&
104
+ (f.ev === "next" || f.ev === "credit" || f.ev === "end" || f.ev === "error")
105
+ );
94
106
  case "drop":
95
107
  return Array.isArray(f.caps);
96
108
  case "cap_revoked":
97
109
  if (!Array.isArray(f.capIds)) return false;
98
110
  for (const id of f.capIds) {
99
- if (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) return false;
111
+ if (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > 0xffffffff)
112
+ return false;
100
113
  }
101
114
  return true;
102
115
  }