@semilayer/client 1.1.0

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.
@@ -0,0 +1,196 @@
1
+ import { StreamSearchParams, SearchResult, StreamQueryParams, SubscribeParams, StreamEvent, SearchParams, SearchResponse, SimilarParams, SimilarResponse, QueryParams, QueryResponse } from '@semilayer/core';
2
+ export { QueryParams, QueryResponse, SearchParams, SearchResponse, SearchResult, SimilarParams, SimilarResponse, StreamEvent, StreamEventKind, StreamQueryParams, StreamSearchParams, SubscribeParams } from '@semilayer/core';
3
+
4
+ declare class BeamError extends Error {
5
+ readonly status: number;
6
+ readonly detail: string;
7
+ readonly path: string;
8
+ constructor(status: number, detail: string, path: string);
9
+ }
10
+ declare class BeamNotFoundError extends BeamError {
11
+ constructor(detail: string, path: string);
12
+ }
13
+ declare class BeamAuthError extends BeamError {
14
+ constructor(detail: string, path: string);
15
+ }
16
+ /**
17
+ * The server returned a stream `error` frame. Carries the frame's `code`
18
+ * field so callers can branch on `rate_limited`, `forbidden`, `not_found`,
19
+ * `bad_request`, `internal`.
20
+ */
21
+ declare class BeamStreamError extends Error {
22
+ readonly code: string;
23
+ readonly detail: string;
24
+ readonly lens: string;
25
+ constructor(code: string, detail: string, lens: string);
26
+ }
27
+ /**
28
+ * The server rate-limited a control frame or row emission. Thrown through
29
+ * the AsyncIterable so the consumer can catch and back off.
30
+ */
31
+ declare class BeamStreamRateLimitError extends BeamStreamError {
32
+ constructor(detail: string, lens: string);
33
+ }
34
+ /**
35
+ * The WebSocket closed with the connection-limit close code (4290) or the
36
+ * rows-per-hour close code (4291). Caller should back off and retry later.
37
+ */
38
+ declare class BeamStreamClosedError extends Error {
39
+ readonly closeCode: number;
40
+ readonly reason: string;
41
+ constructor(closeCode: number, reason: string);
42
+ }
43
+
44
+ /**
45
+ * Minimal WebSocket interface so callers can inject a mock (or the Node `ws`
46
+ * library) without depending on DOM types. Uses `Uint8Array` (not `Buffer`)
47
+ * so the client stays runtime-agnostic — browsers, Node, and edge runtimes
48
+ * all have Uint8Array; only Node has Buffer.
49
+ */
50
+ interface WebSocketLike {
51
+ readyState: number;
52
+ send(data: string): void;
53
+ close(code?: number, reason?: string): void;
54
+ addEventListener(type: 'open', listener: () => void): void;
55
+ addEventListener(type: 'close', listener: (event: {
56
+ code: number;
57
+ reason: string;
58
+ }) => void): void;
59
+ addEventListener(type: 'message', listener: (event: {
60
+ data: string | ArrayBuffer | Uint8Array;
61
+ }) => void): void;
62
+ addEventListener(type: 'error', listener: (event: unknown) => void): void;
63
+ removeEventListener(type: string, listener: unknown): void;
64
+ }
65
+ type WebSocketConstructor = new (url: string) => WebSocketLike;
66
+
67
+ interface BeamConfig {
68
+ /** Base URL of the SemiLayer API (e.g. http://localhost:3001) */
69
+ baseUrl: string;
70
+ /** Service key (sk_...) or publishable key (pk_...) */
71
+ apiKey: string;
72
+ /**
73
+ * End-user JWT passed through as the `X-User-Token` header on REST calls
74
+ * and the `userToken` query param on WebSocket handshakes. SemiLayer
75
+ * validates it against the environment's JWKS config and feeds the claims
76
+ * into per-lens access rules (row-level security via the `(claims) =>
77
+ * AccessRuleResult` function-rule form).
78
+ *
79
+ * This is the **customer's own end-user** identity, distinct from the
80
+ * `apiKey` (which identifies the tenant).
81
+ *
82
+ * Leave undefined for server-to-server calls or sk_ (service key) flows
83
+ * that bypass access rules.
84
+ */
85
+ userToken?: string;
86
+ /** Override fetch implementation (for testing, SSR) */
87
+ fetch?: typeof fetch;
88
+ /**
89
+ * Override the WebSocket base URL (e.g. `wss://ws.semilayer.com`). When
90
+ * omitted, the client derives it from `baseUrl` by swapping the scheme
91
+ * (`https://` → `wss://`, `http://` → `ws://`), with a special-case
92
+ * default to `wss://ws.semilayer.com` for the hosted SaaS URL.
93
+ */
94
+ wsUrl?: string;
95
+ /**
96
+ * Override the WebSocket constructor — used by tests and Node environments
97
+ * that need to inject the `ws` package. Defaults to `globalThis.WebSocket`
98
+ * which is available in browsers, Node 22+, and modern edge runtimes.
99
+ */
100
+ WebSocket?: WebSocketConstructor;
101
+ }
102
+
103
+ /**
104
+ * BeamClient — the canonical SemiLayer runtime client.
105
+ *
106
+ * HTTP methods (always available):
107
+ * - search, similar, query
108
+ *
109
+ * Streaming namespace (opens WebSocket lazily on first call):
110
+ * - stream.search(lens, params) → AsyncIterable<SearchResult>
111
+ * - stream.query (lens, params) → AsyncIterable<row>
112
+ * - stream.subscribe(lens, params) → AsyncIterable<StreamEvent>
113
+ *
114
+ * Single-record observe (top-level because it's semantically distinct):
115
+ * - observe(lens, recordId) → AsyncIterable<record> (current state + every delta)
116
+ *
117
+ * v0.1 behavior on WebSocket disconnect: pending ops throw
118
+ * `BeamStreamClosedError`. Callers that want transparent reconnect wrap the
119
+ * iteration in a retry loop. Transparent reconnect is a v0.2 feature.
120
+ */
121
+ declare class BeamClient {
122
+ private readonly baseUrl;
123
+ private readonly apiKey;
124
+ private readonly userToken;
125
+ private readonly fetchFn;
126
+ private readonly wsUrl;
127
+ private readonly WsCtor;
128
+ /** Streaming ops grouped under a `.stream` sub-object for ergonomic chaining. */
129
+ readonly stream: {
130
+ search<M = Record<string, unknown>>(lens: string, params: StreamSearchParams): AsyncIterable<SearchResult<M>>;
131
+ query<M = Record<string, unknown>>(lens: string, params: StreamQueryParams): AsyncIterable<M>;
132
+ subscribe<M = Record<string, unknown>>(lens: string, params?: SubscribeParams): AsyncIterable<StreamEvent<M>>;
133
+ };
134
+ constructor(config: BeamConfig);
135
+ /**
136
+ * Return a new `BeamClient` bound to an end-user JWT. Cheap (no network,
137
+ * no new socket) — the clone shares the same baseUrl / apiKey / fetch /
138
+ * WebSocket config and only differs in the `userToken` it attaches.
139
+ *
140
+ * Typical SSR pattern:
141
+ *
142
+ * ```ts
143
+ * const beam = new BeamClient({ baseUrl, apiKey: PK })
144
+ *
145
+ * // Inside a request handler, per end user:
146
+ * const userBeam = beam.withUser(session.token)
147
+ * const results = await userBeam.search('products', { query })
148
+ * ```
149
+ *
150
+ * The returned client passes the token as `X-User-Token` on REST calls and
151
+ * as `?userToken=` on WebSocket handshakes.
152
+ */
153
+ withUser(userToken: string): BeamClient;
154
+ search<M = Record<string, unknown>>(lens: string, params: SearchParams): Promise<SearchResponse<M>>;
155
+ similar<M = Record<string, unknown>>(lens: string, params: SimilarParams): Promise<SimilarResponse<M>>;
156
+ query<M = Record<string, unknown>>(lens: string, params?: QueryParams): Promise<QueryResponse<M>>;
157
+ /**
158
+ * Observe a single record by id. First yield is the current record state;
159
+ * subsequent yields fire only when the record actually changes (server-side
160
+ * per-subscription hash dedup). Terminates when the consumer breaks, the
161
+ * abort signal fires, or the server closes the stream.
162
+ */
163
+ observe<M = Record<string, unknown>>(lens: string, recordId: string): AsyncIterable<M>;
164
+ private opCounter;
165
+ private nextOpId;
166
+ /**
167
+ * Run a chunked op (search.stream / query.stream). The transport yields
168
+ * `row` frames; we push each row into the queue as-is. Terminates on
169
+ * `done` / `error` / socket close.
170
+ */
171
+ private runChunkedOp;
172
+ /**
173
+ * Run a long-lived live op (subscribe). The transport yields `event`
174
+ * frames; we wrap each as a `StreamEvent` before pushing.
175
+ */
176
+ private runLiveOp;
177
+ /**
178
+ * Run an observe op. Like subscribe but yields the raw record (not
179
+ * wrapped in a StreamEvent) — first yield is current state, subsequent
180
+ * yields are deltas.
181
+ */
182
+ private runObserveOp;
183
+ /**
184
+ * Build the transport + register the PendingOp + kick off the send.
185
+ * Returns the transport so the iterator wrapper can close it on break.
186
+ */
187
+ private buildOpTransport;
188
+ /**
189
+ * Wrap an AsyncQueue so that a consumer breaking out of `for await` tears
190
+ * down the transport cleanly.
191
+ */
192
+ private wrapIterable;
193
+ private request;
194
+ }
195
+
196
+ export { BeamAuthError, BeamClient, type BeamConfig, BeamError, BeamNotFoundError, BeamStreamClosedError, BeamStreamError, BeamStreamRateLimitError, type WebSocketConstructor, type WebSocketLike };
package/dist/index.js ADDED
@@ -0,0 +1,529 @@
1
+ // src/errors.ts
2
+ var BeamError = class extends Error {
3
+ constructor(status, detail, path) {
4
+ super(`Beam ${status}: ${detail} (${path})`);
5
+ this.status = status;
6
+ this.detail = detail;
7
+ this.path = path;
8
+ this.name = "BeamError";
9
+ }
10
+ status;
11
+ detail;
12
+ path;
13
+ };
14
+ var BeamNotFoundError = class extends BeamError {
15
+ constructor(detail, path) {
16
+ super(404, detail, path);
17
+ this.name = "BeamNotFoundError";
18
+ }
19
+ };
20
+ var BeamAuthError = class extends BeamError {
21
+ constructor(detail, path) {
22
+ super(401, detail, path);
23
+ this.name = "BeamAuthError";
24
+ }
25
+ };
26
+ var BeamStreamError = class extends Error {
27
+ constructor(code, detail, lens) {
28
+ super(`Beam stream ${code}: ${detail} (${lens})`);
29
+ this.code = code;
30
+ this.detail = detail;
31
+ this.lens = lens;
32
+ this.name = "BeamStreamError";
33
+ }
34
+ code;
35
+ detail;
36
+ lens;
37
+ };
38
+ var BeamStreamRateLimitError = class extends BeamStreamError {
39
+ constructor(detail, lens) {
40
+ super("rate_limited", detail, lens);
41
+ this.name = "BeamStreamRateLimitError";
42
+ }
43
+ };
44
+ var BeamStreamClosedError = class extends Error {
45
+ constructor(closeCode, reason) {
46
+ super(`Beam stream closed: ${closeCode} ${reason}`);
47
+ this.closeCode = closeCode;
48
+ this.reason = reason;
49
+ this.name = "BeamStreamClosedError";
50
+ }
51
+ closeCode;
52
+ reason;
53
+ };
54
+
55
+ // src/async-queue.ts
56
+ function createAsyncQueue() {
57
+ const items = [];
58
+ const waiters = [];
59
+ let closed = false;
60
+ let finalError = null;
61
+ function push(item) {
62
+ if (closed) return;
63
+ const waiter = waiters.shift();
64
+ if (waiter) {
65
+ waiter.resolve({ value: item, done: false });
66
+ return;
67
+ }
68
+ items.push(item);
69
+ }
70
+ function done() {
71
+ if (closed) return;
72
+ closed = true;
73
+ while (waiters.length > 0) {
74
+ const waiter = waiters.shift();
75
+ waiter.resolve({ value: void 0, done: true });
76
+ }
77
+ }
78
+ function error(err) {
79
+ if (closed) return;
80
+ closed = true;
81
+ finalError = err;
82
+ while (waiters.length > 0) {
83
+ const waiter = waiters.shift();
84
+ waiter.reject(err);
85
+ }
86
+ }
87
+ async function next() {
88
+ if (items.length > 0) {
89
+ return { value: items.shift(), done: false };
90
+ }
91
+ if (closed) {
92
+ if (finalError) throw finalError;
93
+ return { value: void 0, done: true };
94
+ }
95
+ return new Promise((resolve, reject) => {
96
+ waiters.push({ resolve, reject });
97
+ });
98
+ }
99
+ return {
100
+ push,
101
+ done,
102
+ error,
103
+ get closed() {
104
+ return closed;
105
+ },
106
+ [Symbol.asyncIterator]() {
107
+ return {
108
+ next,
109
+ async return() {
110
+ done();
111
+ return { value: void 0, done: true };
112
+ }
113
+ };
114
+ }
115
+ };
116
+ }
117
+
118
+ // src/ws-transport.ts
119
+ import { STREAM_CLOSE_CODES } from "@semilayer/core";
120
+ var WsTransport = class {
121
+ ws = null;
122
+ state = "idle";
123
+ ops = /* @__PURE__ */ new Map();
124
+ url;
125
+ WsCtor;
126
+ onStateChange;
127
+ openPromise = null;
128
+ constructor(opts) {
129
+ this.url = opts.url;
130
+ const ctor = opts.WebSocket ?? globalThis.WebSocket;
131
+ if (!ctor) {
132
+ throw new Error(
133
+ "WsTransport: no WebSocket constructor found. Pass `WebSocket` in options or run in a WS-capable environment (Node 22+, browser, edge runtime)."
134
+ );
135
+ }
136
+ this.WsCtor = ctor;
137
+ this.onStateChange = opts.onStateChange;
138
+ }
139
+ /** Open the socket and wait for it to be ready. Idempotent. */
140
+ connect() {
141
+ if (this.state === "open") return Promise.resolve();
142
+ if (this.state === "closed") {
143
+ return Promise.reject(new Error("WsTransport: cannot reconnect a closed transport"));
144
+ }
145
+ if (this.openPromise) return this.openPromise;
146
+ this.state = "connecting";
147
+ this.onStateChange?.("connecting");
148
+ const ws = new this.WsCtor(this.url);
149
+ this.ws = ws;
150
+ this.openPromise = new Promise((resolve, reject) => {
151
+ const onOpen = () => {
152
+ this.state = "open";
153
+ this.onStateChange?.("open");
154
+ ws.removeEventListener("open", onOpen);
155
+ ws.removeEventListener("error", onInitialError);
156
+ resolve();
157
+ };
158
+ const onInitialError = (ev) => {
159
+ ws.removeEventListener("open", onOpen);
160
+ ws.removeEventListener("error", onInitialError);
161
+ reject(new Error(`WsTransport: failed to open socket: ${String(ev)}`));
162
+ };
163
+ ws.addEventListener("open", onOpen);
164
+ ws.addEventListener("error", onInitialError);
165
+ });
166
+ ws.addEventListener("message", (event) => this.handleMessage(event.data));
167
+ ws.addEventListener("close", (event) => this.handleClose(event.code, event.reason));
168
+ return this.openPromise;
169
+ }
170
+ /** Register an op — idempotent, last writer wins. */
171
+ registerOp(op) {
172
+ this.ops.set(op.id, op);
173
+ }
174
+ /** Unregister an op (called by the AsyncIterable on early break). */
175
+ unregisterOp(id) {
176
+ this.ops.delete(id);
177
+ }
178
+ /**
179
+ * Send an op frame on the underlying socket. Waits for the connection to
180
+ * be open before sending. Errors if the transport is closed.
181
+ */
182
+ async send(frame) {
183
+ if (this.state === "closed") {
184
+ throw new Error("WsTransport: cannot send \u2014 transport is closed");
185
+ }
186
+ await this.connect();
187
+ if (!this.ws || this.ws.readyState !== 1) {
188
+ throw new Error("WsTransport: socket not open");
189
+ }
190
+ this.ws.send(JSON.stringify(frame));
191
+ }
192
+ /** Send a raw control frame without op semantics (e.g. pong). */
193
+ sendRaw(frame) {
194
+ if (!this.ws || this.ws.readyState !== 1) return;
195
+ this.ws.send(JSON.stringify(frame));
196
+ }
197
+ /**
198
+ * Close the socket. All registered ops get an `onClosed(1000, 'normal')`
199
+ * callback before we mark the transport closed.
200
+ */
201
+ close() {
202
+ if (this.state === "closed") return;
203
+ this.state = "closed";
204
+ this.onStateChange?.("closed");
205
+ for (const op of this.ops.values()) {
206
+ try {
207
+ op.onClosed(STREAM_CLOSE_CODES.normal, "client closed");
208
+ } catch {
209
+ }
210
+ }
211
+ this.ops.clear();
212
+ try {
213
+ this.ws?.close(STREAM_CLOSE_CODES.normal, "client closed");
214
+ } catch {
215
+ }
216
+ this.ws = null;
217
+ }
218
+ /** Number of active ops (for tests + debugging). */
219
+ activeOpCount() {
220
+ return this.ops.size;
221
+ }
222
+ // ── Internal ──────────────────────────────────────────────
223
+ handleMessage(raw) {
224
+ const text = typeof raw === "string" ? raw : raw instanceof ArrayBuffer ? new TextDecoder().decode(new Uint8Array(raw)) : new TextDecoder().decode(raw);
225
+ let frame;
226
+ try {
227
+ frame = JSON.parse(text);
228
+ } catch {
229
+ return;
230
+ }
231
+ if (frame.type === "ping") {
232
+ this.sendRaw({ type: "pong" });
233
+ return;
234
+ }
235
+ if (frame.type === "pong") {
236
+ return;
237
+ }
238
+ if ("id" in frame && frame.id) {
239
+ const op = this.ops.get(frame.id);
240
+ if (!op) return;
241
+ switch (frame.type) {
242
+ case "row":
243
+ op.onRow(frame.data);
244
+ return;
245
+ case "event":
246
+ op.onEvent(frame.kind ?? "update", frame.record);
247
+ return;
248
+ case "done":
249
+ op.onDone(frame.meta ?? { count: 0, durationMs: 0 });
250
+ this.ops.delete(frame.id);
251
+ return;
252
+ case "error":
253
+ op.onError(frame.code ?? "internal", frame.message ?? "stream error");
254
+ this.ops.delete(frame.id);
255
+ return;
256
+ }
257
+ }
258
+ if (frame.type === "error") {
259
+ for (const op of this.ops.values()) {
260
+ op.onError(frame.code ?? "internal", frame.message ?? "stream error");
261
+ }
262
+ this.ops.clear();
263
+ }
264
+ }
265
+ handleClose(code, reason) {
266
+ if (this.state === "closed") return;
267
+ this.state = "closed";
268
+ this.onStateChange?.("closed");
269
+ for (const op of this.ops.values()) {
270
+ try {
271
+ op.onClosed(code, reason);
272
+ } catch {
273
+ }
274
+ }
275
+ this.ops.clear();
276
+ this.ws = null;
277
+ }
278
+ };
279
+ function buildStreamUrl(baseUrl, wsUrl, lens, apiKey, userToken) {
280
+ const origin = wsUrl ?? defaultWsOrigin(baseUrl);
281
+ const clean = origin.replace(/\/$/, "");
282
+ const params = new URLSearchParams({ key: apiKey });
283
+ if (userToken) {
284
+ params.set("userToken", userToken);
285
+ }
286
+ return `${clean}/v1/stream/${encodeURIComponent(lens)}?${params.toString()}`;
287
+ }
288
+ function defaultWsOrigin(baseUrl) {
289
+ const stripped = baseUrl.replace(/\/$/, "");
290
+ if (/^https?:\/\/api\.semilayer\.com$/i.test(stripped)) {
291
+ return "wss://ws.semilayer.com";
292
+ }
293
+ return stripped.replace(/^http:\/\//, "ws://").replace(/^https:\/\//, "wss://");
294
+ }
295
+ function toStreamError(code, message, lens) {
296
+ if (code === "rate_limited") {
297
+ return new BeamStreamRateLimitError(message, lens);
298
+ }
299
+ return new BeamStreamError(code, message, lens);
300
+ }
301
+ function closeCodeToError(code, reason) {
302
+ return new BeamStreamClosedError(code, reason);
303
+ }
304
+
305
+ // src/client.ts
306
+ var BeamClient = class _BeamClient {
307
+ baseUrl;
308
+ apiKey;
309
+ userToken;
310
+ fetchFn;
311
+ wsUrl;
312
+ WsCtor;
313
+ /** Streaming ops grouped under a `.stream` sub-object for ergonomic chaining. */
314
+ stream;
315
+ constructor(config) {
316
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
317
+ this.apiKey = config.apiKey;
318
+ this.userToken = config.userToken;
319
+ this.fetchFn = config.fetch ?? globalThis.fetch;
320
+ this.wsUrl = config.wsUrl;
321
+ this.WsCtor = config.WebSocket;
322
+ this.stream = {
323
+ search: (lens, params) => this.runChunkedOp(lens, {
324
+ id: this.nextOpId(),
325
+ op: "search.stream",
326
+ params
327
+ }),
328
+ query: (lens, params) => this.runChunkedOp(lens, {
329
+ id: this.nextOpId(),
330
+ op: "query.stream",
331
+ params
332
+ }),
333
+ subscribe: (lens, params) => this.runLiveOp(lens, {
334
+ id: this.nextOpId(),
335
+ op: "subscribe",
336
+ params: params ?? {}
337
+ })
338
+ };
339
+ }
340
+ /**
341
+ * Return a new `BeamClient` bound to an end-user JWT. Cheap (no network,
342
+ * no new socket) — the clone shares the same baseUrl / apiKey / fetch /
343
+ * WebSocket config and only differs in the `userToken` it attaches.
344
+ *
345
+ * Typical SSR pattern:
346
+ *
347
+ * ```ts
348
+ * const beam = new BeamClient({ baseUrl, apiKey: PK })
349
+ *
350
+ * // Inside a request handler, per end user:
351
+ * const userBeam = beam.withUser(session.token)
352
+ * const results = await userBeam.search('products', { query })
353
+ * ```
354
+ *
355
+ * The returned client passes the token as `X-User-Token` on REST calls and
356
+ * as `?userToken=` on WebSocket handshakes.
357
+ */
358
+ withUser(userToken) {
359
+ return new _BeamClient({
360
+ baseUrl: this.baseUrl,
361
+ apiKey: this.apiKey,
362
+ userToken,
363
+ fetch: this.fetchFn,
364
+ wsUrl: this.wsUrl,
365
+ WebSocket: this.WsCtor
366
+ });
367
+ }
368
+ // ── HTTP ops (existing) ──────────────────────────────────
369
+ async search(lens, params) {
370
+ return this.request("POST", `/v1/search/${lens}`, params);
371
+ }
372
+ async similar(lens, params) {
373
+ return this.request("POST", `/v1/similar/${lens}`, params);
374
+ }
375
+ async query(lens, params) {
376
+ return this.request("POST", `/v1/query/${lens}`, params ?? {});
377
+ }
378
+ // ── Streaming ops — single-record observation ───────────
379
+ /**
380
+ * Observe a single record by id. First yield is the current record state;
381
+ * subsequent yields fire only when the record actually changes (server-side
382
+ * per-subscription hash dedup). Terminates when the consumer breaks, the
383
+ * abort signal fires, or the server closes the stream.
384
+ */
385
+ observe(lens, recordId) {
386
+ return this.runObserveOp(lens, {
387
+ id: this.nextOpId(),
388
+ op: "observe",
389
+ params: { recordId }
390
+ });
391
+ }
392
+ // ── Internal ─────────────────────────────────────────────
393
+ opCounter = 0;
394
+ nextOpId() {
395
+ this.opCounter += 1;
396
+ return `op_${Date.now().toString(36)}_${this.opCounter}`;
397
+ }
398
+ /**
399
+ * Run a chunked op (search.stream / query.stream). The transport yields
400
+ * `row` frames; we push each row into the queue as-is. Terminates on
401
+ * `done` / `error` / socket close.
402
+ */
403
+ runChunkedOp(lens, frame) {
404
+ const queue = createAsyncQueue();
405
+ const transport = this.buildOpTransport(lens, frame, queue, {
406
+ onRow: (data) => queue.push(data),
407
+ // `event` frames shouldn't arrive on chunked ops, but if they do we
408
+ // silently ignore rather than crash the iterator.
409
+ onEvent: () => {
410
+ }
411
+ });
412
+ return this.wrapIterable(queue, transport);
413
+ }
414
+ /**
415
+ * Run a long-lived live op (subscribe). The transport yields `event`
416
+ * frames; we wrap each as a `StreamEvent` before pushing.
417
+ */
418
+ runLiveOp(lens, frame) {
419
+ const queue = createAsyncQueue();
420
+ const transport = this.buildOpTransport(lens, frame, queue, {
421
+ onRow: () => {
422
+ },
423
+ onEvent: (kind, record) => queue.push({ kind, record })
424
+ });
425
+ return this.wrapIterable(queue, transport);
426
+ }
427
+ /**
428
+ * Run an observe op. Like subscribe but yields the raw record (not
429
+ * wrapped in a StreamEvent) — first yield is current state, subsequent
430
+ * yields are deltas.
431
+ */
432
+ runObserveOp(lens, frame) {
433
+ const queue = createAsyncQueue();
434
+ const transport = this.buildOpTransport(lens, frame, queue, {
435
+ onRow: () => {
436
+ },
437
+ onEvent: (_kind, record) => queue.push(record)
438
+ });
439
+ return this.wrapIterable(queue, transport);
440
+ }
441
+ /**
442
+ * Build the transport + register the PendingOp + kick off the send.
443
+ * Returns the transport so the iterator wrapper can close it on break.
444
+ */
445
+ buildOpTransport(lens, frame, queue, handlers) {
446
+ const url = buildStreamUrl(this.baseUrl, this.wsUrl, lens, this.apiKey, this.userToken);
447
+ const transport = new WsTransport({ url, WebSocket: this.WsCtor });
448
+ const op = {
449
+ id: frame.id,
450
+ kind: frame.op === "search.stream" || frame.op === "query.stream" ? "chunked" : frame.op === "observe" ? "observe" : "live",
451
+ onRow: handlers.onRow,
452
+ onEvent: handlers.onEvent,
453
+ onDone() {
454
+ queue.done();
455
+ transport.close();
456
+ },
457
+ onError(code, message) {
458
+ queue.error(toStreamError(code, message, lens));
459
+ transport.close();
460
+ },
461
+ onClosed(code, reason) {
462
+ if (!queue.closed) {
463
+ queue.error(closeCodeToError(code, reason));
464
+ }
465
+ }
466
+ };
467
+ transport.registerOp(op);
468
+ void transport.send(frame).catch((err) => {
469
+ queue.error(err instanceof Error ? err : new Error(String(err)));
470
+ transport.close();
471
+ });
472
+ return transport;
473
+ }
474
+ /**
475
+ * Wrap an AsyncQueue so that a consumer breaking out of `for await` tears
476
+ * down the transport cleanly.
477
+ */
478
+ wrapIterable(queue, transport) {
479
+ const base = queue[Symbol.asyncIterator].bind(queue);
480
+ return {
481
+ [Symbol.asyncIterator]() {
482
+ const inner = base();
483
+ return {
484
+ async next() {
485
+ return inner.next();
486
+ },
487
+ async return() {
488
+ if (inner.return) await inner.return(void 0);
489
+ transport.close();
490
+ return { value: void 0, done: true };
491
+ }
492
+ };
493
+ }
494
+ };
495
+ }
496
+ async request(method, path, body) {
497
+ const url = `${this.baseUrl}${path}`;
498
+ const headers = {
499
+ "Content-Type": "application/json",
500
+ Authorization: `Bearer ${this.apiKey}`
501
+ };
502
+ if (this.userToken) {
503
+ headers["X-User-Token"] = this.userToken;
504
+ }
505
+ const res = await this.fetchFn(url, {
506
+ method,
507
+ headers,
508
+ body: body ? JSON.stringify(body) : void 0
509
+ });
510
+ if (!res.ok) {
511
+ const parsed = await res.json().catch(() => ({ error: res.statusText }));
512
+ const detail = parsed.error ?? res.statusText;
513
+ if (res.status === 401) throw new BeamAuthError(detail, path);
514
+ if (res.status === 404) throw new BeamNotFoundError(detail, path);
515
+ throw new BeamError(res.status, detail, path);
516
+ }
517
+ return res.json();
518
+ }
519
+ };
520
+ export {
521
+ BeamAuthError,
522
+ BeamClient,
523
+ BeamError,
524
+ BeamNotFoundError,
525
+ BeamStreamClosedError,
526
+ BeamStreamError,
527
+ BeamStreamRateLimitError
528
+ };
529
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/async-queue.ts","../src/ws-transport.ts","../src/client.ts"],"sourcesContent":["export class BeamError extends Error {\n constructor(\n public readonly status: number,\n public readonly detail: string,\n public readonly path: string,\n ) {\n super(`Beam ${status}: ${detail} (${path})`)\n this.name = 'BeamError'\n }\n}\n\nexport class BeamNotFoundError extends BeamError {\n constructor(detail: string, path: string) {\n super(404, detail, path)\n this.name = 'BeamNotFoundError'\n }\n}\n\nexport class BeamAuthError extends BeamError {\n constructor(detail: string, path: string) {\n super(401, detail, path)\n this.name = 'BeamAuthError'\n }\n}\n\n// ── Streaming errors ───────────────────────────────────────\n\n/**\n * The server returned a stream `error` frame. Carries the frame's `code`\n * field so callers can branch on `rate_limited`, `forbidden`, `not_found`,\n * `bad_request`, `internal`.\n */\nexport class BeamStreamError extends Error {\n constructor(\n public readonly code: string,\n public readonly detail: string,\n public readonly lens: string,\n ) {\n super(`Beam stream ${code}: ${detail} (${lens})`)\n this.name = 'BeamStreamError'\n }\n}\n\n/**\n * The server rate-limited a control frame or row emission. Thrown through\n * the AsyncIterable so the consumer can catch and back off.\n */\nexport class BeamStreamRateLimitError extends BeamStreamError {\n constructor(detail: string, lens: string) {\n super('rate_limited', detail, lens)\n this.name = 'BeamStreamRateLimitError'\n }\n}\n\n/**\n * The WebSocket closed with the connection-limit close code (4290) or the\n * rows-per-hour close code (4291). Caller should back off and retry later.\n */\nexport class BeamStreamClosedError extends Error {\n constructor(\n public readonly closeCode: number,\n public readonly reason: string,\n ) {\n super(`Beam stream closed: ${closeCode} ${reason}`)\n this.name = 'BeamStreamClosedError'\n }\n}\n","/**\n * A bounded-by-backpressure async queue backing every streaming op's\n * AsyncIterable. Producers push items via `push(item)`; the consumer pulls\n * via `[Symbol.asyncIterator]().next()`.\n *\n * Signals:\n * - `push(item)` — enqueue (or hand directly to a waiting consumer)\n * - `done()` — terminate cleanly; any further pushes are dropped\n * - `error(err)` — terminate with the given error; the next `next()` rejects\n *\n * Cancellation: the consumer can break out of `for await` and the queue\n * will simply stop being drained — push() calls become no-ops once done()\n * or error() has been invoked.\n */\nexport interface AsyncQueue<T> extends AsyncIterable<T> {\n push(item: T): void\n done(): void\n error(err: Error): void\n /** True once done() or error() has been called. */\n readonly closed: boolean\n}\n\nexport function createAsyncQueue<T>(): AsyncQueue<T> {\n const items: T[] = []\n const waiters: Array<{\n resolve: (result: IteratorResult<T>) => void\n reject: (err: Error) => void\n }> = []\n let closed = false\n let finalError: Error | null = null\n\n function push(item: T): void {\n if (closed) return\n const waiter = waiters.shift()\n if (waiter) {\n waiter.resolve({ value: item, done: false })\n return\n }\n items.push(item)\n }\n\n function done(): void {\n if (closed) return\n closed = true\n while (waiters.length > 0) {\n const waiter = waiters.shift()!\n waiter.resolve({ value: undefined, done: true })\n }\n }\n\n function error(err: Error): void {\n if (closed) return\n closed = true\n finalError = err\n while (waiters.length > 0) {\n const waiter = waiters.shift()!\n waiter.reject(err)\n }\n }\n\n async function next(): Promise<IteratorResult<T>> {\n if (items.length > 0) {\n return { value: items.shift()!, done: false }\n }\n if (closed) {\n if (finalError) throw finalError\n return { value: undefined, done: true }\n }\n return new Promise<IteratorResult<T>>((resolve, reject) => {\n waiters.push({ resolve, reject })\n })\n }\n\n return {\n push,\n done,\n error,\n get closed() {\n return closed\n },\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next,\n async return() {\n done()\n return { value: undefined, done: true }\n },\n }\n },\n }\n}\n","import type {\n StreamOpFrame,\n StreamFrame,\n StreamOp,\n StreamErrorCode,\n} from '@semilayer/core'\nimport { STREAM_CLOSE_CODES } from '@semilayer/core'\nimport {\n BeamStreamClosedError,\n BeamStreamError,\n BeamStreamRateLimitError,\n} from './errors.js'\n\n/**\n * Minimal WebSocket interface so callers can inject a mock (or the Node `ws`\n * library) without depending on DOM types. Uses `Uint8Array` (not `Buffer`)\n * so the client stays runtime-agnostic — browsers, Node, and edge runtimes\n * all have Uint8Array; only Node has Buffer.\n */\nexport interface WebSocketLike {\n readyState: number\n send(data: string): void\n close(code?: number, reason?: string): void\n addEventListener(type: 'open', listener: () => void): void\n addEventListener(type: 'close', listener: (event: { code: number; reason: string }) => void): void\n addEventListener(type: 'message', listener: (event: { data: string | ArrayBuffer | Uint8Array }) => void): void\n addEventListener(type: 'error', listener: (event: unknown) => void): void\n removeEventListener(type: string, listener: unknown): void\n}\n\nexport type WebSocketConstructor = new (url: string) => WebSocketLike\n\n/**\n * Each active op on a single WS connection registers a `PendingOp` handle.\n * The transport routes frames matching the op id to these callbacks.\n */\nexport interface PendingOp {\n id: string\n kind: 'chunked' | 'live' | 'observe'\n onRow(data: unknown): void\n onEvent(kind: 'insert' | 'update' | 'delete', record: unknown): void\n onDone(meta: { count: number; durationMs: number }): void\n onError(code: StreamErrorCode, message: string): void\n /** Called when the underlying socket closes with the given code. */\n onClosed(code: number, reason: string): void\n}\n\nexport interface WsTransportOptions {\n /** Full WS URL including `/v1/stream/:lens?key=...` query. */\n url: string\n /** Injected WebSocket constructor — defaults to `globalThis.WebSocket`. */\n WebSocket?: WebSocketConstructor\n /** Called on each state change (for logging / observability). */\n onStateChange?: (state: 'connecting' | 'open' | 'closed') => void\n}\n\ntype TransportState = 'idle' | 'connecting' | 'open' | 'closed'\n\n/**\n * Transport for a single WebSocket connection to a lens stream endpoint.\n *\n * - Multiplexes N ops over one socket via the frame `id` field\n * - Fires heartbeat `pong` replies when the server sends `ping`\n * - On unexpected close: notifies every active op with `onClosed` — the\n * AsyncIterable consumer sees a `BeamStreamClosedError`\n *\n * v0.1 does NOT automatically reconnect. A transport instance is scoped to\n * one op lifecycle for chunked streams, or to one subscribe/observe session.\n * Callers catch `BeamStreamClosedError` and re-run the op.\n */\nexport class WsTransport {\n private ws: WebSocketLike | null = null\n private state: TransportState = 'idle'\n private readonly ops = new Map<string, PendingOp>()\n private readonly url: string\n private readonly WsCtor: WebSocketConstructor\n private readonly onStateChange?: (state: 'connecting' | 'open' | 'closed') => void\n private openPromise: Promise<void> | null = null\n\n constructor(opts: WsTransportOptions) {\n this.url = opts.url\n const ctor = (opts.WebSocket ?? (globalThis as { WebSocket?: WebSocketConstructor }).WebSocket)\n if (!ctor) {\n throw new Error(\n 'WsTransport: no WebSocket constructor found. Pass `WebSocket` in options or run in a WS-capable environment (Node 22+, browser, edge runtime).',\n )\n }\n this.WsCtor = ctor\n this.onStateChange = opts.onStateChange\n }\n\n /** Open the socket and wait for it to be ready. Idempotent. */\n connect(): Promise<void> {\n if (this.state === 'open') return Promise.resolve()\n if (this.state === 'closed') {\n return Promise.reject(new Error('WsTransport: cannot reconnect a closed transport'))\n }\n if (this.openPromise) return this.openPromise\n\n this.state = 'connecting'\n this.onStateChange?.('connecting')\n const ws = new this.WsCtor(this.url)\n this.ws = ws\n\n this.openPromise = new Promise<void>((resolve, reject) => {\n const onOpen = () => {\n this.state = 'open'\n this.onStateChange?.('open')\n ws.removeEventListener('open', onOpen)\n ws.removeEventListener('error', onInitialError)\n resolve()\n }\n const onInitialError = (ev: unknown) => {\n ws.removeEventListener('open', onOpen)\n ws.removeEventListener('error', onInitialError)\n reject(new Error(`WsTransport: failed to open socket: ${String(ev)}`))\n }\n ws.addEventListener('open', onOpen)\n ws.addEventListener('error', onInitialError)\n })\n\n ws.addEventListener('message', (event) => this.handleMessage(event.data))\n ws.addEventListener('close', (event) => this.handleClose(event.code, event.reason))\n\n return this.openPromise\n }\n\n /** Register an op — idempotent, last writer wins. */\n registerOp(op: PendingOp): void {\n this.ops.set(op.id, op)\n }\n\n /** Unregister an op (called by the AsyncIterable on early break). */\n unregisterOp(id: string): void {\n this.ops.delete(id)\n }\n\n /**\n * Send an op frame on the underlying socket. Waits for the connection to\n * be open before sending. Errors if the transport is closed.\n */\n async send(frame: StreamOpFrame): Promise<void> {\n if (this.state === 'closed') {\n throw new Error('WsTransport: cannot send — transport is closed')\n }\n await this.connect()\n if (!this.ws || this.ws.readyState !== 1 /* OPEN */) {\n throw new Error('WsTransport: socket not open')\n }\n this.ws.send(JSON.stringify(frame))\n }\n\n /** Send a raw control frame without op semantics (e.g. pong). */\n sendRaw(frame: Record<string, unknown>): void {\n if (!this.ws || this.ws.readyState !== 1) return\n this.ws.send(JSON.stringify(frame))\n }\n\n /**\n * Close the socket. All registered ops get an `onClosed(1000, 'normal')`\n * callback before we mark the transport closed.\n */\n close(): void {\n if (this.state === 'closed') return\n this.state = 'closed'\n this.onStateChange?.('closed')\n for (const op of this.ops.values()) {\n try {\n op.onClosed(STREAM_CLOSE_CODES.normal, 'client closed')\n } catch {\n // ignore\n }\n }\n this.ops.clear()\n try {\n this.ws?.close(STREAM_CLOSE_CODES.normal, 'client closed')\n } catch {\n // ignore\n }\n this.ws = null\n }\n\n /** Number of active ops (for tests + debugging). */\n activeOpCount(): number {\n return this.ops.size\n }\n\n // ── Internal ──────────────────────────────────────────────\n\n private handleMessage(raw: string | ArrayBuffer | Uint8Array): void {\n const text =\n typeof raw === 'string'\n ? raw\n : raw instanceof ArrayBuffer\n ? new TextDecoder().decode(new Uint8Array(raw))\n : new TextDecoder().decode(raw)\n\n let frame: StreamFrame\n try {\n frame = JSON.parse(text) as StreamFrame\n } catch {\n return // silently drop malformed frames\n }\n\n // Heartbeat\n if (frame.type === 'ping') {\n this.sendRaw({ type: 'pong' })\n return\n }\n if (frame.type === 'pong') {\n return\n }\n\n // Op-scoped frames\n if ('id' in frame && frame.id) {\n const op = this.ops.get(frame.id)\n if (!op) return // unknown id — op was torn down\n\n switch (frame.type) {\n case 'row':\n op.onRow(frame.data)\n return\n case 'event':\n op.onEvent(frame.kind ?? 'update', frame.record)\n return\n case 'done':\n op.onDone(frame.meta ?? { count: 0, durationMs: 0 })\n this.ops.delete(frame.id)\n return\n case 'error':\n op.onError(frame.code ?? 'internal', frame.message ?? 'stream error')\n this.ops.delete(frame.id)\n return\n }\n }\n\n // Error frame without id — affects every active op\n if (frame.type === 'error') {\n for (const op of this.ops.values()) {\n op.onError(frame.code ?? 'internal', frame.message ?? 'stream error')\n }\n this.ops.clear()\n }\n }\n\n private handleClose(code: number, reason: string): void {\n if (this.state === 'closed') return\n this.state = 'closed'\n this.onStateChange?.('closed')\n for (const op of this.ops.values()) {\n try {\n op.onClosed(code, reason)\n } catch {\n // ignore\n }\n }\n this.ops.clear()\n this.ws = null\n }\n}\n\n/**\n * Build the wss URL for a `/v1/stream/:lens` endpoint.\n *\n * Resolution order:\n * 1. Explicit `wsUrl` override (enterprise / self-hosted / dev override)\n * 2. SaaS canonical: if baseUrl is the hosted `api.semilayer.com`, default\n * to `wss://ws.semilayer.com` — the dedicated streaming subdomain\n * 3. Otherwise, derive from `baseUrl` by swapping scheme (`https://` →\n * `wss://`, `http://` → `ws://`) so self-hosted and localhost work\n * without any config\n *\n * The end-user JWT (`userToken`) is passed as an additional query param when\n * provided. Browsers can't set arbitrary headers on WS upgrades, so query\n * params are the lowest-common-denominator path. TLS encrypts on wire;\n * servers should scrub `userToken` from access logs as an operational\n * practice.\n */\nexport function buildStreamUrl(\n baseUrl: string,\n wsUrl: string | undefined,\n lens: string,\n apiKey: string,\n userToken?: string,\n): string {\n const origin =\n wsUrl ??\n defaultWsOrigin(baseUrl)\n const clean = origin.replace(/\\/$/, '')\n const params = new URLSearchParams({ key: apiKey })\n if (userToken) {\n params.set('userToken', userToken)\n }\n return `${clean}/v1/stream/${encodeURIComponent(lens)}?${params.toString()}`\n}\n\n/**\n * Default WS origin when the caller hasn't set one explicitly. Special-cases\n * the hosted SaaS URL so `new BeamClient({baseUrl: 'https://api.semilayer.com', ...})`\n * opens streams against `wss://ws.semilayer.com` without any extra config.\n */\nfunction defaultWsOrigin(baseUrl: string): string {\n const stripped = baseUrl.replace(/\\/$/, '')\n // Hosted SaaS — route streams to the dedicated subdomain. Both www. and\n // bare subdomain forms match for safety.\n if (/^https?:\\/\\/api\\.semilayer\\.com$/i.test(stripped)) {\n return 'wss://ws.semilayer.com'\n }\n // Everything else: derive from the REST base URL\n return stripped.replace(/^http:\\/\\//, 'ws://').replace(/^https:\\/\\//, 'wss://')\n}\n\n/**\n * Map a server error frame to the typed BeamStream error subclass.\n * Centralized so the op handlers all raise consistent errors.\n */\nexport function toStreamError(\n code: StreamErrorCode,\n message: string,\n lens: string,\n): BeamStreamError {\n if (code === 'rate_limited') {\n return new BeamStreamRateLimitError(message, lens)\n }\n return new BeamStreamError(code, message, lens)\n}\n\n/**\n * Map a close code to a typed error. 4290 = concurrent connection cap, 4291 =\n * rows/hr cap, 4401 = auth failed, 4403 = forbidden. Other codes pass through\n * as a generic closed error.\n */\nexport function closeCodeToError(code: number, reason: string): BeamStreamClosedError {\n return new BeamStreamClosedError(code, reason)\n}\n\n/** Used for typed op discrimination in the client. */\nexport type StreamOpKind = StreamOp\n","import type {\n BeamConfig,\n SearchParams,\n SearchResponse,\n SearchResult,\n SimilarParams,\n SimilarResponse,\n QueryParams,\n QueryResponse,\n StreamSearchParams,\n StreamQueryParams,\n SubscribeParams,\n StreamEvent,\n} from './types.js'\nimport { BeamError, BeamAuthError, BeamNotFoundError } from './errors.js'\nimport { createAsyncQueue } from './async-queue.js'\nimport {\n WsTransport,\n buildStreamUrl,\n toStreamError,\n closeCodeToError,\n type PendingOp,\n} from './ws-transport.js'\nimport type { StreamOpFrame } from '@semilayer/core'\n\n/**\n * BeamClient — the canonical SemiLayer runtime client.\n *\n * HTTP methods (always available):\n * - search, similar, query\n *\n * Streaming namespace (opens WebSocket lazily on first call):\n * - stream.search(lens, params) → AsyncIterable<SearchResult>\n * - stream.query (lens, params) → AsyncIterable<row>\n * - stream.subscribe(lens, params) → AsyncIterable<StreamEvent>\n *\n * Single-record observe (top-level because it's semantically distinct):\n * - observe(lens, recordId) → AsyncIterable<record> (current state + every delta)\n *\n * v0.1 behavior on WebSocket disconnect: pending ops throw\n * `BeamStreamClosedError`. Callers that want transparent reconnect wrap the\n * iteration in a retry loop. Transparent reconnect is a v0.2 feature.\n */\nexport class BeamClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly userToken: string | undefined\n private readonly fetchFn: typeof fetch\n private readonly wsUrl: string | undefined\n private readonly WsCtor: BeamConfig['WebSocket']\n\n /** Streaming ops grouped under a `.stream` sub-object for ergonomic chaining. */\n readonly stream: {\n search<M = Record<string, unknown>>(\n lens: string,\n params: StreamSearchParams,\n ): AsyncIterable<SearchResult<M>>\n query<M = Record<string, unknown>>(\n lens: string,\n params: StreamQueryParams,\n ): AsyncIterable<M>\n subscribe<M = Record<string, unknown>>(\n lens: string,\n params?: SubscribeParams,\n ): AsyncIterable<StreamEvent<M>>\n }\n\n constructor(config: BeamConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.apiKey = config.apiKey\n this.userToken = config.userToken\n this.fetchFn = config.fetch ?? globalThis.fetch\n this.wsUrl = config.wsUrl\n this.WsCtor = config.WebSocket\n\n this.stream = {\n search: <M = Record<string, unknown>>(lens: string, params: StreamSearchParams) =>\n this.runChunkedOp<SearchResult<M>>(lens, {\n id: this.nextOpId(),\n op: 'search.stream',\n params: params as unknown as Record<string, unknown>,\n }),\n query: <M = Record<string, unknown>>(lens: string, params: StreamQueryParams) =>\n this.runChunkedOp<M>(lens, {\n id: this.nextOpId(),\n op: 'query.stream',\n params: params as unknown as Record<string, unknown>,\n }),\n subscribe: <M = Record<string, unknown>>(lens: string, params?: SubscribeParams) =>\n this.runLiveOp<M>(lens, {\n id: this.nextOpId(),\n op: 'subscribe',\n params: (params ?? {}) as Record<string, unknown>,\n }),\n }\n }\n\n /**\n * Return a new `BeamClient` bound to an end-user JWT. Cheap (no network,\n * no new socket) — the clone shares the same baseUrl / apiKey / fetch /\n * WebSocket config and only differs in the `userToken` it attaches.\n *\n * Typical SSR pattern:\n *\n * ```ts\n * const beam = new BeamClient({ baseUrl, apiKey: PK })\n *\n * // Inside a request handler, per end user:\n * const userBeam = beam.withUser(session.token)\n * const results = await userBeam.search('products', { query })\n * ```\n *\n * The returned client passes the token as `X-User-Token` on REST calls and\n * as `?userToken=` on WebSocket handshakes.\n */\n withUser(userToken: string): BeamClient {\n return new BeamClient({\n baseUrl: this.baseUrl,\n apiKey: this.apiKey,\n userToken,\n fetch: this.fetchFn,\n wsUrl: this.wsUrl,\n WebSocket: this.WsCtor,\n })\n }\n\n // ── HTTP ops (existing) ──────────────────────────────────\n\n async search<M = Record<string, unknown>>(\n lens: string,\n params: SearchParams,\n ): Promise<SearchResponse<M>> {\n return this.request<SearchResponse<M>>('POST', `/v1/search/${lens}`, params)\n }\n\n async similar<M = Record<string, unknown>>(\n lens: string,\n params: SimilarParams,\n ): Promise<SimilarResponse<M>> {\n return this.request<SimilarResponse<M>>('POST', `/v1/similar/${lens}`, params)\n }\n\n async query<M = Record<string, unknown>>(\n lens: string,\n params?: QueryParams,\n ): Promise<QueryResponse<M>> {\n return this.request<QueryResponse<M>>('POST', `/v1/query/${lens}`, params ?? {})\n }\n\n // ── Streaming ops — single-record observation ───────────\n\n /**\n * Observe a single record by id. First yield is the current record state;\n * subsequent yields fire only when the record actually changes (server-side\n * per-subscription hash dedup). Terminates when the consumer breaks, the\n * abort signal fires, or the server closes the stream.\n */\n observe<M = Record<string, unknown>>(\n lens: string,\n recordId: string,\n ): AsyncIterable<M> {\n return this.runObserveOp<M>(lens, {\n id: this.nextOpId(),\n op: 'observe',\n params: { recordId },\n })\n }\n\n // ── Internal ─────────────────────────────────────────────\n\n private opCounter = 0\n private nextOpId(): string {\n this.opCounter += 1\n return `op_${Date.now().toString(36)}_${this.opCounter}`\n }\n\n /**\n * Run a chunked op (search.stream / query.stream). The transport yields\n * `row` frames; we push each row into the queue as-is. Terminates on\n * `done` / `error` / socket close.\n */\n private runChunkedOp<T>(lens: string, frame: StreamOpFrame): AsyncIterable<T> {\n const queue = createAsyncQueue<T>()\n const transport = this.buildOpTransport<T>(lens, frame, queue, {\n onRow: (data) => queue.push(data as T),\n // `event` frames shouldn't arrive on chunked ops, but if they do we\n // silently ignore rather than crash the iterator.\n onEvent: () => {},\n })\n return this.wrapIterable(queue, transport)\n }\n\n /**\n * Run a long-lived live op (subscribe). The transport yields `event`\n * frames; we wrap each as a `StreamEvent` before pushing.\n */\n private runLiveOp<M>(lens: string, frame: StreamOpFrame): AsyncIterable<StreamEvent<M>> {\n const queue = createAsyncQueue<StreamEvent<M>>()\n const transport = this.buildOpTransport<StreamEvent<M>>(lens, frame, queue, {\n onRow: () => {},\n onEvent: (kind, record) => queue.push({ kind, record: record as M }),\n })\n return this.wrapIterable(queue, transport)\n }\n\n /**\n * Run an observe op. Like subscribe but yields the raw record (not\n * wrapped in a StreamEvent) — first yield is current state, subsequent\n * yields are deltas.\n */\n private runObserveOp<M>(lens: string, frame: StreamOpFrame): AsyncIterable<M> {\n const queue = createAsyncQueue<M>()\n const transport = this.buildOpTransport<M>(lens, frame, queue, {\n onRow: () => {},\n onEvent: (_kind, record) => queue.push(record as M),\n })\n return this.wrapIterable(queue, transport)\n }\n\n /**\n * Build the transport + register the PendingOp + kick off the send.\n * Returns the transport so the iterator wrapper can close it on break.\n */\n private buildOpTransport<T>(\n lens: string,\n frame: StreamOpFrame,\n queue: ReturnType<typeof createAsyncQueue<T>>,\n handlers: {\n onRow: (data: unknown) => void\n onEvent: (kind: 'insert' | 'update' | 'delete', record: unknown) => void\n },\n ): WsTransport {\n const url = buildStreamUrl(this.baseUrl, this.wsUrl, lens, this.apiKey, this.userToken)\n const transport = new WsTransport({ url, WebSocket: this.WsCtor })\n\n const op: PendingOp = {\n id: frame.id!,\n kind:\n frame.op === 'search.stream' || frame.op === 'query.stream'\n ? 'chunked'\n : frame.op === 'observe'\n ? 'observe'\n : 'live',\n onRow: handlers.onRow,\n onEvent: handlers.onEvent,\n onDone() {\n queue.done()\n transport.close()\n },\n onError(code, message) {\n queue.error(toStreamError(code, message, lens))\n transport.close()\n },\n onClosed(code, reason) {\n if (!queue.closed) {\n queue.error(closeCodeToError(code, reason))\n }\n },\n }\n transport.registerOp(op)\n\n // Kick off the op. Any error from send() surfaces to the first `next()`.\n void transport.send(frame).catch((err) => {\n queue.error(err instanceof Error ? err : new Error(String(err)))\n transport.close()\n })\n\n return transport\n }\n\n /**\n * Wrap an AsyncQueue so that a consumer breaking out of `for await` tears\n * down the transport cleanly.\n */\n private wrapIterable<T>(\n queue: ReturnType<typeof createAsyncQueue<T>>,\n transport: WsTransport,\n ): AsyncIterable<T> {\n const base = queue[Symbol.asyncIterator].bind(queue)\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n const inner = base()\n return {\n async next() {\n return inner.next()\n },\n async return() {\n if (inner.return) await inner.return(undefined as unknown as T)\n transport.close()\n return { value: undefined as unknown as T, done: true }\n },\n }\n },\n }\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n }\n if (this.userToken) {\n headers['X-User-Token'] = this.userToken\n }\n\n const res = await this.fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n if (!res.ok) {\n const parsed = await res.json().catch(() => ({ error: res.statusText }))\n const detail = (parsed as { error?: string }).error ?? res.statusText\n\n if (res.status === 401) throw new BeamAuthError(detail, path)\n if (res.status === 404) throw new BeamNotFoundError(detail, path)\n throw new BeamError(res.status, detail, path)\n }\n\n return res.json() as Promise<T>\n }\n}\n"],"mappings":";AAAO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YACkB,QACA,QACA,MAChB;AACA,UAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,IAAI,GAAG;AAJ3B;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EACA;AAAA,EACA;AAKpB;AAEO,IAAM,oBAAN,cAAgC,UAAU;AAAA,EAC/C,YAAY,QAAgB,MAAc;AACxC,UAAM,KAAK,QAAQ,IAAI;AACvB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,gBAAN,cAA4B,UAAU;AAAA,EAC3C,YAAY,QAAgB,MAAc;AACxC,UAAM,KAAK,QAAQ,IAAI;AACvB,SAAK,OAAO;AAAA,EACd;AACF;AASO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACkB,MACA,QACA,MAChB;AACA,UAAM,eAAe,IAAI,KAAK,MAAM,KAAK,IAAI,GAAG;AAJhC;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EACA;AAAA,EACA;AAKpB;AAMO,IAAM,2BAAN,cAAuC,gBAAgB;AAAA,EAC5D,YAAY,QAAgB,MAAc;AACxC,UAAM,gBAAgB,QAAQ,IAAI;AAClC,SAAK,OAAO;AAAA,EACd;AACF;AAMO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YACkB,WACA,QAChB;AACA,UAAM,uBAAuB,SAAS,IAAI,MAAM,EAAE;AAHlC;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;;;AC5CO,SAAS,mBAAqC;AACnD,QAAM,QAAa,CAAC;AACpB,QAAM,UAGD,CAAC;AACN,MAAI,SAAS;AACb,MAAI,aAA2B;AAE/B,WAAS,KAAK,MAAe;AAC3B,QAAI,OAAQ;AACZ,UAAM,SAAS,QAAQ,MAAM;AAC7B,QAAI,QAAQ;AACV,aAAO,QAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AAEA,WAAS,OAAa;AACpB,QAAI,OAAQ;AACZ,aAAS;AACT,WAAO,QAAQ,SAAS,GAAG;AACzB,YAAM,SAAS,QAAQ,MAAM;AAC7B,aAAO,QAAQ,EAAE,OAAO,QAAW,MAAM,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AAEA,WAAS,MAAM,KAAkB;AAC/B,QAAI,OAAQ;AACZ,aAAS;AACT,iBAAa;AACb,WAAO,QAAQ,SAAS,GAAG;AACzB,YAAM,SAAS,QAAQ,MAAM;AAC7B,aAAO,OAAO,GAAG;AAAA,IACnB;AAAA,EACF;AAEA,iBAAe,OAAmC;AAChD,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO,EAAE,OAAO,MAAM,MAAM,GAAI,MAAM,MAAM;AAAA,IAC9C;AACA,QAAI,QAAQ;AACV,UAAI,WAAY,OAAM;AACtB,aAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,IACxC;AACA,WAAO,IAAI,QAA2B,CAAC,SAAS,WAAW;AACzD,cAAQ,KAAK,EAAE,SAAS,OAAO,CAAC;AAAA,IAClC,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,CAAC,OAAO,aAAa,IAAsB;AACzC,aAAO;AAAA,QACL;AAAA,QACA,MAAM,SAAS;AACb,eAAK;AACL,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACpFA,SAAS,0BAA0B;AAgE5B,IAAM,cAAN,MAAkB;AAAA,EACf,KAA2B;AAAA,EAC3B,QAAwB;AAAA,EACf,MAAM,oBAAI,IAAuB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACT,cAAoC;AAAA,EAE5C,YAAY,MAA0B;AACpC,SAAK,MAAM,KAAK;AAChB,UAAM,OAAQ,KAAK,aAAc,WAAoD;AACrF,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,SAAS;AACd,SAAK,gBAAgB,KAAK;AAAA,EAC5B;AAAA;AAAA,EAGA,UAAyB;AACvB,QAAI,KAAK,UAAU,OAAQ,QAAO,QAAQ,QAAQ;AAClD,QAAI,KAAK,UAAU,UAAU;AAC3B,aAAO,QAAQ,OAAO,IAAI,MAAM,kDAAkD,CAAC;AAAA,IACrF;AACA,QAAI,KAAK,YAAa,QAAO,KAAK;AAElC,SAAK,QAAQ;AACb,SAAK,gBAAgB,YAAY;AACjC,UAAM,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG;AACnC,SAAK,KAAK;AAEV,SAAK,cAAc,IAAI,QAAc,CAAC,SAAS,WAAW;AACxD,YAAM,SAAS,MAAM;AACnB,aAAK,QAAQ;AACb,aAAK,gBAAgB,MAAM;AAC3B,WAAG,oBAAoB,QAAQ,MAAM;AACrC,WAAG,oBAAoB,SAAS,cAAc;AAC9C,gBAAQ;AAAA,MACV;AACA,YAAM,iBAAiB,CAAC,OAAgB;AACtC,WAAG,oBAAoB,QAAQ,MAAM;AACrC,WAAG,oBAAoB,SAAS,cAAc;AAC9C,eAAO,IAAI,MAAM,uCAAuC,OAAO,EAAE,CAAC,EAAE,CAAC;AAAA,MACvE;AACA,SAAG,iBAAiB,QAAQ,MAAM;AAClC,SAAG,iBAAiB,SAAS,cAAc;AAAA,IAC7C,CAAC;AAED,OAAG,iBAAiB,WAAW,CAAC,UAAU,KAAK,cAAc,MAAM,IAAI,CAAC;AACxE,OAAG,iBAAiB,SAAS,CAAC,UAAU,KAAK,YAAY,MAAM,MAAM,MAAM,MAAM,CAAC;AAElF,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAAW,IAAqB;AAC9B,SAAK,IAAI,IAAI,GAAG,IAAI,EAAE;AAAA,EACxB;AAAA;AAAA,EAGA,aAAa,IAAkB;AAC7B,SAAK,IAAI,OAAO,EAAE;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAK,OAAqC;AAC9C,QAAI,KAAK,UAAU,UAAU;AAC3B,YAAM,IAAI,MAAM,qDAAgD;AAAA,IAClE;AACA,UAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,GAAc;AACnD,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,SAAK,GAAG,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACpC;AAAA;AAAA,EAGA,QAAQ,OAAsC;AAC5C,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,EAAG;AAC1C,SAAK,GAAG,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AACZ,QAAI,KAAK,UAAU,SAAU;AAC7B,SAAK,QAAQ;AACb,SAAK,gBAAgB,QAAQ;AAC7B,eAAW,MAAM,KAAK,IAAI,OAAO,GAAG;AAClC,UAAI;AACF,WAAG,SAAS,mBAAmB,QAAQ,eAAe;AAAA,MACxD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,IAAI,MAAM;AACf,QAAI;AACF,WAAK,IAAI,MAAM,mBAAmB,QAAQ,eAAe;AAAA,IAC3D,QAAQ;AAAA,IAER;AACA,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA,EAGA,gBAAwB;AACtB,WAAO,KAAK,IAAI;AAAA,EAClB;AAAA;AAAA,EAIQ,cAAc,KAA8C;AAClE,UAAM,OACJ,OAAO,QAAQ,WACX,MACA,eAAe,cACb,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,GAAG,CAAC,IAC5C,IAAI,YAAY,EAAE,OAAO,GAAG;AAEpC,QAAI;AACJ,QAAI;AACF,cAAQ,KAAK,MAAM,IAAI;AAAA,IACzB,QAAQ;AACN;AAAA,IACF;AAGA,QAAI,MAAM,SAAS,QAAQ;AACzB,WAAK,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC7B;AAAA,IACF;AACA,QAAI,MAAM,SAAS,QAAQ;AACzB;AAAA,IACF;AAGA,QAAI,QAAQ,SAAS,MAAM,IAAI;AAC7B,YAAM,KAAK,KAAK,IAAI,IAAI,MAAM,EAAE;AAChC,UAAI,CAAC,GAAI;AAET,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AACH,aAAG,MAAM,MAAM,IAAI;AACnB;AAAA,QACF,KAAK;AACH,aAAG,QAAQ,MAAM,QAAQ,UAAU,MAAM,MAAM;AAC/C;AAAA,QACF,KAAK;AACH,aAAG,OAAO,MAAM,QAAQ,EAAE,OAAO,GAAG,YAAY,EAAE,CAAC;AACnD,eAAK,IAAI,OAAO,MAAM,EAAE;AACxB;AAAA,QACF,KAAK;AACH,aAAG,QAAQ,MAAM,QAAQ,YAAY,MAAM,WAAW,cAAc;AACpE,eAAK,IAAI,OAAO,MAAM,EAAE;AACxB;AAAA,MACJ;AAAA,IACF;AAGA,QAAI,MAAM,SAAS,SAAS;AAC1B,iBAAW,MAAM,KAAK,IAAI,OAAO,GAAG;AAClC,WAAG,QAAQ,MAAM,QAAQ,YAAY,MAAM,WAAW,cAAc;AAAA,MACtE;AACA,WAAK,IAAI,MAAM;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,YAAY,MAAc,QAAsB;AACtD,QAAI,KAAK,UAAU,SAAU;AAC7B,SAAK,QAAQ;AACb,SAAK,gBAAgB,QAAQ;AAC7B,eAAW,MAAM,KAAK,IAAI,OAAO,GAAG;AAClC,UAAI;AACF,WAAG,SAAS,MAAM,MAAM;AAAA,MAC1B,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,IAAI,MAAM;AACf,SAAK,KAAK;AAAA,EACZ;AACF;AAmBO,SAAS,eACd,SACA,OACA,MACA,QACA,WACQ;AACR,QAAM,SACJ,SACA,gBAAgB,OAAO;AACzB,QAAM,QAAQ,OAAO,QAAQ,OAAO,EAAE;AACtC,QAAM,SAAS,IAAI,gBAAgB,EAAE,KAAK,OAAO,CAAC;AAClD,MAAI,WAAW;AACb,WAAO,IAAI,aAAa,SAAS;AAAA,EACnC;AACA,SAAO,GAAG,KAAK,cAAc,mBAAmB,IAAI,CAAC,IAAI,OAAO,SAAS,CAAC;AAC5E;AAOA,SAAS,gBAAgB,SAAyB;AAChD,QAAM,WAAW,QAAQ,QAAQ,OAAO,EAAE;AAG1C,MAAI,oCAAoC,KAAK,QAAQ,GAAG;AACtD,WAAO;AAAA,EACT;AAEA,SAAO,SAAS,QAAQ,cAAc,OAAO,EAAE,QAAQ,eAAe,QAAQ;AAChF;AAMO,SAAS,cACd,MACA,SACA,MACiB;AACjB,MAAI,SAAS,gBAAgB;AAC3B,WAAO,IAAI,yBAAyB,SAAS,IAAI;AAAA,EACnD;AACA,SAAO,IAAI,gBAAgB,MAAM,SAAS,IAAI;AAChD;AAOO,SAAS,iBAAiB,MAAc,QAAuC;AACpF,SAAO,IAAI,sBAAsB,MAAM,MAAM;AAC/C;;;ACnSO,IAAM,aAAN,MAAM,YAAW;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGR;AAAA,EAeT,YAAY,QAAoB;AAC9B,SAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC/C,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AACxB,SAAK,UAAU,OAAO,SAAS,WAAW;AAC1C,SAAK,QAAQ,OAAO;AACpB,SAAK,SAAS,OAAO;AAErB,SAAK,SAAS;AAAA,MACZ,QAAQ,CAA8B,MAAc,WAClD,KAAK,aAA8B,MAAM;AAAA,QACvC,IAAI,KAAK,SAAS;AAAA,QAClB,IAAI;AAAA,QACJ;AAAA,MACF,CAAC;AAAA,MACH,OAAO,CAA8B,MAAc,WACjD,KAAK,aAAgB,MAAM;AAAA,QACzB,IAAI,KAAK,SAAS;AAAA,QAClB,IAAI;AAAA,QACJ;AAAA,MACF,CAAC;AAAA,MACH,WAAW,CAA8B,MAAc,WACrD,KAAK,UAAa,MAAM;AAAA,QACtB,IAAI,KAAK,SAAS;AAAA,QAClB,IAAI;AAAA,QACJ,QAAS,UAAU,CAAC;AAAA,MACtB,CAAC;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,SAAS,WAA+B;AACtC,WAAO,IAAI,YAAW;AAAA,MACpB,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,OACJ,MACA,QAC4B;AAC5B,WAAO,KAAK,QAA2B,QAAQ,cAAc,IAAI,IAAI,MAAM;AAAA,EAC7E;AAAA,EAEA,MAAM,QACJ,MACA,QAC6B;AAC7B,WAAO,KAAK,QAA4B,QAAQ,eAAe,IAAI,IAAI,MAAM;AAAA,EAC/E;AAAA,EAEA,MAAM,MACJ,MACA,QAC2B;AAC3B,WAAO,KAAK,QAA0B,QAAQ,aAAa,IAAI,IAAI,UAAU,CAAC,CAAC;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,QACE,MACA,UACkB;AAClB,WAAO,KAAK,aAAgB,MAAM;AAAA,MAChC,IAAI,KAAK,SAAS;AAAA,MAClB,IAAI;AAAA,MACJ,QAAQ,EAAE,SAAS;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,YAAY;AAAA,EACZ,WAAmB;AACzB,SAAK,aAAa;AAClB,WAAO,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,SAAS;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aAAgB,MAAc,OAAwC;AAC5E,UAAM,QAAQ,iBAAoB;AAClC,UAAM,YAAY,KAAK,iBAAoB,MAAM,OAAO,OAAO;AAAA,MAC7D,OAAO,CAAC,SAAS,MAAM,KAAK,IAAS;AAAA;AAAA;AAAA,MAGrC,SAAS,MAAM;AAAA,MAAC;AAAA,IAClB,CAAC;AACD,WAAO,KAAK,aAAa,OAAO,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAa,MAAc,OAAqD;AACtF,UAAM,QAAQ,iBAAiC;AAC/C,UAAM,YAAY,KAAK,iBAAiC,MAAM,OAAO,OAAO;AAAA,MAC1E,OAAO,MAAM;AAAA,MAAC;AAAA,MACd,SAAS,CAAC,MAAM,WAAW,MAAM,KAAK,EAAE,MAAM,OAAoB,CAAC;AAAA,IACrE,CAAC;AACD,WAAO,KAAK,aAAa,OAAO,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aAAgB,MAAc,OAAwC;AAC5E,UAAM,QAAQ,iBAAoB;AAClC,UAAM,YAAY,KAAK,iBAAoB,MAAM,OAAO,OAAO;AAAA,MAC7D,OAAO,MAAM;AAAA,MAAC;AAAA,MACd,SAAS,CAAC,OAAO,WAAW,MAAM,KAAK,MAAW;AAAA,IACpD,CAAC;AACD,WAAO,KAAK,aAAa,OAAO,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBACN,MACA,OACA,OACA,UAIa;AACb,UAAM,MAAM,eAAe,KAAK,SAAS,KAAK,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS;AACtF,UAAM,YAAY,IAAI,YAAY,EAAE,KAAK,WAAW,KAAK,OAAO,CAAC;AAEjE,UAAM,KAAgB;AAAA,MACpB,IAAI,MAAM;AAAA,MACV,MACE,MAAM,OAAO,mBAAmB,MAAM,OAAO,iBACzC,YACA,MAAM,OAAO,YACX,YACA;AAAA,MACR,OAAO,SAAS;AAAA,MAChB,SAAS,SAAS;AAAA,MAClB,SAAS;AACP,cAAM,KAAK;AACX,kBAAU,MAAM;AAAA,MAClB;AAAA,MACA,QAAQ,MAAM,SAAS;AACrB,cAAM,MAAM,cAAc,MAAM,SAAS,IAAI,CAAC;AAC9C,kBAAU,MAAM;AAAA,MAClB;AAAA,MACA,SAAS,MAAM,QAAQ;AACrB,YAAI,CAAC,MAAM,QAAQ;AACjB,gBAAM,MAAM,iBAAiB,MAAM,MAAM,CAAC;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AACA,cAAU,WAAW,EAAE;AAGvB,SAAK,UAAU,KAAK,KAAK,EAAE,MAAM,CAAC,QAAQ;AACxC,YAAM,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAC/D,gBAAU,MAAM;AAAA,IAClB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aACN,OACA,WACkB;AAClB,UAAM,OAAO,MAAM,OAAO,aAAa,EAAE,KAAK,KAAK;AACnD,WAAO;AAAA,MACL,CAAC,OAAO,aAAa,IAAsB;AACzC,cAAM,QAAQ,KAAK;AACnB,eAAO;AAAA,UACL,MAAM,OAAO;AACX,mBAAO,MAAM,KAAK;AAAA,UACpB;AAAA,UACA,MAAM,SAAS;AACb,gBAAI,MAAM,OAAQ,OAAM,MAAM,OAAO,MAAyB;AAC9D,sBAAU,MAAM;AAChB,mBAAO,EAAE,OAAO,QAA2B,MAAM,KAAK;AAAA,UACxD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,QAAW,QAAgB,MAAc,MAA4B;AACjF,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAElC,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,eAAe,UAAU,KAAK,MAAM;AAAA,IACtC;AACA,QAAI,KAAK,WAAW;AAClB,cAAQ,cAAc,IAAI,KAAK;AAAA,IACjC;AAEA,UAAM,MAAM,MAAM,KAAK,QAAQ,KAAK;AAAA,MAClC;AAAA,MACA;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACtC,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,SAAS,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE;AACvE,YAAM,SAAU,OAA8B,SAAS,IAAI;AAE3D,UAAI,IAAI,WAAW,IAAK,OAAM,IAAI,cAAc,QAAQ,IAAI;AAC5D,UAAI,IAAI,WAAW,IAAK,OAAM,IAAI,kBAAkB,QAAQ,IAAI;AAChE,YAAM,IAAI,UAAU,IAAI,QAAQ,QAAQ,IAAI;AAAA,IAC9C;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@semilayer/client",
3
+ "version": "1.1.0",
4
+ "description": "SemiLayer Beam client runtime — fetch, auth, pagination",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "lint": "eslint src/",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run"
23
+ },
24
+ "dependencies": {
25
+ "@semilayer/core": "workspace:*"
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.7.0",
30
+ "vitest": "^3.0.0"
31
+ }
32
+ }