theokit 0.2.0 → 0.2.1

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.
Files changed (112) hide show
  1. package/dist/actions-virtual-module-E6PU47GV.js +175 -0
  2. package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
  3. package/dist/actions-virtual-module-E7IELVHL.js +174 -0
  4. package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
  5. package/dist/app-typed-client-4L4L4BWD.js +289 -0
  6. package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
  7. package/dist/app-typed-client-6PAT6VOL.js +289 -0
  8. package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
  9. package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
  10. package/dist/build-EWZDEDT3.js.map +1 -0
  11. package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
  12. package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
  13. package/dist/build-request-body-preview-II2235E6.js +54 -0
  14. package/dist/build-request-body-preview-II2235E6.js.map +1 -0
  15. package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
  16. package/dist/chunk-2BQYEBCK.js.map +1 -0
  17. package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
  18. package/dist/chunk-4W447H7M.js.map +1 -0
  19. package/dist/chunk-7DHGV77J.js +140 -0
  20. package/dist/chunk-7DHGV77J.js.map +1 -0
  21. package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
  22. package/dist/chunk-C27AZN46.js.map +1 -0
  23. package/dist/chunk-ERAZNHHR.js +73 -0
  24. package/dist/chunk-ERAZNHHR.js.map +1 -0
  25. package/dist/chunk-F3TG5FJV.js +97 -0
  26. package/dist/chunk-F3TG5FJV.js.map +1 -0
  27. package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
  28. package/dist/chunk-KJVEJILQ.js.map +1 -0
  29. package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
  30. package/dist/chunk-M23FP2AS.js.map +1 -0
  31. package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
  32. package/dist/chunk-MOT42NAH.js +165 -0
  33. package/dist/chunk-MOT42NAH.js.map +1 -0
  34. package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
  35. package/dist/chunk-OJHO2X7C.js.map +1 -0
  36. package/dist/chunk-QLGES2HC.js +189 -0
  37. package/dist/chunk-QLGES2HC.js.map +1 -0
  38. package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
  39. package/dist/chunk-SEVYARIU.js.map +1 -0
  40. package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
  41. package/dist/chunk-TFECHXR5.js.map +1 -0
  42. package/dist/chunk-WSJKACWB.js +17 -0
  43. package/dist/chunk-WSJKACWB.js.map +1 -0
  44. package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
  45. package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
  46. package/dist/chunk-ZOXNJV2O.js +358 -0
  47. package/dist/chunk-ZOXNJV2O.js.map +1 -0
  48. package/dist/cli/index.js +22 -7
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/client/index.d.ts +31 -1
  51. package/dist/client/index.js +100 -0
  52. package/dist/client/index.js.map +1 -1
  53. package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
  54. package/dist/dev-74KPI5C5.js.map +1 -0
  55. package/dist/dev-emit-3CHM73TV.js +390 -0
  56. package/dist/dev-emit-3CHM73TV.js.map +1 -0
  57. package/dist/dev-emit-I3CKOHML.js +44 -0
  58. package/dist/dev-emit-I3CKOHML.js.map +1 -0
  59. package/dist/devtools/entry.js +545 -181
  60. package/dist/devtools/entry.js.map +1 -1
  61. package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
  62. package/dist/dispatcher-E6QV32BO.js +83 -0
  63. package/dist/dispatcher-E6QV32BO.js.map +1 -0
  64. package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
  65. package/dist/dist-R2Q7GVDW.js.map +1 -0
  66. package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
  67. package/dist/generate-DT4IIAHF.js.map +1 -0
  68. package/dist/index.d.ts +108 -1
  69. package/dist/index.js +5 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
  72. package/dist/openapi-CBASKC7W.js +84 -0
  73. package/dist/openapi-CBASKC7W.js.map +1 -0
  74. package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
  75. package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
  76. package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
  77. package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
  78. package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
  79. package/dist/server/cost/index.js +1 -1
  80. package/dist/server/index.d.ts +219 -47
  81. package/dist/server/index.js +35 -116
  82. package/dist/server/index.js.map +1 -1
  83. package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
  84. package/dist/start-5JTFWF4T.js.map +1 -0
  85. package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
  86. package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
  87. package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
  88. package/dist/vite-plugin/index.d.ts +25 -1
  89. package/dist/vite-plugin/index.js +5 -2
  90. package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
  91. package/package.json +4 -2
  92. package/dist/build-IQQYCA3B.js.map +0 -1
  93. package/dist/chunk-FRBYJANT.js.map +0 -1
  94. package/dist/chunk-GQSTRXXB.js.map +0 -1
  95. package/dist/chunk-JBC7PFV4.js.map +0 -1
  96. package/dist/chunk-JZ5WEG3S.js.map +0 -1
  97. package/dist/chunk-OEB3OIYP.js.map +0 -1
  98. package/dist/chunk-OK2PJOLK.js.map +0 -1
  99. package/dist/chunk-WQCPKA2L.js.map +0 -1
  100. package/dist/chunk-ZK5JCE5J.js.map +0 -1
  101. package/dist/dev-VN6WS3CM.js.map +0 -1
  102. package/dist/dist-Q5VBFWVL.js.map +0 -1
  103. package/dist/generate-SIO4JSKJ.js.map +0 -1
  104. package/dist/start-ZGWW5MOL.js.map +0 -1
  105. package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
  106. /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
  107. /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
  108. /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
  109. /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
  110. /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
  111. /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
  112. /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-3EQP2JYM.js.map} +0 -0
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+ import {
4
+ generateManifest
5
+ } from "./chunk-SEVYARIU.js";
6
+ import {
7
+ HTTP_METHODS
8
+ } from "./chunk-QLGES2HC.js";
9
+ import "./chunk-MOT42NAH.js";
10
+ import "./chunk-KT3MWNZG.js";
11
+
12
+ // src/vite-plugin/app-typed-client.ts
13
+ import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "fs";
14
+ import { dirname, isAbsolute, posix, relative, resolve } from "path";
15
+ var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
16
+ // Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).
17
+ // To refresh: \`pnpm dev\` or \`pnpm build\` regenerates this file on every route change.
18
+
19
+ /* eslint-disable */
20
+ `;
21
+ function makeNode() {
22
+ return { children: /* @__PURE__ */ new Map(), methods: /* @__PURE__ */ new Map(), paramNamesAccumulated: [] };
23
+ }
24
+ function normalizeSegment(raw) {
25
+ let s = raw;
26
+ if (s.startsWith(":...")) s = s.slice(4);
27
+ else if (s.startsWith(":")) s = s.slice(1);
28
+ const camel = s.replace(/-([a-z0-9])/gi, (_, c) => c.toUpperCase());
29
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
30
+ return { identifier: camel, bracket: false };
31
+ }
32
+ return { identifier: s, bracket: true };
33
+ }
34
+ function importPathFor(dtsOutPath, routeFileAbs) {
35
+ let rel = relative(dirname(dtsOutPath), routeFileAbs);
36
+ rel = rel.replace(/\\/g, "/");
37
+ rel = rel.replace(/\.[jt]sx?$/, "");
38
+ if (!rel.startsWith(".") && !rel.startsWith("/")) {
39
+ rel = "./" + rel;
40
+ }
41
+ return rel;
42
+ }
43
+ function routePathSegments(routePath) {
44
+ let p = routePath;
45
+ if (p.startsWith("/api/")) p = p.slice(5);
46
+ else if (p.startsWith("/api")) p = p.slice(4);
47
+ if (p.startsWith("/")) p = p.slice(1);
48
+ if (p === "") return [];
49
+ return p.split("/").filter(Boolean);
50
+ }
51
+ function paramNamesFromRoute(routePath) {
52
+ const out = [];
53
+ const re = /:(?:\.\.\.)?([^/]+)/g;
54
+ let m;
55
+ while ((m = re.exec(routePath)) !== null) {
56
+ out.push(m[1]);
57
+ }
58
+ return out;
59
+ }
60
+ function buildOneRoute(route, ctx) {
61
+ if (!route.methods || route.methods.length === 0) return;
62
+ const idx = ctx.aliasCounter.value++;
63
+ const absRouteFile = isAbsolute(route.filePath) ? route.filePath : resolve(ctx.serverDir, route.filePath);
64
+ const importPath = importPathFor(ctx.dtsOutPath, absRouteFile);
65
+ const aliases = route.methods.filter((m) => HTTP_METHODS.includes(m)).map((method) => ({ method, alias: `_r${idx}_${method}` }));
66
+ if (aliases.length === 0) return;
67
+ const importClause = aliases.map((a) => `${a.method} as ${a.alias}`).join(", ");
68
+ ctx.importLines.push(` import type { ${importClause} } from '${importPath}'`);
69
+ const segments = routePathSegments(route.routePath);
70
+ let node = ctx.root;
71
+ const tokens = [];
72
+ for (const seg of segments) {
73
+ const norm = normalizeSegment(seg);
74
+ tokens.push({ token: norm.identifier, bracket: norm.bracket });
75
+ let child = node.children.get(norm.identifier);
76
+ if (child === void 0) {
77
+ child = makeNode();
78
+ node.children.set(norm.identifier, child);
79
+ }
80
+ node = child;
81
+ }
82
+ node.paramNamesAccumulated = paramNamesFromRoute(route.routePath);
83
+ for (const { method, alias } of aliases) {
84
+ if (node.methods.has(method)) {
85
+ ctx.warnings.push(
86
+ `// WARNING: duplicate ${method} export at /${segments.join("/")} (route file declared twice). Keeping last definition.`
87
+ );
88
+ }
89
+ node.methods.set(method, alias);
90
+ }
91
+ }
92
+ function resolveCollisions(node, pathPrefix, warnings) {
93
+ const methodLowerNames = /* @__PURE__ */ new Set();
94
+ for (const method of node.methods.keys()) {
95
+ methodLowerNames.add(method.toLowerCase());
96
+ }
97
+ const renames = [];
98
+ for (const childName of node.children.keys()) {
99
+ if (methodLowerNames.has(childName)) {
100
+ renames.push({ from: childName, to: `${childName}_` });
101
+ }
102
+ }
103
+ for (const { from, to } of renames) {
104
+ const old = node.children.get(from);
105
+ if (old !== void 0) {
106
+ node.children.delete(from);
107
+ node.children.set(to, old);
108
+ warnings.push(
109
+ `WARNING (EC-3): path collision \u2014 method '${from}' clashed with sub-namespace at ${pathPrefix}/${from}; sub-namespace renamed to '${to}'.`
110
+ );
111
+ }
112
+ }
113
+ for (const [name, child] of node.children) {
114
+ resolveCollisions(child, `${pathPrefix}/${name}`, warnings);
115
+ }
116
+ }
117
+ function renderTreeNode(node, indent, isRoot) {
118
+ const lines = [];
119
+ if (isRoot) {
120
+ lines.push("{");
121
+ } else {
122
+ lines.push("{");
123
+ }
124
+ for (const [method, alias] of node.methods) {
125
+ const lower = method.toLowerCase();
126
+ const paramsField = node.paramNamesAccumulated.length > 0 ? `params: { ${node.paramNamesAccumulated.map((p) => `${p}: string`).join("; ")} } & ` : "";
127
+ lines.push(
128
+ `${indent} ${lower}: (opts${node.paramNamesAccumulated.length === 0 ? "?" : ""}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`
129
+ );
130
+ }
131
+ for (const [name, child] of node.children) {
132
+ const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
133
+ lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + " ", false)}`);
134
+ }
135
+ lines.push(`${indent}}`);
136
+ return lines.join("\n");
137
+ }
138
+ function generateClientDts({
139
+ manifest,
140
+ dtsOutPath,
141
+ serverDir
142
+ }) {
143
+ const ctx = {
144
+ root: makeNode(),
145
+ aliasCounter: { value: 0 },
146
+ importLines: [],
147
+ warnings: [],
148
+ dtsOutPath,
149
+ serverDir
150
+ };
151
+ for (const route of manifest.routes) {
152
+ buildOneRoute(route, ctx);
153
+ }
154
+ resolveCollisions(ctx.root, "", ctx.warnings);
155
+ const importsBlock = ctx.importLines.length > 0 ? ctx.importLines.join("\n") + "\n\n" : "\n";
156
+ const warningsBlock = ctx.warnings.length > 0 ? " // " + ctx.warnings.join("\n // ") + "\n\n" : "";
157
+ 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(
158
+ ctx.root,
159
+ " ",
160
+ /* isRoot */
161
+ true
162
+ );
163
+ return `${FILE_HEADER}
164
+ declare module '@theo/client' {
165
+ ${importsBlock}${warningsBlock} export interface AppClient ${treeBody}
166
+ export const client: AppClient
167
+ }
168
+ `;
169
+ }
170
+ var VIRTUAL_APP_CLIENT_ID = "@theo/client";
171
+ var RESOLVED_APP_CLIENT_ID = "\0@theo/client";
172
+ function writeFileIfChanged(filePath, content) {
173
+ if (existsSync(filePath)) {
174
+ try {
175
+ const current = readFileSync(filePath, "utf-8");
176
+ if (current === content) return { changed: false };
177
+ } catch {
178
+ }
179
+ }
180
+ mkdirSync(dirname(filePath), { recursive: true });
181
+ const tmp = `${filePath}.${process.pid}.tmp`;
182
+ writeFileSync(tmp, content);
183
+ renameSync(tmp, filePath);
184
+ return { changed: true };
185
+ }
186
+ function emitClientDts(opts) {
187
+ if (!existsSync(opts.serverDir)) {
188
+ const stubPath = posix.join(
189
+ opts.distDir.replace(/\\/g, "/"),
190
+ "client.d.ts"
191
+ );
192
+ return {
193
+ changed: writeFileIfChanged(
194
+ stubPath,
195
+ generateClientDts({
196
+ manifest: {
197
+ version: 1,
198
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
199
+ routes: [],
200
+ actions: [],
201
+ websockets: []
202
+ },
203
+ dtsOutPath: stubPath,
204
+ serverDir: opts.serverDir
205
+ })
206
+ ).changed,
207
+ path: stubPath
208
+ };
209
+ }
210
+ const manifest = generateManifest(opts.serverDir);
211
+ const dtsOutPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
212
+ const content = generateClientDts({
213
+ manifest,
214
+ dtsOutPath,
215
+ serverDir: opts.serverDir
216
+ });
217
+ const { changed } = writeFileIfChanged(dtsOutPath, content);
218
+ return { changed, path: dtsOutPath };
219
+ }
220
+ function appTypedClientPlugin(opts) {
221
+ let debounceTimer;
222
+ let viteServer;
223
+ function scheduleEmit() {
224
+ if (debounceTimer) clearTimeout(debounceTimer);
225
+ debounceTimer = setTimeout(() => {
226
+ try {
227
+ emitClientDts(opts);
228
+ if (viteServer) {
229
+ const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID);
230
+ if (mod) viteServer.moduleGraph.invalidateModule(mod);
231
+ }
232
+ } catch (err) {
233
+ console.error("[theokit:app-typed-client] codegen error:", err);
234
+ }
235
+ }, 100);
236
+ }
237
+ return {
238
+ name: "theokit:app-typed-client",
239
+ enforce: "post",
240
+ configResolved() {
241
+ try {
242
+ emitClientDts(opts);
243
+ } catch (err) {
244
+ console.error("[theokit:app-typed-client] initial codegen error:", err);
245
+ }
246
+ },
247
+ buildEnd() {
248
+ try {
249
+ emitClientDts(opts);
250
+ } catch (err) {
251
+ console.error("[theokit:app-typed-client] buildEnd codegen error:", err);
252
+ }
253
+ },
254
+ configureServer(server) {
255
+ viteServer = server;
256
+ const routesGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "routes");
257
+ const onFile = (file) => {
258
+ const normalized = file.replace(/\\/g, "/");
259
+ if (normalized.startsWith(routesGlob)) scheduleEmit();
260
+ };
261
+ server.watcher.on("add", onFile);
262
+ server.watcher.on("change", onFile);
263
+ server.watcher.on("unlink", onFile);
264
+ },
265
+ resolveId(id) {
266
+ if (id === VIRTUAL_APP_CLIENT_ID) return RESOLVED_APP_CLIENT_ID;
267
+ return null;
268
+ },
269
+ load(id) {
270
+ if (id === RESOLVED_APP_CLIENT_ID) {
271
+ return [
272
+ `import { createAppClient } from 'theokit/client'`,
273
+ `export const client = createAppClient()`,
274
+ `export default client`,
275
+ ``
276
+ ].join("\n");
277
+ }
278
+ return null;
279
+ }
280
+ };
281
+ }
282
+ export {
283
+ RESOLVED_APP_CLIENT_ID,
284
+ VIRTUAL_APP_CLIENT_ID,
285
+ appTypedClientPlugin,
286
+ emitClientDts,
287
+ generateClientDts
288
+ };
289
+ //# sourceMappingURL=app-typed-client-4L4L4BWD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/app-typed-client.ts"],"sourcesContent":["/**\n * Phase 2 + Phase 4 of G1 (g1-client-codegen-plan.md):\n *\n * 1. `generateClientDts(manifest, dtsOutPath)` — pure function emitting the\n * `.theo/client.d.ts` shape (declared module `@theo/client`).\n * 2. `appTypedClientPlugin(opts)` — Vite plugin wrapper that calls (1),\n * writes the file atomically, and rewrites on route file changes.\n *\n * Implementation notes:\n * - Pure (1) is unit-testable without Vite or a filesystem (see G1-P2 tests).\n * - EC-2: kebab-case segments → camelCase; unsafe chars → bracket-access.\n * - EC-3: collision between HTTP method name and sub-segment name → method\n * wins; sub-segment ganha suffix `_` + warning comment.\n * - EC-6: write é `writeFileSync(tmp) + renameSync(tmp, final)` (POSIX atomic).\n * - EC-8: imports normalizam `\\\\` → `/`.\n */\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from 'node:fs'\nimport { dirname, isAbsolute, posix, relative, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { HTTP_METHODS, type HttpMethod } from '../core/contracts/http-methods.js'\nimport {\n generateManifest,\n type ManifestRoute,\n type TheoManifest,\n} from '../server/scan/manifest.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this file on every route change.\n\n/* eslint-disable */\n`\n\ninterface TreeNode {\n /** sub-namespaces — keyed by segment name (already normalized). */\n children: Map<string, TreeNode>\n /** HTTP methods declared at this path → import alias. */\n methods: Map<HttpMethod, string>\n /** Whether any descendant declares dynamic params (for params type construction). */\n paramNamesAccumulated: string[]\n}\n\nfunction makeNode(): TreeNode {\n return { children: new Map(), methods: new Map(), paramNamesAccumulated: [] }\n}\n\n/**\n * Normalize a path segment to a valid TS identifier when possible.\n * EC-2: kebab-case → camelCase; pure dynamic (`:name`) → identifier `name`;\n * starts with digit or unsafe char → emit as bracket-access key.\n */\nfunction normalizeSegment(raw: string): { identifier: string; bracket: boolean } {\n // Dynamic segment: leading `:` (e.g. `:id` or `:...slug`).\n let s = raw\n if (s.startsWith(':...')) s = s.slice(4)\n else if (s.startsWith(':')) s = s.slice(1)\n\n // kebab → camel\n const camel = s.replace(/-([a-z0-9])/gi, (_, c: string) => c.toUpperCase())\n\n // Valid TS identifier (cannot start with digit, must be [a-zA-Z_$][a-zA-Z0-9_$]*)\n if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {\n return { identifier: camel, bracket: false }\n }\n // Fallback: bracket-access (keep raw form intact for JSON-string key)\n return { identifier: s, bracket: true }\n}\n\nfunction importPathFor(dtsOutPath: string, routeFileAbs: string): string {\n let rel = relative(dirname(dtsOutPath), routeFileAbs)\n // EC-8: normalize Windows path separators.\n rel = rel.replace(/\\\\/g, '/')\n // Strip extension (`.ts` / `.tsx`); TS module resolution likes extensionless.\n rel = rel.replace(/\\.[jt]sx?$/, '')\n if (!rel.startsWith('.') && !rel.startsWith('/')) {\n rel = './' + rel\n }\n return rel\n}\n\n/**\n * Convert a route path (e.g. `/api/users/:id/posts`) into the tree segments\n * we want under the `client` root. Strips the `/api/` prefix and splits.\n */\nfunction routePathSegments(routePath: string): string[] {\n let p = routePath\n if (p.startsWith('/api/')) p = p.slice(5)\n else if (p.startsWith('/api')) p = p.slice(4)\n if (p.startsWith('/')) p = p.slice(1)\n if (p === '') return []\n return p.split('/').filter(Boolean)\n}\n\nfunction paramNamesFromRoute(routePath: string): string[] {\n const out: string[] = []\n const re = /:(?:\\.\\.\\.)?([^/]+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(routePath)) !== null) {\n out.push(m[1])\n }\n return out\n}\n\ninterface BuildContext {\n root: TreeNode\n /** Sequential index per route file for aliasing imports. */\n aliasCounter: { value: number }\n /** Lines emitted at the top of the declared module (one per route file). */\n importLines: string[]\n /** Diagnostic warnings rendered as block comments inside the declared module. */\n warnings: string[]\n dtsOutPath: string\n serverDir: string\n}\n\nfunction buildOneRoute(route: ManifestRoute, ctx: BuildContext): void {\n if (!route.methods || route.methods.length === 0) return\n // Build alias prefix once per route file (`_r0`, `_r1`, ...).\n const idx = ctx.aliasCounter.value++\n // Resolve absolute path for the route file (manifest stores it relative to serverDir).\n const absRouteFile = isAbsolute(route.filePath)\n ? route.filePath\n : resolve(ctx.serverDir, route.filePath)\n const importPath = importPathFor(ctx.dtsOutPath, absRouteFile)\n const aliases = route.methods\n .filter((m): m is HttpMethod => (HTTP_METHODS as readonly string[]).includes(m))\n .map((method) => ({ method, alias: `_r${idx}_${method}` }))\n if (aliases.length === 0) return\n const importClause = aliases.map((a) => `${a.method} as ${a.alias}`).join(', ')\n ctx.importLines.push(` import type { ${importClause} } from '${importPath}'`)\n\n // Walk segments + register methods at the leaf node.\n const segments = routePathSegments(route.routePath)\n let node = ctx.root\n const tokens: Array<{ token: string; bracket: boolean }> = []\n for (const seg of segments) {\n const norm = normalizeSegment(seg)\n tokens.push({ token: norm.identifier, bracket: norm.bracket })\n let child = node.children.get(norm.identifier)\n if (child === undefined) {\n child = makeNode()\n node.children.set(norm.identifier, child)\n }\n node = child\n }\n // Accumulated param names for typed params (e.g. `{ id: string }`).\n node.paramNamesAccumulated = paramNamesFromRoute(route.routePath)\n\n // Register each HTTP method (collision detection runs in a post-pass).\n for (const { method, alias } of aliases) {\n if (node.methods.has(method)) {\n ctx.warnings.push(\n `// WARNING: duplicate ${method} export at /${segments.join('/')} (route file declared twice). Keeping last definition.`,\n )\n }\n node.methods.set(method, alias)\n }\n}\n\n/**\n * EC-3: walk the tree post-build and resolve any node where a method's\n * lowercase name clashes with a child segment. HTTP method wins (more common);\n * the offending child gets a `_` suffix + a warning entry.\n */\nfunction resolveCollisions(node: TreeNode, pathPrefix: string, warnings: string[]): void {\n const methodLowerNames = new Set<string>()\n for (const method of node.methods.keys()) {\n methodLowerNames.add(method.toLowerCase())\n }\n const renames: Array<{ from: string; to: string }> = []\n for (const childName of node.children.keys()) {\n if (methodLowerNames.has(childName)) {\n renames.push({ from: childName, to: `${childName}_` })\n }\n }\n for (const { from, to } of renames) {\n const old = node.children.get(from)\n if (old !== undefined) {\n node.children.delete(from)\n node.children.set(to, old)\n warnings.push(\n `WARNING (EC-3): path collision — method '${from}' clashed with sub-namespace at ${pathPrefix}/${from}; sub-namespace renamed to '${to}'.`,\n )\n }\n }\n // Recurse into all children (including the renamed ones).\n for (const [name, child] of node.children) {\n resolveCollisions(child, `${pathPrefix}/${name}`, warnings)\n }\n}\n\nfunction renderTreeNode(node: TreeNode, indent: string, isRoot: boolean): string {\n const lines: string[] = []\n if (isRoot) {\n lines.push('{')\n } else {\n lines.push('{')\n }\n // Methods first\n for (const [method, alias] of node.methods) {\n const lower = method.toLowerCase()\n const paramsField = node.paramNamesAccumulated.length > 0\n ? `params: { ${node.paramNamesAccumulated.map((p) => `${p}: string`).join('; ')} } & `\n : ''\n lines.push(\n `${indent} ${lower}: (opts${node.paramNamesAccumulated.length === 0 ? '?' : ''}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`,\n )\n }\n // Children\n for (const [name, child] of node.children) {\n const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, \"\\\\'\")}'`\n lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + ' ', false)}`)\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nexport interface GenerateClientDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted. Used to compute import paths. */\n dtsOutPath: string\n /** Absolute serverDir; manifest's filePath entries are relative to this. */\n serverDir: string\n}\n\nexport function generateClientDts({\n manifest,\n dtsOutPath,\n serverDir,\n}: GenerateClientDtsOptions): string {\n const ctx: BuildContext = {\n root: makeNode(),\n aliasCounter: { value: 0 },\n importLines: [],\n warnings: [],\n dtsOutPath,\n serverDir,\n }\n for (const route of manifest.routes) {\n buildOneRoute(route, ctx)\n }\n // EC-3: resolve collisions in a post-build pass (collisions materialize\n // when two routes are both processed, not during a single route's build).\n resolveCollisions(ctx.root, '', ctx.warnings)\n\n const importsBlock =\n ctx.importLines.length > 0 ? ctx.importLines.join('\\n') + '\\n\\n' : '\\n'\n const warningsBlock =\n ctx.warnings.length > 0\n ? ' // ' + ctx.warnings.join('\\n // ') + '\\n\\n'\n : ''\n\n // Empty manifest (no routes with methods) → emit a stub interface so consumers\n // still get `client.X` autocomplete-friendly errors instead of \"Module not found\".\n const treeBody =\n ctx.root.children.size === 0 && ctx.root.methods.size === 0\n ? '{\\n // No routes detected — add `server/routes/*.ts` to populate this client.\\n }'\n : renderTreeNode(ctx.root, ' ', /* isRoot */ true)\n\n return `${FILE_HEADER}\ndeclare module '@theo/client' {\n${importsBlock}${warningsBlock} export interface AppClient ${treeBody}\n export const client: AppClient\n}\n`\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Phase 4 — Vite plugin\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AppTypedClientPluginOptions {\n /** Project root (cwd). */\n cwd: string\n /** Absolute path to the user's `server/` dir. */\n serverDir: string\n /** Absolute path to the `.theo/` output dir (where `client.d.ts` lands). */\n distDir: string\n}\n\nconst VIRTUAL_APP_CLIENT_ID = '@theo/client'\nconst RESOLVED_APP_CLIENT_ID = '\\0@theo/client'\n\n/** Atomic write via tmp + rename (EC-6). Skips when content is unchanged. */\nfunction writeFileIfChanged(filePath: string, content: string): { changed: boolean } {\n if (existsSync(filePath)) {\n try {\n const current = readFileSync(filePath, 'utf-8')\n if (current === content) return { changed: false }\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 return { changed: true }\n}\n\nfunction emitClientDts(opts: AppTypedClientPluginOptions): { changed: boolean; path: string } {\n if (!existsSync(opts.serverDir)) {\n // No server dir — emit empty stub to avoid \"module not found\".\n const stubPath = posix.join(\n opts.distDir.replace(/\\\\/g, '/'),\n 'client.d.ts',\n )\n return {\n changed: writeFileIfChanged(\n stubPath,\n generateClientDts({\n manifest: {\n version: 1,\n generatedAt: new Date().toISOString(),\n routes: [],\n actions: [],\n websockets: [],\n },\n dtsOutPath: stubPath,\n serverDir: opts.serverDir,\n }),\n ).changed,\n path: stubPath,\n }\n }\n const manifest = generateManifest(opts.serverDir)\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'client.d.ts')\n const content = generateClientDts({\n manifest,\n dtsOutPath,\n serverDir: opts.serverDir,\n })\n const { changed } = writeFileIfChanged(dtsOutPath, content)\n return { changed, path: dtsOutPath }\n}\n\nexport function appTypedClientPlugin(opts: AppTypedClientPluginOptions): Plugin {\n let debounceTimer: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n function scheduleEmit(): void {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n try {\n emitClientDts(opts)\n // Invalidate the virtual module so re-imports pick up potential new shape.\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n // Surface but don't crash dev server.\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:app-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitClientDts(opts)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitClientDts(opts)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const routesGlob = posix.join(opts.serverDir.replace(/\\\\/g, '/'), 'routes')\n const onFile = (file: string): void => {\n const normalized = file.replace(/\\\\/g, '/')\n if (normalized.startsWith(routesGlob)) 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 if (id === VIRTUAL_APP_CLIENT_ID) return RESOLVED_APP_CLIENT_ID\n return null\n },\n load(id) {\n if (id === RESOLVED_APP_CLIENT_ID) {\n return [\n `import { createAppClient } from 'theokit/client'`,\n `export const client = createAppClient()`,\n `export default client`,\n ``,\n ].join('\\n')\n }\n return null\n },\n }\n}\n\nexport { VIRTUAL_APP_CLIENT_ID, RESOLVED_APP_CLIENT_ID, emitClientDts }\n"],"mappings":"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","names":[]}
@@ -0,0 +1,289 @@
1
+ import {
2
+ generateManifest
3
+ } from "./chunk-ERAZNHHR.js";
4
+ import "./chunk-2BQYEBCK.js";
5
+ import "./chunk-7DHGV77J.js";
6
+ import "./chunk-X2VVCJ4V.js";
7
+ import {
8
+ HTTP_METHODS
9
+ } from "./chunk-WSJKACWB.js";
10
+ import "./chunk-DGUM43GV.js";
11
+
12
+ // src/vite-plugin/app-typed-client.ts
13
+ import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "fs";
14
+ import { dirname, isAbsolute, posix, relative, resolve } from "path";
15
+ var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
16
+ // Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).
17
+ // To refresh: \`pnpm dev\` or \`pnpm build\` regenerates this file on every route change.
18
+
19
+ /* eslint-disable */
20
+ `;
21
+ function makeNode() {
22
+ return { children: /* @__PURE__ */ new Map(), methods: /* @__PURE__ */ new Map(), paramNamesAccumulated: [] };
23
+ }
24
+ function normalizeSegment(raw) {
25
+ let s = raw;
26
+ if (s.startsWith(":...")) s = s.slice(4);
27
+ else if (s.startsWith(":")) s = s.slice(1);
28
+ const camel = s.replace(/-([a-z0-9])/gi, (_, c) => c.toUpperCase());
29
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
30
+ return { identifier: camel, bracket: false };
31
+ }
32
+ return { identifier: s, bracket: true };
33
+ }
34
+ function importPathFor(dtsOutPath, routeFileAbs) {
35
+ let rel = relative(dirname(dtsOutPath), routeFileAbs);
36
+ rel = rel.replace(/\\/g, "/");
37
+ rel = rel.replace(/\.[jt]sx?$/, "");
38
+ if (!rel.startsWith(".") && !rel.startsWith("/")) {
39
+ rel = "./" + rel;
40
+ }
41
+ return rel;
42
+ }
43
+ function routePathSegments(routePath) {
44
+ let p = routePath;
45
+ if (p.startsWith("/api/")) p = p.slice(5);
46
+ else if (p.startsWith("/api")) p = p.slice(4);
47
+ if (p.startsWith("/")) p = p.slice(1);
48
+ if (p === "") return [];
49
+ return p.split("/").filter(Boolean);
50
+ }
51
+ function paramNamesFromRoute(routePath) {
52
+ const out = [];
53
+ const re = /:(?:\.\.\.)?([^/]+)/g;
54
+ let m;
55
+ while ((m = re.exec(routePath)) !== null) {
56
+ out.push(m[1]);
57
+ }
58
+ return out;
59
+ }
60
+ function buildOneRoute(route, ctx) {
61
+ if (!route.methods || route.methods.length === 0) return;
62
+ const idx = ctx.aliasCounter.value++;
63
+ const absRouteFile = isAbsolute(route.filePath) ? route.filePath : resolve(ctx.serverDir, route.filePath);
64
+ const importPath = importPathFor(ctx.dtsOutPath, absRouteFile);
65
+ const aliases = route.methods.filter((m) => HTTP_METHODS.includes(m)).map((method) => ({ method, alias: `_r${idx}_${method}` }));
66
+ if (aliases.length === 0) return;
67
+ const importClause = aliases.map((a) => `${a.method} as ${a.alias}`).join(", ");
68
+ ctx.importLines.push(` import type { ${importClause} } from '${importPath}'`);
69
+ const segments = routePathSegments(route.routePath);
70
+ let node = ctx.root;
71
+ const tokens = [];
72
+ for (const seg of segments) {
73
+ const norm = normalizeSegment(seg);
74
+ tokens.push({ token: norm.identifier, bracket: norm.bracket });
75
+ let child = node.children.get(norm.identifier);
76
+ if (child === void 0) {
77
+ child = makeNode();
78
+ node.children.set(norm.identifier, child);
79
+ }
80
+ node = child;
81
+ }
82
+ node.paramNamesAccumulated = paramNamesFromRoute(route.routePath);
83
+ for (const { method, alias } of aliases) {
84
+ if (node.methods.has(method)) {
85
+ ctx.warnings.push(
86
+ `// WARNING: duplicate ${method} export at /${segments.join("/")} (route file declared twice). Keeping last definition.`
87
+ );
88
+ }
89
+ node.methods.set(method, alias);
90
+ }
91
+ }
92
+ function resolveCollisions(node, pathPrefix, warnings) {
93
+ const methodLowerNames = /* @__PURE__ */ new Set();
94
+ for (const method of node.methods.keys()) {
95
+ methodLowerNames.add(method.toLowerCase());
96
+ }
97
+ const renames = [];
98
+ for (const childName of node.children.keys()) {
99
+ if (methodLowerNames.has(childName)) {
100
+ renames.push({ from: childName, to: `${childName}_` });
101
+ }
102
+ }
103
+ for (const { from, to } of renames) {
104
+ const old = node.children.get(from);
105
+ if (old !== void 0) {
106
+ node.children.delete(from);
107
+ node.children.set(to, old);
108
+ warnings.push(
109
+ `WARNING (EC-3): path collision \u2014 method '${from}' clashed with sub-namespace at ${pathPrefix}/${from}; sub-namespace renamed to '${to}'.`
110
+ );
111
+ }
112
+ }
113
+ for (const [name, child] of node.children) {
114
+ resolveCollisions(child, `${pathPrefix}/${name}`, warnings);
115
+ }
116
+ }
117
+ function renderTreeNode(node, indent, isRoot) {
118
+ const lines = [];
119
+ if (isRoot) {
120
+ lines.push("{");
121
+ } else {
122
+ lines.push("{");
123
+ }
124
+ for (const [method, alias] of node.methods) {
125
+ const lower = method.toLowerCase();
126
+ const paramsField = node.paramNamesAccumulated.length > 0 ? `params: { ${node.paramNamesAccumulated.map((p) => `${p}: string`).join("; ")} } & ` : "";
127
+ lines.push(
128
+ `${indent} ${lower}: (opts${node.paramNamesAccumulated.length === 0 ? "?" : ""}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`
129
+ );
130
+ }
131
+ for (const [name, child] of node.children) {
132
+ const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
133
+ lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + " ", false)}`);
134
+ }
135
+ lines.push(`${indent}}`);
136
+ return lines.join("\n");
137
+ }
138
+ function generateClientDts({
139
+ manifest,
140
+ dtsOutPath,
141
+ serverDir
142
+ }) {
143
+ const ctx = {
144
+ root: makeNode(),
145
+ aliasCounter: { value: 0 },
146
+ importLines: [],
147
+ warnings: [],
148
+ dtsOutPath,
149
+ serverDir
150
+ };
151
+ for (const route of manifest.routes) {
152
+ buildOneRoute(route, ctx);
153
+ }
154
+ resolveCollisions(ctx.root, "", ctx.warnings);
155
+ const importsBlock = ctx.importLines.length > 0 ? ctx.importLines.join("\n") + "\n\n" : "\n";
156
+ const warningsBlock = ctx.warnings.length > 0 ? " // " + ctx.warnings.join("\n // ") + "\n\n" : "";
157
+ 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(
158
+ ctx.root,
159
+ " ",
160
+ /* isRoot */
161
+ true
162
+ );
163
+ return `${FILE_HEADER}
164
+ declare module '@theo/client' {
165
+ ${importsBlock}${warningsBlock} export interface AppClient ${treeBody}
166
+ export const client: AppClient
167
+ }
168
+ `;
169
+ }
170
+ var VIRTUAL_APP_CLIENT_ID = "@theo/client";
171
+ var RESOLVED_APP_CLIENT_ID = "\0@theo/client";
172
+ function writeFileIfChanged(filePath, content) {
173
+ if (existsSync(filePath)) {
174
+ try {
175
+ const current = readFileSync(filePath, "utf-8");
176
+ if (current === content) return { changed: false };
177
+ } catch {
178
+ }
179
+ }
180
+ mkdirSync(dirname(filePath), { recursive: true });
181
+ const tmp = `${filePath}.${process.pid}.tmp`;
182
+ writeFileSync(tmp, content);
183
+ renameSync(tmp, filePath);
184
+ return { changed: true };
185
+ }
186
+ function emitClientDts(opts) {
187
+ if (!existsSync(opts.serverDir)) {
188
+ const stubPath = posix.join(
189
+ opts.distDir.replace(/\\/g, "/"),
190
+ "client.d.ts"
191
+ );
192
+ return {
193
+ changed: writeFileIfChanged(
194
+ stubPath,
195
+ generateClientDts({
196
+ manifest: {
197
+ version: 1,
198
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
199
+ routes: [],
200
+ actions: [],
201
+ websockets: []
202
+ },
203
+ dtsOutPath: stubPath,
204
+ serverDir: opts.serverDir
205
+ })
206
+ ).changed,
207
+ path: stubPath
208
+ };
209
+ }
210
+ const manifest = generateManifest(opts.serverDir);
211
+ const dtsOutPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
212
+ const content = generateClientDts({
213
+ manifest,
214
+ dtsOutPath,
215
+ serverDir: opts.serverDir
216
+ });
217
+ const { changed } = writeFileIfChanged(dtsOutPath, content);
218
+ return { changed, path: dtsOutPath };
219
+ }
220
+ function appTypedClientPlugin(opts) {
221
+ let debounceTimer;
222
+ let viteServer;
223
+ function scheduleEmit() {
224
+ if (debounceTimer) clearTimeout(debounceTimer);
225
+ debounceTimer = setTimeout(() => {
226
+ try {
227
+ emitClientDts(opts);
228
+ if (viteServer) {
229
+ const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID);
230
+ if (mod) viteServer.moduleGraph.invalidateModule(mod);
231
+ }
232
+ } catch (err) {
233
+ console.error("[theokit:app-typed-client] codegen error:", err);
234
+ }
235
+ }, 100);
236
+ }
237
+ return {
238
+ name: "theokit:app-typed-client",
239
+ enforce: "post",
240
+ configResolved() {
241
+ try {
242
+ emitClientDts(opts);
243
+ } catch (err) {
244
+ console.error("[theokit:app-typed-client] initial codegen error:", err);
245
+ }
246
+ },
247
+ buildEnd() {
248
+ try {
249
+ emitClientDts(opts);
250
+ } catch (err) {
251
+ console.error("[theokit:app-typed-client] buildEnd codegen error:", err);
252
+ }
253
+ },
254
+ configureServer(server) {
255
+ viteServer = server;
256
+ const routesGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "routes");
257
+ const onFile = (file) => {
258
+ const normalized = file.replace(/\\/g, "/");
259
+ if (normalized.startsWith(routesGlob)) scheduleEmit();
260
+ };
261
+ server.watcher.on("add", onFile);
262
+ server.watcher.on("change", onFile);
263
+ server.watcher.on("unlink", onFile);
264
+ },
265
+ resolveId(id) {
266
+ if (id === VIRTUAL_APP_CLIENT_ID) return RESOLVED_APP_CLIENT_ID;
267
+ return null;
268
+ },
269
+ load(id) {
270
+ if (id === RESOLVED_APP_CLIENT_ID) {
271
+ return [
272
+ `import { createAppClient } from 'theokit/client'`,
273
+ `export const client = createAppClient()`,
274
+ `export default client`,
275
+ ``
276
+ ].join("\n");
277
+ }
278
+ return null;
279
+ }
280
+ };
281
+ }
282
+ export {
283
+ RESOLVED_APP_CLIENT_ID,
284
+ VIRTUAL_APP_CLIENT_ID,
285
+ appTypedClientPlugin,
286
+ emitClientDts,
287
+ generateClientDts
288
+ };
289
+ //# sourceMappingURL=app-typed-client-6PAT6VOL.js.map