theokit 0.1.0-alpha.8 → 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.
- package/dist/actions-virtual-module-E6PU47GV.js +175 -0
- package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
- package/dist/actions-virtual-module-E7IELVHL.js +174 -0
- package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/app-typed-client-4L4L4BWD.js +289 -0
- package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
- package/dist/app-typed-client-6PAT6VOL.js +289 -0
- package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
- package/dist/aws-lambda-PONVF3UX.js +0 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
- package/dist/build-EWZDEDT3.js.map +1 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +54 -0
- package/dist/build-request-body-preview-II2235E6.js.map +1 -0
- package/dist/bun-DITMZMKL.js +0 -0
- package/dist/check-PZR67OY5.js +0 -0
- package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
- package/dist/chunk-2BQYEBCK.js.map +1 -0
- package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
- package/dist/chunk-4W447H7M.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/chunk-7DHGV77J.js +140 -0
- package/dist/chunk-7DHGV77J.js.map +1 -0
- package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
- package/dist/chunk-C27AZN46.js.map +1 -0
- package/dist/chunk-ERAZNHHR.js +73 -0
- package/dist/chunk-ERAZNHHR.js.map +1 -0
- package/dist/chunk-F3TG5FJV.js +97 -0
- package/dist/chunk-F3TG5FJV.js.map +1 -0
- package/dist/chunk-KJVEJILQ.js +109 -0
- package/dist/chunk-KJVEJILQ.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/{chunk-MCP6Y5BA.js → chunk-M23FP2AS.js} +25 -1
- package/dist/chunk-M23FP2AS.js.map +1 -0
- package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
- package/dist/chunk-MOT42NAH.js +165 -0
- package/dist/chunk-MOT42NAH.js.map +1 -0
- package/dist/chunk-O7335ZGH.js +0 -0
- package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
- package/dist/chunk-OJHO2X7C.js.map +1 -0
- package/dist/chunk-QLGES2HC.js +189 -0
- package/dist/chunk-QLGES2HC.js.map +1 -0
- package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
- package/dist/chunk-SEVYARIU.js.map +1 -0
- package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
- package/dist/chunk-TFECHXR5.js.map +1 -0
- package/dist/chunk-WSJKACWB.js +17 -0
- package/dist/chunk-WSJKACWB.js.map +1 -0
- package/dist/chunk-WZ7CPVQB.js +0 -0
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/chunk-Y5KE6YFP.js +0 -0
- package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
- package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
- package/dist/chunk-ZOXNJV2O.js +358 -0
- package/dist/chunk-ZOXNJV2O.js.map +1 -0
- package/dist/cli/index.js +22 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +31 -1
- package/dist/client/index.js +100 -0
- package/dist/client/index.js.map +1 -1
- package/dist/cloudflare-RBANU3SE.js +0 -0
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/deno-deploy-J7LI6HNL.js +0 -0
- package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
- package/dist/dev-74KPI5C5.js.map +1 -0
- package/dist/dev-emit-3CHM73TV.js +390 -0
- package/dist/dev-emit-3CHM73TV.js.map +1 -0
- package/dist/dev-emit-I3CKOHML.js +44 -0
- package/dist/dev-emit-I3CKOHML.js.map +1 -0
- package/dist/devtools/entry.js +653 -243
- package/dist/devtools/entry.js.map +1 -1
- package/dist/dispatcher-AQJGI3HU.js +8 -0
- package/dist/dispatcher-E6QV32BO.js +83 -0
- package/dist/dispatcher-E6QV32BO.js.map +1 -0
- package/dist/{dist-UZV7J3YI.js → dist-R2Q7GVDW.js} +10412 -11961
- package/dist/dist-R2Q7GVDW.js.map +1 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
- package/dist/generate-DT4IIAHF.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
- package/dist/lib-NST3PNZX.js +0 -0
- package/dist/netlify-IFY6ZGFV.js +0 -0
- package/dist/node-KWZI3CKV.js +0 -0
- package/dist/openapi-CBASKC7W.js +84 -0
- package/dist/openapi-CBASKC7W.js.map +1 -0
- package/dist/proper-lockfile-MVKMQGFK.js +0 -0
- package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
- package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
- package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
- package/dist/scan-C2BOKLSY.js +0 -0
- package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
- package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
- package/dist/server/cost/index.d.ts +7 -0
- package/dist/server/cost/index.js +1 -1
- package/dist/server/index.d.ts +219 -47
- package/dist/server/index.js +101 -114
- package/dist/server/index.js.map +1 -1
- package/dist/services-typed-client-ZX5JUHH3.js +0 -0
- package/dist/sqlite-vec-54KB4RD3.js +0 -0
- package/dist/{start-CSLJQXV3.js → start-5JTFWF4T.js} +20 -14
- package/dist/start-5JTFWF4T.js.map +1 -0
- package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/theo-cloud-3K4DGB3S.js +0 -0
- package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
- package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
- package/dist/vercel-RHADE5IP.js +0 -0
- package/dist/vite-plugin/index.d.ts +25 -1
- package/dist/vite-plugin/index.js +5 -2
- package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
- package/dist/vite-plugin-3EQP2JYM.js.map +1 -0
- package/package.json +10 -8
- package/LICENSE +0 -201
- package/dist/build-IQQYCA3B.js.map +0 -1
- package/dist/chunk-FRBYJANT.js.map +0 -1
- package/dist/chunk-GQSTRXXB.js.map +0 -1
- package/dist/chunk-JZ5WEG3S.js.map +0 -1
- package/dist/chunk-MCP6Y5BA.js.map +0 -1
- package/dist/chunk-OEB3OIYP.js.map +0 -1
- package/dist/chunk-WQCPKA2L.js.map +0 -1
- package/dist/chunk-ZK5JCE5J.js.map +0 -1
- package/dist/dev-VN6WS3CM.js.map +0 -1
- package/dist/dist-UZV7J3YI.js.map +0 -1
- package/dist/generate-SIO4JSKJ.js.map +0 -1
- package/dist/start-CSLJQXV3.js.map +0 -1
- package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
- /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
- /package/dist/{schema-MVB2FVK6.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
- /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
- /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
- /package/dist/{vite-plugin-WA3G45YY.js.map → schema-JAKF23GR.js.map} +0 -0
- /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx/esm";
|
|
3
|
+
import {
|
|
4
|
+
scanServerActionsEnriched
|
|
5
|
+
} from "./chunk-MOT42NAH.js";
|
|
6
|
+
import "./chunk-KT3MWNZG.js";
|
|
7
|
+
|
|
8
|
+
// src/vite-plugin/actions-virtual-module.ts
|
|
9
|
+
import { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from "fs";
|
|
10
|
+
import { dirname, join, posix, resolve } from "path";
|
|
11
|
+
var VIRTUAL_ID = "@theo/actions";
|
|
12
|
+
var RESOLVED_ID = `\0${VIRTUAL_ID}`;
|
|
13
|
+
function actionsVirtualModule(opts) {
|
|
14
|
+
const { serverDir, distDir } = opts;
|
|
15
|
+
function emitActionsDts() {
|
|
16
|
+
if (distDir === void 0) return;
|
|
17
|
+
const entries = safeScan(serverDir);
|
|
18
|
+
const dtsPath = posix.join(resolve(distDir).replace(/\\/g, "/"), "actions.d.ts");
|
|
19
|
+
const content = generateActionsDts(entries);
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(dirname(dtsPath), { recursive: true });
|
|
22
|
+
const tmp = `${dtsPath}.${process.pid}.tmp`;
|
|
23
|
+
writeFileSync(tmp, content);
|
|
24
|
+
renameSync(tmp, dtsPath);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("[theokit:actions-virtual-module] dts emit error:", err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: "theo:actions-virtual-module",
|
|
31
|
+
enforce: "pre",
|
|
32
|
+
configResolved() {
|
|
33
|
+
emitActionsDts();
|
|
34
|
+
},
|
|
35
|
+
buildEnd() {
|
|
36
|
+
emitActionsDts();
|
|
37
|
+
},
|
|
38
|
+
resolveId(id) {
|
|
39
|
+
if (id === VIRTUAL_ID) return RESOLVED_ID;
|
|
40
|
+
return void 0;
|
|
41
|
+
},
|
|
42
|
+
load(id) {
|
|
43
|
+
if (id !== RESOLVED_ID) return void 0;
|
|
44
|
+
const entries = safeScan(serverDir);
|
|
45
|
+
const ctx = this;
|
|
46
|
+
const envName = ctx?.environment?.name;
|
|
47
|
+
const isClient = envName === "client" || envName === void 0;
|
|
48
|
+
return isClient ? generateClientFacade(entries) : generateServerManifest(entries);
|
|
49
|
+
},
|
|
50
|
+
configureServer(server) {
|
|
51
|
+
const actionsDir = join(serverDir, "actions");
|
|
52
|
+
if (!existsSync(actionsDir)) return;
|
|
53
|
+
const watcher = fsWatch(actionsDir, { recursive: true }, () => {
|
|
54
|
+
emitActionsDts();
|
|
55
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
|
|
56
|
+
if (mod) {
|
|
57
|
+
server.moduleGraph.invalidateModule(mod);
|
|
58
|
+
}
|
|
59
|
+
server.ws.send({ type: "full-reload" });
|
|
60
|
+
});
|
|
61
|
+
server.httpServer?.once("close", () => {
|
|
62
|
+
watcher.close();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function generateActionsDts(entries) {
|
|
68
|
+
const lines = entries.map(
|
|
69
|
+
(entry) => ` ${JSON.stringify(entry.name)}: (input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`
|
|
70
|
+
).join("\n");
|
|
71
|
+
return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.
|
|
72
|
+
declare module '@theo/actions' {
|
|
73
|
+
export const actions: {
|
|
74
|
+
${lines}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
function safeScan(serverDir) {
|
|
80
|
+
try {
|
|
81
|
+
return scanServerActionsEnriched(serverDir);
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function generateClientFacade(entries) {
|
|
87
|
+
const entryLines = entries.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`).join("\n");
|
|
88
|
+
return `// AUTO-GENERATED by @theo/actions virtual module
|
|
89
|
+
/* eslint-disable */
|
|
90
|
+
const ACTION_URL_MAP = {
|
|
91
|
+
${entryLines}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Devtools telemetry \u2014 fires in the SAME realm as the Overlay React tree
|
|
95
|
+
// (browser), so the Actions tab updates immediately on each call.
|
|
96
|
+
// The Overlay component installs \`window.__theoDevtoolsDispatcher\` on
|
|
97
|
+
// mount (dev only; prod tree-shakes the Overlay entirely). Reading the
|
|
98
|
+
// global avoids a cross-package dynamic import that Vite's virtual-module
|
|
99
|
+
// resolver cannot trace. Same pattern as React DevTools'
|
|
100
|
+
// \`__REACT_DEVTOOLS_GLOBAL_HOOK__\`.
|
|
101
|
+
function emitTelemetry(name, startedAt, input, outcome) {
|
|
102
|
+
if (typeof window === 'undefined') return
|
|
103
|
+
const dispatcher = window.__theoDevtoolsDispatcher
|
|
104
|
+
if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return
|
|
105
|
+
try {
|
|
106
|
+
dispatcher.onActionCall({
|
|
107
|
+
id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),
|
|
108
|
+
timestamp: startedAt,
|
|
109
|
+
name,
|
|
110
|
+
input,
|
|
111
|
+
output: outcome.status === 'success' ? outcome.data : undefined,
|
|
112
|
+
error: outcome.status === 'error' ? outcome.error : undefined,
|
|
113
|
+
durationMs: Date.now() - startedAt,
|
|
114
|
+
status: outcome.status,
|
|
115
|
+
})
|
|
116
|
+
} catch (_) {
|
|
117
|
+
// dispatcher threw \u2014 devtools UI bug; never bubble to consumer code
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function callAction(name, url, input) {
|
|
122
|
+
const startedAt = Date.now()
|
|
123
|
+
const isForm = typeof FormData !== 'undefined' && input instanceof FormData
|
|
124
|
+
const headers = { 'x-theo-action': '1' }
|
|
125
|
+
const body = isForm ? input : JSON.stringify(input ?? {})
|
|
126
|
+
if (!isForm) headers['content-type'] = 'application/json'
|
|
127
|
+
let response
|
|
128
|
+
try {
|
|
129
|
+
response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })
|
|
130
|
+
} catch (e) {
|
|
131
|
+
const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }
|
|
132
|
+
emitTelemetry(name, startedAt, input, { status: 'error', error: err })
|
|
133
|
+
return { data: undefined, error: err }
|
|
134
|
+
}
|
|
135
|
+
const contentType = response.headers.get('content-type') ?? ''
|
|
136
|
+
let result
|
|
137
|
+
if (response.status === 204) {
|
|
138
|
+
result = { data: undefined, error: undefined }
|
|
139
|
+
} else if (contentType.startsWith('application/json+devalue')) {
|
|
140
|
+
const text = await response.text()
|
|
141
|
+
const { parse } = await import('devalue')
|
|
142
|
+
result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }
|
|
143
|
+
} else if (contentType.startsWith('application/json')) {
|
|
144
|
+
const json = await response.json()
|
|
145
|
+
result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }
|
|
146
|
+
} else {
|
|
147
|
+
result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }
|
|
148
|
+
}
|
|
149
|
+
emitTelemetry(name, startedAt, input, result.error
|
|
150
|
+
? { status: 'error', error: result.error }
|
|
151
|
+
: { status: 'success', data: result.data })
|
|
152
|
+
return result
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const actions = new Proxy({}, {
|
|
156
|
+
get(target, prop) {
|
|
157
|
+
if (typeof prop !== 'string') return undefined
|
|
158
|
+
const url = ACTION_URL_MAP[prop]
|
|
159
|
+
if (!url) return undefined
|
|
160
|
+
return (input) => callAction(prop, url, input)
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
function generateServerManifest(entries) {
|
|
166
|
+
return `// AUTO-GENERATED by @theo/actions virtual module (server env)
|
|
167
|
+
/* eslint-disable */
|
|
168
|
+
export const manifest = ${JSON.stringify(entries, null, 2)}
|
|
169
|
+
export const actions = manifest
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
actionsVirtualModule
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=actions-virtual-module-E6PU47GV.js.map
|
|
@@ -0,0 +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/scan/action-scan.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 `.theo/` 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 `.theo/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(\n (entry) =>\n ` ${JSON.stringify(entry.name)}: (input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`,\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 return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\nconst ACTION_URL_MAP = {\n${entryLines}\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\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 return (input) => callAction(prop, url, input)\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":[]}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
scanServerActionsEnriched
|
|
3
|
+
} from "./chunk-7DHGV77J.js";
|
|
4
|
+
import "./chunk-X2VVCJ4V.js";
|
|
5
|
+
import "./chunk-DGUM43GV.js";
|
|
6
|
+
|
|
7
|
+
// src/vite-plugin/actions-virtual-module.ts
|
|
8
|
+
import { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from "fs";
|
|
9
|
+
import { dirname, join, posix, resolve } from "path";
|
|
10
|
+
var VIRTUAL_ID = "@theo/actions";
|
|
11
|
+
var RESOLVED_ID = `\0${VIRTUAL_ID}`;
|
|
12
|
+
function actionsVirtualModule(opts) {
|
|
13
|
+
const { serverDir, distDir } = opts;
|
|
14
|
+
function emitActionsDts() {
|
|
15
|
+
if (distDir === void 0) return;
|
|
16
|
+
const entries = safeScan(serverDir);
|
|
17
|
+
const dtsPath = posix.join(resolve(distDir).replace(/\\/g, "/"), "actions.d.ts");
|
|
18
|
+
const content = generateActionsDts(entries);
|
|
19
|
+
try {
|
|
20
|
+
mkdirSync(dirname(dtsPath), { recursive: true });
|
|
21
|
+
const tmp = `${dtsPath}.${process.pid}.tmp`;
|
|
22
|
+
writeFileSync(tmp, content);
|
|
23
|
+
renameSync(tmp, dtsPath);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("[theokit:actions-virtual-module] dts emit error:", err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
name: "theo:actions-virtual-module",
|
|
30
|
+
enforce: "pre",
|
|
31
|
+
configResolved() {
|
|
32
|
+
emitActionsDts();
|
|
33
|
+
},
|
|
34
|
+
buildEnd() {
|
|
35
|
+
emitActionsDts();
|
|
36
|
+
},
|
|
37
|
+
resolveId(id) {
|
|
38
|
+
if (id === VIRTUAL_ID) return RESOLVED_ID;
|
|
39
|
+
return void 0;
|
|
40
|
+
},
|
|
41
|
+
load(id) {
|
|
42
|
+
if (id !== RESOLVED_ID) return void 0;
|
|
43
|
+
const entries = safeScan(serverDir);
|
|
44
|
+
const ctx = this;
|
|
45
|
+
const envName = ctx?.environment?.name;
|
|
46
|
+
const isClient = envName === "client" || envName === void 0;
|
|
47
|
+
return isClient ? generateClientFacade(entries) : generateServerManifest(entries);
|
|
48
|
+
},
|
|
49
|
+
configureServer(server) {
|
|
50
|
+
const actionsDir = join(serverDir, "actions");
|
|
51
|
+
if (!existsSync(actionsDir)) return;
|
|
52
|
+
const watcher = fsWatch(actionsDir, { recursive: true }, () => {
|
|
53
|
+
emitActionsDts();
|
|
54
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
|
|
55
|
+
if (mod) {
|
|
56
|
+
server.moduleGraph.invalidateModule(mod);
|
|
57
|
+
}
|
|
58
|
+
server.ws.send({ type: "full-reload" });
|
|
59
|
+
});
|
|
60
|
+
server.httpServer?.once("close", () => {
|
|
61
|
+
watcher.close();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function generateActionsDts(entries) {
|
|
67
|
+
const lines = entries.map(
|
|
68
|
+
(entry) => ` ${JSON.stringify(entry.name)}: (input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`
|
|
69
|
+
).join("\n");
|
|
70
|
+
return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.
|
|
71
|
+
declare module '@theo/actions' {
|
|
72
|
+
export const actions: {
|
|
73
|
+
${lines}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
function safeScan(serverDir) {
|
|
79
|
+
try {
|
|
80
|
+
return scanServerActionsEnriched(serverDir);
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function generateClientFacade(entries) {
|
|
86
|
+
const entryLines = entries.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`).join("\n");
|
|
87
|
+
return `// AUTO-GENERATED by @theo/actions virtual module
|
|
88
|
+
/* eslint-disable */
|
|
89
|
+
const ACTION_URL_MAP = {
|
|
90
|
+
${entryLines}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Devtools telemetry \u2014 fires in the SAME realm as the Overlay React tree
|
|
94
|
+
// (browser), so the Actions tab updates immediately on each call.
|
|
95
|
+
// The Overlay component installs \`window.__theoDevtoolsDispatcher\` on
|
|
96
|
+
// mount (dev only; prod tree-shakes the Overlay entirely). Reading the
|
|
97
|
+
// global avoids a cross-package dynamic import that Vite's virtual-module
|
|
98
|
+
// resolver cannot trace. Same pattern as React DevTools'
|
|
99
|
+
// \`__REACT_DEVTOOLS_GLOBAL_HOOK__\`.
|
|
100
|
+
function emitTelemetry(name, startedAt, input, outcome) {
|
|
101
|
+
if (typeof window === 'undefined') return
|
|
102
|
+
const dispatcher = window.__theoDevtoolsDispatcher
|
|
103
|
+
if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return
|
|
104
|
+
try {
|
|
105
|
+
dispatcher.onActionCall({
|
|
106
|
+
id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),
|
|
107
|
+
timestamp: startedAt,
|
|
108
|
+
name,
|
|
109
|
+
input,
|
|
110
|
+
output: outcome.status === 'success' ? outcome.data : undefined,
|
|
111
|
+
error: outcome.status === 'error' ? outcome.error : undefined,
|
|
112
|
+
durationMs: Date.now() - startedAt,
|
|
113
|
+
status: outcome.status,
|
|
114
|
+
})
|
|
115
|
+
} catch (_) {
|
|
116
|
+
// dispatcher threw \u2014 devtools UI bug; never bubble to consumer code
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function callAction(name, url, input) {
|
|
121
|
+
const startedAt = Date.now()
|
|
122
|
+
const isForm = typeof FormData !== 'undefined' && input instanceof FormData
|
|
123
|
+
const headers = { 'x-theo-action': '1' }
|
|
124
|
+
const body = isForm ? input : JSON.stringify(input ?? {})
|
|
125
|
+
if (!isForm) headers['content-type'] = 'application/json'
|
|
126
|
+
let response
|
|
127
|
+
try {
|
|
128
|
+
response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })
|
|
129
|
+
} catch (e) {
|
|
130
|
+
const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }
|
|
131
|
+
emitTelemetry(name, startedAt, input, { status: 'error', error: err })
|
|
132
|
+
return { data: undefined, error: err }
|
|
133
|
+
}
|
|
134
|
+
const contentType = response.headers.get('content-type') ?? ''
|
|
135
|
+
let result
|
|
136
|
+
if (response.status === 204) {
|
|
137
|
+
result = { data: undefined, error: undefined }
|
|
138
|
+
} else if (contentType.startsWith('application/json+devalue')) {
|
|
139
|
+
const text = await response.text()
|
|
140
|
+
const { parse } = await import('devalue')
|
|
141
|
+
result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }
|
|
142
|
+
} else if (contentType.startsWith('application/json')) {
|
|
143
|
+
const json = await response.json()
|
|
144
|
+
result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }
|
|
145
|
+
} else {
|
|
146
|
+
result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }
|
|
147
|
+
}
|
|
148
|
+
emitTelemetry(name, startedAt, input, result.error
|
|
149
|
+
? { status: 'error', error: result.error }
|
|
150
|
+
: { status: 'success', data: result.data })
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const actions = new Proxy({}, {
|
|
155
|
+
get(target, prop) {
|
|
156
|
+
if (typeof prop !== 'string') return undefined
|
|
157
|
+
const url = ACTION_URL_MAP[prop]
|
|
158
|
+
if (!url) return undefined
|
|
159
|
+
return (input) => callAction(prop, url, input)
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
function generateServerManifest(entries) {
|
|
165
|
+
return `// AUTO-GENERATED by @theo/actions virtual module (server env)
|
|
166
|
+
/* eslint-disable */
|
|
167
|
+
export const manifest = ${JSON.stringify(entries, null, 2)}
|
|
168
|
+
export const actions = manifest
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
actionsVirtualModule
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=actions-virtual-module-E7IELVHL.js.map
|
|
@@ -0,0 +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/scan/action-scan.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 `.theo/` 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 `.theo/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(\n (entry) =>\n ` ${JSON.stringify(entry.name)}: (input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`,\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 return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\nconst ACTION_URL_MAP = {\n${entryLines}\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\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 return (input) => callAction(prop, url, input)\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":[]}
|
package/dist/add-2ZFFGGE4.js
CHANGED
|
File without changes
|