theokit 0.1.0-alpha.4 → 0.1.0-alpha.6
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/adapters/web-shim.d.ts +21 -2
- package/dist/adapters/web-shim.js +30 -10
- package/dist/adapters/web-shim.js.map +1 -1
- package/dist/adapters/ws-shim.js +35 -23
- package/dist/adapters/ws-shim.js.map +1 -1
- package/dist/{add-WM3ED75L.js → add-2ZFFGGE4.js} +17 -12
- package/dist/add-2ZFFGGE4.js.map +1 -0
- package/dist/agent-events-lSIS9uYs.d.ts +76 -0
- package/dist/{aws-lambda-ZO4GOYP2.js → aws-lambda-PONVF3UX.js} +16 -13
- package/dist/aws-lambda-PONVF3UX.js.map +1 -0
- package/dist/broadcast-4SX7R5XE.js +14 -0
- package/dist/broadcast-QOQGUNNK.js +16 -0
- package/dist/broadcast-QOQGUNNK.js.map +1 -0
- package/dist/build-IQQYCA3B.js +571 -0
- package/dist/build-IQQYCA3B.js.map +1 -0
- package/dist/{bun-7OKHVGC2.js → bun-DITMZMKL.js} +17 -14
- package/dist/bun-DITMZMKL.js.map +1 -0
- package/dist/{check-JKDIEYLK.js → check-PZR67OY5.js} +10 -5
- package/dist/check-PZR67OY5.js.map +1 -0
- package/dist/chunk-3Q36SEDK.js +285 -0
- package/dist/chunk-3Q36SEDK.js.map +1 -0
- package/dist/chunk-4QTKSLTO.js +385 -0
- package/dist/chunk-4QTKSLTO.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +129 -0
- package/dist/chunk-5ODOE6EF.js.map +1 -0
- package/dist/chunk-7BIM27LV.js +126 -0
- package/dist/chunk-7BIM27LV.js.map +1 -0
- package/dist/chunk-C52GSJPF.js +523 -0
- package/dist/chunk-C52GSJPF.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-FH3DYBAN.js +39 -0
- package/dist/chunk-FH3DYBAN.js.map +1 -0
- package/dist/chunk-FRBYJANT.js +2133 -0
- package/dist/chunk-FRBYJANT.js.map +1 -0
- package/dist/chunk-GQSTRXXB.js +168 -0
- package/dist/chunk-GQSTRXXB.js.map +1 -0
- package/dist/chunk-GY5Q27BJ.js +107 -0
- package/dist/chunk-GY5Q27BJ.js.map +1 -0
- package/dist/{chunk-2H6ZSJMK.js → chunk-JZ5WEG3S.js} +4 -6
- package/dist/chunk-JZ5WEG3S.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +18 -0
- package/dist/chunk-KT3MWNZG.js.map +1 -0
- package/dist/chunk-MCP6Y5BA.js +161 -0
- package/dist/chunk-MCP6Y5BA.js.map +1 -0
- package/dist/chunk-O4BLVPML.js +173 -0
- package/dist/chunk-O4BLVPML.js.map +1 -0
- package/dist/{chunk-IM6C5SOI.js → chunk-O7335ZGH.js} +12 -14
- package/dist/chunk-O7335ZGH.js.map +1 -0
- package/dist/chunk-OEB3OIYP.js +2121 -0
- package/dist/chunk-OEB3OIYP.js.map +1 -0
- package/dist/chunk-PB4GR7EP.js +51 -0
- package/dist/chunk-PB4GR7EP.js.map +1 -0
- package/dist/chunk-S3JBIFKS.js +202 -0
- package/dist/chunk-S3JBIFKS.js.map +1 -0
- package/dist/chunk-VRHXOKRP.js +601 -0
- package/dist/chunk-VRHXOKRP.js.map +1 -0
- package/dist/chunk-WQCPKA2L.js +1535 -0
- package/dist/chunk-WQCPKA2L.js.map +1 -0
- package/dist/chunk-WZ7CPVQB.js +636 -0
- package/dist/chunk-WZ7CPVQB.js.map +1 -0
- package/dist/chunk-X2VVCJ4V.js +28 -0
- package/dist/chunk-X2VVCJ4V.js.map +1 -0
- package/dist/{chunk-BJBRL6MW.js → chunk-XMS5VRIK.js} +2 -5
- package/dist/chunk-XMS5VRIK.js.map +1 -0
- package/dist/chunk-Y5KE6YFP.js +65 -0
- package/dist/chunk-Y5KE6YFP.js.map +1 -0
- package/dist/chunk-YLBSSEHX.js +50 -0
- package/dist/chunk-YLBSSEHX.js.map +1 -0
- package/dist/chunk-ZK5JCE5J.js +2038 -0
- package/dist/chunk-ZK5JCE5J.js.map +1 -0
- package/dist/cli/index.js +33 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +8 -33
- package/dist/client/index.js +118 -85
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-LII4TDWG.js → cloudflare-RBANU3SE.js} +21 -19
- package/dist/cloudflare-RBANU3SE.js.map +1 -0
- package/dist/configure-agent-registry-6YGTJYBC.js +33 -0
- package/dist/configure-agent-registry-6YGTJYBC.js.map +1 -0
- package/dist/{deno-deploy-HH72FFQ5.js → deno-deploy-J7LI6HNL.js} +16 -13
- package/dist/deno-deploy-J7LI6HNL.js.map +1 -0
- package/dist/dev-VN6WS3CM.js +84 -0
- package/dist/dev-VN6WS3CM.js.map +1 -0
- package/dist/devtools/entry.d.ts +5 -0
- package/dist/devtools/entry.js +1892 -0
- package/dist/devtools/entry.js.map +1 -0
- package/dist/dist-UZV7J3YI.js +18637 -0
- package/dist/dist-UZV7J3YI.js.map +1 -0
- package/dist/{docker-NQ5NM4IW.js → docker-EZDA5FT7.js} +2 -1
- package/dist/docker-EZDA5FT7.js.map +1 -0
- package/dist/{generate-MYEUR7I7.js → generate-SIO4JSKJ.js} +47 -22
- package/dist/generate-SIO4JSKJ.js.map +1 -0
- package/dist/index-li83Swxa.d.ts +506 -0
- package/dist/index.d.ts +1281 -9
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/{info-QGSURJCC.js → info-B3UOXFUF.js} +10 -7
- package/dist/info-B3UOXFUF.js.map +1 -0
- package/dist/lib-NST3PNZX.js +781 -0
- package/dist/lib-NST3PNZX.js.map +1 -0
- package/dist/{netlify-26SFJJPO.js → netlify-IFY6ZGFV.js} +43 -35
- package/dist/netlify-IFY6ZGFV.js.map +1 -0
- package/dist/node-KWZI3CKV.js +11 -0
- package/dist/node-KWZI3CKV.js.map +1 -0
- package/dist/proper-lockfile-MVKMQGFK.js +1592 -0
- package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
- package/dist/rate-limit-store-BEJnhWdw.d.ts +72 -0
- package/dist/react-query/index.d.ts +33 -0
- package/dist/react-query/index.js +11 -0
- package/dist/react-query/index.js.map +1 -0
- package/dist/registry-BB3UYAOG.js +25 -0
- package/dist/registry-BB3UYAOG.js.map +1 -0
- package/dist/{routes-A2VOUPII.js → routes-CAGFZKUK.js} +7 -4
- package/dist/routes-CAGFZKUK.js.map +1 -0
- package/dist/scan-C2BOKLSY.js +10 -0
- package/dist/scan-C2BOKLSY.js.map +1 -0
- package/dist/schema-Cxq2qqwh.d.ts +232 -0
- package/dist/schema-MVB2FVK6.js +29 -0
- package/dist/schema-MVB2FVK6.js.map +1 -0
- package/dist/server/auth/index.d.ts +374 -0
- package/dist/server/auth/index.js +54 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/cost/index.d.ts +172 -0
- package/dist/server/cost/index.js +12 -0
- package/dist/server/cost/index.js.map +1 -0
- package/dist/server/cron/index.d.ts +208 -0
- package/dist/server/cron/index.js +36 -0
- package/dist/server/cron/index.js.map +1 -0
- package/dist/server/index.d.ts +1977 -212
- package/dist/server/index.js +1995 -252
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +2 -0
- package/dist/server/jobs/index.js +38 -0
- package/dist/server/jobs/index.js.map +1 -0
- package/dist/services-typed-client-XWYYBWGW.js +46 -0
- package/dist/services-typed-client-XWYYBWGW.js.map +1 -0
- package/dist/services-typed-client-ZX5JUHH3.js +48 -0
- package/dist/services-typed-client-ZX5JUHH3.js.map +1 -0
- package/dist/sqlite-vec-54KB4RD3.js +41 -0
- package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
- package/dist/start-CSLJQXV3.js +609 -0
- package/dist/start-CSLJQXV3.js.map +1 -0
- package/dist/{static-5VE3U6UW.js → static-BH6JQNM2.js} +21 -25
- package/dist/static-BH6JQNM2.js.map +1 -0
- package/dist/storage-manager-D5KBXXKB.js +184 -0
- package/dist/storage-manager-D5KBXXKB.js.map +1 -0
- package/dist/storage-types-DtIVejC1.d.ts +96 -0
- package/dist/theo-cloud-3K4DGB3S.js +25 -0
- package/dist/theo-cloud-3K4DGB3S.js.map +1 -0
- package/dist/upgrade-readiness-OHL36FQL.js +249 -0
- package/dist/upgrade-readiness-OHL36FQL.js.map +1 -0
- package/dist/{vercel-UYRJLIUO.js → vercel-RHADE5IP.js} +15 -17
- package/dist/vercel-RHADE5IP.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +26 -2
- package/dist/vite-plugin/index.js +12 -4
- package/dist/vite-plugin-WA3G45YY.js +27 -0
- package/dist/vite-plugin-WA3G45YY.js.map +1 -0
- package/package.json +50 -4
- package/dist/add-WM3ED75L.js.map +0 -1
- package/dist/agent-types-CPKa4aJQ.d.ts +0 -41
- package/dist/aws-lambda-ZO4GOYP2.js.map +0 -1
- package/dist/build-WZUEDJYM.js +0 -88
- package/dist/build-WZUEDJYM.js.map +0 -1
- package/dist/bun-7OKHVGC2.js.map +0 -1
- package/dist/check-JKDIEYLK.js.map +0 -1
- package/dist/chunk-2H6ZSJMK.js.map +0 -1
- package/dist/chunk-3FMYMTOZ.js +0 -884
- package/dist/chunk-3FMYMTOZ.js.map +0 -1
- package/dist/chunk-BJBRL6MW.js.map +0 -1
- package/dist/chunk-IM6C5SOI.js.map +0 -1
- package/dist/chunk-NXAFLRZX.js +0 -159
- package/dist/chunk-NXAFLRZX.js.map +0 -1
- package/dist/chunk-RSUGZCW3.js +0 -766
- package/dist/chunk-RSUGZCW3.js.map +0 -1
- package/dist/chunk-RTLNG5GW.js +0 -758
- package/dist/chunk-RTLNG5GW.js.map +0 -1
- package/dist/chunk-UB6YKEU6.js +0 -178
- package/dist/chunk-UB6YKEU6.js.map +0 -1
- package/dist/chunk-YEPCVSSU.js +0 -44
- package/dist/chunk-YEPCVSSU.js.map +0 -1
- package/dist/chunk-ZJH7C62S.js +0 -1201
- package/dist/chunk-ZJH7C62S.js.map +0 -1
- package/dist/cloudflare-LII4TDWG.js.map +0 -1
- package/dist/deno-deploy-HH72FFQ5.js.map +0 -1
- package/dist/dev-GMIWKTOP.js +0 -49
- package/dist/dev-GMIWKTOP.js.map +0 -1
- package/dist/docker-NQ5NM4IW.js.map +0 -1
- package/dist/generate-MYEUR7I7.js.map +0 -1
- package/dist/info-QGSURJCC.js.map +0 -1
- package/dist/netlify-26SFJJPO.js.map +0 -1
- package/dist/rate-limit-C6hHXIj1.d.ts +0 -13
- package/dist/routes-A2VOUPII.js.map +0 -1
- package/dist/scan-MK37O2RS.js +0 -9
- package/dist/start-GWH7GHUC.js +0 -299
- package/dist/start-GWH7GHUC.js.map +0 -1
- package/dist/static-5VE3U6UW.js.map +0 -1
- package/dist/vercel-UYRJLIUO.js.map +0 -1
- /package/dist/{scan-MK37O2RS.js.map → broadcast-4SX7R5XE.js.map} +0 -0
|
@@ -37,12 +37,31 @@ interface ShimContext {
|
|
|
37
37
|
/** Resolves to a Web Standard Response once `res.end()` is called. */
|
|
38
38
|
toResponse: () => Promise<Response>;
|
|
39
39
|
}
|
|
40
|
+
interface CreateWebShimOptions {
|
|
41
|
+
/**
|
|
42
|
+
* CR-018 fix: how to resolve the client IP from forwarded headers.
|
|
43
|
+
*
|
|
44
|
+
* - `'platform'` (default for `cf-connecting-ip`): trust runtime-injected
|
|
45
|
+
* headers only (`cf-connecting-ip` on Cloudflare, `x-real-ip` on
|
|
46
|
+
* Netlify/Vercel when the platform writes it). Ignores
|
|
47
|
+
* `x-forwarded-for` because clients can spoof it.
|
|
48
|
+
* - `'trusted-proxy'`: read the **rightmost** entry of `x-forwarded-for`.
|
|
49
|
+
* Only safe when the request literally went through a trusted proxy
|
|
50
|
+
* that strips client-set headers and appends the real client IP last.
|
|
51
|
+
* - `'none'`: skip all forwarded-header lookups and report
|
|
52
|
+
* `'0.0.0.0'`. Force this when the adapter has no reliable way to
|
|
53
|
+
* identify the client (rate-limiters then must use a different key).
|
|
54
|
+
*
|
|
55
|
+
* Default: `'platform'`.
|
|
56
|
+
*/
|
|
57
|
+
trustedProxy?: 'platform' | 'trusted-proxy' | 'none';
|
|
58
|
+
}
|
|
40
59
|
/**
|
|
41
60
|
* Build Node-style req/res objects around a Web Standard Request.
|
|
42
61
|
* `toResponse()` returns a Promise that resolves when `res.end()` has been
|
|
43
62
|
* invoked, materializing a Web Standard Response with the accumulated body
|
|
44
63
|
* + headers + status.
|
|
45
64
|
*/
|
|
46
|
-
declare function createWebShim(request: Request): ShimContext;
|
|
65
|
+
declare function createWebShim(request: Request, options?: CreateWebShimOptions): ShimContext;
|
|
47
66
|
|
|
48
|
-
export { type ShimContext, type ShimRequest, type ShimResponse, createWebShim };
|
|
67
|
+
export { type CreateWebShimOptions, type ShimContext, type ShimRequest, type ShimResponse, createWebShim };
|
|
@@ -1,40 +1,60 @@
|
|
|
1
|
+
import "../chunk-DGUM43GV.js";
|
|
2
|
+
|
|
1
3
|
// src/adapters/web-shim.ts
|
|
2
|
-
function
|
|
4
|
+
function resolveRemoteAddress(headers, policy) {
|
|
5
|
+
if (policy === "none") return "0.0.0.0";
|
|
6
|
+
const cf = headers["cf-connecting-ip"];
|
|
7
|
+
if (cf) return cf;
|
|
8
|
+
if (policy === "trusted-proxy") {
|
|
9
|
+
const xff = headers["x-forwarded-for"];
|
|
10
|
+
if (xff) {
|
|
11
|
+
const parts = xff.split(",").map((p) => p.trim()).filter(Boolean);
|
|
12
|
+
if (parts.length > 0) return parts[parts.length - 1];
|
|
13
|
+
}
|
|
14
|
+
const xri = headers["x-real-ip"];
|
|
15
|
+
if (xri) return xri;
|
|
16
|
+
}
|
|
17
|
+
return "0.0.0.0";
|
|
18
|
+
}
|
|
19
|
+
function createWebShim(request, options) {
|
|
3
20
|
const url = new URL(request.url);
|
|
4
21
|
const headers = {};
|
|
5
22
|
request.headers.forEach((value, key) => {
|
|
6
23
|
headers[key] = value;
|
|
7
24
|
});
|
|
8
|
-
const
|
|
9
|
-
let bodyConsumed = false;
|
|
25
|
+
const pumpState = { consumed: false };
|
|
10
26
|
const dataListeners = [];
|
|
11
27
|
const endListeners = [];
|
|
12
28
|
const errorListeners = [];
|
|
13
29
|
async function pumpBody() {
|
|
14
|
-
if (
|
|
15
|
-
|
|
30
|
+
if (pumpState.consumed) return;
|
|
31
|
+
pumpState.consumed = true;
|
|
16
32
|
if (!request.body) {
|
|
17
33
|
for (const cb of endListeners) cb();
|
|
18
34
|
return;
|
|
19
35
|
}
|
|
20
36
|
try {
|
|
21
37
|
const reader = request.body.getReader();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
let done = false;
|
|
39
|
+
while (!done) {
|
|
40
|
+
const chunk = await reader.read();
|
|
41
|
+
done = chunk.done;
|
|
42
|
+
if (!done && chunk.value) {
|
|
43
|
+
for (const cb of dataListeners) cb(chunk.value);
|
|
44
|
+
}
|
|
26
45
|
}
|
|
27
46
|
for (const cb of endListeners) cb();
|
|
28
47
|
} catch (err) {
|
|
29
48
|
for (const cb of errorListeners) cb(err);
|
|
30
49
|
}
|
|
31
50
|
}
|
|
51
|
+
const trustedProxy = options?.trustedProxy ?? "platform";
|
|
32
52
|
const req = {
|
|
33
53
|
method: request.method,
|
|
34
54
|
url: url.pathname + url.search,
|
|
35
55
|
headers,
|
|
36
56
|
socket: {
|
|
37
|
-
remoteAddress: headers
|
|
57
|
+
remoteAddress: resolveRemoteAddress(headers, trustedProxy)
|
|
38
58
|
},
|
|
39
59
|
on(event, cb) {
|
|
40
60
|
if (event === "data") dataListeners.push(cb);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/web-shim.ts"],"sourcesContent":["/**\n * Web-to-Node bridge used by adapters whose runtimes provide a `Request`\n * (Web Standard) but where TheoKit's `executeRoute` expects a Node-style\n * IncomingMessage/ServerResponse pair.\n *\n * Used by adapters that target Node-compatible runtimes (Bun, Netlify\n * Functions, AWS Lambda Node). Cloudflare uses an inline version of the same\n * pattern — long-term it should consolidate here too.\n *\n * NOT used by Deno Deploy: Deno's stdlib does not have `Buffer`/`node:http`\n * by default and forcing the shim there bloats the bundle. Deno wiring is\n * tracked separately and will likely require executeRoute to accept Web\n * Standard Request directly.\n */\n\nexport interface ShimRequest {\n method: string\n url: string\n headers: Record<string, string>\n socket: { remoteAddress: string }\n on: (event: 'data' | 'end' | 'error', cb: (chunk?: unknown) => void) => unknown\n}\n\nexport interface ShimResponse {\n statusCode: number\n headersSent: boolean\n writableEnded: boolean\n writeHead: (status: number, headers?: Record<string, string>) => void\n setHeader: (key: string, value: string) => void\n getHeader: (key: string) => string | undefined\n write: (chunk: Uint8Array | string) => boolean\n end: (body?: Uint8Array | string) => void\n}\n\nexport interface ShimContext {\n req: ShimRequest\n res: ShimResponse\n /** Resolves to a Web Standard Response once `res.end()` is called. */\n toResponse: () => Promise<Response>\n}\n\n/**\n * Build Node-style req/res objects around a Web Standard Request.\n * `toResponse()` returns a Promise that resolves when `res.end()` has been\n * invoked, materializing a Web Standard Response with the accumulated body\n * + headers + status.\n */\nexport function createWebShim(request: Request): ShimContext {\n const url = new URL(request.url)\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n const
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/web-shim.ts"],"sourcesContent":["/**\n * Web-to-Node bridge used by adapters whose runtimes provide a `Request`\n * (Web Standard) but where TheoKit's `executeRoute` expects a Node-style\n * IncomingMessage/ServerResponse pair.\n *\n * Used by adapters that target Node-compatible runtimes (Bun, Netlify\n * Functions, AWS Lambda Node). Cloudflare uses an inline version of the same\n * pattern — long-term it should consolidate here too.\n *\n * NOT used by Deno Deploy: Deno's stdlib does not have `Buffer`/`node:http`\n * by default and forcing the shim there bloats the bundle. Deno wiring is\n * tracked separately and will likely require executeRoute to accept Web\n * Standard Request directly.\n */\n\nexport interface ShimRequest {\n method: string\n url: string\n headers: Record<string, string>\n socket: { remoteAddress: string }\n on: (event: 'data' | 'end' | 'error', cb: (chunk?: unknown) => void) => unknown\n}\n\nexport interface ShimResponse {\n statusCode: number\n headersSent: boolean\n writableEnded: boolean\n writeHead: (status: number, headers?: Record<string, string>) => void\n setHeader: (key: string, value: string) => void\n getHeader: (key: string) => string | undefined\n write: (chunk: Uint8Array | string) => boolean\n end: (body?: Uint8Array | string) => void\n}\n\nexport interface ShimContext {\n req: ShimRequest\n res: ShimResponse\n /** Resolves to a Web Standard Response once `res.end()` is called. */\n toResponse: () => Promise<Response>\n}\n\nexport interface CreateWebShimOptions {\n /**\n * CR-018 fix: how to resolve the client IP from forwarded headers.\n *\n * - `'platform'` (default for `cf-connecting-ip`): trust runtime-injected\n * headers only (`cf-connecting-ip` on Cloudflare, `x-real-ip` on\n * Netlify/Vercel when the platform writes it). Ignores\n * `x-forwarded-for` because clients can spoof it.\n * - `'trusted-proxy'`: read the **rightmost** entry of `x-forwarded-for`.\n * Only safe when the request literally went through a trusted proxy\n * that strips client-set headers and appends the real client IP last.\n * - `'none'`: skip all forwarded-header lookups and report\n * `'0.0.0.0'`. Force this when the adapter has no reliable way to\n * identify the client (rate-limiters then must use a different key).\n *\n * Default: `'platform'`.\n */\n trustedProxy?: 'platform' | 'trusted-proxy' | 'none'\n}\n\nfunction resolveRemoteAddress(\n headers: Record<string, string>,\n policy: NonNullable<CreateWebShimOptions['trustedProxy']>,\n): string {\n if (policy === 'none') return '0.0.0.0'\n\n // Runtime-injected headers — these come from the platform itself and\n // cannot be set by the client.\n const cf = headers['cf-connecting-ip']\n if (cf) return cf\n\n if (policy === 'trusted-proxy') {\n // Take the RIGHTMOST entry of x-forwarded-for. The rightmost is the\n // hop nearest the application, which a trusted proxy appended; entries\n // to the left may have been forged by the client.\n const xff = headers['x-forwarded-for']\n if (xff) {\n const parts = xff\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean)\n if (parts.length > 0) return parts[parts.length - 1]\n }\n // x-real-ip is typically platform-set (NGINX/Netlify/Vercel).\n const xri = headers['x-real-ip']\n if (xri) return xri\n }\n\n return '0.0.0.0'\n}\n\n/**\n * Build Node-style req/res objects around a Web Standard Request.\n * `toResponse()` returns a Promise that resolves when `res.end()` has been\n * invoked, materializing a Web Standard Response with the accumulated body\n * + headers + status.\n */\nexport function createWebShim(request: Request, options?: CreateWebShimOptions): ShimContext {\n const url = new URL(request.url)\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n const pumpState = { consumed: false }\n\n // Consume the request body once, then dispatch data/end events to whatever\n // listener attaches later (executeRoute's body parser).\n const dataListeners: ((chunk: Uint8Array) => void)[] = []\n const endListeners: (() => void)[] = []\n const errorListeners: ((err: unknown) => void)[] = []\n\n async function pumpBody(): Promise<void> {\n if (pumpState.consumed) return\n pumpState.consumed = true\n if (!request.body) {\n for (const cb of endListeners) cb()\n return\n }\n try {\n const reader = request.body.getReader()\n let done = false\n while (!done) {\n const chunk = await reader.read()\n done = chunk.done\n if (!done && chunk.value) {\n for (const cb of dataListeners) cb(chunk.value)\n }\n }\n for (const cb of endListeners) cb()\n } catch (err) {\n for (const cb of errorListeners) cb(err)\n }\n }\n\n const trustedProxy = options?.trustedProxy ?? 'platform'\n\n const req: ShimRequest = {\n method: request.method,\n url: url.pathname + url.search,\n headers,\n socket: {\n remoteAddress: resolveRemoteAddress(headers, trustedProxy),\n },\n on(event, cb) {\n if (event === 'data') dataListeners.push(cb)\n if (event === 'end') endListeners.push(cb)\n if (event === 'error') errorListeners.push(cb)\n // Lazily start pumping when end is registered (executeRoute always\n // listens for end before doing anything with the body).\n if (event === 'end') void pumpBody()\n return req\n },\n }\n\n const responseChunks: Uint8Array[] = []\n const responseHeaders: Record<string, string> = {}\n let responseStatus = 200\n let resolveResponse!: (r: Response) => void\n const responsePromise = new Promise<Response>((resolve) => {\n resolveResponse = resolve\n })\n\n function chunkToBytes(chunk: Uint8Array | string): Uint8Array {\n if (typeof chunk === 'string') return new TextEncoder().encode(chunk)\n return chunk\n }\n\n const res: ShimResponse = {\n statusCode: 200,\n headersSent: false,\n writableEnded: false,\n writeHead(status, headers) {\n responseStatus = status\n this.statusCode = status\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n responseHeaders[k.toLowerCase()] = v\n }\n }\n this.headersSent = true\n },\n setHeader(key, value) {\n responseHeaders[key.toLowerCase()] = value\n },\n getHeader(key) {\n return responseHeaders[key.toLowerCase()]\n },\n write(chunk) {\n responseChunks.push(chunkToBytes(chunk))\n return true\n },\n end(body) {\n if (body !== undefined) responseChunks.push(chunkToBytes(body))\n this.writableEnded = true\n const totalLength = responseChunks.reduce((n, c) => n + c.length, 0)\n const concat = new Uint8Array(totalLength)\n let offset = 0\n for (const c of responseChunks) {\n concat.set(c, offset)\n offset += c.length\n }\n resolveResponse(\n new Response(concat, {\n status: responseStatus,\n headers: responseHeaders,\n }),\n )\n },\n }\n\n return {\n req,\n res,\n toResponse: () => responsePromise,\n }\n}\n"],"mappings":"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","names":["headers"]}
|
package/dist/adapters/ws-shim.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import "../chunk-DGUM43GV.js";
|
|
2
|
+
|
|
1
3
|
// src/adapters/ws-shim.ts
|
|
2
4
|
function createNodeWsBridge(handler) {
|
|
3
5
|
return {
|
|
4
6
|
attach(nativeWs) {
|
|
5
7
|
const wsLike = {
|
|
6
|
-
send: (data) =>
|
|
7
|
-
|
|
8
|
+
send: (data) => {
|
|
9
|
+
nativeWs.send(data);
|
|
10
|
+
},
|
|
11
|
+
close: (code, reason) => {
|
|
12
|
+
nativeWs.close(code, reason);
|
|
13
|
+
},
|
|
8
14
|
on: (event, cb) => {
|
|
9
15
|
nativeWs.on(event, cb);
|
|
10
16
|
}
|
|
11
17
|
};
|
|
12
18
|
nativeWs.on("open", () => handler.onOpen?.(wsLike));
|
|
13
|
-
nativeWs.on(
|
|
14
|
-
"message",
|
|
15
|
-
(data) => handler.onMessage?.(wsLike, data)
|
|
16
|
-
);
|
|
19
|
+
nativeWs.on("message", (data) => handler.onMessage?.(wsLike, data));
|
|
17
20
|
nativeWs.on("close", (codeMaybe) => {
|
|
18
21
|
const code = typeof codeMaybe === "number" ? codeMaybe : void 0;
|
|
19
22
|
handler.onClose?.(wsLike, code);
|
|
@@ -25,8 +28,12 @@ function createNodeWsBridge(handler) {
|
|
|
25
28
|
}
|
|
26
29
|
function bunCtxToWsLike(bunWs) {
|
|
27
30
|
return {
|
|
28
|
-
send: (data) =>
|
|
29
|
-
|
|
31
|
+
send: (data) => {
|
|
32
|
+
bunWs.send(data);
|
|
33
|
+
},
|
|
34
|
+
close: (code, reason) => {
|
|
35
|
+
bunWs.close(code, reason);
|
|
36
|
+
},
|
|
30
37
|
on: () => {
|
|
31
38
|
}
|
|
32
39
|
};
|
|
@@ -49,8 +56,12 @@ function createDenoWsBridge(handler, denoNs) {
|
|
|
49
56
|
handle(request) {
|
|
50
57
|
const { response, socket } = denoNs.upgradeWebSocket(request);
|
|
51
58
|
const wsLike = {
|
|
52
|
-
send: (data) =>
|
|
53
|
-
|
|
59
|
+
send: (data) => {
|
|
60
|
+
socket.send(data);
|
|
61
|
+
},
|
|
62
|
+
close: (code, reason) => {
|
|
63
|
+
socket.close(code, reason);
|
|
64
|
+
},
|
|
54
65
|
on: () => {
|
|
55
66
|
}
|
|
56
67
|
};
|
|
@@ -63,10 +74,7 @@ function createDenoWsBridge(handler, denoNs) {
|
|
|
63
74
|
const ev = e;
|
|
64
75
|
handler.onClose?.(wsLike, ev.code, ev.reason);
|
|
65
76
|
});
|
|
66
|
-
socket.addEventListener(
|
|
67
|
-
"error",
|
|
68
|
-
(err) => handler.onError?.(wsLike, err)
|
|
69
|
-
);
|
|
77
|
+
socket.addEventListener("error", (err) => handler.onError?.(wsLike, err));
|
|
70
78
|
return response;
|
|
71
79
|
}
|
|
72
80
|
};
|
|
@@ -76,17 +84,24 @@ function createCloudflareWsBridge(handler) {
|
|
|
76
84
|
handle(_request) {
|
|
77
85
|
const g = globalThis;
|
|
78
86
|
if (!g.WebSocketPair) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"WebSocketPair is not available \u2014 Cloudflare Workers runtime expected."
|
|
81
|
-
);
|
|
87
|
+
throw new Error("WebSocketPair is not available \u2014 Cloudflare Workers runtime expected.");
|
|
82
88
|
}
|
|
83
89
|
const pair = new g.WebSocketPair();
|
|
84
90
|
const client = pair[0];
|
|
85
91
|
const server = pair[1];
|
|
86
92
|
server.accept();
|
|
87
93
|
const wsLike = {
|
|
88
|
-
send: (data) =>
|
|
89
|
-
|
|
94
|
+
send: (data) => {
|
|
95
|
+
server.send(data);
|
|
96
|
+
},
|
|
97
|
+
close: (code, reason) => {
|
|
98
|
+
server.close(code, reason);
|
|
99
|
+
},
|
|
100
|
+
// Cloudflare's WebSocketPair exposes events via addEventListener;
|
|
101
|
+
// the WebSocketLike `on` contract is for Node-style ws. We leave
|
|
102
|
+
// it as a no-op so user handlers that expect Node-style `on(...)`
|
|
103
|
+
// do not crash — but the events flow through addEventListener
|
|
104
|
+
// below, not through this method.
|
|
90
105
|
on: () => {
|
|
91
106
|
}
|
|
92
107
|
};
|
|
@@ -99,10 +114,7 @@ function createCloudflareWsBridge(handler) {
|
|
|
99
114
|
const ev = e;
|
|
100
115
|
handler.onClose?.(wsLike, ev.code, ev.reason);
|
|
101
116
|
});
|
|
102
|
-
server.addEventListener(
|
|
103
|
-
"error",
|
|
104
|
-
(err) => handler.onError?.(wsLike, err)
|
|
105
|
-
);
|
|
117
|
+
server.addEventListener("error", (err) => handler.onError?.(wsLike, err));
|
|
106
118
|
try {
|
|
107
119
|
return new Response(null, {
|
|
108
120
|
status: 101,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/ws-shim.ts"],"sourcesContent":["/**\n * T3.1 — WebSocket cross-runtime bridges.\n *\n * Exports 4 bridges (Node `ws` package, Bun.serve, Deno.upgradeWebSocket,\n * Cloudflare WebSocketPair) that adapt each runtime's native WebSocket API\n * to a common `WebSocketLike` interface that user handlers (`defineWebSocket`)\n * can consume uniformly.\n */\n\nexport interface WebSocketLike {\n send(data: string | Uint8Array | ArrayBuffer): void\n close(code?: number, reason?: string): void\n on(
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/ws-shim.ts"],"sourcesContent":["/**\n * T3.1 — WebSocket cross-runtime bridges.\n *\n * Exports 4 bridges (Node `ws` package, Bun.serve, Deno.upgradeWebSocket,\n * Cloudflare WebSocketPair) that adapt each runtime's native WebSocket API\n * to a common `WebSocketLike` interface that user handlers (`defineWebSocket`)\n * can consume uniformly.\n */\n\nexport interface WebSocketLike {\n send(data: string | Uint8Array | ArrayBuffer): void\n close(code?: number, reason?: string): void\n on(event: 'message' | 'close' | 'error', cb: (data?: unknown) => void): void\n}\n\nexport interface WsHandler {\n onOpen?: (ws: WebSocketLike) => void\n onMessage?: (ws: WebSocketLike, data: unknown) => void\n onClose?: (ws: WebSocketLike, code?: number, reason?: string) => void\n onError?: (ws: WebSocketLike, err: unknown) => void\n}\n\n// ============================================================\n// Node bridge — wraps `ws` package WebSocket\n// ============================================================\n\ninterface NodeWsLike {\n on(event: string, cb: (arg?: unknown) => void): unknown\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport function createNodeWsBridge(handler: WsHandler): {\n attach(nativeWs: NodeWsLike): WebSocketLike\n} {\n return {\n attach(nativeWs: NodeWsLike): WebSocketLike {\n const wsLike: WebSocketLike = {\n send: (data) => {\n nativeWs.send(data)\n },\n close: (code, reason) => {\n nativeWs.close(code, reason)\n },\n on: (event, cb) => {\n nativeWs.on(event, cb)\n },\n }\n nativeWs.on('open', () => handler.onOpen?.(wsLike))\n nativeWs.on('message', (data) => handler.onMessage?.(wsLike, data))\n nativeWs.on('close', (codeMaybe) => {\n const code = typeof codeMaybe === 'number' ? codeMaybe : undefined\n handler.onClose?.(wsLike, code)\n })\n nativeWs.on('error', (err) => handler.onError?.(wsLike, err))\n return wsLike\n },\n }\n}\n\n// ============================================================\n// Bun bridge — returns config for Bun.serve({ websocket })\n// ============================================================\n\ninterface BunWsContext {\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport interface BunWebSocketConfig {\n open: (ws: BunWsContext) => void\n message: (ws: BunWsContext, message: unknown) => void\n close: (ws: BunWsContext, code?: number, reason?: string) => void\n}\n\nfunction bunCtxToWsLike(bunWs: BunWsContext): WebSocketLike {\n return {\n send: (data) => {\n bunWs.send(data)\n },\n close: (code, reason) => {\n bunWs.close(code, reason)\n },\n on: () => {\n // Bun pushes events via the config handlers — no per-instance on()\n },\n }\n}\n\nexport function createBunWsBridge(handler: WsHandler): BunWebSocketConfig {\n return {\n open(bunWs) {\n handler.onOpen?.(bunCtxToWsLike(bunWs))\n },\n message(bunWs, msg) {\n handler.onMessage?.(bunCtxToWsLike(bunWs), msg)\n },\n close(bunWs, code, reason) {\n handler.onClose?.(bunCtxToWsLike(bunWs), code, reason)\n },\n }\n}\n\n// ============================================================\n// Deno bridge — uses Deno.upgradeWebSocket(request)\n// ============================================================\n\ninterface DenoLike {\n upgradeWebSocket(req: Request): {\n response: Response\n socket: {\n send(data: unknown): void\n close(code?: number, reason?: string): void\n addEventListener(event: string, cb: (e: unknown) => void): void\n }\n }\n}\n\nexport function createDenoWsBridge(\n handler: WsHandler,\n denoNs: DenoLike,\n): { handle(request: Request): Response } {\n return {\n handle(request: Request): Response {\n const { response, socket } = denoNs.upgradeWebSocket(request)\n const wsLike: WebSocketLike = {\n send: (data) => {\n socket.send(data)\n },\n close: (code, reason) => {\n socket.close(code, reason)\n },\n on: () => {\n // Deno uses addEventListener — adapter dispatches below\n },\n }\n socket.addEventListener('open', () => handler.onOpen?.(wsLike))\n socket.addEventListener('message', (e) => {\n const data = (e as { data?: unknown }).data\n handler.onMessage?.(wsLike, data)\n })\n socket.addEventListener('close', (e) => {\n const ev = e as { code?: number; reason?: string }\n handler.onClose?.(wsLike, ev.code, ev.reason)\n })\n socket.addEventListener('error', (err) => handler.onError?.(wsLike, err))\n return response\n },\n }\n}\n\n// ============================================================\n// Cloudflare bridge — uses globalThis.WebSocketPair\n// ============================================================\n\ninterface CfServerWs {\n accept(): void\n addEventListener(event: string, cb: (e: unknown) => void): void\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport function createCloudflareWsBridge(handler: WsHandler): {\n handle(request: Request): Response\n} {\n return {\n handle(_request: Request): Response {\n const g = globalThis as {\n WebSocketPair?: new () => { 0: unknown; 1: CfServerWs }\n }\n if (!g.WebSocketPair) {\n throw new Error('WebSocketPair is not available — Cloudflare Workers runtime expected.')\n }\n const pair = new g.WebSocketPair()\n const client = pair[0]\n const server = pair[1]\n server.accept()\n const wsLike: WebSocketLike = {\n send: (data) => {\n server.send(data)\n },\n close: (code, reason) => {\n server.close(code, reason)\n },\n // Cloudflare's WebSocketPair exposes events via addEventListener;\n // the WebSocketLike `on` contract is for Node-style ws. We leave\n // it as a no-op so user handlers that expect Node-style `on(...)`\n // do not crash — but the events flow through addEventListener\n // below, not through this method.\n on: (): void => {\n /* intentional no-op on Cloudflare; see comment above */\n },\n }\n server.addEventListener('open', () => handler.onOpen?.(wsLike))\n server.addEventListener('message', (e) => {\n const data = (e as { data?: unknown }).data\n handler.onMessage?.(wsLike, data)\n })\n server.addEventListener('close', (e) => {\n const ev = e as { code?: number; reason?: string }\n handler.onClose?.(wsLike, ev.code, ev.reason)\n })\n server.addEventListener('error', (err) => handler.onError?.(wsLike, err))\n // CF requires returning Response with status 101 and webSocket pair.\n // Node's Response throws on 101; fall back to a plain object that\n // satisfies the CF runtime's protocol but matches our test shape.\n try {\n return new Response(null, {\n status: 101,\n webSocket: client,\n } as ResponseInit & { webSocket: unknown })\n } catch {\n return { status: 101, webSocket: client } as unknown as Response\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
+
import "./chunk-KT3MWNZG.js";
|
|
3
4
|
|
|
4
5
|
// src/cli/commands/add.ts
|
|
6
|
+
import { spawn } from "child_process";
|
|
5
7
|
import { existsSync } from "fs";
|
|
6
8
|
import { resolve } from "path";
|
|
7
|
-
import { spawn } from "child_process";
|
|
8
9
|
var InvalidPackageNameError = class extends Error {
|
|
9
10
|
constructor(input, reason) {
|
|
10
11
|
super(`Invalid package name "${input}": ${reason}`);
|
|
@@ -25,10 +26,7 @@ function validatePackageInput(input) {
|
|
|
25
26
|
throw new InvalidPackageNameError(input, "must be a non-empty string");
|
|
26
27
|
}
|
|
27
28
|
if (!PACKAGE_NAME_REGEX.test(input)) {
|
|
28
|
-
throw new InvalidPackageNameError(
|
|
29
|
-
input,
|
|
30
|
-
"allowed: lowercase letters, digits, hyphens only"
|
|
31
|
-
);
|
|
29
|
+
throw new InvalidPackageNameError(input, "allowed: lowercase letters, digits, hyphens only");
|
|
32
30
|
}
|
|
33
31
|
return true;
|
|
34
32
|
}
|
|
@@ -66,7 +64,10 @@ function levenshtein(a, b) {
|
|
|
66
64
|
const n = b.length;
|
|
67
65
|
if (m === 0) return n;
|
|
68
66
|
if (n === 0) return m;
|
|
69
|
-
const dp = Array.from(
|
|
67
|
+
const dp = Array.from(
|
|
68
|
+
{ length: m + 1 },
|
|
69
|
+
() => Array.from({ length: n + 1 }).fill(0)
|
|
70
|
+
);
|
|
70
71
|
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
71
72
|
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
72
73
|
for (let i = 1; i <= m; i++) {
|
|
@@ -89,11 +90,11 @@ function findSuggestion(input, candidates) {
|
|
|
89
90
|
async function runAdd(deps) {
|
|
90
91
|
validatePackageInput(deps.input);
|
|
91
92
|
const registry = deps.registry ?? KNOWN_PACKAGES;
|
|
92
|
-
|
|
93
|
-
if (!entry) {
|
|
93
|
+
if (!Object.hasOwn(registry, deps.input)) {
|
|
94
94
|
const suggestion = findSuggestion(deps.input, Object.keys(registry));
|
|
95
95
|
throw new UnknownPackageError(deps.input, suggestion);
|
|
96
96
|
}
|
|
97
|
+
const entry = registry[deps.input];
|
|
97
98
|
if (entry.kind === "bundled") {
|
|
98
99
|
return {
|
|
99
100
|
packageInstalled: `${deps.input} (bundled inside theokit)`,
|
|
@@ -127,10 +128,14 @@ async function runAdd(deps) {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
function defaultSpawnPm(cmd, args, opts) {
|
|
130
|
-
return new Promise((
|
|
131
|
+
return new Promise((resolve2) => {
|
|
131
132
|
const proc = spawn(cmd, args, { cwd: opts.cwd, shell: opts.shell, stdio: "inherit" });
|
|
132
|
-
proc.on("close", (code) =>
|
|
133
|
-
|
|
133
|
+
proc.on("close", (code) => {
|
|
134
|
+
resolve2({ ok: code === 0, code: code ?? 1 });
|
|
135
|
+
});
|
|
136
|
+
proc.on("error", () => {
|
|
137
|
+
resolve2({ ok: false, code: 1 });
|
|
138
|
+
});
|
|
134
139
|
});
|
|
135
140
|
}
|
|
136
141
|
async function addCommand(input) {
|
|
@@ -157,4 +162,4 @@ export {
|
|
|
157
162
|
runAdd,
|
|
158
163
|
validatePackageInput
|
|
159
164
|
};
|
|
160
|
-
//# sourceMappingURL=add-
|
|
165
|
+
//# sourceMappingURL=add-2ZFFGGE4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/add.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * CLI `theo add` — detects pkg manager + writes manifests under user `cwd`.\n * Build-time tool. No HTTP input.\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport class InvalidPackageNameError extends Error {\n constructor(input: string, reason: string) {\n super(`Invalid package name \"${input}\": ${reason}`)\n this.name = 'InvalidPackageNameError'\n }\n}\n\nexport class UnknownPackageError extends Error {\n constructor(input: string, suggestion?: string) {\n super(\n suggestion\n ? `Unknown package: ${input}. Did you mean: ${suggestion}?`\n : `Unknown package: ${input}.`,\n )\n this.name = 'UnknownPackageError'\n }\n}\n\n/**\n * EC-4 — strict whitelist of characters allowed in package input.\n * Lowercase ASCII letters, digits, hyphens. Must start with letter/digit.\n * Rejects: shell metacharacters, path traversal, scope syntax, uppercase.\n */\nconst PACKAGE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/\n\nexport function validatePackageInput(input: string): true {\n if (typeof input !== 'string' || input.length === 0) {\n throw new InvalidPackageNameError(input, 'must be a non-empty string')\n }\n if (!PACKAGE_NAME_REGEX.test(input)) {\n throw new InvalidPackageNameError(input, 'allowed: lowercase letters, digits, hyphens only')\n }\n return true\n}\n\nexport type PackageManager = 'pnpm' | 'bun' | 'yarn' | 'npm'\n\nexport function detectPackageManager(lockfiles: Record<string, boolean>): PackageManager {\n if (lockfiles['pnpm-lock.yaml']) return 'pnpm'\n if (lockfiles['bun.lockb']) return 'bun'\n if (lockfiles['yarn.lock']) return 'yarn'\n if (lockfiles['package-lock.json']) return 'npm'\n return 'npm'\n}\n\nexport interface PackageRegistryEntry {\n /** 'bundled' = adapter already shipped inside theokit; no npm install needed.\n * 'external' = adapter/plugin lives as a separate npm package. */\n kind: 'bundled' | 'external'\n /** Required when kind === 'external'. */\n npm?: string\n /** Usage hint printed to the user (e.g. `theokit build --target=bun`). */\n usage: string\n}\n\nexport const KNOWN_PACKAGES: Record<string, PackageRegistryEntry> = {\n bun: {\n kind: 'bundled',\n usage: 'theokit build --target=bun',\n },\n deno: {\n kind: 'bundled',\n usage: 'theokit build --target=deno-deploy',\n },\n netlify: {\n kind: 'bundled',\n usage: 'theokit build --target=netlify',\n },\n 'aws-lambda': {\n kind: 'bundled',\n usage: 'theokit build --target=aws-lambda',\n },\n static: {\n kind: 'bundled',\n usage: 'theokit build --target=static',\n },\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length\n const n = b.length\n if (m === 0) return n\n if (n === 0) return m\n const dp: number[][] = Array.from({ length: m + 1 }, () =>\n Array.from<number>({ length: n + 1 }).fill(0),\n )\n for (let i = 0; i <= m; i++) dp[i][0] = i\n for (let j = 0; j <= n; j++) dp[0][j] = j\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1\n dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)\n }\n }\n return dp[m][n]\n}\n\nfunction findSuggestion(input: string, candidates: string[]): string | undefined {\n let best: { name: string; d: number } | null = null\n for (const c of candidates) {\n const d = levenshtein(input, c)\n if (best === null || d < best.d) best = { name: c, d }\n }\n if (best && best.d <= 3) return best.name\n return undefined\n}\n\nexport interface RunAddDeps {\n input: string\n cwd: string\n existsSync?: (path: string) => boolean\n spawnPm?: (\n cmd: string,\n args: string[],\n opts: { cwd: string; shell: boolean },\n ) => Promise<{ ok: boolean; code: number }>\n registry?: Record<string, PackageRegistryEntry>\n}\n\nexport interface AddResult {\n packageInstalled: string\n packageManager: PackageManager | 'bundled'\n usage: string\n exitCode: 0\n}\n\nexport async function runAdd(deps: RunAddDeps): Promise<AddResult> {\n validatePackageInput(deps.input)\n\n const registry = deps.registry ?? KNOWN_PACKAGES\n if (!Object.hasOwn(registry, deps.input)) {\n const suggestion = findSuggestion(deps.input, Object.keys(registry))\n throw new UnknownPackageError(deps.input, suggestion)\n }\n const entry = registry[deps.input]\n\n // T6.1 — bundled adapters need no install\n if (entry.kind === 'bundled') {\n return {\n packageInstalled: `${deps.input} (bundled inside theokit)`,\n packageManager: 'bundled',\n usage: entry.usage,\n exitCode: 0,\n }\n }\n\n if (!entry.npm) {\n throw new Error(`External package \"${deps.input}\" missing npm field in registry`)\n }\n\n const existsFn = deps.existsSync ?? ((p: string) => existsSync(p))\n const lockfiles = {\n 'pnpm-lock.yaml': existsFn(resolve(deps.cwd, 'pnpm-lock.yaml')),\n 'bun.lockb': existsFn(resolve(deps.cwd, 'bun.lockb')),\n 'yarn.lock': existsFn(resolve(deps.cwd, 'yarn.lock')),\n 'package-lock.json': existsFn(resolve(deps.cwd, 'package-lock.json')),\n }\n const pm = detectPackageManager(lockfiles)\n\n const args = pm === 'npm' ? ['install', entry.npm] : ['add', entry.npm]\n const spawnFn = deps.spawnPm ?? defaultSpawnPm\n const result = await spawnFn(pm, args, { cwd: deps.cwd, shell: false })\n\n if (!result.ok) {\n throw new Error(`Package manager exited with non-zero exit code ${result.code}`)\n }\n\n return {\n packageInstalled: entry.npm,\n packageManager: pm,\n usage: entry.usage,\n exitCode: 0,\n }\n}\n\nfunction defaultSpawnPm(\n cmd: string,\n args: string[],\n opts: { cwd: string; shell: boolean },\n): Promise<{ ok: boolean; code: number }> {\n return new Promise((resolve) => {\n const proc = spawn(cmd, args, { cwd: opts.cwd, shell: opts.shell, stdio: 'inherit' })\n proc.on('close', (code) => {\n resolve({ ok: code === 0, code: code ?? 1 })\n })\n proc.on('error', () => {\n resolve({ ok: false, code: 1 })\n })\n })\n}\n\nexport async function addCommand(input: string): Promise<void> {\n try {\n const result = await runAdd({ input, cwd: process.cwd() })\n console.log(`\\n ✓ Installed ${result.packageInstalled} via ${result.packageManager}\\n`)\n console.log(` Next: import { /* ... */ } from '${result.packageInstalled}'\\n`)\n } catch (err) {\n console.error(`\\n ✗ ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n"],"mappings":"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","names":["resolve"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/contracts/agent-events.ts
|
|
3
|
+
*
|
|
4
|
+
* Canonical home for the AgentEvent wire-format contract (T2.2 of
|
|
5
|
+
* architecture-cleanup plan; ADR-0001 v3 invariant #3 exception).
|
|
6
|
+
*
|
|
7
|
+
* Server emits via SSE; client consumes. Discriminated union by `type`.
|
|
8
|
+
* Both `server/agent/*` and `client/*` import from here.
|
|
9
|
+
*
|
|
10
|
+
* Per ADR-0001 v3: `cache → core/contracts`, `client → core/contracts`,
|
|
11
|
+
* `devtools → core/contracts` are LEGAL direct imports (this is the
|
|
12
|
+
* documented exception to the `no-cross-module-deep-import` rule).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Discriminated union for AgentRunError codes (Phase 1 — Production-Readiness #3).
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `@usetheo/sdk`'s `AgentRunErrorCode` without hard-importing the SDK
|
|
18
|
+
* type (D2 decoupling). EC-7 forward-compat: `(string & {})` fallback preserves
|
|
19
|
+
* autocompletion for the known codes while accepting future codes the SDK may
|
|
20
|
+
* introduce — autocomplete works AND TS doesn't reject unknown codes.
|
|
21
|
+
*/
|
|
22
|
+
type AgentRunErrorCode = 'auth' | 'rate_limit' | 'quota_exceeded' | 'invalid_model' | 'invalid_request' | 'invalid_input' | 'context_too_large' | 'safety_blocked' | 'provider_unreachable' | 'tool_runtime_error' | 'aborted' | 'unknown' | (string & {});
|
|
23
|
+
interface AgentMessageEvent {
|
|
24
|
+
type: 'message';
|
|
25
|
+
content: string;
|
|
26
|
+
/** Optional id for client-side dedup / animation keys. */
|
|
27
|
+
id?: string;
|
|
28
|
+
}
|
|
29
|
+
interface AgentToolCallEvent {
|
|
30
|
+
type: 'tool_call';
|
|
31
|
+
name: string;
|
|
32
|
+
args: Record<string, unknown>;
|
|
33
|
+
id?: string;
|
|
34
|
+
}
|
|
35
|
+
interface AgentToolResultEvent {
|
|
36
|
+
type: 'tool_result';
|
|
37
|
+
name: string;
|
|
38
|
+
data: unknown;
|
|
39
|
+
id?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error event emitted via SSE.
|
|
43
|
+
*
|
|
44
|
+
* Phase 1 — Production-Readiness #3:
|
|
45
|
+
* New optional fields `code/provider/retriable/retryAfterMs` discriminate
|
|
46
|
+
* error classes for client-side handling (auth → sign-in CTA; rate_limit →
|
|
47
|
+
* countdown; quota_exceeded → upsell; etc.).
|
|
48
|
+
*
|
|
49
|
+
* Backward compat (D4): all new fields are optional. Existing clients that
|
|
50
|
+
* read only `event.message` keep working unchanged. New clients can safely
|
|
51
|
+
* `switch (event.code)` — `undefined` (legacy server) is a valid case.
|
|
52
|
+
*
|
|
53
|
+
* EC-15 (DOCUMENT): `message` is *trusted* to not contain secrets. SDK's
|
|
54
|
+
* `AgentRunError.message` is propagated verbatim. The SDK's `providerError`
|
|
55
|
+
* is NEVER serialized into this event — quarantined to prevent leakage.
|
|
56
|
+
*/
|
|
57
|
+
interface AgentErrorEvent {
|
|
58
|
+
type: 'error';
|
|
59
|
+
message: string;
|
|
60
|
+
id?: string;
|
|
61
|
+
/** Discriminated error class — see AgentRunErrorCode. */
|
|
62
|
+
code?: AgentRunErrorCode;
|
|
63
|
+
/** Provider id (e.g., 'openai', 'anthropic', 'openrouter'). */
|
|
64
|
+
provider?: string;
|
|
65
|
+
/** Whether the SAME request can be retried as-is. */
|
|
66
|
+
retriable?: boolean;
|
|
67
|
+
/** Hint from provider's Retry-After header (milliseconds). Zero is valid (immediate retry). */
|
|
68
|
+
retryAfterMs?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Runtime AgentEvent — discriminated union of the 4 variants emitted by
|
|
72
|
+
* agent endpoints. Server produces; client consumes.
|
|
73
|
+
*/
|
|
74
|
+
type AgentEvent = AgentMessageEvent | AgentToolCallEvent | AgentToolResultEvent | AgentErrorEvent;
|
|
75
|
+
|
|
76
|
+
export type { AgentEvent as A, AgentErrorEvent as a, AgentMessageEvent as b, AgentToolCallEvent as c, AgentToolResultEvent as d, AgentRunErrorCode as e };
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
nodeAdapter
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-Y5KE6YFP.js";
|
|
6
|
+
import {
|
|
7
|
+
assertServicesUnsupported,
|
|
8
|
+
readManifest
|
|
9
|
+
} from "./chunk-WZ7CPVQB.js";
|
|
10
|
+
import "./chunk-KT3MWNZG.js";
|
|
11
11
|
|
|
12
12
|
// src/adapters/aws-lambda.ts
|
|
13
13
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -137,27 +137,30 @@ function renderAwsLambdaEntry() {
|
|
|
137
137
|
` const { req, res, toResponse } = createWebShim(request)`,
|
|
138
138
|
` const requestId = randomUUID()`,
|
|
139
139
|
` const method = request.method.toUpperCase()`,
|
|
140
|
-
` await executeRoute(match.route, method, match.params, req, res, loaderCache, serverDir, requestId)`,
|
|
140
|
+
` await executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId })`,
|
|
141
141
|
` const response = await toResponse()`,
|
|
142
142
|
` return responseToV2Result(response)`,
|
|
143
143
|
`}`
|
|
144
144
|
].join("\n");
|
|
145
145
|
}
|
|
146
|
-
async function buildAwsLambda(config, cwd, deps = {}) {
|
|
146
|
+
async function buildAwsLambda(config, cwd, deps = {}, ctx) {
|
|
147
|
+
assertServicesUnsupported("aws-lambda", readManifest(cwd));
|
|
147
148
|
const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter);
|
|
148
|
-
await runNodeBuild(config, cwd);
|
|
149
|
+
await runNodeBuild(config, cwd, ctx);
|
|
149
150
|
const outputDir = resolve(cwd, ".theo/aws");
|
|
150
151
|
const ensureDir = deps.ensureDir ?? ((p) => mkdirSync(p, { recursive: true }));
|
|
151
152
|
ensureDir(outputDir);
|
|
152
153
|
const entry = renderAwsLambdaEntry();
|
|
153
|
-
const write = deps.writeEntry ?? ((p, c) =>
|
|
154
|
+
const write = deps.writeEntry ?? ((p, c) => {
|
|
155
|
+
writeFileSync(p, c);
|
|
156
|
+
});
|
|
154
157
|
write(resolve(outputDir, "handler.mjs"), entry);
|
|
155
158
|
console.log("\n \u2713 AWS Lambda output \u2192 .theo/aws/handler.mjs\n");
|
|
156
159
|
}
|
|
157
160
|
var awsLambdaAdapter = {
|
|
158
161
|
name: "aws-lambda",
|
|
159
|
-
build(config, cwd) {
|
|
160
|
-
return buildAwsLambda(config, cwd);
|
|
162
|
+
build(config, cwd, ctx) {
|
|
163
|
+
return buildAwsLambda(config, cwd, {}, ctx);
|
|
161
164
|
}
|
|
162
165
|
};
|
|
163
166
|
export {
|
|
@@ -167,4 +170,4 @@ export {
|
|
|
167
170
|
renderAwsLambdaEntry,
|
|
168
171
|
responseToLambdaResultV2
|
|
169
172
|
};
|
|
170
|
-
//# sourceMappingURL=aws-lambda-
|
|
173
|
+
//# sourceMappingURL=aws-lambda-PONVF3UX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/aws-lambda.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * AWS Lambda adapter. Writes to `cwd/.theo/lambda/`. Build-time tool.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { nodeAdapter } from './node.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport interface AwsLambdaBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeEntry?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n}\n\nexport interface RequestShape {\n method: string\n path: string\n headers: Record<string, string>\n body?: string\n}\n\nexport interface LambdaResultV2 {\n statusCode: number\n headers: Record<string, string>\n body: string\n isBase64Encoded: boolean\n}\n\nconst BINARY_CONTENT_TYPES = [\n 'application/octet-stream',\n 'application/pdf',\n 'application/zip',\n 'image/',\n 'audio/',\n 'video/',\n]\n\nfunction isBinaryContentType(contentType: string | undefined): boolean {\n if (!contentType) return false\n const ct = contentType.toLowerCase()\n return BINARY_CONTENT_TYPES.some((prefix) => ct.startsWith(prefix))\n}\n\ninterface APIGatewayProxyEventV2 {\n version: string\n requestContext: {\n http: { method: string; path: string }\n }\n headers?: Record<string, string | undefined>\n body?: string\n isBase64Encoded?: boolean\n}\n\nexport function eventV2ToRequestShape(event: APIGatewayProxyEventV2): RequestShape {\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(event.headers ?? {})) {\n if (v !== undefined) headers[k] = v\n }\n let body: string | undefined = event.body\n if (body !== undefined && event.isBase64Encoded) {\n body = Buffer.from(body, 'base64').toString('utf-8')\n }\n return {\n method: event.requestContext.http.method,\n path: event.requestContext.http.path,\n headers,\n body,\n }\n}\n\nexport function responseToLambdaResultV2(\n statusCode: number,\n headers: Record<string, string>,\n body: string,\n): LambdaResultV2 {\n const isBinary = isBinaryContentType(headers['content-type'])\n return {\n statusCode,\n headers,\n body: isBinary ? Buffer.from(body, 'binary').toString('base64') : body,\n isBase64Encoded: isBinary,\n }\n}\n\nexport function renderAwsLambdaEntry(): string {\n return [\n `// Generated by Theo — AWS Lambda Adapter`,\n `// Use with API Gateway HTTP API v2 (default).`,\n ``,\n `import { resolve } from 'node:path'`,\n `import { randomUUID } from 'node:crypto'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, 'server')`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `function eventV2ToRequest(event) {`,\n ` const method = event.requestContext?.http?.method ?? 'GET'`,\n ` const path = event.requestContext?.http?.path ?? '/'`,\n ` const host = event.headers?.host ?? 'lambda'`,\n ` const qs = event.rawQueryString ? '?' + event.rawQueryString : ''`,\n ` const url = 'https://' + host + path + qs`,\n ` const headers = new Headers()`,\n ` for (const [k, v] of Object.entries(event.headers ?? {})) {`,\n ` if (v !== undefined) headers.set(k, String(v))`,\n ` }`,\n ` let body = event.body`,\n ` if (body && event.isBase64Encoded) {`,\n ` body = Buffer.from(body, 'base64')`,\n ` }`,\n ` return new Request(url, {`,\n ` method,`,\n ` headers,`,\n ` body: method === 'GET' || method === 'HEAD' ? undefined : body,`,\n ` })`,\n `}`,\n ``,\n `async function responseToV2Result(response) {`,\n ` const headers = {}`,\n ` response.headers.forEach((v, k) => { headers[k] = v })`,\n ` const ct = headers['content-type'] ?? ''`,\n ` const isBinary = /^application\\\\/(octet-stream|pdf|zip)|^(image|audio|video)\\\\//.test(ct)`,\n ` if (isBinary) {`,\n ` const buf = Buffer.from(await response.arrayBuffer())`,\n ` return {`,\n ` statusCode: response.status,`,\n ` headers,`,\n ` body: buf.toString('base64'),`,\n ` isBase64Encoded: true,`,\n ` }`,\n ` }`,\n ` return {`,\n ` statusCode: response.status,`,\n ` headers,`,\n ` body: await response.text(),`,\n ` isBase64Encoded: false,`,\n ` }`,\n `}`,\n ``,\n `export const handler = async (event) => {`,\n ` const path = event.requestContext?.http?.path ?? '/'`,\n ``,\n ` if (!path.startsWith('/api/')) {`,\n ` return {`,\n ` statusCode: 404,`,\n ` headers: { 'content-type': 'text/plain' },`,\n ` body: 'Static assets must be served from CloudFront or a separate static host.',`,\n ` isBase64Encoded: false,`,\n ` }`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ` const match = matchRoute(path, routesCache)`,\n ` if (!match) {`,\n ` return {`,\n ` statusCode: 404,`,\n ` headers: { 'content-type': 'application/json' },`,\n ` body: JSON.stringify({ error: { code: 'NOT_FOUND' } }),`,\n ` isBase64Encoded: false,`,\n ` }`,\n ` }`,\n ``,\n ` const request = eventV2ToRequest(event)`,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ` const requestId = randomUUID()`,\n ` const method = request.method.toUpperCase()`,\n ` await executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId })`,\n ` const response = await toResponse()`,\n ` return responseToV2Result(response)`,\n `}`,\n ].join('\\n')\n}\n\nexport async function buildAwsLambda(\n config: TheoConfig,\n cwd: string,\n deps: AwsLambdaBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('aws-lambda', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const outputDir = resolve(cwd, '.theo/aws')\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n ensureDir(outputDir)\n\n const entry = renderAwsLambdaEntry()\n const write =\n deps.writeEntry ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n write(resolve(outputDir, 'handler.mjs'), entry)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ AWS Lambda output → .theo/aws/handler.mjs\\n')\n}\n\nexport const awsLambdaAdapter: DeployAdapter = {\n name: 'aws-lambda',\n build(config, cwd, ctx) {\n return buildAwsLambda(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
broadcastCsrfWarn,
|
|
3
|
+
broadcastError,
|
|
4
|
+
broadcastRequest,
|
|
5
|
+
broadcastToDevtools
|
|
6
|
+
} from "./chunk-7BIM27LV.js";
|
|
7
|
+
import "./chunk-DGUM43GV.js";
|
|
8
|
+
export {
|
|
9
|
+
broadcastCsrfWarn,
|
|
10
|
+
broadcastError,
|
|
11
|
+
broadcastRequest,
|
|
12
|
+
broadcastToDevtools
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=broadcast-4SX7R5XE.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx/esm";
|
|
3
|
+
import {
|
|
4
|
+
broadcastCsrfWarn,
|
|
5
|
+
broadcastError,
|
|
6
|
+
broadcastRequest,
|
|
7
|
+
broadcastToDevtools
|
|
8
|
+
} from "./chunk-5ODOE6EF.js";
|
|
9
|
+
import "./chunk-KT3MWNZG.js";
|
|
10
|
+
export {
|
|
11
|
+
broadcastCsrfWarn,
|
|
12
|
+
broadcastError,
|
|
13
|
+
broadcastRequest,
|
|
14
|
+
broadcastToDevtools
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=broadcast-QOQGUNNK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|