theokit 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions-virtual-module-E6PU47GV.js +175 -0
- package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
- package/dist/actions-virtual-module-E7IELVHL.js +174 -0
- package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
- package/dist/app-typed-client-RZMVWHJ3.js +289 -0
- package/dist/app-typed-client-RZMVWHJ3.js.map +1 -0
- package/dist/app-typed-client-YIBP6JWH.js +289 -0
- package/dist/app-typed-client-YIBP6JWH.js.map +1 -0
- package/dist/{aws-lambda-PONVF3UX.js → aws-lambda-XYCGZH3I.js} +2 -2
- package/dist/{build-IQQYCA3B.js → build-NCGIV4LL.js} +41 -14
- package/dist/build-NCGIV4LL.js.map +1 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +54 -0
- package/dist/build-request-body-preview-II2235E6.js.map +1 -0
- package/dist/{bun-DITMZMKL.js → bun-K73TQEWC.js} +2 -2
- package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
- package/dist/chunk-2BQYEBCK.js.map +1 -0
- package/dist/chunk-7DHGV77J.js +140 -0
- package/dist/chunk-7DHGV77J.js.map +1 -0
- package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
- package/dist/chunk-C27AZN46.js.map +1 -0
- package/dist/chunk-ERAZNHHR.js +73 -0
- package/dist/chunk-ERAZNHHR.js.map +1 -0
- package/dist/{chunk-ZK5JCE5J.js → chunk-EXJWO2HB.js} +201 -43
- package/dist/chunk-EXJWO2HB.js.map +1 -0
- package/dist/chunk-F3TG5FJV.js +97 -0
- package/dist/chunk-F3TG5FJV.js.map +1 -0
- package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
- package/dist/chunk-KJVEJILQ.js.map +1 -0
- package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
- package/dist/chunk-M23FP2AS.js.map +1 -0
- package/dist/{chunk-FRBYJANT.js → chunk-M6MYZSD2.js} +195 -41
- package/dist/chunk-M6MYZSD2.js.map +1 -0
- package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
- package/dist/chunk-MOT42NAH.js +165 -0
- package/dist/chunk-MOT42NAH.js.map +1 -0
- package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
- package/dist/chunk-OJHO2X7C.js.map +1 -0
- package/dist/chunk-QLGES2HC.js +189 -0
- package/dist/chunk-QLGES2HC.js.map +1 -0
- package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
- package/dist/chunk-SEVYARIU.js.map +1 -0
- package/dist/chunk-WSJKACWB.js +17 -0
- package/dist/chunk-WSJKACWB.js.map +1 -0
- package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
- package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
- package/dist/{chunk-Y5KE6YFP.js → chunk-ZJW5FFYJ.js} +3 -3
- package/dist/chunk-ZJW5FFYJ.js.map +1 -0
- package/dist/chunk-ZOXNJV2O.js +358 -0
- package/dist/chunk-ZOXNJV2O.js.map +1 -0
- package/dist/cli/index.js +22 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +31 -1
- package/dist/client/index.js +100 -0
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-RBANU3SE.js → cloudflare-LCAOTRYZ.js} +2 -2
- package/dist/{deno-deploy-J7LI6HNL.js → deno-deploy-ZN4RE5HF.js} +2 -2
- package/dist/{dev-VN6WS3CM.js → dev-WDXC74CK.js} +15 -7
- package/dist/dev-WDXC74CK.js.map +1 -0
- package/dist/dev-emit-3CHM73TV.js +390 -0
- package/dist/dev-emit-3CHM73TV.js.map +1 -0
- package/dist/dev-emit-I3CKOHML.js +44 -0
- package/dist/dev-emit-I3CKOHML.js.map +1 -0
- package/dist/devtools/entry.js +545 -181
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
- package/dist/dispatcher-E6QV32BO.js +83 -0
- package/dist/dispatcher-E6QV32BO.js.map +1 -0
- package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
- package/dist/dist-R2Q7GVDW.js.map +1 -0
- package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
- package/dist/generate-DT4IIAHF.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
- package/dist/{netlify-IFY6ZGFV.js → netlify-5VQ22Z2P.js} +2 -2
- package/dist/{node-KWZI3CKV.js → node-3APTLGWB.js} +2 -2
- package/dist/openapi-CBASKC7W.js +84 -0
- package/dist/openapi-CBASKC7W.js.map +1 -0
- package/dist/registry-5QFTLOL2.js +25 -0
- package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
- package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
- package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
- package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
- package/dist/server/cost/index.js +1 -1
- package/dist/server/index.d.ts +219 -47
- package/dist/server/index.js +35 -116
- package/dist/server/index.js.map +1 -1
- package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
- package/dist/start-5JTFWF4T.js.map +1 -0
- package/dist/{static-BH6JQNM2.js → static-TZBVBDTB.js} +4 -4
- package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
- package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
- package/dist/{vercel-RHADE5IP.js → vercel-MWW24ABC.js} +2 -2
- package/dist/vite-plugin/index.d.ts +25 -1
- package/dist/vite-plugin/index.js +5 -2
- package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-4WZ3C2AC.js} +7 -6
- package/package.json +4 -2
- package/dist/build-IQQYCA3B.js.map +0 -1
- package/dist/chunk-FRBYJANT.js.map +0 -1
- package/dist/chunk-GQSTRXXB.js.map +0 -1
- package/dist/chunk-JBC7PFV4.js.map +0 -1
- package/dist/chunk-JZ5WEG3S.js.map +0 -1
- package/dist/chunk-OEB3OIYP.js.map +0 -1
- package/dist/chunk-OK2PJOLK.js.map +0 -1
- package/dist/chunk-WQCPKA2L.js.map +0 -1
- package/dist/chunk-Y5KE6YFP.js.map +0 -1
- package/dist/chunk-ZK5JCE5J.js.map +0 -1
- package/dist/dev-VN6WS3CM.js.map +0 -1
- package/dist/dist-Q5VBFWVL.js.map +0 -1
- package/dist/generate-SIO4JSKJ.js.map +0 -1
- package/dist/registry-BB3UYAOG.js +0 -25
- package/dist/start-ZGWW5MOL.js.map +0 -1
- package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
- /package/dist/{aws-lambda-PONVF3UX.js.map → aws-lambda-XYCGZH3I.js.map} +0 -0
- /package/dist/{bun-DITMZMKL.js.map → bun-K73TQEWC.js.map} +0 -0
- /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
- /package/dist/{cloudflare-RBANU3SE.js.map → cloudflare-LCAOTRYZ.js.map} +0 -0
- /package/dist/{deno-deploy-J7LI6HNL.js.map → deno-deploy-ZN4RE5HF.js.map} +0 -0
- /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
- /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
- /package/dist/{netlify-IFY6ZGFV.js.map → netlify-5VQ22Z2P.js.map} +0 -0
- /package/dist/{node-KWZI3CKV.js.map → node-3APTLGWB.js.map} +0 -0
- /package/dist/{registry-BB3UYAOG.js.map → registry-5QFTLOL2.js.map} +0 -0
- /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
- /package/dist/{static-BH6JQNM2.js.map → static-TZBVBDTB.js.map} +0 -0
- /package/dist/{vercel-RHADE5IP.js.map → vercel-MWW24ABC.js.map} +0 -0
- /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-4WZ3C2AC.js.map} +0 -0
package/dist/client/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
buildUseTheoQueryConfig,
|
|
3
3
|
stableQueryKey
|
|
4
4
|
} from "../chunk-FH3DYBAN.js";
|
|
5
|
+
import {
|
|
6
|
+
HTTP_METHOD_LOWERCASE
|
|
7
|
+
} from "../chunk-WSJKACWB.js";
|
|
5
8
|
import "../chunk-DGUM43GV.js";
|
|
6
9
|
|
|
7
10
|
// src/client/theo-fetch.ts
|
|
@@ -240,6 +243,102 @@ function resolveClientTransformerName() {
|
|
|
240
243
|
return g.__THEO_TRANSFORMER__ ?? "json";
|
|
241
244
|
}
|
|
242
245
|
|
|
246
|
+
// src/client/app-client.ts
|
|
247
|
+
var HTTP_METHODS_SET = new Set(HTTP_METHOD_LOWERCASE);
|
|
248
|
+
var NON_INTERCEPT_KEYS = /* @__PURE__ */ new Set([
|
|
249
|
+
"then",
|
|
250
|
+
"catch",
|
|
251
|
+
"finally",
|
|
252
|
+
"toJSON",
|
|
253
|
+
"toString",
|
|
254
|
+
"valueOf",
|
|
255
|
+
"constructor",
|
|
256
|
+
"prototype"
|
|
257
|
+
]);
|
|
258
|
+
function injectParams(path, params) {
|
|
259
|
+
if (!params) {
|
|
260
|
+
if (/:(?:\.\.\.)?[A-Za-z_]/.test(path)) {
|
|
261
|
+
throw new TheoFetchError(0, {
|
|
262
|
+
error: {
|
|
263
|
+
code: "MISSING_PARAM",
|
|
264
|
+
message: `Route ${path} requires params but none were provided. Pass them via opts.params, e.g. client.posts.get({ params: { id: '123' } }).`
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return path;
|
|
269
|
+
}
|
|
270
|
+
return path.replace(/:(?:\.\.\.)?([A-Za-z_][A-Za-z0-9_]*)/g, (_match, key) => {
|
|
271
|
+
const value = params[key];
|
|
272
|
+
if (value === void 0 || value === null || value === "") {
|
|
273
|
+
throw new TheoFetchError(0, {
|
|
274
|
+
error: {
|
|
275
|
+
code: "MISSING_PARAM",
|
|
276
|
+
message: `Param '${key}' is required for path '${path}' but was missing/empty.`
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return encodeURIComponent(String(value));
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function makeProxy(ctx) {
|
|
284
|
+
const handler = {
|
|
285
|
+
get(_target, key) {
|
|
286
|
+
if (typeof key !== "string") return void 0;
|
|
287
|
+
if (NON_INTERCEPT_KEYS.has(key)) return void 0;
|
|
288
|
+
if (HTTP_METHODS_SET.has(key)) {
|
|
289
|
+
return async (opts) => {
|
|
290
|
+
if (ctx.segments.length === 0) {
|
|
291
|
+
throw new TheoFetchError(0, {
|
|
292
|
+
error: {
|
|
293
|
+
code: "INVALID_PATH",
|
|
294
|
+
message: `client.${key}() called without a route segment. Use client.<resource>.${key}(...) instead.`
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
let finalPath = `${ctx.baseUrl}/${ctx.segments.join("/")}`;
|
|
299
|
+
if (opts?.params) {
|
|
300
|
+
const segmentsWithDynamic = ctx.segments.map(
|
|
301
|
+
(seg) => Object.prototype.hasOwnProperty.call(opts.params, seg) ? `:${seg}` : seg
|
|
302
|
+
);
|
|
303
|
+
finalPath = injectParams(
|
|
304
|
+
`${ctx.baseUrl}/${segmentsWithDynamic.join("/")}`,
|
|
305
|
+
opts.params
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
const { params: _ignored, ...rest } = opts ?? {};
|
|
309
|
+
const init = { method: key.toUpperCase(), ...rest };
|
|
310
|
+
return ctx.fetchImpl(finalPath, init);
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return makeProxy({ ...ctx, segments: [...ctx.segments, key] });
|
|
314
|
+
},
|
|
315
|
+
apply() {
|
|
316
|
+
return Promise.reject(
|
|
317
|
+
new TheoFetchError(0, {
|
|
318
|
+
error: {
|
|
319
|
+
code: "INVALID_CALL",
|
|
320
|
+
message: "client(...) is not callable. Use client.<resource>.<method>(opts?) \u2014 e.g. client.posts.get()."
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return new Proxy(() => void 0, handler);
|
|
327
|
+
}
|
|
328
|
+
function createAppClient(baseUrlOrOptions, legacyFetchImpl) {
|
|
329
|
+
let baseUrl = "/api";
|
|
330
|
+
let fetchImpl = theoFetch;
|
|
331
|
+
if (typeof baseUrlOrOptions === "string") {
|
|
332
|
+
baseUrl = baseUrlOrOptions || "/api";
|
|
333
|
+
} else if (baseUrlOrOptions && typeof baseUrlOrOptions === "object") {
|
|
334
|
+
if (baseUrlOrOptions.baseUrl) baseUrl = baseUrlOrOptions.baseUrl;
|
|
335
|
+
if (baseUrlOrOptions.fetchImpl) fetchImpl = baseUrlOrOptions.fetchImpl;
|
|
336
|
+
}
|
|
337
|
+
if (legacyFetchImpl) fetchImpl = legacyFetchImpl;
|
|
338
|
+
if (baseUrl.length > 1 && baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
339
|
+
return makeProxy({ segments: [], baseUrl, fetchImpl });
|
|
340
|
+
}
|
|
341
|
+
|
|
243
342
|
// src/client/use-agent-stream.ts
|
|
244
343
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
245
344
|
|
|
@@ -355,6 +454,7 @@ export {
|
|
|
355
454
|
TheoFetchError,
|
|
356
455
|
buildUseTheoQueryConfig,
|
|
357
456
|
consumeAgentStream,
|
|
457
|
+
createAppClient,
|
|
358
458
|
createBatcher,
|
|
359
459
|
parseSSEChunk,
|
|
360
460
|
stableQueryKey,
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 superjson from 'superjson'\nimport type { z } from 'zod'\n\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 // `raw` is typed `string`, but stay defensive: callers pass `await response.text()`\n // which can be empty.\n if (raw === '') {\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\n ? undefined\n : 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\n ? undefined\n : 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 errorMessage = typeof error?.message === 'string' ? error.message : undefined\n super(errorMessage ?? `HTTP ${String(status)}`)\n this.name = 'TheoFetchError'\n this.status = status\n this.code = typeof error?.code === 'string' ? error.code : undefined\n this.issues = Array.isArray(error?.issues) ? error.issues : undefined\n }\n}\n\n/**\n * Serialize a query-param value to a string. Handles primitives, dates,\n * and falls back to JSON for arrays/objects. Avoids the\n * `[object Object]` foot-gun that `no-base-to-string` warns against.\n */\nfunction stringifyQueryValue(value: unknown): string {\n if (value === null) return 'null'\n if (value instanceof Date) return value.toISOString()\n switch (typeof value) {\n case 'string':\n return value\n case 'number':\n case 'boolean':\n case 'bigint':\n return String(value)\n default:\n return JSON.stringify(value)\n }\n}\n\n// --- Main Function ---\n\ninterface TheoFetchInternalOptions {\n method?: string\n query?: Record<string, unknown>\n body?: unknown\n headers?: HeadersInit\n signal?: AbortSignal\n}\n\n/**\n * Resolve the request origin per the documented fallback hierarchy. Pure\n * helper extracted to keep `theoFetch` under the complexity ceiling.\n * 1. `globalThis.location.origin` (browser)\n * 2. `globalThis.__THEO_ORIGIN__` (build-time literal)\n * 3. `process.env.THEO_ORIGIN` (escape hatch)\n * 4. `http://localhost` (placeholder for URL parsing — the URL is built\n * relative so only pathname+search ever flows to the wire)\n */\nfunction resolveRequestOrigin(): string {\n const g = globalThis as { location?: { origin?: string }; __THEO_ORIGIN__?: string }\n const fromEnv = typeof process !== 'undefined' ? process.env.THEO_ORIGIN : undefined\n return g.location?.origin ?? g.__THEO_ORIGIN__ ?? fromEnv ?? 'http://localhost'\n}\n\nfunction buildFetchUrl(path: string, query: Record<string, unknown> | undefined): URL {\n const fetchUrl = new URL(path, resolveRequestOrigin())\n if (!query) return fetchUrl\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) {\n fetchUrl.searchParams.set(k, stringifyQueryValue(v))\n }\n }\n return fetchUrl\n}\n\n/**\n * Normalize `HeadersInit` (Headers / `[string, string][]` / Record) into a\n * single plain `Record<string, string>`. Plain-object output is required\n * by callers that introspect headers via index access (test fixtures use\n * `(init.headers as Record<string, string>)['Content-Type']`).\n */\nfunction normalizeHeaders(input: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {}\n if (!input) return out\n if (input instanceof Headers) {\n input.forEach((value, key) => {\n out[key] = value\n })\n return out\n }\n if (Array.isArray(input)) {\n for (const [key, value] of input) out[key] = value\n return out\n }\n for (const [key, value] of Object.entries(input)) {\n out[key] = value\n }\n return out\n}\n\nfunction buildRequestInit(opts: TheoFetchInternalOptions): RequestInit {\n const init: RequestInit = {}\n if (opts.method !== undefined) init.method = opts.method\n if (opts.signal !== undefined) init.signal = opts.signal\n\n const headers = normalizeHeaders(opts.headers)\n init.headers = headers\n\n if (opts.body !== undefined) {\n init.body = JSON.stringify(opts.body)\n headers['Content-Type'] = 'application/json'\n }\n\n // Phase 5 — Auto-attach `X-Theo-Action: 1` for state-mutating methods so\n // the framework's CSRF check passes when servers run in `strict` mode.\n // Safe methods (GET/HEAD/OPTIONS) skip the header to keep them cacheable.\n const method = (opts.method ?? 'GET').toUpperCase()\n if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {\n headers['X-Theo-Action'] = '1'\n }\n return init\n}\n\nasync function tryBatcher(\n url: string,\n options: TheoFetchInternalOptions,\n): Promise<{ matched: true; result: unknown } | { matched: false }> {\n const batcher = getGlobalBatcher()\n if (!batcher) return { matched: false }\n try {\n const result = await batcher.dispatch({\n path: url,\n method: options.method ?? 'GET',\n query: options.query,\n body: options.body,\n })\n return { matched: true, result }\n } catch {\n // Batcher failure: fall through to direct fetch (graceful degrade).\n return { matched: false }\n }\n}\n\nexport async function theoFetch<T>(\n url: string,\n options?: TheoFetchOptions<T>,\n): Promise<InferResponse<T>> {\n const internal = (options ?? {}) as unknown as TheoFetchInternalOptions\n\n // T1.5 — Transparent batching when globalThis.__THEO_BATCHING__ is truthy.\n const batchAttempt = await tryBatcher(url, internal)\n if (batchAttempt.matched) {\n return batchAttempt.result as InferResponse<T>\n }\n\n const fetchUrl = buildFetchUrl(url, internal.query)\n const init = buildRequestInit(internal)\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(\n text,\n serverTransformerName,\n clientTransformerName,\n ) 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 // `results[i]` could be undefined at runtime when the transport\n // returns fewer entries than the chunk; TypeScript's strict\n // typing without `noUncheckedIndexedAccess` does not surface\n // this, so we hand-narrow.\n const result = results.length > i ? results[i] : undefined\n if (result === undefined) {\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: unknown) => {\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\nconst 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(options: CreateBatchTransportOptions = {}): 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 globalBatcher ??= createBatcher({ transport: createBatchTransport() })\n return globalBatcher\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { AgentEvent } from '../core/contracts/agent-events.js'\n\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 '../core/contracts/agent-events.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 * Dispatch SSE events from a list of `\\n\\n`-separated chunk parts.\n * Extracted to keep the streaming loop under the max-depth ceiling.\n */\nfunction dispatchSseParts(parts: string[], onEvent: (event: AgentEvent) => void): void {\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) onEvent(evt)\n }\n }\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 *\n * T1.1 — Attaches `X-Theo-Action: '1'` so 0.3.0 strict CSRF mode accepts\n * the request. The user can override (or suppress) by passing the header\n * in `options.headers` — spread order ensures their value wins.\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 'X-Theo-Action': '1',\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 let done = false\n while (!done) {\n const chunk = await reader.read()\n done = chunk.done\n if (done) continue\n buf += decoder.decode(chunk.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 dispatchSseParts(parts, options.onEvent)\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/app-client.ts","../../src/client/use-agent-stream.ts","../../src/client/agent-stream-core.ts"],"sourcesContent":["import superjson from 'superjson'\nimport type { z } from 'zod'\n\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 // `raw` is typed `string`, but stay defensive: callers pass `await response.text()`\n // which can be empty.\n if (raw === '') {\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\n ? undefined\n : 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\n ? undefined\n : 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 errorMessage = typeof error?.message === 'string' ? error.message : undefined\n super(errorMessage ?? `HTTP ${String(status)}`)\n this.name = 'TheoFetchError'\n this.status = status\n this.code = typeof error?.code === 'string' ? error.code : undefined\n this.issues = Array.isArray(error?.issues) ? error.issues : undefined\n }\n}\n\n/**\n * Serialize a query-param value to a string. Handles primitives, dates,\n * and falls back to JSON for arrays/objects. Avoids the\n * `[object Object]` foot-gun that `no-base-to-string` warns against.\n */\nfunction stringifyQueryValue(value: unknown): string {\n if (value === null) return 'null'\n if (value instanceof Date) return value.toISOString()\n switch (typeof value) {\n case 'string':\n return value\n case 'number':\n case 'boolean':\n case 'bigint':\n return String(value)\n default:\n return JSON.stringify(value)\n }\n}\n\n// --- Main Function ---\n\ninterface TheoFetchInternalOptions {\n method?: string\n query?: Record<string, unknown>\n body?: unknown\n headers?: HeadersInit\n signal?: AbortSignal\n}\n\n/**\n * Resolve the request origin per the documented fallback hierarchy. Pure\n * helper extracted to keep `theoFetch` under the complexity ceiling.\n * 1. `globalThis.location.origin` (browser)\n * 2. `globalThis.__THEO_ORIGIN__` (build-time literal)\n * 3. `process.env.THEO_ORIGIN` (escape hatch)\n * 4. `http://localhost` (placeholder for URL parsing — the URL is built\n * relative so only pathname+search ever flows to the wire)\n */\nfunction resolveRequestOrigin(): string {\n const g = globalThis as { location?: { origin?: string }; __THEO_ORIGIN__?: string }\n const fromEnv = typeof process !== 'undefined' ? process.env.THEO_ORIGIN : undefined\n return g.location?.origin ?? g.__THEO_ORIGIN__ ?? fromEnv ?? 'http://localhost'\n}\n\nfunction buildFetchUrl(path: string, query: Record<string, unknown> | undefined): URL {\n const fetchUrl = new URL(path, resolveRequestOrigin())\n if (!query) return fetchUrl\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) {\n fetchUrl.searchParams.set(k, stringifyQueryValue(v))\n }\n }\n return fetchUrl\n}\n\n/**\n * Normalize `HeadersInit` (Headers / `[string, string][]` / Record) into a\n * single plain `Record<string, string>`. Plain-object output is required\n * by callers that introspect headers via index access (test fixtures use\n * `(init.headers as Record<string, string>)['Content-Type']`).\n */\nfunction normalizeHeaders(input: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {}\n if (!input) return out\n if (input instanceof Headers) {\n input.forEach((value, key) => {\n out[key] = value\n })\n return out\n }\n if (Array.isArray(input)) {\n for (const [key, value] of input) out[key] = value\n return out\n }\n for (const [key, value] of Object.entries(input)) {\n out[key] = value\n }\n return out\n}\n\nfunction buildRequestInit(opts: TheoFetchInternalOptions): RequestInit {\n const init: RequestInit = {}\n if (opts.method !== undefined) init.method = opts.method\n if (opts.signal !== undefined) init.signal = opts.signal\n\n const headers = normalizeHeaders(opts.headers)\n init.headers = headers\n\n if (opts.body !== undefined) {\n init.body = JSON.stringify(opts.body)\n headers['Content-Type'] = 'application/json'\n }\n\n // Phase 5 — Auto-attach `X-Theo-Action: 1` for state-mutating methods so\n // the framework's CSRF check passes when servers run in `strict` mode.\n // Safe methods (GET/HEAD/OPTIONS) skip the header to keep them cacheable.\n const method = (opts.method ?? 'GET').toUpperCase()\n if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {\n headers['X-Theo-Action'] = '1'\n }\n return init\n}\n\nasync function tryBatcher(\n url: string,\n options: TheoFetchInternalOptions,\n): Promise<{ matched: true; result: unknown } | { matched: false }> {\n const batcher = getGlobalBatcher()\n if (!batcher) return { matched: false }\n try {\n const result = await batcher.dispatch({\n path: url,\n method: options.method ?? 'GET',\n query: options.query,\n body: options.body,\n })\n return { matched: true, result }\n } catch {\n // Batcher failure: fall through to direct fetch (graceful degrade).\n return { matched: false }\n }\n}\n\nexport async function theoFetch<T>(\n url: string,\n options?: TheoFetchOptions<T>,\n): Promise<InferResponse<T>> {\n const internal = (options ?? {}) as unknown as TheoFetchInternalOptions\n\n // T1.5 — Transparent batching when globalThis.__THEO_BATCHING__ is truthy.\n const batchAttempt = await tryBatcher(url, internal)\n if (batchAttempt.matched) {\n return batchAttempt.result as InferResponse<T>\n }\n\n const fetchUrl = buildFetchUrl(url, internal.query)\n const init = buildRequestInit(internal)\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(\n text,\n serverTransformerName,\n clientTransformerName,\n ) 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 // `results[i]` could be undefined at runtime when the transport\n // returns fewer entries than the chunk; TypeScript's strict\n // typing without `noUncheckedIndexedAccess` does not surface\n // this, so we hand-narrow.\n const result = results.length > i ? results[i] : undefined\n if (result === undefined) {\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: unknown) => {\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\nconst 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(options: CreateBatchTransportOptions = {}): 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 globalBatcher ??= createBatcher({ transport: createBatchTransport() })\n return globalBatcher\n}\n","/**\n * Phase 3 of G1 (g1-client-codegen-plan.md):\n *\n * `createAppClient(baseUrl?, fetchImpl?)` returns a Proxy that walks property\n * access (`client.posts.id.get(...)`) into a `theoFetch` invocation against\n * `{baseUrl}/posts/{params.id}` with method `GET`.\n *\n * Type safety is delivered by the `.d.ts` file emitted in Phase 2 — this\n * runtime is structurally untyped. Power users may use `theoFetch` directly\n * when they need to escape the Proxy facade.\n *\n * Edge cases absorbed:\n * - EC-1 (thenable trap): `then` / `catch` / `finally` / `toJSON` / `Symbol.*`\n * return `undefined` so that accidental `await client.posts` resolves to a\n * plain Proxy value (it is NOT thenable) instead of looping.\n * - EC-7 (abort signal): `opts.signal` is spread through to the underlying\n * fetchImpl unchanged — verified in unit tests.\n *\n * Bundle target: ≤ 2KB gzipped.\n */\n\nimport { theoFetch, TheoFetchError } from './theo-fetch.js'\nimport type { TheoFetchOptions } from './theo-fetch.js'\nimport { HTTP_METHOD_LOWERCASE } from '../core/contracts/http-methods.js'\n\nconst HTTP_METHODS_SET = new Set(HTTP_METHOD_LOWERCASE)\n\n/**\n * Keys that JavaScript runtime / inspectors / Promise resolution probe\n * unconditionally. Returning `undefined` for these keeps the Proxy from\n * pretending to be a thenable / iterable / serializable thing.\n */\nconst NON_INTERCEPT_KEYS = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n])\n\ntype FetchImpl = typeof theoFetch\n\ninterface CallOptions {\n params?: Record<string, string | number>\n query?: Record<string, unknown>\n body?: unknown\n signal?: AbortSignal\n headers?: HeadersInit\n // arbitrary RequestInit fields (mode, credentials, cache, etc.)\n [k: string]: unknown\n}\n\nfunction injectParams(path: string, params?: Record<string, string | number>): string {\n if (!params) {\n if (/:(?:\\.\\.\\.)?[A-Za-z_]/.test(path)) {\n throw new TheoFetchError(0, {\n error: {\n code: 'MISSING_PARAM',\n message: `Route ${path} requires params but none were provided. Pass them via opts.params, e.g. client.posts.get({ params: { id: '123' } }).`,\n },\n })\n }\n return path\n }\n return path.replace(/:(?:\\.\\.\\.)?([A-Za-z_][A-Za-z0-9_]*)/g, (_match, key: string) => {\n const value = params[key]\n if (value === undefined || value === null || value === '') {\n throw new TheoFetchError(0, {\n error: {\n code: 'MISSING_PARAM',\n message: `Param '${key}' is required for path '${path}' but was missing/empty.`,\n },\n })\n }\n return encodeURIComponent(String(value))\n })\n}\n\ninterface ProxyContext {\n segments: string[]\n baseUrl: string\n fetchImpl: FetchImpl\n}\n\nfunction makeProxy(ctx: ProxyContext): unknown {\n const handler: ProxyHandler<() => void> = {\n get(_target, key) {\n if (typeof key !== 'string') return undefined\n if (NON_INTERCEPT_KEYS.has(key)) return undefined\n if (HTTP_METHODS_SET.has(key)) {\n return async (opts?: CallOptions) => {\n if (ctx.segments.length === 0) {\n throw new TheoFetchError(0, {\n error: {\n code: 'INVALID_PATH',\n message: `client.${key}() called without a route segment. Use client.<resource>.${key}(...) instead.`,\n },\n })\n }\n let finalPath = `${ctx.baseUrl}/${ctx.segments.join('/')}`\n if (opts?.params) {\n // Build a path with `:name` placeholders re-inserted from segments\n // whose name matches a key in opts.params (treat as dynamic).\n const segmentsWithDynamic = ctx.segments.map((seg) =>\n Object.prototype.hasOwnProperty.call(opts.params, seg) ? `:${seg}` : seg,\n )\n finalPath = injectParams(\n `${ctx.baseUrl}/${segmentsWithDynamic.join('/')}`,\n opts.params,\n )\n }\n const { params: _ignored, ...rest } = opts ?? {}\n const init = { method: key.toUpperCase(), ...rest } as unknown as TheoFetchOptions<unknown>\n return ctx.fetchImpl(finalPath, init)\n }\n }\n // Continue traversal — append segment to the path.\n return makeProxy({ ...ctx, segments: [...ctx.segments, key] })\n },\n apply() {\n return Promise.reject(\n new TheoFetchError(0, {\n error: {\n code: 'INVALID_CALL',\n message:\n 'client(...) is not callable. Use client.<resource>.<method>(opts?) — e.g. client.posts.get().',\n },\n }),\n )\n },\n }\n return new Proxy(() => undefined, handler)\n}\n\nexport interface CreateAppClientOptions {\n /** Base URL for the API. Defaults to `/api`. */\n baseUrl?: string\n /** Test-only fetch override. Production callers should not pass this. */\n fetchImpl?: FetchImpl\n}\n\nexport function createAppClient<TAppClient = unknown>(\n baseUrlOrOptions?: string | CreateAppClientOptions,\n legacyFetchImpl?: FetchImpl,\n): TAppClient {\n let baseUrl = '/api'\n let fetchImpl: FetchImpl = theoFetch\n if (typeof baseUrlOrOptions === 'string') {\n baseUrl = baseUrlOrOptions || '/api'\n } else if (baseUrlOrOptions && typeof baseUrlOrOptions === 'object') {\n if (baseUrlOrOptions.baseUrl) baseUrl = baseUrlOrOptions.baseUrl\n if (baseUrlOrOptions.fetchImpl) fetchImpl = baseUrlOrOptions.fetchImpl\n }\n // Legacy 2nd-arg fetchImpl overrides regardless of 1st-arg shape (test seam).\n if (legacyFetchImpl) fetchImpl = legacyFetchImpl\n // Strip trailing slash for predictable joining.\n if (baseUrl.length > 1 && baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1)\n return makeProxy({ segments: [], baseUrl, fetchImpl }) as TAppClient\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { AgentEvent } from '../core/contracts/agent-events.js'\n\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 '../core/contracts/agent-events.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 * Dispatch SSE events from a list of `\\n\\n`-separated chunk parts.\n * Extracted to keep the streaming loop under the max-depth ceiling.\n */\nfunction dispatchSseParts(parts: string[], onEvent: (event: AgentEvent) => void): void {\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) onEvent(evt)\n }\n }\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 *\n * T1.1 — Attaches `X-Theo-Action: '1'` so 0.3.0 strict CSRF mode accepts\n * the request. The user can override (or suppress) by passing the header\n * in `options.headers` — spread order ensures their value wins.\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 'X-Theo-Action': '1',\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 let done = false\n while (!done) {\n const chunk = await reader.read()\n done = chunk.done\n if (done) continue\n buf += decoder.decode(chunk.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 dispatchSseParts(parts, options.onEvent)\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":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
nodeAdapter
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZJW5FFYJ.js";
|
|
6
6
|
import {
|
|
7
7
|
assertServicesUnsupported,
|
|
8
8
|
readManifest
|
|
@@ -120,4 +120,4 @@ export {
|
|
|
120
120
|
renderCloudflareWorkerEntry,
|
|
121
121
|
renderWranglerToml
|
|
122
122
|
};
|
|
123
|
-
//# sourceMappingURL=cloudflare-
|
|
123
|
+
//# sourceMappingURL=cloudflare-LCAOTRYZ.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
nodeAdapter
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZJW5FFYJ.js";
|
|
6
6
|
import {
|
|
7
7
|
assertServicesUnsupported,
|
|
8
8
|
readManifest
|
|
@@ -105,4 +105,4 @@ export {
|
|
|
105
105
|
denoDeployAdapter,
|
|
106
106
|
renderDenoEntry
|
|
107
107
|
};
|
|
108
|
-
//# sourceMappingURL=deno-deploy-
|
|
108
|
+
//# sourceMappingURL=deno-deploy-ZN4RE5HF.js.map
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
+
import {
|
|
4
|
+
preflightNodeAndBindings
|
|
5
|
+
} from "./chunk-F3TG5FJV.js";
|
|
3
6
|
import {
|
|
4
7
|
validateProjectStructure
|
|
5
8
|
} from "./chunk-XMS5VRIK.js";
|
|
6
9
|
import {
|
|
7
10
|
theoPluginAsync
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-GQSTRXXB.js";
|
|
11
|
+
} from "./chunk-EXJWO2HB.js";
|
|
12
|
+
import "./chunk-C27AZN46.js";
|
|
11
13
|
import {
|
|
12
14
|
loadConfig,
|
|
13
15
|
loadEnv
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-MG7H3LAW.js";
|
|
17
|
+
import "./chunk-ZBWROIIO.js";
|
|
16
18
|
import "./chunk-5ODOE6EF.js";
|
|
17
19
|
import "./chunk-O7335ZGH.js";
|
|
18
20
|
import {
|
|
19
21
|
orchestrateDev
|
|
20
22
|
} from "./chunk-WZ7CPVQB.js";
|
|
23
|
+
import "./chunk-QLGES2HC.js";
|
|
24
|
+
import "./chunk-MOT42NAH.js";
|
|
21
25
|
import "./chunk-KT3MWNZG.js";
|
|
22
26
|
|
|
23
27
|
// src/cli/commands/dev.ts
|
|
24
28
|
import react from "@vitejs/plugin-react";
|
|
25
29
|
import { createServer } from "vite";
|
|
26
30
|
async function startDevServer(cwd, options) {
|
|
31
|
+
preflightNodeAndBindings(cwd);
|
|
27
32
|
loadEnv({ cwd, mode: "development" });
|
|
28
33
|
const config = await loadConfig(cwd);
|
|
29
34
|
validateProjectStructure(cwd);
|
|
@@ -46,7 +51,10 @@ async function startDevServer(cwd, options) {
|
|
|
46
51
|
rateLimit: flatRateLimit,
|
|
47
52
|
ssr: config.ssr,
|
|
48
53
|
// Wave 2 (T3.1) — wire typed-client plugin when services declared.
|
|
49
|
-
services: config.services
|
|
54
|
+
services: config.services,
|
|
55
|
+
// T4.1 (canvas-ecosystem-refactor / ADR D6) — passthrough peer-deps
|
|
56
|
+
// that plugins import dynamically (e.g., mermaid via plugin-canvas).
|
|
57
|
+
viteOptimizeDeps: config.viteOptimizeDeps
|
|
50
58
|
});
|
|
51
59
|
server = await createServer({
|
|
52
60
|
root: cwd,
|
|
@@ -81,4 +89,4 @@ export {
|
|
|
81
89
|
devCommand,
|
|
82
90
|
startDevServer
|
|
83
91
|
};
|
|
84
|
-
//# sourceMappingURL=dev-
|
|
92
|
+
//# sourceMappingURL=dev-WDXC74CK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/dev.ts"],"sourcesContent":["import react from '@vitejs/plugin-react'\nimport { createServer, type ViteDevServer } from 'vite'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { loadEnv } from '../../config/load-env.js'\nimport { validateProjectStructure } from '../../core/validate-structure.js'\nimport { orchestrateDev } from '../../services/index.js'\nimport { theoPluginAsync } from '../../vite-plugin/index.js'\nimport { preflightNodeAndBindings } from '../preflight-node-version.js'\n\ninterface DevOptions {\n port?: number\n}\n\nexport async function startDevServer(cwd: string, options?: DevOptions): Promise<ViteDevServer> {\n // Preflight (FIRST — BEFORE anything that touches native bindings):\n // verifies Node version + native binding ABI match. Emits actionable\n // error referencing `nvm use` + `pnpm rebuild` when the dev server\n // was spawned under the wrong Node (common gotcha with `nohup env ...`\n // stripping the nvm PATH prefix).\n preflightNodeAndBindings(cwd)\n\n // Phase 1 (T1.2) — Load .env files into process.env BEFORE any module\n // reads them. Must run before loadConfig() so theo.config.ts functions\n // referencing process.env.* resolve correctly.\n loadEnv({ cwd, mode: 'development' })\n\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n // Wave 2 (T1.1) — spawn polyglot services BEFORE Vite. Healthcheck gates\n // readiness. Empty `services: {}` → early return (Wave 1 BC preserved).\n const orchestration = await orchestrateDev({\n cwd,\n services: config.services,\n })\n if (!orchestration.allHealthy) {\n await orchestration.stop()\n throw new Error(\n `[services] services failed healthcheck: ${orchestration.unhealthy.join(', ')}. ` +\n `Check that each declared service binds its port and responds 200 on its ` +\n `healthcheck path within 30s.`,\n )\n }\n\n // Phase 7 — legacy fs-mtime sweep removed: SDK's Agent.registry handles\n // LRU + idle eviction natively (configurable via theo.config.ts >\n // agents.registry; lazy-configured on first request).\n\n const port = options?.port ?? config.port\n // Narrow the rateLimit union: only the legacy flat shape (windowMs+max)\n // flows into the theoPlugin's RateLimitConfig parameter. The per-route\n // variant is consumed inside the api-middleware via the loaded user\n // config separately.\n const flatRateLimit =\n config.rateLimit && 'windowMs' in config.rateLimit && 'max' in config.rateLimit\n ? config.rateLimit\n : undefined\n // theoPluginAsync auto-chains @usetheo/ui + @tailwindcss/vite via\n // top-level Plugin[] return (sync `theoPlugin()` factory's config()\n // hook returning {plugins:[...]} is silently dropped by Vite — see\n // commit history). Spread the result so Vite flattens them.\n let server: ViteDevServer\n try {\n const theoPlugins = await theoPluginAsync({\n root: cwd,\n rateLimit: flatRateLimit,\n ssr: config.ssr,\n // Wave 2 (T3.1) — wire typed-client plugin when services declared.\n services: config.services,\n // T4.1 (canvas-ecosystem-refactor / ADR D6) — passthrough peer-deps\n // that plugins import dynamically (e.g., mermaid via plugin-canvas).\n viteOptimizeDeps: config.viteOptimizeDeps,\n })\n server = await createServer({\n root: cwd,\n plugins: [react(), ...theoPlugins],\n server: { port },\n logLevel: options?.port === 0 ? 'silent' : undefined,\n })\n\n await server.listen()\n } catch (err) {\n // EC-1 / try-finally pattern: if Vite create/listen fails AFTER services\n // started, stop them so we don't leak child processes.\n await orchestration.stop()\n throw err\n }\n\n // EC-1 fix: attach orchestration.stop() to the underlying HTTP server's\n // close event via Node-native API. We do NOT mutate `server.close`\n // (fragile across Vite upgrades). httpServer is `http.Server | null`.\n server.httpServer?.on('close', () => {\n void orchestration.stop()\n })\n\n return server\n}\n\nexport async function devCommand(options: DevOptions): Promise<void> {\n try {\n const cwd = process.cwd()\n const server = await startDevServer(cwd, options)\n server.printUrls()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,WAAW;AAClB,SAAS,oBAAwC;AAajD,eAAsB,eAAe,KAAa,SAA8C;AAM9F,2BAAyB,GAAG;AAK5B,UAAQ,EAAE,KAAK,MAAM,cAAc,CAAC;AAEpC,QAAM,SAAS,MAAM,WAAW,GAAG;AACnC,2BAAyB,GAAG;AAI5B,QAAM,gBAAgB,MAAM,eAAe;AAAA,IACzC;AAAA,IACA,UAAU,OAAO;AAAA,EACnB,CAAC;AACD,MAAI,CAAC,cAAc,YAAY;AAC7B,UAAM,cAAc,KAAK;AACzB,UAAM,IAAI;AAAA,MACR,2CAA2C,cAAc,UAAU,KAAK,IAAI,CAAC;AAAA,IAG/E;AAAA,EACF;AAMA,QAAM,OAAO,SAAS,QAAQ,OAAO;AAKrC,QAAM,gBACJ,OAAO,aAAa,cAAc,OAAO,aAAa,SAAS,OAAO,YAClE,OAAO,YACP;AAKN,MAAI;AACJ,MAAI;AACF,UAAM,cAAc,MAAM,gBAAgB;AAAA,MACxC,MAAM;AAAA,MACN,WAAW;AAAA,MACX,KAAK,OAAO;AAAA;AAAA,MAEZ,UAAU,OAAO;AAAA;AAAA;AAAA,MAGjB,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AACD,aAAS,MAAM,aAAa;AAAA,MAC1B,MAAM;AAAA,MACN,SAAS,CAAC,MAAM,GAAG,GAAG,WAAW;AAAA,MACjC,QAAQ,EAAE,KAAK;AAAA,MACf,UAAU,SAAS,SAAS,IAAI,WAAW;AAAA,IAC7C,CAAC;AAED,UAAM,OAAO,OAAO;AAAA,EACtB,SAAS,KAAK;AAGZ,UAAM,cAAc,KAAK;AACzB,UAAM;AAAA,EACR;AAKA,SAAO,YAAY,GAAG,SAAS,MAAM;AACnC,SAAK,cAAc,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO;AACT;AAEA,eAAsB,WAAW,SAAoC;AACnE,MAAI;AACF,UAAM,MAAM,QAAQ,IAAI;AACxB,UAAM,SAAS,MAAM,eAAe,KAAK,OAAO;AAChD,WAAO,UAAU;AAAA,EACnB,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAQ,MAAM;AAAA,WAAS,GAAG;AAAA,CAAI;AAC9B,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|