{"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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"}