iii-sdk 0.17.0 → 0.18.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,401 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let node_stream = require("node:stream");
29
+ let ws = require("ws");
30
+ let node_fs = require("node:fs");
31
+ node_fs = __toESM(node_fs);
32
+ let node_path = require("node:path");
33
+ node_path = __toESM(node_path);
34
+
35
+ //#region src/channels.ts
36
+ /**
37
+ * Direction of a streaming channel endpoint. Mirrors the Rust SDK's
38
+ * `ChannelDirection` enum and matches the literal values used by
39
+ * {@link StreamChannelRef.direction}.
40
+ */
41
+ const ChannelDirection = {
42
+ Read: "read",
43
+ Write: "write"
44
+ };
45
+ const ChannelItem = {
46
+ Text(value) {
47
+ return {
48
+ type: "text",
49
+ value
50
+ };
51
+ },
52
+ Binary(value) {
53
+ return {
54
+ type: "binary",
55
+ value
56
+ };
57
+ }
58
+ };
59
+ /**
60
+ * Write end of a streaming channel. Provides both a Node.js `Writable` stream
61
+ * and a `sendMessage` method for sending structured text messages.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { createChannel } from 'iii-sdk/helpers'
66
+ * const channel = await createChannel(iii)
67
+ *
68
+ * // Stream binary data
69
+ * channel.writer.stream.write(Buffer.from('hello'))
70
+ * channel.writer.stream.end()
71
+ *
72
+ * // Or send text messages
73
+ * channel.writer.sendMessage(JSON.stringify({ type: 'event', data: 'test' }))
74
+ * channel.writer.close()
75
+ * ```
76
+ */
77
+ var ChannelWriter = class ChannelWriter {
78
+ static {
79
+ this.FRAME_SIZE = 64 * 1024;
80
+ }
81
+ constructor(engineWsBase, ref) {
82
+ this.ws = null;
83
+ this.wsReady = false;
84
+ this.pendingMessages = [];
85
+ this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, "write");
86
+ this.stream = new node_stream.Writable({
87
+ write: (chunk, _encoding, callback) => {
88
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
89
+ this.sendChunked(buf, callback);
90
+ },
91
+ final: (callback) => {
92
+ if (!this.ws) {
93
+ callback();
94
+ return;
95
+ }
96
+ const doClose = () => {
97
+ if (this.ws) this.ws.close(1e3, "stream_complete");
98
+ callback();
99
+ };
100
+ if (this.wsReady) setTimeout(doClose, 10);
101
+ else this.ws.on("open", () => setTimeout(doClose, 10));
102
+ },
103
+ destroy: (err, callback) => {
104
+ if (this.ws) this.ws.terminate();
105
+ callback(err);
106
+ }
107
+ });
108
+ }
109
+ ensureConnected() {
110
+ if (this.ws) return;
111
+ this.ws = new ws.WebSocket(this.url);
112
+ this.ws.on("open", () => {
113
+ this.wsReady = true;
114
+ for (const { data, callback } of this.pendingMessages) this.ws?.send(data, callback);
115
+ this.pendingMessages.length = 0;
116
+ });
117
+ this.ws.on("error", (err) => {
118
+ this.stream.destroy(err);
119
+ });
120
+ this.ws.on("close", () => {
121
+ if (!this.stream.destroyed) this.stream.destroy();
122
+ });
123
+ }
124
+ /** Send a text message through the channel. */
125
+ sendMessage(msg) {
126
+ this.ensureConnected();
127
+ this.sendRaw(msg, (err) => {
128
+ if (err) this.stream.destroy(err);
129
+ });
130
+ }
131
+ /** Close the channel writer. */
132
+ close() {
133
+ if (!this.ws) return;
134
+ const doClose = () => {
135
+ if (this.ws) this.ws.close(1e3, "channel_close");
136
+ };
137
+ if (this.wsReady) doClose();
138
+ else this.ws.on("open", () => doClose());
139
+ }
140
+ sendChunked(data, callback) {
141
+ let offset = 0;
142
+ const sendNext = (err) => {
143
+ if (err) {
144
+ callback(err);
145
+ return;
146
+ }
147
+ if (offset >= data.length) {
148
+ callback(null);
149
+ return;
150
+ }
151
+ const end = Math.min(offset + ChannelWriter.FRAME_SIZE, data.length);
152
+ const part = data.subarray(offset, end);
153
+ offset = end;
154
+ this.sendRaw(part, sendNext);
155
+ };
156
+ sendNext(null);
157
+ }
158
+ sendRaw(data, callback) {
159
+ this.ensureConnected();
160
+ if (this.wsReady && this.ws) this.ws.send(data, (err) => callback(err ?? null));
161
+ else this.pendingMessages.push({
162
+ data,
163
+ callback
164
+ });
165
+ }
166
+ };
167
+ /**
168
+ * Read end of a streaming channel. Provides both a Node.js `Readable` stream
169
+ * for binary data and an `onMessage` callback for structured text messages.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * import { createChannel } from 'iii-sdk/helpers'
174
+ * const channel = await createChannel(iii)
175
+ *
176
+ * // Stream binary data
177
+ * channel.reader.stream.on('data', (chunk) => console.log(chunk))
178
+ *
179
+ * // Or receive text messages
180
+ * channel.reader.onMessage((msg) => console.log('Got:', msg))
181
+ * ```
182
+ */
183
+ var ChannelReader = class {
184
+ constructor(engineWsBase, ref) {
185
+ this.ws = null;
186
+ this.connected = false;
187
+ this.messageCallbacks = [];
188
+ this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, "read");
189
+ const self = this;
190
+ this.stream = new node_stream.Readable({
191
+ read() {
192
+ self.ensureConnected();
193
+ if (self.ws) self.ws.resume();
194
+ },
195
+ destroy(err, callback) {
196
+ if (self.ws && self.ws.readyState !== ws.WebSocket.CLOSED) self.ws.terminate();
197
+ self.ws = null;
198
+ callback(err);
199
+ }
200
+ });
201
+ }
202
+ ensureConnected() {
203
+ if (this.connected) return;
204
+ this.connected = true;
205
+ this.ws = new ws.WebSocket(this.url);
206
+ this.ws.on("open", () => {
207
+ this.ws.binaryType = "nodebuffer";
208
+ });
209
+ this.ws.on("message", (data, isBinary) => {
210
+ if (isBinary) {
211
+ if (!this.stream.push(data)) this.ws?.pause();
212
+ } else {
213
+ const msg = data.toString("utf-8");
214
+ for (const cb of this.messageCallbacks) cb(msg);
215
+ }
216
+ });
217
+ this.ws.on("close", () => {
218
+ this.ws = null;
219
+ if (!this.stream.destroyed) this.stream.push(null);
220
+ });
221
+ this.ws.on("error", (err) => {
222
+ this.stream.destroy(err);
223
+ });
224
+ }
225
+ /** Register a callback to receive text messages from the channel. */
226
+ onMessage(callback) {
227
+ this.messageCallbacks.push(callback);
228
+ }
229
+ async readAll() {
230
+ this.ensureConnected();
231
+ const chunks = [];
232
+ for await (const chunk of this.stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
233
+ return Buffer.concat(chunks);
234
+ }
235
+ close() {
236
+ if (this.ws && this.ws.readyState !== ws.WebSocket.CLOSED) this.ws.close(1e3, "channel_close");
237
+ }
238
+ };
239
+ function buildChannelUrl(engineWsBase, channelId, accessKey, direction) {
240
+ return `${engineWsBase.replace(/\/$/, "")}/ws/channels/${channelId}?key=${encodeURIComponent(accessKey)}&dir=${direction}`;
241
+ }
242
+
243
+ //#endregion
244
+ //#region src/utils.ts
245
+ /**
246
+ * Returns a project identifier for telemetry, derived from the current working
247
+ * directory. Reads `package.json` `name` if present at `cwd`; otherwise falls
248
+ * back to the basename of `cwd`. Returns `undefined` only when both signals
249
+ * are unavailable (e.g. cwd is the filesystem root).
250
+ *
251
+ * No directory walking — only inspects `cwd` itself, so the SDK never reads
252
+ * files outside the user's explicit working directory.
253
+ */
254
+ function detectProjectName(cwd = process.cwd()) {
255
+ try {
256
+ const manifest = node_path.join(cwd, "package.json");
257
+ if (node_fs.existsSync(manifest)) {
258
+ const parsed = JSON.parse(node_fs.readFileSync(manifest, "utf8"));
259
+ if (typeof parsed.name === "string") {
260
+ const trimmed = parsed.name.trim();
261
+ if (trimmed) return trimmed;
262
+ }
263
+ }
264
+ } catch {}
265
+ return node_path.basename(cwd).trim() || void 0;
266
+ }
267
+ /**
268
+ * Helper that wraps an HTTP-style handler (with separate `req`/`res` arguments)
269
+ * into the function handler format expected by the SDK.
270
+ *
271
+ * @param callback - Async handler receiving an {@link HttpRequest} and {@link HttpResponse}.
272
+ * @returns A function handler compatible with {@link ISdk.registerFunction}.
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * import { http } from 'iii-sdk'
277
+ *
278
+ * iii.registerFunction(
279
+ * 'my-api',
280
+ * http(async (req, res) => {
281
+ * res.status(200)
282
+ * res.headers({ 'content-type': 'application/json' })
283
+ * res.stream.end(JSON.stringify({ hello: 'world' }))
284
+ * res.close()
285
+ * }),
286
+ * )
287
+ * ```
288
+ */
289
+ const http = (callback) => {
290
+ return async (req) => {
291
+ const { response, ...request } = req;
292
+ return callback(request, {
293
+ status: (status_code) => response.sendMessage(JSON.stringify({
294
+ type: "set_status",
295
+ status_code
296
+ })),
297
+ headers: (headers) => response.sendMessage(JSON.stringify({
298
+ type: "set_headers",
299
+ headers
300
+ })),
301
+ stream: response.stream,
302
+ close: () => response.close()
303
+ });
304
+ };
305
+ };
306
+ /**
307
+ * Type guard that checks if a value is a {@link StreamChannelRef}.
308
+ *
309
+ * @param value - Value to check.
310
+ * @returns `true` if the value is a valid `StreamChannelRef`.
311
+ */
312
+ const isChannelRef = (value) => {
313
+ if (typeof value !== "object" || value === null) return false;
314
+ const maybe = value;
315
+ return typeof maybe.channel_id === "string" && typeof maybe.access_key === "string" && (maybe.direction === "read" || maybe.direction === "write");
316
+ };
317
+ /**
318
+ * Recursively extract all {@link StreamChannelRef} values from a JSON-like
319
+ * input, returning each match paired with its dotted/bracketed path. Mirrors
320
+ * the Rust SDK's `extract_channel_refs`.
321
+ *
322
+ * @param data - Arbitrary JSON-like value.
323
+ * @returns Array of `[path, ref]` tuples. Empty when no refs are found.
324
+ */
325
+ const extractChannelRefs = (data) => {
326
+ const refs = [];
327
+ extractRefsRecursive(data, "", refs);
328
+ return refs;
329
+ };
330
+ const extractRefsRecursive = (data, prefix, refs) => {
331
+ if (isChannelRef(data)) {
332
+ refs.push([prefix, data]);
333
+ return;
334
+ }
335
+ if (Array.isArray(data)) {
336
+ for (let i = 0; i < data.length; i++) {
337
+ const path = prefix === "" ? `[${i}]` : `${prefix}[${i}]`;
338
+ extractRefsRecursive(data[i], path, refs);
339
+ }
340
+ return;
341
+ }
342
+ if (typeof data !== "object" || data === null) return;
343
+ for (const [key, value] of Object.entries(data)) extractRefsRecursive(value, prefix === "" ? key : `${prefix}.${key}`, refs);
344
+ };
345
+
346
+ //#endregion
347
+ Object.defineProperty(exports, 'ChannelDirection', {
348
+ enumerable: true,
349
+ get: function () {
350
+ return ChannelDirection;
351
+ }
352
+ });
353
+ Object.defineProperty(exports, 'ChannelItem', {
354
+ enumerable: true,
355
+ get: function () {
356
+ return ChannelItem;
357
+ }
358
+ });
359
+ Object.defineProperty(exports, 'ChannelReader', {
360
+ enumerable: true,
361
+ get: function () {
362
+ return ChannelReader;
363
+ }
364
+ });
365
+ Object.defineProperty(exports, 'ChannelWriter', {
366
+ enumerable: true,
367
+ get: function () {
368
+ return ChannelWriter;
369
+ }
370
+ });
371
+ Object.defineProperty(exports, '__toESM', {
372
+ enumerable: true,
373
+ get: function () {
374
+ return __toESM;
375
+ }
376
+ });
377
+ Object.defineProperty(exports, 'detectProjectName', {
378
+ enumerable: true,
379
+ get: function () {
380
+ return detectProjectName;
381
+ }
382
+ });
383
+ Object.defineProperty(exports, 'extractChannelRefs', {
384
+ enumerable: true,
385
+ get: function () {
386
+ return extractChannelRefs;
387
+ }
388
+ });
389
+ Object.defineProperty(exports, 'http', {
390
+ enumerable: true,
391
+ get: function () {
392
+ return http;
393
+ }
394
+ });
395
+ Object.defineProperty(exports, 'isChannelRef', {
396
+ enumerable: true,
397
+ get: function () {
398
+ return isChannelRef;
399
+ }
400
+ });
401
+ //# sourceMappingURL=utils-CuS1Knym.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-CuS1Knym.cjs","names":["Writable","WebSocket","Readable","path","fs"],"sources":["../src/channels.ts","../src/utils.ts"],"sourcesContent":["import { Readable, Writable } from 'node:stream'\nimport { WebSocket } from 'ws'\nimport type { StreamChannelRef } from './iii-types'\n\n/**\n * Direction of a streaming channel endpoint. Mirrors the Rust SDK's\n * `ChannelDirection` enum and matches the literal values used by\n * {@link StreamChannelRef.direction}.\n */\nexport const ChannelDirection = {\n Read: 'read',\n Write: 'write',\n} as const\nexport type ChannelDirection = (typeof ChannelDirection)[keyof typeof ChannelDirection]\n\n/**\n * Discriminated runtime tag for an item observed on a streaming channel.\n * Mirrors the Rust SDK's `ChannelItem` enum (`Text` / `Binary`). Carrier for\n * factory + type-guard helpers so callers can construct and discriminate\n * channel items without depending on Rust-specific shape.\n */\nexport type ChannelItem =\n | { type: 'text'; value: string }\n | { type: 'binary'; value: Uint8Array }\n\nexport const ChannelItem = {\n /** Construct a text channel item. */\n Text(value: string): ChannelItem {\n return { type: 'text', value }\n },\n /** Construct a binary channel item. */\n Binary(value: Uint8Array): ChannelItem {\n return { type: 'binary', value }\n },\n} as const\n\n/**\n * Write end of a streaming channel. Provides both a Node.js `Writable` stream\n * and a `sendMessage` method for sending structured text messages.\n *\n * @example\n * ```typescript\n * import { createChannel } from 'iii-sdk/helpers'\n * const channel = await createChannel(iii)\n *\n * // Stream binary data\n * channel.writer.stream.write(Buffer.from('hello'))\n * channel.writer.stream.end()\n *\n * // Or send text messages\n * channel.writer.sendMessage(JSON.stringify({ type: 'event', data: 'test' }))\n * channel.writer.close()\n * ```\n */\nexport class ChannelWriter {\n private static readonly FRAME_SIZE = 64 * 1024\n private ws: WebSocket | null = null\n private wsReady = false\n private readonly pendingMessages: {\n data: Buffer | string\n callback: (err?: Error | null) => void\n }[] = []\n /** Node.js Writable stream for binary data. */\n public readonly stream: Writable\n private readonly url: string\n\n constructor(engineWsBase: string, ref: StreamChannelRef) {\n this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, 'write')\n\n this.stream = new Writable({\n write: (chunk: Buffer, _encoding, callback) => {\n const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n this.sendChunked(buf, callback)\n },\n final: (callback) => {\n if (!this.ws) {\n callback()\n return\n }\n // Delay the close frame slightly to allow the TCP stack to flush\n // all buffered send() data. Without this, the close frame can arrive\n // at the engine before all data frames, causing data truncation.\n const doClose = () => {\n if (this.ws) {\n this.ws.close(1000, 'stream_complete')\n }\n callback()\n }\n if (this.wsReady) {\n setTimeout(doClose, 10)\n } else {\n this.ws.on('open', () => setTimeout(doClose, 10))\n }\n },\n destroy: (err, callback) => {\n if (this.ws) this.ws.terminate()\n callback(err)\n },\n })\n }\n\n private ensureConnected(): void {\n if (this.ws) return\n this.ws = new WebSocket(this.url)\n\n this.ws.on('open', () => {\n this.wsReady = true\n for (const { data, callback } of this.pendingMessages) {\n this.ws?.send(data, callback)\n }\n this.pendingMessages.length = 0\n })\n\n this.ws.on('error', (err) => {\n this.stream.destroy(err)\n })\n\n this.ws.on('close', () => {\n if (!this.stream.destroyed) {\n this.stream.destroy()\n }\n })\n }\n\n /** Send a text message through the channel. */\n sendMessage(msg: string): void {\n this.ensureConnected()\n this.sendRaw(msg, (err) => {\n if (err) this.stream.destroy(err)\n })\n }\n\n /** Close the channel writer. */\n close(): void {\n if (!this.ws) return\n const doClose = () => {\n if (this.ws) {\n this.ws.close(1000, 'channel_close')\n }\n }\n if (this.wsReady) {\n doClose()\n } else {\n this.ws.on('open', () => doClose())\n }\n }\n\n private sendChunked(data: Buffer, callback: (err?: Error | null) => void): void {\n let offset = 0\n const sendNext = (err?: Error | null): void => {\n if (err) {\n callback(err)\n return\n }\n\n if (offset >= data.length) {\n callback(null)\n return\n }\n\n const end = Math.min(offset + ChannelWriter.FRAME_SIZE, data.length)\n const part = data.subarray(offset, end)\n offset = end\n this.sendRaw(part, sendNext)\n }\n sendNext(null)\n }\n\n private sendRaw(data: Buffer | string, callback: (err?: Error | null) => void): void {\n this.ensureConnected()\n if (this.wsReady && this.ws) {\n this.ws.send(data, (err) => callback(err ?? null))\n } else {\n this.pendingMessages.push({ data, callback })\n }\n }\n}\n\n/**\n * Read end of a streaming channel. Provides both a Node.js `Readable` stream\n * for binary data and an `onMessage` callback for structured text messages.\n *\n * @example\n * ```typescript\n * import { createChannel } from 'iii-sdk/helpers'\n * const channel = await createChannel(iii)\n *\n * // Stream binary data\n * channel.reader.stream.on('data', (chunk) => console.log(chunk))\n *\n * // Or receive text messages\n * channel.reader.onMessage((msg) => console.log('Got:', msg))\n * ```\n */\nexport class ChannelReader {\n private ws: WebSocket | null = null\n private connected = false\n private readonly messageCallbacks: Array<(msg: string) => void> = []\n /** Node.js Readable stream for binary data. */\n public readonly stream: Readable\n private readonly url: string\n\n constructor(engineWsBase: string, ref: StreamChannelRef) {\n this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, 'read')\n\n const self = this\n this.stream = new Readable({\n read() {\n self.ensureConnected()\n if (self.ws) self.ws.resume()\n },\n destroy(err, callback) {\n if (self.ws && self.ws.readyState !== WebSocket.CLOSED) {\n self.ws.terminate()\n }\n self.ws = null\n callback(err)\n },\n })\n }\n\n private ensureConnected(): void {\n if (this.connected) return\n this.connected = true\n this.ws = new WebSocket(this.url)\n\n this.ws.on('open', () => {\n ;(this.ws as unknown as { binaryType: string }).binaryType = 'nodebuffer'\n })\n\n this.ws.on('message', (data: Buffer, isBinary: boolean) => {\n if (isBinary) {\n if (!this.stream.push(data)) {\n this.ws?.pause()\n }\n } else {\n const msg = data.toString('utf-8')\n for (const cb of this.messageCallbacks) {\n cb(msg)\n }\n }\n })\n\n this.ws.on('close', () => {\n this.ws = null\n if (!this.stream.destroyed) this.stream.push(null)\n })\n\n this.ws.on('error', (err) => {\n this.stream.destroy(err)\n })\n }\n\n /** Register a callback to receive text messages from the channel. */\n onMessage(callback: (msg: string) => void): void {\n this.messageCallbacks.push(callback)\n }\n\n async readAll(): Promise<Buffer> {\n this.ensureConnected()\n const chunks: Buffer[] = []\n\n for await (const chunk of this.stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))\n }\n\n return Buffer.concat(chunks)\n }\n\n close(): void {\n if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {\n this.ws.close(1000, 'channel_close')\n }\n }\n}\n\nfunction buildChannelUrl(\n engineWsBase: string,\n channelId: string,\n accessKey: string,\n direction: 'read' | 'write',\n): string {\n const base = engineWsBase.replace(/\\/$/, '')\n return `${base}/ws/channels/${channelId}?key=${encodeURIComponent(accessKey)}&dir=${direction}`\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { StreamChannelRef } from './iii-types'\nimport type { ApiResponse, HttpRequest, HttpResponse, InternalHttpRequest } from './types'\n\n/**\n * Returns a project identifier for telemetry, derived from the current working\n * directory. Reads `package.json` `name` if present at `cwd`; otherwise falls\n * back to the basename of `cwd`. Returns `undefined` only when both signals\n * are unavailable (e.g. cwd is the filesystem root).\n *\n * No directory walking — only inspects `cwd` itself, so the SDK never reads\n * files outside the user's explicit working directory.\n */\nexport function detectProjectName(cwd: string = process.cwd()): string | undefined {\n try {\n const manifest = path.join(cwd, 'package.json')\n if (fs.existsSync(manifest)) {\n const parsed = JSON.parse(fs.readFileSync(manifest, 'utf8')) as { name?: unknown }\n if (typeof parsed.name === 'string') {\n const trimmed = parsed.name.trim()\n if (trimmed) return trimmed\n }\n }\n } catch {\n // fall through to directory-name fallback\n }\n\n const base = path.basename(cwd).trim()\n return base || undefined\n}\n\n/**\n * Helper that wraps an HTTP-style handler (with separate `req`/`res` arguments)\n * into the function handler format expected by the SDK.\n *\n * @param callback - Async handler receiving an {@link HttpRequest} and {@link HttpResponse}.\n * @returns A function handler compatible with {@link ISdk.registerFunction}.\n *\n * @example\n * ```typescript\n * import { http } from 'iii-sdk'\n *\n * iii.registerFunction(\n * 'my-api',\n * http(async (req, res) => {\n * res.status(200)\n * res.headers({ 'content-type': 'application/json' })\n * res.stream.end(JSON.stringify({ hello: 'world' }))\n * res.close()\n * }),\n * )\n * ```\n */\nexport const http = (\n // biome-ignore lint/suspicious/noConfusingVoidType: void is necessary here\n callback: (req: HttpRequest, res: HttpResponse) => Promise<void | ApiResponse>,\n) => {\n return async (req: InternalHttpRequest) => {\n const { response, ...request } = req\n\n const httpResponse: HttpResponse = {\n status: (status_code: number) =>\n response.sendMessage(JSON.stringify({ type: 'set_status', status_code })),\n headers: (headers: Record<string, string>) =>\n response.sendMessage(JSON.stringify({ type: 'set_headers', headers })),\n stream: response.stream,\n close: () => response.close(),\n }\n\n return callback(request, httpResponse)\n }\n}\n\n/**\n * Type guard that checks if a value is a {@link StreamChannelRef}.\n *\n * @param value - Value to check.\n * @returns `true` if the value is a valid `StreamChannelRef`.\n */\nexport const isChannelRef = (value: unknown): value is StreamChannelRef => {\n if (typeof value !== 'object' || value === null) return false\n const maybe = value as Partial<StreamChannelRef>\n return (\n typeof maybe.channel_id === 'string' &&\n typeof maybe.access_key === 'string' &&\n (maybe.direction === 'read' || maybe.direction === 'write')\n )\n}\n\n/**\n * Recursively extract all {@link StreamChannelRef} values from a JSON-like\n * input, returning each match paired with its dotted/bracketed path. Mirrors\n * the Rust SDK's `extract_channel_refs`.\n *\n * @param data - Arbitrary JSON-like value.\n * @returns Array of `[path, ref]` tuples. Empty when no refs are found.\n */\nexport const extractChannelRefs = (data: unknown): Array<[string, StreamChannelRef]> => {\n const refs: Array<[string, StreamChannelRef]> = []\n extractRefsRecursive(data, '', refs)\n return refs\n}\n\nconst extractRefsRecursive = (\n data: unknown,\n prefix: string,\n refs: Array<[string, StreamChannelRef]>,\n): void => {\n if (isChannelRef(data)) {\n refs.push([prefix, data])\n return\n }\n if (Array.isArray(data)) {\n for (let i = 0; i < data.length; i++) {\n const path = prefix === '' ? `[${i}]` : `${prefix}[${i}]`\n extractRefsRecursive(data[i], path, refs)\n }\n return\n }\n if (typeof data !== 'object' || data === null) return\n\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n const path = prefix === '' ? key : `${prefix}.${key}`\n extractRefsRecursive(value, path, refs)\n }\n}\n"],"mappings":"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"}