theokit 0.1.0-alpha.3 → 0.1.0-alpha.5

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,39 @@
1
+ // src/client/react-query-adapter.ts
2
+ function stableStringify(value) {
3
+ if (value === null || typeof value !== "object") {
4
+ return JSON.stringify(value);
5
+ }
6
+ if (Array.isArray(value)) {
7
+ return "[" + value.map((v) => stableStringify(v)).join(",") + "]";
8
+ }
9
+ const keys = Object.keys(value).sort();
10
+ const pairs = keys.map(
11
+ (k) => JSON.stringify(k) + ":" + stableStringify(value[k])
12
+ );
13
+ return "{" + pairs.join(",") + "}";
14
+ }
15
+ function stableQueryKey(path, options) {
16
+ const key = [path];
17
+ if (options.query !== void 0) {
18
+ key.push({ kind: "query", payload: stableStringify(options.query) });
19
+ }
20
+ if (options.body !== void 0) {
21
+ key.push({ kind: "body", payload: stableStringify(options.body) });
22
+ }
23
+ if (options.params !== void 0) {
24
+ key.push({ kind: "params", payload: stableStringify(options.params) });
25
+ }
26
+ return key;
27
+ }
28
+ function buildUseTheoQueryConfig(path, options, fetcher) {
29
+ return {
30
+ queryKey: stableQueryKey(path, options),
31
+ queryFn: () => fetcher(path, options)
32
+ };
33
+ }
34
+
35
+ export {
36
+ stableQueryKey,
37
+ buildUseTheoQueryConfig
38
+ };
39
+ //# sourceMappingURL=chunk-6WHMT7FB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/react-query-adapter.ts"],"sourcesContent":["/**\n * T5.3 — React Query adapter primitives.\n *\n * Exposes:\n * - `stableQueryKey(path, options)` — produces a deterministic queryKey\n * that is equal across calls when the logical content of query/body is\n * equal, regardless of property order or inline-object identity. Solves\n * EC-10 (inline-object → infinite refetch).\n * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the\n * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from\n * `@tanstack/react-query`.\n *\n * The canonical implementation lives here in `theokit/client`. The\n * dedicated subpath `theokit/react-query` re-exports this surface so\n * consumers who only need the React Query bridge can import from a\n * clearly named entry point — same shape as `theokit/server`,\n * `theokit/vite-plugin`, etc. No separate npm package.\n */\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => stableStringify(v)).join(',') + ']'\n }\n const keys = Object.keys(value as Record<string, unknown>).sort()\n const pairs = keys.map(\n (k) => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]),\n )\n return '{' + pairs.join(',') + '}'\n}\n\nexport interface FetchOptionsLike {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\nexport type QueryKey = readonly unknown[]\n\nexport function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey {\n const key: unknown[] = [path]\n if (options.query !== undefined) {\n key.push({ kind: 'query', payload: stableStringify(options.query) })\n }\n if (options.body !== undefined) {\n key.push({ kind: 'body', payload: stableStringify(options.body) })\n }\n if (options.params !== undefined) {\n key.push({ kind: 'params', payload: stableStringify(options.params) })\n }\n return key\n}\n\nexport type Fetcher<TResult = unknown> = (\n path: string,\n options: FetchOptionsLike,\n) => Promise<TResult>\n\nexport interface UseTheoQueryConfig<TResult = unknown> {\n queryKey: QueryKey\n queryFn: () => Promise<TResult>\n}\n\nexport function buildUseTheoQueryConfig<TResult = unknown>(\n path: string,\n options: FetchOptionsLike,\n fetcher: Fetcher<TResult>,\n): UseTheoQueryConfig<TResult> {\n return {\n queryKey: stableQueryKey(path, options),\n queryFn: () => fetcher(path, options),\n }\n}\n"],"mappings":";AAmBA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI;AAAA,EAChE;AACA,QAAM,OAAO,OAAO,KAAK,KAAgC,EAAE,KAAK;AAChE,QAAM,QAAQ,KAAK;AAAA,IACjB,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAiB,MAAkC,CAAC,CAAC;AAAA,EACxF;AACA,SAAO,MAAM,MAAM,KAAK,GAAG,IAAI;AACjC;AAUO,SAAS,eAAe,MAAc,SAAqC;AAChF,QAAM,MAAiB,CAAC,IAAI;AAC5B,MAAI,QAAQ,UAAU,QAAW;AAC/B,QAAI,KAAK,EAAE,MAAM,SAAS,SAAS,gBAAgB,QAAQ,KAAK,EAAE,CAAC;AAAA,EACrE;AACA,MAAI,QAAQ,SAAS,QAAW;AAC9B,QAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,gBAAgB,QAAQ,IAAI,EAAE,CAAC;AAAA,EACnE;AACA,MAAI,QAAQ,WAAW,QAAW;AAChC,QAAI,KAAK,EAAE,MAAM,UAAU,SAAS,gBAAgB,QAAQ,MAAM,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAYO,SAAS,wBACd,MACA,SACA,SAC6B;AAC7B,SAAO;AAAA,IACL,UAAU,eAAe,MAAM,OAAO;AAAA,IACtC,SAAS,MAAM,QAAQ,MAAM,OAAO;AAAA,EACtC;AACF;","names":[]}
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ export { FetchOptionsLike, Fetcher, QueryKey, UseTheoQueryConfig, buildUseTheoQueryConfig, stableQueryKey } from '../react-query/index.js';
2
3
  import { A as AgentEvent } from '../agent-types-CPKa4aJQ.js';
3
4
  export { a as AgentErrorEvent, b as AgentMessageEvent, c as AgentToolCallEvent, d as AgentToolResultEvent } from '../agent-types-CPKa4aJQ.js';
4
5
 
@@ -76,36 +77,6 @@ interface Batcher {
76
77
  }
77
78
  declare function createBatcher(options: BatcherOptions): Batcher;
78
79
 
