theokit 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-virtual-module-6HQK5GHV.js → actions-virtual-module-RNPMHJOM.js} +3 -3
- package/dist/{actions-virtual-module-HYCFRMC3.js → actions-virtual-module-YGKSEIOJ.js} +4 -3
- package/dist/{actions-virtual-module-HYCFRMC3.js.map → actions-virtual-module-YGKSEIOJ.js.map} +1 -1
- package/dist/{agent-PSJ77NJA.js → agent-NDHBKRAV.js} +3 -3
- package/dist/{agents-typed-client-MAAILRI6.js → agents-typed-client-HSZKZY2P.js} +23 -5
- package/dist/agents-typed-client-HSZKZY2P.js.map +1 -0
- package/dist/{agents-typed-client-5DCGDWFZ.js → agents-typed-client-WFK7M5A7.js} +23 -5
- package/dist/agents-typed-client-WFK7M5A7.js.map +1 -0
- package/dist/{app-typed-client-K3PXIZDZ.js → app-typed-client-FQPUOL6A.js} +113 -24
- package/dist/app-typed-client-FQPUOL6A.js.map +1 -0
- package/dist/{app-typed-client-X2A6NMI2.js → app-typed-client-YSLQU5HT.js} +114 -24
- package/dist/app-typed-client-YSLQU5HT.js.map +1 -0
- package/dist/{build-4HOXGMZV.js → build-PF65J7N6.js} +6 -6
- package/dist/{chunk-CEHXC5NI.js → chunk-32GOXYA7.js} +161 -142
- package/dist/chunk-32GOXYA7.js.map +1 -0
- package/dist/{chunk-J5PK5FRN.js → chunk-BEQQ6WCK.js} +19 -227
- package/dist/chunk-BEQQ6WCK.js.map +1 -0
- package/dist/{chunk-EZWFDHTI.js → chunk-CL3HJTWQ.js} +5 -5
- package/dist/{chunk-AXF3KYY5.js → chunk-ELRVWUOZ.js} +97 -41
- package/dist/chunk-ELRVWUOZ.js.map +1 -0
- package/dist/{chunk-4FE56YQP.js → chunk-GW5H4KB4.js} +2 -2
- package/dist/{chunk-PMBYKLLN.js → chunk-HEDLJJDF.js} +232 -1
- package/dist/chunk-HEDLJJDF.js.map +1 -0
- package/dist/{chunk-GNUXRXHI.js → chunk-HQQADLLD.js} +110 -7
- package/dist/chunk-HQQADLLD.js.map +1 -0
- package/dist/{chunk-XQPBWMC2.js → chunk-I4BTOOZ2.js} +123 -352
- package/dist/chunk-I4BTOOZ2.js.map +1 -0
- package/dist/chunk-KLFCJQXU.js +425 -0
- package/dist/chunk-KLFCJQXU.js.map +1 -0
- package/dist/{chunk-2WTL2GZO.js → chunk-QWBXUZAX.js} +2 -1
- package/dist/cli/index.js +5 -5
- package/dist/client/core.d.ts +12 -1
- package/dist/client/core.js +1 -1
- package/dist/client/index.d.ts +53 -8
- package/dist/client/index.js +29 -7
- package/dist/client/index.js.map +1 -1
- package/dist/controller-swc-transform-URELVCBM.js +27 -0
- package/dist/controller-swc-transform-URELVCBM.js.map +1 -0
- package/dist/controller-swc-transform-XV3ETSE3.js +28 -0
- package/dist/controller-swc-transform-XV3ETSE3.js.map +1 -0
- package/dist/{dev-GA572OS7.js → dev-YP2QH576.js} +9 -9
- package/dist/index.js +4 -4
- package/dist/{internal-api-N6JGTRMN.js → internal-api-MRTUTAM7.js} +12 -3
- package/dist/{internal-api-TV4CFQY2.js → internal-api-ORXPQTEI.js} +13 -3
- package/dist/{mcp-Z3SZULLS.js → mcp-FWJE2QXV.js} +3 -3
- package/dist/{registry-3NB7KOUI.js → registry-FKPH7XEL.js} +1 -1
- package/dist/server/http/index.js +1 -1
- package/dist/server/index.js +2 -2
- package/dist/{start-SWWCB36Q.js → start-MK3MNSFB.js} +8 -8
- package/dist/vite-plugin/index.js +4 -4
- package/dist/{vite-plugin-OY5ESFTR.js → vite-plugin-42TH3C64.js} +6 -6
- package/package.json +2 -2
- package/dist/agents-typed-client-5DCGDWFZ.js.map +0 -1
- package/dist/agents-typed-client-MAAILRI6.js.map +0 -1
- package/dist/app-typed-client-K3PXIZDZ.js.map +0 -1
- package/dist/app-typed-client-X2A6NMI2.js.map +0 -1
- package/dist/chunk-AXF3KYY5.js.map +0 -1
- package/dist/chunk-CEHXC5NI.js.map +0 -1
- package/dist/chunk-GNUXRXHI.js.map +0 -1
- package/dist/chunk-J5PK5FRN.js.map +0 -1
- package/dist/chunk-PMBYKLLN.js.map +0 -1
- package/dist/chunk-VB2WWBIT.js +0 -1
- package/dist/chunk-XQPBWMC2.js.map +0 -1
- package/dist/vite-plugin-OY5ESFTR.js.map +0 -1
- /package/dist/{actions-virtual-module-6HQK5GHV.js.map → actions-virtual-module-RNPMHJOM.js.map} +0 -0
- /package/dist/{agent-PSJ77NJA.js.map → agent-NDHBKRAV.js.map} +0 -0
- /package/dist/{build-4HOXGMZV.js.map → build-PF65J7N6.js.map} +0 -0
- /package/dist/{chunk-EZWFDHTI.js.map → chunk-CL3HJTWQ.js.map} +0 -0
- /package/dist/{chunk-4FE56YQP.js.map → chunk-GW5H4KB4.js.map} +0 -0
- /package/dist/{chunk-2WTL2GZO.js.map → chunk-QWBXUZAX.js.map} +0 -0
- /package/dist/{dev-GA572OS7.js.map → dev-YP2QH576.js.map} +0 -0
- /package/dist/{chunk-VB2WWBIT.js.map → internal-api-MRTUTAM7.js.map} +0 -0
- /package/dist/{internal-api-N6JGTRMN.js.map → internal-api-ORXPQTEI.js.map} +0 -0
- /package/dist/{mcp-Z3SZULLS.js.map → mcp-FWJE2QXV.js.map} +0 -0
- /package/dist/{registry-3NB7KOUI.js.map → registry-FKPH7XEL.js.map} +0 -0
- /package/dist/{start-SWWCB36Q.js.map → start-MK3MNSFB.js.map} +0 -0
- /package/dist/{internal-api-TV4CFQY2.js.map → vite-plugin-42TH3C64.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./chunk-
|
|
2
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-KLFCJQXU.js";
|
|
2
|
+
import "./chunk-QWBXUZAX.js";
|
|
3
3
|
import "./chunk-GY5Q27BJ.js";
|
|
4
4
|
import "./chunk-5QW7IQQU.js";
|
|
5
5
|
import "./chunk-WFNLNIJX.js";
|
|
@@ -221,4 +221,4 @@ export const actions = manifest
|
|
|
221
221
|
export {
|
|
222
222
|
actionsVirtualModule
|
|
223
223
|
};
|
|
224
|
-
//# sourceMappingURL=actions-virtual-module-
|
|
224
|
+
//# sourceMappingURL=actions-virtual-module-RNPMHJOM.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
import "./chunk-HQQADLLD.js";
|
|
4
|
+
import "./chunk-HEDLJJDF.js";
|
|
5
|
+
import "./chunk-M6NMS5LR.js";
|
|
5
6
|
import "./chunk-NQRNDPZJ.js";
|
|
6
7
|
import "./chunk-L4GCEMNW.js";
|
|
7
8
|
import {
|
|
@@ -211,4 +212,4 @@ export const actions = manifest
|
|
|
211
212
|
export {
|
|
212
213
|
actionsVirtualModule
|
|
213
214
|
};
|
|
214
|
-
//# sourceMappingURL=actions-virtual-module-
|
|
215
|
+
//# sourceMappingURL=actions-virtual-module-YGKSEIOJ.js.map
|
package/dist/{actions-virtual-module-HYCFRMC3.js.map → actions-virtual-module-YGKSEIOJ.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/internal-api.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theokit/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theokit/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/internal-api.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theokit/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theokit/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,13 +3,13 @@ import "tsx/esm";
|
|
|
3
3
|
import {
|
|
4
4
|
agentCommand,
|
|
5
5
|
createAgentSsrLoader
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-M6NMS5LR.js";
|
|
6
|
+
} from "./chunk-CL3HJTWQ.js";
|
|
8
7
|
import "./chunk-GDN3PXFH.js";
|
|
8
|
+
import "./chunk-M6NMS5LR.js";
|
|
9
9
|
import "./chunk-L4GCEMNW.js";
|
|
10
10
|
import "./chunk-P37RZRFV.js";
|
|
11
11
|
export {
|
|
12
12
|
agentCommand,
|
|
13
13
|
createAgentSsrLoader
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=agent-
|
|
15
|
+
//# sourceMappingURL=agent-NDHBKRAV.js.map
|
|
@@ -28,6 +28,7 @@ function generateAgentsDts({
|
|
|
28
28
|
const agents = manifest.agents ?? [];
|
|
29
29
|
const imports = [];
|
|
30
30
|
const entries = [];
|
|
31
|
+
const handles = [];
|
|
31
32
|
for (const agent of agents) {
|
|
32
33
|
const alias = aliasFor(agent.name);
|
|
33
34
|
imports.push(
|
|
@@ -36,7 +37,13 @@ function generateAgentsDts({
|
|
|
36
37
|
entries.push(
|
|
37
38
|
` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`
|
|
38
39
|
);
|
|
40
|
+
handles.push(
|
|
41
|
+
` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`
|
|
42
|
+
);
|
|
39
43
|
}
|
|
44
|
+
const handleBlock = handles.length > 0 ? `
|
|
45
|
+
${handles.join("\n")}
|
|
46
|
+
` : "";
|
|
40
47
|
const body = entries.length > 0 ? `{
|
|
41
48
|
${entries.join("\n")}
|
|
42
49
|
}` : `{
|
|
@@ -47,7 +54,7 @@ ${entries.join("\n")}
|
|
|
47
54
|
return `${FILE_HEADER}
|
|
48
55
|
declare module '@theo/agents' {
|
|
49
56
|
import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'
|
|
50
|
-
import type { UseAgentReturn, AgentTransport } from 'theokit/client'
|
|
57
|
+
import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'
|
|
51
58
|
${importBlock}
|
|
52
59
|
export interface AppAgents ${body}
|
|
53
60
|
|
|
@@ -55,12 +62,19 @@ ${importBlock}
|
|
|
55
62
|
export function useAgent<K extends keyof AppAgents>(
|
|
56
63
|
name: K,
|
|
57
64
|
): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>
|
|
65
|
+
// M47 \u2014 bind by a typed handle: \`import { chat } from '@theo/agents'; useAgent(chat)\`. No magic
|
|
66
|
+
// string (the path lives on the handle) and no duplicated input type (inferred from the handle).
|
|
67
|
+
export function useAgent<TInput, TTools extends string>(
|
|
68
|
+
handle: AgentHandle<TInput, TTools>,
|
|
69
|
+
): UseAgentReturn<TInput, TTools>
|
|
58
70
|
// M41 (ADR-0050) \u2014 terminal/desktop (and advanced): bind by an explicit AgentTransport
|
|
59
71
|
// (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.
|
|
60
72
|
export function useAgent<TInput = unknown>(
|
|
61
73
|
transport: AgentTransport,
|
|
62
74
|
): UseAgentReturn<TInput>
|
|
63
|
-
|
|
75
|
+
|
|
76
|
+
// M47 \u2014 one client-safe handle per agent (runtime value = { path }; types are phantom).
|
|
77
|
+
${handleBlock}}
|
|
64
78
|
`;
|
|
65
79
|
}
|
|
66
80
|
var VIRTUAL_AGENTS_ID = "@theo/agents";
|
|
@@ -139,8 +153,12 @@ function agentsTypedClientPlugin(opts) {
|
|
|
139
153
|
},
|
|
140
154
|
load(id) {
|
|
141
155
|
if (id === RESOLVED_AGENTS_ID) {
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
const agents = opts.scanManifest(opts.projectRoot).agents ?? [];
|
|
157
|
+
const handleLines = agents.map(
|
|
158
|
+
(a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`
|
|
159
|
+
);
|
|
160
|
+
return `export { useAgent, agentHandle } from 'theokit/client'
|
|
161
|
+
` + (handleLines.length > 0 ? handleLines.join("\n") + "\n" : "");
|
|
144
162
|
}
|
|
145
163
|
return null;
|
|
146
164
|
}
|
|
@@ -150,4 +168,4 @@ export {
|
|
|
150
168
|
agentsTypedClientPlugin,
|
|
151
169
|
generateAgentsDts
|
|
152
170
|
};
|
|
153
|
-
//# sourceMappingURL=agents-typed-client-
|
|
171
|
+
//# sourceMappingURL=agents-typed-client-HSZKZY2P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/agents-typed-client.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Codegen-time fs ops: all paths derive from `opts.projectRoot` / `opts.distDir`\n * (vite plugin opts — trusted, set by the framework's CLI). Never serves HTTP input.\n */\n/**\n * M2 (theokit-ai-first) — end-to-end typed binding for the `agents/*.ts` convention.\n *\n * Sibling of `app-typed-client.ts` (kept separate so the `@theo/client` output stays\n * byte-unchanged). Two parts:\n *\n * 1. `generateAgentsDts({ manifest, dtsOutPath, projectRoot })` — pure function emitting\n * `.theokit/agents.d.ts` (the virtual `@theo/agents` module) with `AppAgents` mapping\n * each scanned agent to `InferAgentInput` of its `defineAgent({ input })` schema, plus a\n * typed `useAgent` signature — so the client binds to `/api/agents/<name>` with the right\n * request type and ZERO manual wiring (DoD line 2).\n * 2. `agentsTypedClientPlugin(opts)` — Vite plugin that emits (1), rewrites on `agents/*`\n * changes, and serves the virtual `@theo/agents` runtime (`useAgent` from `theokit/client`).\n */\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, posix } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport type { TheoManifest } from '../server/internal-api.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/agents-typed-client.ts (M2).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this on every agents/ change.\n\n/* eslint-disable */\n`\n\n/** A safe TS identifier alias for an agent (name may contain path separators). */\nfunction aliasFor(name: string): string {\n return `_agent_${name.replace(/[^\\w$]/g, '_')}`\n}\n\n/** Relative `import type` path from the .d.ts dir to the agent source (extension stripped). */\nfunction importPath(dtsOutPath: string, projectRoot: string, filePath: string): string {\n const fromDir = dirname(dtsOutPath).replace(/\\\\/g, '/')\n const abs = posix.join(projectRoot.replace(/\\\\/g, '/'), filePath.replace(/\\\\/g, '/'))\n const noExt = abs.replace(/\\.[jt]sx?$/, '')\n let rel = posix.relative(fromDir, noExt)\n if (!rel.startsWith('.')) rel = `./${rel}`\n return rel\n}\n\nexport interface GenerateAgentsDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted (drives the import paths). */\n dtsOutPath: string\n /** Absolute project root — agents live at `<projectRoot>/agents`. */\n projectRoot: string\n}\n\nexport function generateAgentsDts({\n manifest,\n dtsOutPath,\n projectRoot,\n}: GenerateAgentsDtsOptions): string {\n const agents = manifest.agents ?? []\n const imports: string[] = []\n const entries: string[] = []\n const handles: string[] = []\n for (const agent of agents) {\n const alias = aliasFor(agent.name)\n imports.push(\n ` import type ${alias} from '${importPath(dtsOutPath, projectRoot, agent.filePath)}'`,\n )\n entries.push(\n ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`,\n )\n // M47 — a named, client-safe handle per agent: `import { chat } from '@theo/agents'; useAgent(chat)`.\n // cmd-click on the generated const hops to the agent source via the type-only import above.\n handles.push(\n ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`,\n )\n }\n const handleBlock = handles.length > 0 ? `\\n${handles.join('\\n')}\\n` : ''\n\n const body =\n entries.length > 0\n ? `{\\n${entries.join('\\n')}\\n }`\n : `{\\n // No agents detected — add \\`agents/*.ts\\` to populate this binding.\\n }`\n\n const importBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n\n return `${FILE_HEADER}\ndeclare module '@theo/agents' {\n import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'\n import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'\n${importBlock}\n export interface AppAgents ${body}\n\n // Web (and any HTTP surface): bind by agent name — \\`send\\` is typed to the agent's \\`input\\` schema.\n export function useAgent<K extends keyof AppAgents>(\n name: K,\n ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>\n // M47 — bind by a typed handle: \\`import { chat } from '@theo/agents'; useAgent(chat)\\`. No magic\n // string (the path lives on the handle) and no duplicated input type (inferred from the handle).\n export function useAgent<TInput, TTools extends string>(\n handle: AgentHandle<TInput, TTools>,\n ): UseAgentReturn<TInput, TTools>\n // M41 (ADR-0050) — terminal/desktop (and advanced): bind by an explicit AgentTransport\n // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.\n export function useAgent<TInput = unknown>(\n transport: AgentTransport,\n ): UseAgentReturn<TInput>\n\n // M47 — one client-safe handle per agent (runtime value = { path }; types are phantom).\n${handleBlock}}\n`\n}\n\n// ─── Vite plugin ─────────────────────────────────────────────────────────────\n\nconst VIRTUAL_AGENTS_ID = '@theo/agents'\nconst RESOLVED_AGENTS_ID = '\\0@theo/agents'\n\nexport interface AgentsTypedClientPluginOptions {\n /** Absolute project root (agents live at `<projectRoot>/<agentsDir>`). */\n projectRoot: string\n /** Agents dir name (config `agentsDir`, default \"agents\"), relative to projectRoot — the watch glob. */\n agentsDir?: string\n /** Absolute `.theokit/` output dir (where `agents.d.ts` lands). */\n distDir: string\n /** Injects the scanned agents; returns the current manifest-shaped agent list. */\n scanManifest: (projectRoot: string) => TheoManifest\n}\n\n/** Atomic write via tmp + rename; skips when content is unchanged. */\nfunction writeIfChanged(filePath: string, content: string): void {\n if (existsSync(filePath)) {\n try {\n if (readFileSync(filePath, 'utf-8') === content) return\n } catch {\n /* fall through to write */\n }\n }\n mkdirSync(dirname(filePath), { recursive: true })\n const tmp = `${filePath}.${process.pid}.tmp`\n writeFileSync(tmp, content)\n renameSync(tmp, filePath)\n}\n\nfunction emitAgentsDts(opts: AgentsTypedClientPluginOptions): string {\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'agents.d.ts')\n const content = generateAgentsDts({\n manifest: opts.scanManifest(opts.projectRoot),\n dtsOutPath,\n projectRoot: opts.projectRoot,\n })\n writeIfChanged(dtsOutPath, content)\n return dtsOutPath\n}\n\nexport function agentsTypedClientPlugin(opts: AgentsTypedClientPluginOptions): Plugin {\n let debounce: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n const scheduleEmit = (): void => {\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(() => {\n try {\n emitAgentsDts(opts)\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_AGENTS_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n console.error('[theokit:agents-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:agents-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const agentsGlob = posix.join(\n opts.projectRoot.replace(/\\\\/g, '/'),\n opts.agentsDir ?? 'agents',\n )\n const onFile = (file: string): void => {\n if (file.replace(/\\\\/g, '/').startsWith(agentsGlob)) scheduleEmit()\n }\n server.watcher.on('add', onFile)\n server.watcher.on('change', onFile)\n server.watcher.on('unlink', onFile)\n },\n resolveId(id) {\n return id === VIRTUAL_AGENTS_ID ? RESOLVED_AGENTS_ID : null\n },\n load(id) {\n if (id === RESOLVED_AGENTS_ID) {\n // M47 — the runtime module re-exports `useAgent` AND emits one handle value per agent\n // (`export const chat = agentHandle('/api/agents/chat')`), so `import { chat } from '@theo/agents'`\n // resolves at runtime. Types come from the sibling `agents.d.ts` module augmentation.\n const agents = opts.scanManifest(opts.projectRoot).agents ?? []\n const handleLines = agents.map(\n (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`,\n )\n return (\n `export { useAgent, agentHandle } from 'theokit/client'\\n` +\n (handleLines.length > 0 ? handleLines.join('\\n') + '\\n' : '')\n )\n }\n return null\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -29,6 +29,7 @@ function generateAgentsDts({
|
|
|
29
29
|
const agents = manifest.agents ?? [];
|
|
30
30
|
const imports = [];
|
|
31
31
|
const entries = [];
|
|
32
|
+
const handles = [];
|
|
32
33
|
for (const agent of agents) {
|
|
33
34
|
const alias = aliasFor(agent.name);
|
|
34
35
|
imports.push(
|
|
@@ -37,7 +38,13 @@ function generateAgentsDts({
|
|
|
37
38
|
entries.push(
|
|
38
39
|
` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`
|
|
39
40
|
);
|
|
41
|
+
handles.push(
|
|
42
|
+
` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`
|
|
43
|
+
);
|
|
40
44
|
}
|
|
45
|
+
const handleBlock = handles.length > 0 ? `
|
|
46
|
+
${handles.join("\n")}
|
|
47
|
+
` : "";
|
|
41
48
|
const body = entries.length > 0 ? `{
|
|
42
49
|
${entries.join("\n")}
|
|
43
50
|
}` : `{
|
|
@@ -48,7 +55,7 @@ ${entries.join("\n")}
|
|
|
48
55
|
return `${FILE_HEADER}
|
|
49
56
|
declare module '@theo/agents' {
|
|
50
57
|
import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'
|
|
51
|
-
import type { UseAgentReturn, AgentTransport } from 'theokit/client'
|
|
58
|
+
import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'
|
|
52
59
|
${importBlock}
|
|
53
60
|
export interface AppAgents ${body}
|
|
54
61
|
|
|
@@ -56,12 +63,19 @@ ${importBlock}
|
|
|
56
63
|
export function useAgent<K extends keyof AppAgents>(
|
|
57
64
|
name: K,
|
|
58
65
|
): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>
|
|
66
|
+
// M47 \u2014 bind by a typed handle: \`import { chat } from '@theo/agents'; useAgent(chat)\`. No magic
|
|
67
|
+
// string (the path lives on the handle) and no duplicated input type (inferred from the handle).
|
|
68
|
+
export function useAgent<TInput, TTools extends string>(
|
|
69
|
+
handle: AgentHandle<TInput, TTools>,
|
|
70
|
+
): UseAgentReturn<TInput, TTools>
|
|
59
71
|
// M41 (ADR-0050) \u2014 terminal/desktop (and advanced): bind by an explicit AgentTransport
|
|
60
72
|
// (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.
|
|
61
73
|
export function useAgent<TInput = unknown>(
|
|
62
74
|
transport: AgentTransport,
|
|
63
75
|
): UseAgentReturn<TInput>
|
|
64
|
-
|
|
76
|
+
|
|
77
|
+
// M47 \u2014 one client-safe handle per agent (runtime value = { path }; types are phantom).
|
|
78
|
+
${handleBlock}}
|
|
65
79
|
`;
|
|
66
80
|
}
|
|
67
81
|
var VIRTUAL_AGENTS_ID = "@theo/agents";
|
|
@@ -140,8 +154,12 @@ function agentsTypedClientPlugin(opts) {
|
|
|
140
154
|
},
|
|
141
155
|
load(id) {
|
|
142
156
|
if (id === RESOLVED_AGENTS_ID) {
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
const agents = opts.scanManifest(opts.projectRoot).agents ?? [];
|
|
158
|
+
const handleLines = agents.map(
|
|
159
|
+
(a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`
|
|
160
|
+
);
|
|
161
|
+
return `export { useAgent, agentHandle } from 'theokit/client'
|
|
162
|
+
` + (handleLines.length > 0 ? handleLines.join("\n") + "\n" : "");
|
|
145
163
|
}
|
|
146
164
|
return null;
|
|
147
165
|
}
|
|
@@ -151,4 +169,4 @@ export {
|
|
|
151
169
|
agentsTypedClientPlugin,
|
|
152
170
|
generateAgentsDts
|
|
153
171
|
};
|
|
154
|
-
//# sourceMappingURL=agents-typed-client-
|
|
172
|
+
//# sourceMappingURL=agents-typed-client-WFK7M5A7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/agents-typed-client.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Codegen-time fs ops: all paths derive from `opts.projectRoot` / `opts.distDir`\n * (vite plugin opts — trusted, set by the framework's CLI). Never serves HTTP input.\n */\n/**\n * M2 (theokit-ai-first) — end-to-end typed binding for the `agents/*.ts` convention.\n *\n * Sibling of `app-typed-client.ts` (kept separate so the `@theo/client` output stays\n * byte-unchanged). Two parts:\n *\n * 1. `generateAgentsDts({ manifest, dtsOutPath, projectRoot })` — pure function emitting\n * `.theokit/agents.d.ts` (the virtual `@theo/agents` module) with `AppAgents` mapping\n * each scanned agent to `InferAgentInput` of its `defineAgent({ input })` schema, plus a\n * typed `useAgent` signature — so the client binds to `/api/agents/<name>` with the right\n * request type and ZERO manual wiring (DoD line 2).\n * 2. `agentsTypedClientPlugin(opts)` — Vite plugin that emits (1), rewrites on `agents/*`\n * changes, and serves the virtual `@theo/agents` runtime (`useAgent` from `theokit/client`).\n */\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, posix } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport type { TheoManifest } from '../server/internal-api.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/agents-typed-client.ts (M2).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this on every agents/ change.\n\n/* eslint-disable */\n`\n\n/** A safe TS identifier alias for an agent (name may contain path separators). */\nfunction aliasFor(name: string): string {\n return `_agent_${name.replace(/[^\\w$]/g, '_')}`\n}\n\n/** Relative `import type` path from the .d.ts dir to the agent source (extension stripped). */\nfunction importPath(dtsOutPath: string, projectRoot: string, filePath: string): string {\n const fromDir = dirname(dtsOutPath).replace(/\\\\/g, '/')\n const abs = posix.join(projectRoot.replace(/\\\\/g, '/'), filePath.replace(/\\\\/g, '/'))\n const noExt = abs.replace(/\\.[jt]sx?$/, '')\n let rel = posix.relative(fromDir, noExt)\n if (!rel.startsWith('.')) rel = `./${rel}`\n return rel\n}\n\nexport interface GenerateAgentsDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted (drives the import paths). */\n dtsOutPath: string\n /** Absolute project root — agents live at `<projectRoot>/agents`. */\n projectRoot: string\n}\n\nexport function generateAgentsDts({\n manifest,\n dtsOutPath,\n projectRoot,\n}: GenerateAgentsDtsOptions): string {\n const agents = manifest.agents ?? []\n const imports: string[] = []\n const entries: string[] = []\n const handles: string[] = []\n for (const agent of agents) {\n const alias = aliasFor(agent.name)\n imports.push(\n ` import type ${alias} from '${importPath(dtsOutPath, projectRoot, agent.filePath)}'`,\n )\n entries.push(\n ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`,\n )\n // M47 — a named, client-safe handle per agent: `import { chat } from '@theo/agents'; useAgent(chat)`.\n // cmd-click on the generated const hops to the agent source via the type-only import above.\n handles.push(\n ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`,\n )\n }\n const handleBlock = handles.length > 0 ? `\\n${handles.join('\\n')}\\n` : ''\n\n const body =\n entries.length > 0\n ? `{\\n${entries.join('\\n')}\\n }`\n : `{\\n // No agents detected — add \\`agents/*.ts\\` to populate this binding.\\n }`\n\n const importBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n\n return `${FILE_HEADER}\ndeclare module '@theo/agents' {\n import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'\n import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'\n${importBlock}\n export interface AppAgents ${body}\n\n // Web (and any HTTP surface): bind by agent name — \\`send\\` is typed to the agent's \\`input\\` schema.\n export function useAgent<K extends keyof AppAgents>(\n name: K,\n ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>\n // M47 — bind by a typed handle: \\`import { chat } from '@theo/agents'; useAgent(chat)\\`. No magic\n // string (the path lives on the handle) and no duplicated input type (inferred from the handle).\n export function useAgent<TInput, TTools extends string>(\n handle: AgentHandle<TInput, TTools>,\n ): UseAgentReturn<TInput, TTools>\n // M41 (ADR-0050) — terminal/desktop (and advanced): bind by an explicit AgentTransport\n // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.\n export function useAgent<TInput = unknown>(\n transport: AgentTransport,\n ): UseAgentReturn<TInput>\n\n // M47 — one client-safe handle per agent (runtime value = { path }; types are phantom).\n${handleBlock}}\n`\n}\n\n// ─── Vite plugin ─────────────────────────────────────────────────────────────\n\nconst VIRTUAL_AGENTS_ID = '@theo/agents'\nconst RESOLVED_AGENTS_ID = '\\0@theo/agents'\n\nexport interface AgentsTypedClientPluginOptions {\n /** Absolute project root (agents live at `<projectRoot>/<agentsDir>`). */\n projectRoot: string\n /** Agents dir name (config `agentsDir`, default \"agents\"), relative to projectRoot — the watch glob. */\n agentsDir?: string\n /** Absolute `.theokit/` output dir (where `agents.d.ts` lands). */\n distDir: string\n /** Injects the scanned agents; returns the current manifest-shaped agent list. */\n scanManifest: (projectRoot: string) => TheoManifest\n}\n\n/** Atomic write via tmp + rename; skips when content is unchanged. */\nfunction writeIfChanged(filePath: string, content: string): void {\n if (existsSync(filePath)) {\n try {\n if (readFileSync(filePath, 'utf-8') === content) return\n } catch {\n /* fall through to write */\n }\n }\n mkdirSync(dirname(filePath), { recursive: true })\n const tmp = `${filePath}.${process.pid}.tmp`\n writeFileSync(tmp, content)\n renameSync(tmp, filePath)\n}\n\nfunction emitAgentsDts(opts: AgentsTypedClientPluginOptions): string {\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'agents.d.ts')\n const content = generateAgentsDts({\n manifest: opts.scanManifest(opts.projectRoot),\n dtsOutPath,\n projectRoot: opts.projectRoot,\n })\n writeIfChanged(dtsOutPath, content)\n return dtsOutPath\n}\n\nexport function agentsTypedClientPlugin(opts: AgentsTypedClientPluginOptions): Plugin {\n let debounce: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n const scheduleEmit = (): void => {\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(() => {\n try {\n emitAgentsDts(opts)\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_AGENTS_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n console.error('[theokit:agents-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:agents-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const agentsGlob = posix.join(\n opts.projectRoot.replace(/\\\\/g, '/'),\n opts.agentsDir ?? 'agents',\n )\n const onFile = (file: string): void => {\n if (file.replace(/\\\\/g, '/').startsWith(agentsGlob)) scheduleEmit()\n }\n server.watcher.on('add', onFile)\n server.watcher.on('change', onFile)\n server.watcher.on('unlink', onFile)\n },\n resolveId(id) {\n return id === VIRTUAL_AGENTS_ID ? RESOLVED_AGENTS_ID : null\n },\n load(id) {\n if (id === RESOLVED_AGENTS_ID) {\n // M47 — the runtime module re-exports `useAgent` AND emits one handle value per agent\n // (`export const chat = agentHandle('/api/agents/chat')`), so `import { chat } from '@theo/agents'`\n // resolves at runtime. Types come from the sibling `agents.d.ts` module augmentation.\n const agents = opts.scanManifest(opts.projectRoot).agents ?? []\n const handleLines = agents.map(\n (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`,\n )\n return (\n `export { useAgent, agentHandle } from 'theokit/client'\\n` +\n (handleLines.length > 0 ? handleLines.join('\\n') + '\\n' : '')\n )\n }\n return null\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
scanControllerModules
|
|
3
|
+
} from "./chunk-KLFCJQXU.js";
|
|
4
|
+
import "./chunk-QWBXUZAX.js";
|
|
3
5
|
import "./chunk-GY5Q27BJ.js";
|
|
4
6
|
import "./chunk-5QW7IQQU.js";
|
|
5
7
|
import "./chunk-WFNLNIJX.js";
|
|
@@ -25,6 +27,29 @@ import "./chunk-DGUM43GV.js";
|
|
|
25
27
|
// src/vite-plugin/app-typed-client.ts
|
|
26
28
|
import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "fs";
|
|
27
29
|
import { dirname, isAbsolute, posix, relative, resolve } from "path";
|
|
30
|
+
|
|
31
|
+
// src/vite-plugin/controller-client-emit.ts
|
|
32
|
+
import { loadControllerWithSwc, walkControllerMetadata } from "@theokit/http";
|
|
33
|
+
async function collectControllerRouteData(opts) {
|
|
34
|
+
const loader = opts.loadModule ?? loadControllerWithSwc;
|
|
35
|
+
const modules = await scanControllerModules(opts.controllersDir, loader);
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const { filePath, cls } of modules) {
|
|
38
|
+
const className = cls.name;
|
|
39
|
+
for (const walk of walkControllerMetadata(cls)) {
|
|
40
|
+
out.push({
|
|
41
|
+
filePath,
|
|
42
|
+
className,
|
|
43
|
+
fullPath: walk.fullPath,
|
|
44
|
+
verb: walk.verb,
|
|
45
|
+
methodName: String(walk.propertyKey)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/vite-plugin/app-typed-client.ts
|
|
28
53
|
var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
|
|
29
54
|
// Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).
|
|
30
55
|
// To refresh: \`pnpm dev\` or \`pnpm build\` regenerates this file on every route change.
|
|
@@ -32,7 +57,12 @@ var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
|
|
|
32
57
|
/* eslint-disable */
|
|
33
58
|
`;
|
|
34
59
|
function makeNode() {
|
|
35
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
children: /* @__PURE__ */ new Map(),
|
|
62
|
+
methods: /* @__PURE__ */ new Map(),
|
|
63
|
+
controllerMethods: /* @__PURE__ */ new Map(),
|
|
64
|
+
paramNamesAccumulated: []
|
|
65
|
+
};
|
|
36
66
|
}
|
|
37
67
|
function normalizeSegment(raw) {
|
|
38
68
|
let s = raw;
|
|
@@ -100,11 +130,38 @@ function buildOneRoute(route, ctx) {
|
|
|
100
130
|
node.methods.set(method, alias);
|
|
101
131
|
}
|
|
102
132
|
}
|
|
133
|
+
function buildOneController(cr, classAlias, ctx) {
|
|
134
|
+
const verb = cr.verb.toUpperCase();
|
|
135
|
+
if (!HTTP_METHODS.includes(verb)) return;
|
|
136
|
+
const method = verb;
|
|
137
|
+
const segments = routePathSegments(cr.fullPath);
|
|
138
|
+
let node = ctx.root;
|
|
139
|
+
for (const seg of segments) {
|
|
140
|
+
const norm = normalizeSegment(seg);
|
|
141
|
+
let child = node.children.get(norm.identifier);
|
|
142
|
+
if (child === void 0) {
|
|
143
|
+
child = makeNode();
|
|
144
|
+
node.children.set(norm.identifier, child);
|
|
145
|
+
}
|
|
146
|
+
node = child;
|
|
147
|
+
}
|
|
148
|
+
node.paramNamesAccumulated = paramNamesFromRoute(cr.fullPath);
|
|
149
|
+
if (node.methods.has(method)) {
|
|
150
|
+
ctx.warnings.push(
|
|
151
|
+
`WARNING (#122): controller ${cr.className}.${cr.methodName} (${method} /${segments.join("/")}) collides with a file-based route at the same path; file route wins.`
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
node.controllerMethods.set(method, { classAlias, methodName: cr.methodName });
|
|
156
|
+
}
|
|
103
157
|
function resolveCollisions(node, pathPrefix, warnings) {
|
|
104
158
|
const methodLowerNames = /* @__PURE__ */ new Set();
|
|
105
159
|
for (const method of node.methods.keys()) {
|
|
106
160
|
methodLowerNames.add(method.toLowerCase());
|
|
107
161
|
}
|
|
162
|
+
for (const method of node.controllerMethods.keys()) {
|
|
163
|
+
methodLowerNames.add(method.toLowerCase());
|
|
164
|
+
}
|
|
108
165
|
const renames = [];
|
|
109
166
|
for (const childName of node.children.keys()) {
|
|
110
167
|
if (methodLowerNames.has(childName)) {
|
|
@@ -136,6 +193,15 @@ function renderTreeNode(node, indent, _isRoot) {
|
|
|
136
193
|
`${indent} ${lower}: (opts${optionalMark}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`
|
|
137
194
|
);
|
|
138
195
|
}
|
|
196
|
+
for (const [method, leaf] of node.controllerMethods) {
|
|
197
|
+
const lower = method.toLowerCase();
|
|
198
|
+
const paramsList = node.paramNamesAccumulated.map((p) => `${p}: string`).join("; ");
|
|
199
|
+
const paramsField = node.paramNamesAccumulated.length > 0 ? `{ params: { ${paramsList} } } & ` : "";
|
|
200
|
+
const optionalMark = node.paramNamesAccumulated.length === 0 ? "?" : "";
|
|
201
|
+
lines.push(
|
|
202
|
+
`${indent} ${lower}: (opts${optionalMark}: ${paramsField}{ body?: unknown; query?: Record<string, string> }) => Promise<Awaited<ReturnType<InstanceType<typeof ${leaf.classAlias}>['${leaf.methodName}']>>>`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
139
205
|
for (const [name, child] of node.children) {
|
|
140
206
|
const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
|
|
141
207
|
lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + " ", false)}`);
|
|
@@ -146,7 +212,8 @@ function renderTreeNode(node, indent, _isRoot) {
|
|
|
146
212
|
function generateClientDts({
|
|
147
213
|
manifest,
|
|
148
214
|
dtsOutPath,
|
|
149
|
-
serverDir
|
|
215
|
+
serverDir,
|
|
216
|
+
controllerRoutes
|
|
150
217
|
}) {
|
|
151
218
|
const ctx = {
|
|
152
219
|
root: makeNode(),
|
|
@@ -159,10 +226,23 @@ function generateClientDts({
|
|
|
159
226
|
for (const route of manifest.routes) {
|
|
160
227
|
buildOneRoute(route, ctx);
|
|
161
228
|
}
|
|
229
|
+
if (controllerRoutes?.length) {
|
|
230
|
+
const classAliases = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const cr of controllerRoutes) {
|
|
232
|
+
let alias = classAliases.get(cr.filePath);
|
|
233
|
+
if (alias === void 0) {
|
|
234
|
+
alias = `_c${classAliases.size}`;
|
|
235
|
+
classAliases.set(cr.filePath, alias);
|
|
236
|
+
const importPath = importPathFor(dtsOutPath, cr.filePath);
|
|
237
|
+
ctx.importLines.push(` import type { ${cr.className} as ${alias} } from '${importPath}'`);
|
|
238
|
+
}
|
|
239
|
+
buildOneController(cr, alias, ctx);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
162
242
|
resolveCollisions(ctx.root, "", ctx.warnings);
|
|
163
243
|
const importsBlock = ctx.importLines.length > 0 ? ctx.importLines.join("\n") + "\n\n" : "\n";
|
|
164
244
|
const warningsBlock = ctx.warnings.length > 0 ? " // " + ctx.warnings.join("\n // ") + "\n\n" : "";
|
|
165
|
-
const treeBody = ctx.root.children.size === 0 && ctx.root.methods.size === 0 ? "{\n // No routes detected \u2014 add `server/routes/*.ts` to populate this client.\n }" : renderTreeNode(
|
|
245
|
+
const treeBody = ctx.root.children.size === 0 && ctx.root.methods.size === 0 && ctx.root.controllerMethods.size === 0 ? "{\n // No routes detected \u2014 add `server/routes/*.ts` to populate this client.\n }" : renderTreeNode(
|
|
166
246
|
ctx.root,
|
|
167
247
|
" ",
|
|
168
248
|
/* isRoot */
|
|
@@ -177,6 +257,11 @@ ${importsBlock}${warningsBlock} export interface AppClient ${treeBody}
|
|
|
177
257
|
}
|
|
178
258
|
var VIRTUAL_APP_CLIENT_ID = "@theo/client";
|
|
179
259
|
var RESOLVED_APP_CLIENT_ID = "\0@theo/client";
|
|
260
|
+
function shouldReemitForFile(file, routesGlob, controllersGlob) {
|
|
261
|
+
const normalized = file.replace(/\\/g, "/");
|
|
262
|
+
if (normalized.includes(".__decorated__.")) return false;
|
|
263
|
+
return normalized.startsWith(routesGlob) || normalized.startsWith(controllersGlob);
|
|
264
|
+
}
|
|
180
265
|
function writeFileIfChanged(filePath, content) {
|
|
181
266
|
if (existsSync(filePath)) {
|
|
182
267
|
try {
|
|
@@ -191,7 +276,7 @@ function writeFileIfChanged(filePath, content) {
|
|
|
191
276
|
renameSync(tmp, filePath);
|
|
192
277
|
return { changed: true };
|
|
193
278
|
}
|
|
194
|
-
function emitClientDts(opts) {
|
|
279
|
+
async function emitClientDts(opts) {
|
|
195
280
|
if (!existsSync(opts.serverDir)) {
|
|
196
281
|
const stubPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
|
|
197
282
|
return {
|
|
@@ -216,11 +301,14 @@ function emitClientDts(opts) {
|
|
|
216
301
|
if (opts.extraRoutes?.length) {
|
|
217
302
|
manifest.routes.push(...opts.extraRoutes);
|
|
218
303
|
}
|
|
304
|
+
const controllersDir = resolve(opts.serverDir, "controllers");
|
|
305
|
+
const controllerRoutes = existsSync(controllersDir) ? await collectControllerRouteData({ controllersDir }) : [];
|
|
219
306
|
const dtsOutPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
|
|
220
307
|
const content = generateClientDts({
|
|
221
308
|
manifest,
|
|
222
309
|
dtsOutPath,
|
|
223
|
-
serverDir: opts.serverDir
|
|
310
|
+
serverDir: opts.serverDir,
|
|
311
|
+
controllerRoutes
|
|
224
312
|
});
|
|
225
313
|
const { changed } = writeFileIfChanged(dtsOutPath, content);
|
|
226
314
|
return { changed, path: dtsOutPath };
|
|
@@ -231,30 +319,32 @@ function appTypedClientPlugin(opts) {
|
|
|
231
319
|
function scheduleEmit() {
|
|
232
320
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
233
321
|
debounceTimer = setTimeout(() => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
322
|
+
void (async () => {
|
|
323
|
+
try {
|
|
324
|
+
await emitClientDts(opts);
|
|
325
|
+
if (viteServer) {
|
|
326
|
+
const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID);
|
|
327
|
+
if (mod) viteServer.moduleGraph.invalidateModule(mod);
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error("[theokit:app-typed-client] codegen error:", err);
|
|
239
331
|
}
|
|
240
|
-
}
|
|
241
|
-
console.error("[theokit:app-typed-client] codegen error:", err);
|
|
242
|
-
}
|
|
332
|
+
})();
|
|
243
333
|
}, 100);
|
|
244
334
|
}
|
|
245
335
|
return {
|
|
246
336
|
name: "theokit:app-typed-client",
|
|
247
337
|
enforce: "post",
|
|
248
|
-
configResolved() {
|
|
338
|
+
async configResolved() {
|
|
249
339
|
try {
|
|
250
|
-
emitClientDts(opts);
|
|
340
|
+
await emitClientDts(opts);
|
|
251
341
|
} catch (err) {
|
|
252
342
|
console.error("[theokit:app-typed-client] initial codegen error:", err);
|
|
253
343
|
}
|
|
254
344
|
},
|
|
255
|
-
buildEnd() {
|
|
345
|
+
async buildEnd() {
|
|
256
346
|
try {
|
|
257
|
-
emitClientDts(opts);
|
|
347
|
+
await emitClientDts(opts);
|
|
258
348
|
} catch (err) {
|
|
259
349
|
console.error("[theokit:app-typed-client] buildEnd codegen error:", err);
|
|
260
350
|
}
|
|
@@ -264,9 +354,7 @@ function appTypedClientPlugin(opts) {
|
|
|
264
354
|
const routesGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "routes");
|
|
265
355
|
const controllersGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "controllers");
|
|
266
356
|
const onFile = (file) => {
|
|
267
|
-
|
|
268
|
-
if (normalized.startsWith(routesGlob) || normalized.startsWith(controllersGlob))
|
|
269
|
-
scheduleEmit();
|
|
357
|
+
if (shouldReemitForFile(file, routesGlob, controllersGlob)) scheduleEmit();
|
|
270
358
|
};
|
|
271
359
|
server.watcher.on("add", onFile);
|
|
272
360
|
server.watcher.on("change", onFile);
|
|
@@ -294,6 +382,7 @@ export {
|
|
|
294
382
|
VIRTUAL_APP_CLIENT_ID,
|
|
295
383
|
appTypedClientPlugin,
|
|
296
384
|
emitClientDts,
|
|
297
|
-
generateClientDts
|
|
385
|
+
generateClientDts,
|
|
386
|
+
shouldReemitForFile
|
|
298
387
|
};
|
|
299
|
-
//# sourceMappingURL=app-typed-client-
|
|
388
|
+
//# sourceMappingURL=app-typed-client-FQPUOL6A.js.map
|