79
- /**
80
- * T5.3 — React Query adapter primitives.
81
- *
82
- * Exposes:
83
- * - `stableQueryKey(path, options)` — produces a deterministic queryKey
84
- * that is equal across calls when the logical content of query/body is
85
- * equal, regardless of property order or inline-object identity. Solves
86
- * EC-10 (inline-object → infinite refetch).
87
- * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the
88
- * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from
89
- * `@tanstack/react-query`.
90
- *
91
- * NOTE: this module ships inside `theokit/client` rather than as a separate
92
- * `@theokit/react-query` npm package for the 0.2.0 release. A package split
93
- * is cheap to add later once we have downstream adopters.
94
- */
95
- interface FetchOptionsLike {
96
- query?: unknown;
97
- body?: unknown;
98
- params?: unknown;
99
- }
100
- type QueryKey = readonly unknown[];
101
- declare function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey;
102
- type Fetcher<TResult = unknown> = (path: string, options: FetchOptionsLike) => Promise<TResult>;
103
- interface UseTheoQueryConfig<TResult = unknown> {
104
- queryKey: QueryKey;
105
- queryFn: () => Promise<TResult>;
106
- }
107
- declare function buildUseTheoQueryConfig<TResult = unknown>(path: string, options: FetchOptionsLike, fetcher: Fetcher<TResult>): UseTheoQueryConfig<TResult>;
108
-
109
80
  /**
110
81
  * T5.2 — useAgentStream
111
82
  *
@@ -174,4 +145,4 @@ declare function parseSSEChunk(line: string): AgentEvent | null;
174
145
  */
175
146
  declare function consumeAgentStream<TBody = unknown>(path: string, options: ConsumeOptions<TBody>): Promise<void>;
176
147
 
177
- export { AgentEvent, type AgentStreamStatus, type BatchRequest, type BatchResponse, type BatchTransport, type Batcher, type BatcherOptions, type ConsumeOptions, type FetchOptionsLike, type Fetcher, type InferBody, type InferQuery, type InferResponse, type QueryKey, TheoFetchError, type TheoFetchOptions, type UseAgentStreamOptions, type UseAgentStreamReturn, type UseTheoQueryConfig, buildUseTheoQueryConfig, consumeAgentStream, createBatcher, parseSSEChunk, stableQueryKey, theoFetch, useAgentStream };
148
+ export { AgentEvent, type AgentStreamStatus, type BatchRequest, type BatchResponse, type BatchTransport, type Batcher, type BatcherOptions, type ConsumeOptions, type InferBody, type InferQuery, type InferResponse, TheoFetchError, type TheoFetchOptions, type UseAgentStreamOptions, type UseAgentStreamReturn, consumeAgentStream, createBatcher, parseSSEChunk, theoFetch, useAgentStream };
@@ -1,3 +1,8 @@
1
+ import {
2
+ buildUseTheoQueryConfig,
3
+ stableQueryKey
4
+ } from "../chunk-6WHMT7FB.js";
5
+
1
6
  // src/client/theo-fetch.ts
2
7
  import superjson from "superjson";
3
8
 
@@ -179,40 +184,6 @@ function resolveClientTransformerName() {
179
184
  return g.__THEO_TRANSFORMER__ ?? "json";
180
185
  }
181
186
 
182
- // src/client/react-query-adapter.ts
183
- function stableStringify(value) {
184
- if (value === null || typeof value !== "object") {
185
- return JSON.stringify(value);
186
- }
187
- if (Array.isArray(value)) {
188
- return "[" + value.map((v) => stableStringify(v)).join(",") + "]";
189
- }
190
- const keys = Object.keys(value).sort();
191
- const pairs = keys.map(
192
- (k) => JSON.stringify(k) + ":" + stableStringify(value[k])
193
- );
194
- return "{" + pairs.join(",") + "}";
195
- }
196
- function stableQueryKey(path, options) {
197
- const key = [path];
198
- if (options.query !== void 0) {
199
- key.push({ kind: "query", payload: stableStringify(options.query) });
200
- }
201
- if (options.body !== void 0) {
202
- key.push({ kind: "body", payload: stableStringify(options.body) });
203
- }
204
- if (options.params !== void 0) {
205
- key.push({ kind: "params", payload: stableStringify(options.params) });
206
- }
207
- return key;
208
- }
209
- function buildUseTheoQueryConfig(path, options, fetcher) {
210
- return {
211
- queryKey: stableQueryKey(path, options),
212
- queryFn: () => fetcher(path, options)
213
- };
214
- }
215
-
216
187
  // src/client/use-agent-stream.ts
217
188
  import { useCallback, useEffect, useRef, useState } from "react";
218
189
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/theo-fetch.ts","../../src/client/batch.ts","../../src/client/batch-transport.ts","../../src/client/react-query-adapter.ts","../../src/client/use-agent-stream.ts","../../src/client/agent-stream-core.ts"],"sourcesContent":["import type { z } from 'zod'\nimport superjson from 'superjson'\nimport { getGlobalBatcher } from './batch-transport.js'\n\n// --- Transformer (T1.3) ---\n\n/**\n * Module-scoped flag preventing the transformer-mismatch warning from\n * firing more than once per session (EC-6).\n */\nlet mismatchWarned = false\n\n/** Test-only helper to reset the warned flag between assertions. */\nexport function __resetMismatchWarningForTests(): void {\n mismatchWarned = false\n}\n\n/**\n * Deserialize a fetch response body according to the negotiated transformer.\n *\n * `serverTransformerName` comes from the `x-theo-transformer` response header\n * (null when absent — server is using the default JSON path).\n * `clientTransformerName` is the transformer the client was built with\n * (typically injected via a Vite virtual module; falls back to `'json'`).\n *\n * Mismatch fires a single console.warn and falls back to JSON.parse (EC-5/EC-6).\n */\nexport function deserializeFetchResponse(\n raw: string,\n serverTransformerName: string | null,\n clientTransformerName: string,\n): unknown {\n if (raw === '' || raw === null || raw === undefined) {\n return null\n }\n\n const serverEffective = serverTransformerName ?? 'json'\n if (serverEffective !== clientTransformerName && !mismatchWarned) {\n mismatchWarned = true\n console.warn(\n `[theokit] transformer mismatch: server=${serverEffective}, client=${clientTransformerName}. Falling back to JSON.parse.`,\n )\n }\n\n if (serverEffective === 'superjson' && clientTransformerName === 'superjson') {\n const wrapped = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(wrapped)\n }\n\n // Default path (json) or mismatch fallback\n return JSON.parse(raw)\n}\n\n// --- Utility Types ---\n\n/** Infer the response type from a route's handler return */\nexport type InferResponse<T> = T extends { handler: (...args: never[]) => infer R }\n ? Awaited<R>\n : unknown\n\n/** Extract the query Zod schema type, handling optional properties */\ntype ExtractQuery<T> = T extends { query?: infer Q } ? (Q extends z.ZodType ? Q : never) : never\n\n/** Extract the body Zod schema type, handling optional properties */\ntype ExtractBody<T> = T extends { body?: infer B } ? (B extends z.ZodType ? B : never) : never\n\n/** Infer query type from a route's query Zod schema */\nexport type InferQuery<T> = [ExtractQuery<T>] extends [never]\n ? undefined\n : ExtractQuery<T> extends z.ZodUndefined ? undefined : z.infer<ExtractQuery<T>>\n\n/** Infer body type from a route's body Zod schema */\nexport type InferBody<T> = [ExtractBody<T>] extends [never]\n ? undefined\n : ExtractBody<T> extends z.ZodUndefined ? undefined : z.infer<ExtractBody<T>>\n\n/** Build the options type based on what schemas the route has */\nexport type TheoFetchOptions<T> = Omit<RequestInit, 'body' | 'method'> &\n (InferQuery<T> extends undefined ? { query?: never } : { query: InferQuery<T> }) &\n (InferBody<T> extends undefined ? { body?: never } : { body: InferBody<T> })\n\n// --- Error Class ---\n\nexport class TheoFetchError extends Error {\n status: number\n code?: string\n issues?: unknown[]\n\n constructor(status: number, body?: unknown) {\n const parsed = body && typeof body === 'object' ? (body as Record<string, unknown>) : null\n const error = parsed?.error as Record<string, unknown> | undefined\n const message = (error?.message as string) ?? `HTTP ${status}`\n super(message)\n this.name = 'TheoFetchError'\n this.status = status\n this.code = error?.code as string | undefined\n this.issues = error?.issues as unknown[] | undefined\n }\n}\n\n// --- Main Function ---\n\nexport async function theoFetch<T>(\n url: string,\n options?: TheoFetchOptions<T>,\n): Promise<InferResponse<T>> {\n // T1.5 — Transparent batching: when globalThis.__THEO_BATCHING__ is truthy\n // (set via Vite virtual module when config.batching=true), route this call\n // through the global batcher. Falls back to direct fetch on batcher failure\n // (degrade gracefully).\n const batcher = getGlobalBatcher()\n if (batcher) {\n try {\n const method = (options as { method?: string } | undefined)?.method ?? 'GET'\n const result = await batcher.dispatch({\n path: url,\n method,\n query: (options as { query?: Record<string, unknown> } | undefined)?.query,\n body: (options as { body?: unknown } | undefined)?.body,\n })\n return result as InferResponse<T>\n } catch (err) {\n // Fall through to direct fetch path below\n void err\n }\n }\n\n const fetchUrl = new URL(url, globalThis.location?.origin ?? 'http://localhost:3000')\n\n // Append query params (skip undefined values)\n if (options && 'query' in options && options.query) {\n for (const [k, v] of Object.entries(options.query as Record<string, unknown>)) {\n if (v !== undefined) {\n fetchUrl.searchParams.set(k, String(v))\n }\n }\n }\n\n // Build fetch init\n const { query: _q, body: _b, ...restOptions } = (options ?? {}) as Record<string, unknown>\n const init: RequestInit = { ...(restOptions as RequestInit) }\n\n if (options && 'body' in options && options.body !== undefined) {\n init.body = JSON.stringify(options.body)\n init.headers = {\n ...(init.headers as Record<string, string> ?? {}),\n 'Content-Type': 'application/json',\n }\n }\n\n const response = await fetch(fetchUrl.toString(), init)\n\n if (!response.ok) {\n let errorBody: unknown\n try {\n errorBody = await response.json()\n } catch {\n // Non-JSON error response\n }\n throw new TheoFetchError(response.status, errorBody)\n }\n\n // Handle 204 No Content (EC-1)\n if (response.status === 204) {\n return null as InferResponse<T>\n }\n\n // Check for empty body\n const contentLength = response.headers.get('content-length')\n if (contentLength === '0') {\n return null as InferResponse<T>\n }\n\n // T1.3 — transformer-aware deserialization\n const serverTransformerName = response.headers.get('x-theo-transformer')\n const clientTransformerName = resolveClientTransformerName()\n const text = await response.text()\n return deserializeFetchResponse(text, serverTransformerName, clientTransformerName) as InferResponse<T>\n}\n\n/**\n * Read the client-configured transformer name. Default `'json'`.\n *\n * In a Vite build, this is overridden by virtual module\n * `/@theo/runtime-config` which sets `globalThis.__THEO_TRANSFORMER__`.\n * Outside Vite (Node SSR, tests) the default applies.\n */\nfunction resolveClientTransformerName(): string {\n const g = globalThis as { __THEO_TRANSFORMER__?: string }\n return g.__THEO_TRANSFORMER__ ?? 'json'\n}\n","/**\n * T5.1 — client-side microtask batching.\n *\n * Collects all `dispatch` calls within the same microtask and sends them as a\n * single HTTP POST to the configured transport. Each caller's promise resolves\n * with its own result; one failed item does not break the others (per-item\n * error isolation).\n *\n * Designed as a transport-agnostic primitive so unit tests do not require\n * network access. The default transport (fetch to `/api/__theo_batch__`)\n * lives alongside this module but is created by the consumer (e.g., theoFetch).\n */\n\nexport interface BatchRequest {\n path: string\n method: string\n query?: Record<string, unknown>\n body?: unknown\n headers?: Record<string, string>\n}\n\nexport type BatchResponse =\n | { index: number; data: unknown }\n | { index: number; error: { message: string; code?: string } }\n\nexport type BatchTransport = (requests: BatchRequest[]) => Promise<BatchResponse[]>\n\nexport interface BatcherOptions {\n transport: BatchTransport\n /** Maximum batch size before flushing into multiple parallel batches. */\n max?: number\n}\n\nexport interface Batcher {\n dispatch(req: BatchRequest): Promise<unknown>\n}\n\ninterface PendingCall {\n req: BatchRequest\n resolve: (value: unknown) => void\n reject: (reason: unknown) => void\n}\n\nexport function createBatcher(options: BatcherOptions): Batcher {\n const max = options.max ?? 32\n let queue: PendingCall[] = []\n let flushScheduled = false\n\n function flush(): void {\n flushScheduled = false\n const current = queue\n queue = []\n if (current.length === 0) return\n\n // Split into chunks respecting max\n const chunks: PendingCall[][] = []\n for (let i = 0; i < current.length; i += max) {\n chunks.push(current.slice(i, i + max))\n }\n\n for (const chunk of chunks) {\n const payload: BatchRequest[] = chunk.map((p) => p.req)\n options\n .transport(payload)\n .then((results) => {\n for (let i = 0; i < chunk.length; i++) {\n const result = results[i]\n if (!result) {\n chunk[i].reject(new Error('Batch transport returned no result for index ' + i))\n continue\n }\n if ('error' in result) {\n const err = new Error(result.error.message)\n ;(err as { code?: string }).code = result.error.code\n chunk[i].reject(err)\n } else {\n chunk[i].resolve(result.data)\n }\n }\n })\n .catch((err) => {\n for (const item of chunk) item.reject(err)\n })\n }\n }\n\n return {\n dispatch(req: BatchRequest): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n queue.push({ req, resolve, reject })\n if (!flushScheduled) {\n flushScheduled = true\n queueMicrotask(flush)\n }\n })\n },\n }\n}\n","/**\n * T1.5 — Default HTTP transport for the client batcher.\n *\n * Wraps `fetch` to POST `/api/__theo_batch__` with the collected requests\n * and return the array of per-item results.\n */\n\nimport {\n createBatcher,\n type BatchTransport,\n type Batcher,\n type BatchRequest,\n type BatchResponse,\n} from './batch.js'\n\nexport const BATCH_ENDPOINT = '/api/__theo_batch__'\n\nexport interface CreateBatchTransportOptions {\n /** Override fetch (default: globalThis.fetch). Used by tests. */\n fetchImpl?: typeof fetch\n /** Override endpoint (default '/api/__theo_batch__'). */\n endpoint?: string\n}\n\nexport function createBatchTransport(\n options: CreateBatchTransportOptions = {},\n): BatchTransport {\n const fetchImpl = options.fetchImpl ?? fetch\n const endpoint = options.endpoint ?? BATCH_ENDPOINT\n return async (requests: BatchRequest[]): Promise<BatchResponse[]> => {\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ requests }),\n })\n if (!response.ok) {\n throw new Error(`Batch transport returned ${response.status}`)\n }\n const payload = (await response.json()) as { results?: BatchResponse[] }\n if (!Array.isArray(payload.results)) {\n throw new Error('Batch response missing results array')\n }\n // Map results to indexed BatchResponse shape (server already returns the right form)\n return payload.results.map((item, index) => {\n if ('error' in item) {\n return { index, error: (item as { error: { message: string; code?: string } }).error }\n }\n return { index, data: (item as { data: unknown }).data }\n })\n }\n}\n\n// --- EC-7: singleton batcher per page, lazy-instantiated ---\n\nlet globalBatcher: Batcher | undefined\n\n/** Test-only — reset the module-scope singleton between assertions. */\nexport function __resetGlobalBatcherForTests(): void {\n globalBatcher = undefined\n}\n\n/**\n * Returns the global batcher singleton when `globalThis.__THEO_BATCHING__` is\n * truthy; undefined otherwise. Lazy-instantiated on first call.\n */\nexport function getGlobalBatcher(): Batcher | undefined {\n const g = globalThis as { __THEO_BATCHING__?: boolean }\n if (!g.__THEO_BATCHING__) return undefined\n if (!globalBatcher) {\n globalBatcher = createBatcher({ transport: createBatchTransport() })\n }\n return globalBatcher\n}\n","/**\n * T5.3 — React Query adapter primitives.\n *\n * Exposes:\n * - `stableQueryKey(path, options)` — produces a deterministic queryKey\n * that is equal across calls when the logical content of query/body is\n * equal, regardless of property order or inline-object identity. Solves\n * EC-10 (inline-object → infinite refetch).\n * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the\n * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from\n * `@tanstack/react-query`.\n *\n * NOTE: this module ships inside `theokit/client` rather than as a separate\n * `@theokit/react-query` npm package for the 0.2.0 release. A package split\n * is cheap to add later once we have downstream adopters.\n */\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => stableStringify(v)).join(',') + ']'\n }\n const keys = Object.keys(value as Record<string, unknown>).sort()\n const pairs = keys.map(\n (k) => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]),\n )\n return '{' + pairs.join(',') + '}'\n}\n\nexport interface FetchOptionsLike {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\nexport type QueryKey = readonly unknown[]\n\nexport function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey {\n const key: unknown[] = [path]\n if (options.query !== undefined) {\n key.push({ kind: 'query', payload: stableStringify(options.query) })\n }\n if (options.body !== undefined) {\n key.push({ kind: 'body', payload: stableStringify(options.body) })\n }\n if (options.params !== undefined) {\n key.push({ kind: 'params', payload: stableStringify(options.params) })\n }\n return key\n}\n\nexport type Fetcher<TResult = unknown> = (\n path: string,\n options: FetchOptionsLike,\n) => Promise<TResult>\n\nexport interface UseTheoQueryConfig<TResult = unknown> {\n queryKey: QueryKey\n queryFn: () => Promise<TResult>\n}\n\nexport function buildUseTheoQueryConfig<TResult = unknown>(\n path: string,\n options: FetchOptionsLike,\n fetcher: Fetcher<TResult>,\n): UseTheoQueryConfig<TResult> {\n return {\n queryKey: stableQueryKey(path, options),\n queryFn: () => fetcher(path, options),\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AgentEvent } from '../server/agent-types.js'\nimport { consumeAgentStream } from './agent-stream-core.js'\n\n/**\n * T5.2 — useAgentStream\n *\n * React hook to consume an agent endpoint defined with `defineAgentEndpoint`.\n *\n * Transport (ADR D7, EC-3): fetch + ReadableStream — NOT EventSource.\n * EventSource is GET-only; agent endpoints need POST + body.\n *\n * Returns:\n * events — array of AgentEvents accumulated so far\n * send — call with a payload to (re)open a stream\n * status — 'idle' | 'streaming' | 'done' | 'error'\n * abort — manually cancel an in-flight stream\n *\n * Cleanup (EC-8): on unmount, the current AbortController fires .abort(),\n * which propagates to the underlying fetch and the ReadableStream reader.\n * Safe under React.StrictMode double-mount.\n */\n\nexport type AgentStreamStatus = 'idle' | 'streaming' | 'done' | 'error'\n\nexport interface UseAgentStreamReturn<TBody = unknown> {\n events: AgentEvent[]\n status: AgentStreamStatus\n send: (body: TBody) => void\n abort: () => void\n reset: () => void\n}\n\nexport interface UseAgentStreamOptions {\n /** Extra headers (e.g., auth). */\n headers?: Record<string, string>\n /** Override fetch (rare — primarily for tests). */\n fetch?: typeof fetch\n}\n\nexport function useAgentStream<TBody = unknown>(\n path: string,\n options: UseAgentStreamOptions = {},\n): UseAgentStreamReturn<TBody> {\n const [events, setEvents] = useState<AgentEvent[]>([])\n const [status, setStatus] = useState<AgentStreamStatus>('idle')\n const controllerRef = useRef<AbortController | null>(null)\n\n const abort = useCallback(() => {\n if (controllerRef.current) {\n controllerRef.current.abort()\n controllerRef.current = null\n }\n }, [])\n\n const reset = useCallback(() => {\n abort()\n setEvents([])\n setStatus('idle')\n }, [abort])\n\n const send = useCallback(\n (body: TBody) => {\n // Cancel any in-flight stream first.\n abort()\n const controller = new AbortController()\n controllerRef.current = controller\n setStatus('streaming')\n\n let sawError = false\n consumeAgentStream<TBody>(path, {\n body,\n fetch: options.fetch,\n headers: options.headers,\n signal: controller.signal,\n onEvent: (event) => {\n if (event.type === 'error') sawError = true\n setEvents((prev) => [...prev, event])\n },\n })\n .then(() => {\n if (controllerRef.current !== controller) return // superseded\n setStatus(sawError ? 'error' : 'done')\n })\n .catch(() => {\n if (controllerRef.current !== controller) return // superseded / aborted\n if (controller.signal.aborted) return\n setStatus('error')\n })\n },\n [abort, path, options.fetch, options.headers],\n )\n\n // EC-8: cleanup on unmount aborts in-flight stream.\n useEffect(() => {\n return () => {\n if (controllerRef.current) {\n controllerRef.current.abort()\n controllerRef.current = null\n }\n }\n }, [])\n\n return { events, status, send, abort, reset }\n}\n","import type { AgentEvent } from '../server/agent-types.js'\n\n/**\n * T5.2 — Pure SSE consumer used by useAgentStream.\n *\n * Split out from the React hook so the wire behavior can be tested\n * without a DOM. The hook is glue: useState + useEffect + this primitive.\n *\n * Transport (ADR D7, EC-3): fetch + ReadableStream — NOT EventSource.\n * EventSource is GET-only and cannot carry a body. Agent endpoints need\n * POST + JSON body, so we use fetch + manual SSE chunk parsing.\n */\n\nexport interface ConsumeOptions<TBody = unknown> {\n /** Request body — JSON-serialized into the POST. */\n body: TBody\n /** Called once per SSE event parsed off the stream. */\n onEvent: (event: AgentEvent) => void\n /** Optional fetch override (tests). */\n fetch?: typeof fetch\n /** Optional abort signal — passed through to fetch. */\n signal?: AbortSignal\n /** Extra headers (e.g., auth). */\n headers?: Record<string, string>\n}\n\n/**\n * Parse a single SSE line of the form `data: <json>`.\n * Returns null for non-data lines, comments, or malformed JSON.\n */\nexport function parseSSEChunk(line: string): AgentEvent | null {\n if (!line.startsWith('data:')) return null\n const raw = line.slice(5).trim()\n if (!raw) return null\n try {\n return JSON.parse(raw) as AgentEvent\n } catch {\n return null\n }\n}\n\n/**\n * POSTs to `path` with `body`, reads the SSE response, and invokes\n * `onEvent` for each parsed AgentEvent. Resolves when the server\n * closes the stream or the signal aborts.\n */\nexport async function consumeAgentStream<TBody = unknown>(\n path: string,\n options: ConsumeOptions<TBody>,\n): Promise<void> {\n const fetchImpl = options.fetch ?? globalThis.fetch\n const response = await fetchImpl(path, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n ...options.headers,\n },\n body: JSON.stringify(options.body),\n signal: options.signal,\n })\n\n if (!response.body) return\n\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buf = ''\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buf += decoder.decode(value, { stream: true })\n\n // SSE separates events with a blank line: `\\n\\n`.\n // Split, keep the trailing partial in buf.\n const parts = buf.split('\\n\\n')\n buf = parts.pop() ?? ''\n for (const part of parts) {\n // A chunk may contain multiple lines; keep only the data: line.\n for (const line of part.split('\\n')) {\n const evt = parseSSEChunk(line)\n if (evt) options.onEvent(evt)\n }\n }\n }\n } finally {\n try {\n reader.releaseLock()\n } catch {\n // ignore — reader may already be released if the stream errored\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/client/theo-fetch.ts","../../src/client/batch.ts","../../src/client/batch-transport.ts","../../src/client/use-agent-stream.ts","../../src/client/agent-stream-core.ts"],"sourcesContent":["import type { z } from 'zod'\nimport superjson from 'superjson'\nimport { getGlobalBatcher } from './batch-transport.js'\n\n// --- Transformer (T1.3) ---\n\n/**\n * Module-scoped flag preventing the transformer-mismatch warning from\n * firing more than once per session (EC-6).\n */\nlet mismatchWarned = false\n\n/** Test-only helper to reset the warned flag between assertions. */\nexport function __resetMismatchWarningForTests(): void {\n mismatchWarned = false\n}\n\n/**\n * Deserialize a fetch response body according to the negotiated transformer.\n *\n * `serverTransformerName` comes from the `x-theo-transformer` response header\n * (null when absent — server is using the default JSON path).\n * `clientTransformerName` is the transformer the client was built with\n * (typically injected via a Vite virtual module; falls back to `'json'`).\n *\n * Mismatch fires a single console.warn and falls back to JSON.parse (EC-5/EC-6).\n */\nexport function deserializeFetchResponse(\n raw: string,\n serverTransformerName: string | null,\n clientTransformerName: string,\n): unknown {\n if (raw === '' || raw === null || raw === undefined) {\n return null\n }\n\n const serverEffective = serverTransformerName ?? 'json'\n if (serverEffective !== clientTransformerName && !mismatchWarned) {\n mismatchWarned = true\n console.warn(\n `[theokit] transformer mismatch: server=${serverEffective}, client=${clientTransformerName}. Falling back to JSON.parse.`,\n )\n }\n\n if (serverEffective === 'superjson' && clientTransformerName === 'superjson') {\n const wrapped = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(wrapped)\n }\n\n // Default path (json) or mismatch fallback\n return JSON.parse(raw)\n}\n\n// --- Utility Types ---\n\n/** Infer the response type from a route's handler return */\nexport type InferResponse<T> = T extends { handler: (...args: never[]) => infer R }\n ? Awaited<R>\n : unknown\n\n/** Extract the query Zod schema type, handling optional properties */\ntype ExtractQuery<T> = T extends { query?: infer Q } ? (Q extends z.ZodType ? Q : never) : never\n\n/** Extract the body Zod schema type, handling optional properties */\ntype ExtractBody<T> = T extends { body?: infer B } ? (B extends z.ZodType ? B : never) : never\n\n/** Infer query type from a route's query Zod schema */\nexport type InferQuery<T> = [ExtractQuery<T>] extends [never]\n ? undefined\n : ExtractQuery<T> extends z.ZodUndefined ? undefined : z.infer<ExtractQuery<T>>\n\n/** Infer body type from a route's body Zod schema */\nexport type InferBody<T> = [ExtractBody<T>] extends [never]\n ? undefined\n : ExtractBody<T> extends z.ZodUndefined ? undefined : z.infer<ExtractBody<T>>\n\n/** Build the options type based on what schemas the route has */\nexport type TheoFetchOptions<T> = Omit<RequestInit, 'body' | 'method'> &\n (InferQuery<T> extends undefined ? { query?: never } : { query: InferQuery<T> }) &\n (InferBody<T> extends undefined ? { body?: never } : { body: InferBody<T> })\n\n// --- Error Class ---\n\nexport class TheoFetchError extends Error {\n status: number\n code?: string\n issues?: unknown[]\n\n constructor(status: number, body?: unknown) {\n const parsed = body && typeof body === 'object' ? (body as Record<string, unknown>) : null\n const error = parsed?.error as Record<string, unknown> | undefined\n const message = (error?.message as string) ?? `HTTP ${status}`\n super(message)\n this.name = 'TheoFetchError'\n this.status = status\n this.code = error?.code as string | undefined\n this.issues = error?.issues as unknown[] | undefined\n }\n}\n\n// --- Main Function ---\n\nexport async function theoFetch<T>(\n url: string,\n options?: TheoFetchOptions<T>,\n): Promise<InferResponse<T>> {\n // T1.5 — Transparent batching: when globalThis.__THEO_BATCHING__ is truthy\n // (set via Vite virtual module when config.batching=true), route this call\n // through the global batcher. Falls back to direct fetch on batcher failure\n // (degrade gracefully).\n const batcher = getGlobalBatcher()\n if (batcher) {\n try {\n const method = (options as { method?: string } | undefined)?.method ?? 'GET'\n const result = await batcher.dispatch({\n path: url,\n method,\n query: (options as { query?: Record<string, unknown> } | undefined)?.query,\n body: (options as { body?: unknown } | undefined)?.body,\n })\n return result as InferResponse<T>\n } catch (err) {\n // Fall through to direct fetch path below\n void err\n }\n }\n\n const fetchUrl = new URL(url, globalThis.location?.origin ?? 'http://localhost:3000')\n\n // Append query params (skip undefined values)\n if (options && 'query' in options && options.query) {\n for (const [k, v] of Object.entries(options.query as Record<string, unknown>)) {\n if (v !== undefined) {\n fetchUrl.searchParams.set(k, String(v))\n }\n }\n }\n\n // Build fetch init\n const { query: _q, body: _b, ...restOptions } = (options ?? {}) as Record<string, unknown>\n const init: RequestInit = { ...(restOptions as RequestInit) }\n\n if (options && 'body' in options && options.body !== undefined) {\n init.body = JSON.stringify(options.body)\n init.headers = {\n ...(init.headers as Record<string, string> ?? {}),\n 'Content-Type': 'application/json',\n }\n }\n\n const response = await fetch(fetchUrl.toString(), init)\n\n if (!response.ok) {\n let errorBody: unknown\n try {\n errorBody = await response.json()\n } catch {\n // Non-JSON error response\n }\n throw new TheoFetchError(response.status, errorBody)\n }\n\n // Handle 204 No Content (EC-1)\n if (response.status === 204) {\n return null as InferResponse<T>\n }\n\n // Check for empty body\n const contentLength = response.headers.get('content-length')\n if (contentLength === '0') {\n return null as InferResponse<T>\n }\n\n // T1.3 — transformer-aware deserialization\n const serverTransformerName = response.headers.get('x-theo-transformer')\n const clientTransformerName = resolveClientTransformerName()\n const text = await response.text()\n return deserializeFetchResponse(text, serverTransformerName, clientTransformerName) as InferResponse<T>\n}\n\n/**\n * Read the client-configured transformer name. Default `'json'`.\n *\n * In a Vite build, this is overridden by virtual module\n * `/@theo/runtime-config` which sets `globalThis.__THEO_TRANSFORMER__`.\n * Outside Vite (Node SSR, tests) the default applies.\n */\nfunction resolveClientTransformerName(): string {\n const g = globalThis as { __THEO_TRANSFORMER__?: string }\n return g.__THEO_TRANSFORMER__ ?? 'json'\n}\n","/**\n * T5.1 — client-side microtask batching.\n *\n * Collects all `dispatch` calls within the same microtask and sends them as a\n * single HTTP POST to the configured transport. Each caller's promise resolves\n * with its own result; one failed item does not break the others (per-item\n * error isolation).\n *\n * Designed as a transport-agnostic primitive so unit tests do not require\n * network access. The default transport (fetch to `/api/__theo_batch__`)\n * lives alongside this module but is created by the consumer (e.g., theoFetch).\n */\n\nexport interface BatchRequest {\n path: string\n method: string\n query?: Record<string, unknown>\n body?: unknown\n headers?: Record<string, string>\n}\n\nexport type BatchResponse =\n | { index: number; data: unknown }\n | { index: number; error: { message: string; code?: string } }\n\nexport type BatchTransport = (requests: BatchRequest[]) => Promise<BatchResponse[]>\n\nexport interface BatcherOptions {\n transport: BatchTransport\n /** Maximum batch size before flushing into multiple parallel batches. */\n max?: number\n}\n\nexport interface Batcher {\n dispatch(req: BatchRequest): Promise<unknown>\n}\n\ninterface PendingCall {\n req: BatchRequest\n resolve: (value: unknown) => void\n reject: (reason: unknown) => void\n}\n\nexport function createBatcher(options: BatcherOptions): Batcher {\n const max = options.max ?? 32\n let queue: PendingCall[] = []\n let flushScheduled = false\n\n function flush(): void {\n flushScheduled = false\n const current = queue\n queue = []\n if (current.length === 0) return\n\n // Split into chunks respecting max\n const chunks: PendingCall[][] = []\n for (let i = 0; i < current.length; i += max) {\n chunks.push(current.slice(i, i + max))\n }\n\n for (const chunk of chunks) {\n const payload: BatchRequest[] = chunk.map((p) => p.req)\n options\n .transport(payload)\n .then((results) => {\n for (let i = 0; i < chunk.length; i++) {\n const result = results[i]\n if (!result) {\n chunk[i].reject(new Error('Batch transport returned no result for index ' + i))\n continue\n }\n if ('error' in result) {\n const err = new Error(result.error.message)\n ;(err as { code?: string }).code = result.error.code\n chunk[i].reject(err)\n } else {\n chunk[i].resolve(result.data)\n }\n }\n })\n .catch((err) => {\n for (const item of chunk) item.reject(err)\n })\n }\n }\n\n return {\n dispatch(req: BatchRequest): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n queue.push({ req, resolve, reject })\n if (!flushScheduled) {\n flushScheduled = true\n queueMicrotask(flush)\n }\n })\n },\n }\n}\n","/**\n * T1.5 — Default HTTP transport for the client batcher.\n *\n * Wraps `fetch` to POST `/api/__theo_batch__` with the collected requests\n * and return the array of per-item results.\n */\n\nimport {\n createBatcher,\n type BatchTransport,\n type Batcher,\n type BatchRequest,\n type BatchResponse,\n} from './batch.js'\n\nexport const BATCH_ENDPOINT = '/api/__theo_batch__'\n\nexport interface CreateBatchTransportOptions {\n /** Override fetch (default: globalThis.fetch). Used by tests. */\n fetchImpl?: typeof fetch\n /** Override endpoint (default '/api/__theo_batch__'). */\n endpoint?: string\n}\n\nexport function createBatchTransport(\n options: CreateBatchTransportOptions = {},\n): BatchTransport {\n const fetchImpl = options.fetchImpl ?? fetch\n const endpoint = options.endpoint ?? BATCH_ENDPOINT\n return async (requests: BatchRequest[]): Promise<BatchResponse[]> => {\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ requests }),\n })\n if (!response.ok) {\n throw new Error(`Batch transport returned ${response.status}`)\n }\n const payload = (await response.json()) as { results?: BatchResponse[] }\n if (!Array.isArray(payload.results)) {\n throw new Error('Batch response missing results array')\n }\n // Map results to indexed BatchResponse shape (server already returns the right form)\n return payload.results.map((item, index) => {\n if ('error' in item) {\n return { index, error: (item as { error: { message: string; code?: string } }).error }\n }\n return { index, data: (item as { data: unknown }).data }\n })\n }\n}\n\n// --- EC-7: singleton batcher per page, lazy-instantiated ---\n\nlet globalBatcher: Batcher | undefined\n\n/** Test-only — reset the module-scope singleton between assertions. */\nexport function __resetGlobalBatcherForTests(): void {\n globalBatcher = undefined\n}\n\n/**\n * Returns the global batcher singleton when `globalThis.__THEO_BATCHING__` is\n * truthy; undefined otherwise. Lazy-instantiated on first call.\n */\nexport function getGlobalBatcher(): Batcher | undefined {\n const g = globalThis as { __THEO_BATCHING__?: boolean }\n if (!g.__THEO_BATCHING__) return undefined\n if (!globalBatcher) {\n globalBatcher = createBatcher({ transport: createBatchTransport() })\n }\n return globalBatcher\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AgentEvent } from '../server/agent-types.js'\nimport { consumeAgentStream } from './agent-stream-core.js'\n\n/**\n * T5.2 — useAgentStream\n *\n * React hook to consume an agent endpoint defined with `defineAgentEndpoint`.\n *\n * Transport (ADR D7, EC-3): fetch + ReadableStream — NOT EventSource.\n * EventSource is GET-only; agent endpoints need POST + body.\n *\n * Returns:\n * events — array of AgentEvents accumulated so far\n * send — call with a payload to (re)open a stream\n * status — 'idle' | 'streaming' | 'done' | 'error'\n * abort — manually cancel an in-flight stream\n *\n * Cleanup (EC-8): on unmount, the current AbortController fires .abort(),\n * which propagates to the underlying fetch and the ReadableStream reader.\n * Safe under React.StrictMode double-mount.\n */\n\nexport type AgentStreamStatus = 'idle' | 'streaming' | 'done' | 'error'\n\nexport interface UseAgentStreamReturn<TBody = unknown> {\n events: AgentEvent[]\n status: AgentStreamStatus\n send: (body: TBody) => void\n abort: () => void\n reset: () => void\n}\n\nexport interface UseAgentStreamOptions {\n /** Extra headers (e.g., auth). */\n headers?: Record<string, string>\n /** Override fetch (rare — primarily for tests). */\n fetch?: typeof fetch\n}\n\nexport function useAgentStream<TBody = unknown>(\n path: string,\n options: UseAgentStreamOptions = {},\n): UseAgentStreamReturn<TBody> {\n const [events, setEvents] = useState<AgentEvent[]>([])\n const [status, setStatus] = useState<AgentStreamStatus>('idle')\n const controllerRef = useRef<AbortController | null>(null)\n\n const abort = useCallback(() => {\n if (controllerRef.current) {\n controllerRef.current.abort()\n controllerRef.current = null\n }\n }, [])\n\n const reset = useCallback(() => {\n abort()\n setEvents([])\n setStatus('idle')\n }, [abort])\n\n const send = useCallback(\n (body: TBody) => {\n // Cancel any in-flight stream first.\n abort()\n const controller = new AbortController()\n controllerRef.current = controller\n setStatus('streaming')\n\n let sawError = false\n consumeAgentStream<TBody>(path, {\n body,\n fetch: options.fetch,\n headers: options.headers,\n signal: controller.signal,\n onEvent: (event) => {\n if (event.type === 'error') sawError = true\n setEvents((prev) => [...prev, event])\n },\n })\n .then(() => {\n if (controllerRef.current !== controller) return // superseded\n setStatus(sawError ? 'error' : 'done')\n })\n .catch(() => {\n if (controllerRef.current !== controller) return // superseded / aborted\n if (controller.signal.aborted) return\n setStatus('error')\n })\n },\n [abort, path, options.fetch, options.headers],\n )\n\n // EC-8: cleanup on unmount aborts in-flight stream.\n useEffect(() => {\n return () => {\n if (controllerRef.current) {\n controllerRef.current.abort()\n controllerRef.current = null\n }\n }\n }, [])\n\n return { events, status, send, abort, reset }\n}\n","import type { AgentEvent } from '../server/agent-types.js'\n\n/**\n * T5.2 — Pure SSE consumer used by useAgentStream.\n *\n * Split out from the React hook so the wire behavior can be tested\n * without a DOM. The hook is glue: useState + useEffect + this primitive.\n *\n * Transport (ADR D7, EC-3): fetch + ReadableStream — NOT EventSource.\n * EventSource is GET-only and cannot carry a body. Agent endpoints need\n * POST + JSON body, so we use fetch + manual SSE chunk parsing.\n */\n\nexport interface ConsumeOptions<TBody = unknown> {\n /** Request body — JSON-serialized into the POST. */\n body: TBody\n /** Called once per SSE event parsed off the stream. */\n onEvent: (event: AgentEvent) => void\n /** Optional fetch override (tests). */\n fetch?: typeof fetch\n /** Optional abort signal — passed through to fetch. */\n signal?: AbortSignal\n /** Extra headers (e.g., auth). */\n headers?: Record<string, string>\n}\n\n/**\n * Parse a single SSE line of the form `data: <json>`.\n * Returns null for non-data lines, comments, or malformed JSON.\n */\nexport function parseSSEChunk(line: string): AgentEvent | null {\n if (!line.startsWith('data:')) return null\n const raw = line.slice(5).trim()\n if (!raw) return null\n try {\n return JSON.parse(raw) as AgentEvent\n } catch {\n return null\n }\n}\n\n/**\n * POSTs to `path` with `body`, reads the SSE response, and invokes\n * `onEvent` for each parsed AgentEvent. Resolves when the server\n * closes the stream or the signal aborts.\n */\nexport async function consumeAgentStream<TBody = unknown>(\n path: string,\n options: ConsumeOptions<TBody>,\n): Promise<void> {\n const fetchImpl = options.fetch ?? globalThis.fetch\n const response = await fetchImpl(path, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n ...options.headers,\n },\n body: JSON.stringify(options.body),\n signal: options.signal,\n })\n\n if (!response.body) return\n\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buf = ''\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buf += decoder.decode(value, { stream: true })\n\n // SSE separates events with a blank line: `\\n\\n`.\n // Split, keep the trailing partial in buf.\n const parts = buf.split('\\n\\n')\n buf = parts.pop() ?? ''\n for (const part of parts) {\n // A chunk may contain multiple lines; keep only the data: line.\n for (const line of part.split('\\n')) {\n const evt = parseSSEChunk(line)\n if (evt) options.onEvent(evt)\n }\n }\n }\n } finally {\n try {\n reader.releaseLock()\n } catch {\n // ignore — reader may already be released if the stream errored\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * T5.3 — React Query adapter primitives.
3
+ *
4
+ * Exposes:
5
+ * - `stableQueryKey(path, options)` — produces a deterministic queryKey
6
+ * that is equal across calls when the logical content of query/body is
7
+ * equal, regardless of property order or inline-object identity. Solves
8
+ * EC-10 (inline-object → infinite refetch).
9
+ * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the
10
+ * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from
11
+ * `@tanstack/react-query`.
12
+ *
13
+ * The canonical implementation lives here in `theokit/client`. The
14
+ * dedicated subpath `theokit/react-query` re-exports this surface so
15
+ * consumers who only need the React Query bridge can import from a
16
+ * clearly named entry point — same shape as `theokit/server`,
17
+ * `theokit/vite-plugin`, etc. No separate npm package.
18
+ */
19
+ interface FetchOptionsLike {
20
+ query?: unknown;
21
+ body?: unknown;
22
+ params?: unknown;
23
+ }
24
+ type QueryKey = readonly unknown[];
25
+ declare function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey;
26
+ type Fetcher<TResult = unknown> = (path: string, options: FetchOptionsLike) => Promise<TResult>;
27
+ interface UseTheoQueryConfig<TResult = unknown> {
28
+ queryKey: QueryKey;
29
+ queryFn: () => Promise<TResult>;
30
+ }
31
+ declare function buildUseTheoQueryConfig<TResult = unknown>(path: string, options: FetchOptionsLike, fetcher: Fetcher<TResult>): UseTheoQueryConfig<TResult>;
32
+
33
+ export { type FetchOptionsLike, type Fetcher, type Fetcher as FetcherFn, type QueryKey, type UseTheoQueryConfig, type UseTheoQueryConfig as UseTheoQueryInternals, buildUseTheoQueryConfig, buildUseTheoQueryConfig as buildUseTheoQueryInternals, stableQueryKey };
@@ -0,0 +1,10 @@
1
+ import {
2
+ buildUseTheoQueryConfig,
3
+ stableQueryKey
4
+ } from "../chunk-6WHMT7FB.js";
5
+ export {
6
+ buildUseTheoQueryConfig,
7
+ buildUseTheoQueryConfig as buildUseTheoQueryInternals,
8
+ stableQueryKey
9
+ };
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theokit",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -23,6 +23,10 @@
23
23
  "types": "./dist/client/index.d.ts",
24
24
  "import": "./dist/client/index.js"
25
25
  },
26
+ "./react-query": {
27
+ "types": "./dist/react-query/index.d.ts",
28
+ "import": "./dist/react-query/index.js"
29
+ },
26
30
  "./adapters/web-shim": {
27
31
  "types": "./dist/adapters/web-shim.d.ts",
28
32
  "import": "./dist/adapters/web-shim.js"