theokit 0.68.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/{actions-virtual-module-SE44BKR4.js → actions-virtual-module-ICWX5PZ2.js} +8 -8
- package/dist/{actions-virtual-module-3OPYLD76.js → actions-virtual-module-LY6VGJZ2.js} +5 -5
- package/dist/adapters/agent-mount.d.ts +191 -3
- package/dist/adapters/agent-mount.js +19 -4
- package/dist/adapters/security-headers.d.ts +17 -1
- package/dist/adapters/security-headers.js +7 -0
- package/dist/adapters/security-headers.js.map +1 -1
- package/dist/{agent-UBDDP2L5.js → agent-7DQ3TKJY.js} +4 -4
- package/dist/agent-scan-BLXaFL6R.d.ts +12 -0
- package/dist/{app-typed-client-BE3PV3JY.js → app-typed-client-2S76VTMB.js} +5 -5
- package/dist/{app-typed-client-RAFEBA7S.js → app-typed-client-6YQREFRS.js} +8 -8
- package/dist/{aws-lambda-R76PWR27.js → aws-lambda-OCJM5YGB.js} +35 -23
- package/dist/aws-lambda-OCJM5YGB.js.map +1 -0
- package/dist/{build-U4HVYY6F.js → build-NIHGHPYS.js} +12 -7
- package/dist/build-NIHGHPYS.js.map +1 -0
- package/dist/{bun-NPBIAWSU.js → bun-KRUAWYRW.js} +51 -39
- package/dist/bun-KRUAWYRW.js.map +1 -0
- package/dist/{chunk-GQNBZDYK.js → chunk-2FHNJ7LI.js} +1 -1
- package/dist/{chunk-GQNBZDYK.js.map → chunk-2FHNJ7LI.js.map} +1 -1
- package/dist/chunk-3NAKOWTX.js +573 -0
- package/dist/chunk-3NAKOWTX.js.map +1 -0
- package/dist/chunk-4D4YVXHR.js +816 -0
- package/dist/chunk-4D4YVXHR.js.map +1 -0
- package/dist/{chunk-G2NWAVGG.js → chunk-5XXXJ6DT.js} +65 -6
- package/dist/chunk-5XXXJ6DT.js.map +1 -0
- package/dist/{chunk-CO7Q75IS.js → chunk-6SK4MX2N.js} +10 -10
- package/dist/{chunk-UETVDZBE.js → chunk-BINRCILF.js} +4 -1
- package/dist/{chunk-UETVDZBE.js.map → chunk-BINRCILF.js.map} +1 -1
- package/dist/{chunk-AROOYXI5.js → chunk-BTDTHXUI.js} +2 -2
- package/dist/{chunk-GP6OJBXP.js → chunk-CPXIYCAY.js} +2 -2
- package/dist/chunk-FFGP2LGQ.js +465 -0
- package/dist/chunk-FFGP2LGQ.js.map +1 -0
- package/dist/{chunk-KDP2HURW.js → chunk-FHQP23Z5.js} +10 -2
- package/dist/{chunk-KDP2HURW.js.map → chunk-FHQP23Z5.js.map} +1 -1
- package/dist/{chunk-BCFA6LHL.js → chunk-GVJFVBCJ.js} +2 -2
- package/dist/{chunk-LADTWXOB.js → chunk-H5KSRGRS.js} +43 -554
- package/dist/chunk-H5KSRGRS.js.map +1 -0
- package/dist/{chunk-ENRT6DAU.js → chunk-IDWN2OSG.js} +15 -2
- package/dist/chunk-IDWN2OSG.js.map +1 -0
- package/dist/{chunk-GDF2XQOO.js → chunk-JMN4COMK.js} +5 -3
- package/dist/chunk-JMN4COMK.js.map +1 -0
- package/dist/{chunk-Q7T3UQ4L.js → chunk-KOG5K26V.js} +16 -738
- package/dist/chunk-KOG5K26V.js.map +1 -0
- package/dist/{chunk-FYYKMPST.js → chunk-MLF25NC4.js} +4 -4
- package/dist/{chunk-7THO4PRL.js → chunk-PGDBSK2L.js} +33 -11
- package/dist/chunk-PGDBSK2L.js.map +1 -0
- package/dist/{chunk-5NNFO7R7.js → chunk-R7G2B2WH.js} +10 -2
- package/dist/chunk-R7G2B2WH.js.map +1 -0
- package/dist/{chunk-GAJOZJWD.js → chunk-SX7ZAHNE.js} +15 -2
- package/dist/chunk-SX7ZAHNE.js.map +1 -0
- package/dist/chunk-V5SSTT3C.js +52 -0
- package/dist/chunk-V5SSTT3C.js.map +1 -0
- package/dist/chunk-XG6SQUEP.js +27 -0
- package/dist/chunk-XG6SQUEP.js.map +1 -0
- package/dist/{chunk-QGNSZ2ZK.js → chunk-YWWMYEAC.js} +3 -3
- package/dist/cli/index.js +7 -7
- package/dist/client/index.d.ts +20 -0
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-5HU56XIH.js → cloudflare-GK4VD3EU.js} +36 -13
- package/dist/cloudflare-GK4VD3EU.js.map +1 -0
- package/dist/{define-middleware-D2n71Svm.d.ts → define-middleware-BOHpPOBs.d.ts} +1 -1
- package/dist/{deno-deploy-BXOQ45LD.js → deno-deploy-WMNXRVGL.js} +37 -36
- package/dist/deno-deploy-WMNXRVGL.js.map +1 -0
- package/dist/{dev-E3USPAWQ.js → dev-KKO3JIN7.js} +7 -7
- package/dist/{dev-emit-ZLB2J5ME.js → dev-emit-23AA3V64.js} +3 -3
- package/dist/{dev-emit-7RT624PM.js → dev-emit-UJJXY3ET.js} +3 -3
- package/dist/{index-B9iVBHJI.d.ts → index-inRdhi59.d.ts} +39 -4
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/{internal-api-OOTVIOH2.js → internal-api-HUIBRF2X.js} +5 -5
- package/dist/{internal-api-VAKXXW6F.js → internal-api-S72T2XKD.js} +10 -9
- package/dist/match-DaKsL73w.d.ts +28 -0
- package/dist/{mcp-7UOQM4FW.js → mcp-I6MCW6KX.js} +4 -4
- package/dist/{match-Dw4CNXJH.d.ts → module-loader-DCip7NoL.d.ts} +1 -28
- package/dist/{netlify-LPJAPMQ2.js → netlify-VOL54SC7.js} +23 -20
- package/dist/netlify-VOL54SC7.js.map +1 -0
- package/dist/{openapi-SKKXWFX4.js → openapi-24PTE7P6.js} +3 -3
- package/dist/{preview-GPMF43QX.js → preview-EOL3LMK7.js} +3 -3
- package/dist/{registry-UOWTDUFL.js → registry-DDBXDHJ2.js} +7 -7
- package/dist/server/cost/index.d.ts +14 -1
- package/dist/server/cost/index.js +1 -1
- package/dist/server/define/index.d.ts +1 -1
- package/dist/server/define/index.js +2 -3
- package/dist/server/http/index.d.ts +4 -3
- package/dist/server/http/index.js +13 -8
- package/dist/server/index.d.ts +6 -5
- package/dist/server/index.js +17 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/scan/index.d.ts +4 -3
- package/dist/server/scan/index.js +2 -2
- package/dist/{server-boundary-A3HW27C7.js → server-boundary-ERBSHRHT.js} +5 -5
- package/dist/{server-boundary-V6PCM22J.js → server-boundary-I5XWYINJ.js} +8 -8
- package/dist/{start-6WEPXA3X.js → start-OD4DG4C6.js} +6 -6
- package/dist/{vercel-YUZADKGV.js → vercel-LZTDSDJF.js} +42 -28
- package/dist/vercel-LZTDSDJF.js.map +1 -0
- package/dist/vite-plugin/index.js +9 -8
- package/dist/{vite-plugin-WBF5NXGW.js → vite-plugin-SH3VGQEE.js} +7 -7
- package/package.json +4 -4
- package/dist/agent-scan-BZa-pagG.d.ts +0 -16
- package/dist/aws-lambda-R76PWR27.js.map +0 -1
- package/dist/build-U4HVYY6F.js.map +0 -1
- package/dist/bun-NPBIAWSU.js.map +0 -1
- package/dist/chunk-5NNFO7R7.js.map +0 -1
- package/dist/chunk-6B3CMPE4.js +0 -192
- package/dist/chunk-6B3CMPE4.js.map +0 -1
- package/dist/chunk-7L7LQ2GU.js +0 -16
- package/dist/chunk-7L7LQ2GU.js.map +0 -1
- package/dist/chunk-7THO4PRL.js.map +0 -1
- package/dist/chunk-ENRT6DAU.js.map +0 -1
- package/dist/chunk-G2NWAVGG.js.map +0 -1
- package/dist/chunk-GAJOZJWD.js.map +0 -1
- package/dist/chunk-GDF2XQOO.js.map +0 -1
- package/dist/chunk-LADTWXOB.js.map +0 -1
- package/dist/chunk-O55RLSGG.js +0 -127
- package/dist/chunk-O55RLSGG.js.map +0 -1
- package/dist/chunk-Q7T3UQ4L.js.map +0 -1
- package/dist/cloudflare-5HU56XIH.js.map +0 -1
- package/dist/deno-deploy-BXOQ45LD.js.map +0 -1
- package/dist/netlify-LPJAPMQ2.js.map +0 -1
- package/dist/vercel-YUZADKGV.js.map +0 -1
- /package/dist/{actions-virtual-module-SE44BKR4.js.map → actions-virtual-module-ICWX5PZ2.js.map} +0 -0
- /package/dist/{actions-virtual-module-3OPYLD76.js.map → actions-virtual-module-LY6VGJZ2.js.map} +0 -0
- /package/dist/{agent-UBDDP2L5.js.map → agent-7DQ3TKJY.js.map} +0 -0
- /package/dist/{app-typed-client-BE3PV3JY.js.map → app-typed-client-2S76VTMB.js.map} +0 -0
- /package/dist/{app-typed-client-RAFEBA7S.js.map → app-typed-client-6YQREFRS.js.map} +0 -0
- /package/dist/{chunk-CO7Q75IS.js.map → chunk-6SK4MX2N.js.map} +0 -0
- /package/dist/{chunk-AROOYXI5.js.map → chunk-BTDTHXUI.js.map} +0 -0
- /package/dist/{chunk-GP6OJBXP.js.map → chunk-CPXIYCAY.js.map} +0 -0
- /package/dist/{chunk-BCFA6LHL.js.map → chunk-GVJFVBCJ.js.map} +0 -0
- /package/dist/{chunk-FYYKMPST.js.map → chunk-MLF25NC4.js.map} +0 -0
- /package/dist/{chunk-QGNSZ2ZK.js.map → chunk-YWWMYEAC.js.map} +0 -0
- /package/dist/{dev-E3USPAWQ.js.map → dev-KKO3JIN7.js.map} +0 -0
- /package/dist/{dev-emit-ZLB2J5ME.js.map → dev-emit-23AA3V64.js.map} +0 -0
- /package/dist/{dev-emit-7RT624PM.js.map → dev-emit-UJJXY3ET.js.map} +0 -0
- /package/dist/{internal-api-OOTVIOH2.js.map → internal-api-HUIBRF2X.js.map} +0 -0
- /package/dist/{internal-api-VAKXXW6F.js.map → internal-api-S72T2XKD.js.map} +0 -0
- /package/dist/{mcp-7UOQM4FW.js.map → mcp-I6MCW6KX.js.map} +0 -0
- /package/dist/{openapi-SKKXWFX4.js.map → openapi-24PTE7P6.js.map} +0 -0
- /package/dist/{preview-GPMF43QX.js.map → preview-EOL3LMK7.js.map} +0 -0
- /package/dist/{registry-UOWTDUFL.js.map → registry-DDBXDHJ2.js.map} +0 -0
- /package/dist/{server-boundary-A3HW27C7.js.map → server-boundary-ERBSHRHT.js.map} +0 -0
- /package/dist/{server-boundary-V6PCM22J.js.map → server-boundary-I5XWYINJ.js.map} +0 -0
- /package/dist/{start-6WEPXA3X.js.map → start-OD4DG4C6.js.map} +0 -0
- /package/dist/{vite-plugin-WBF5NXGW.js.map → vite-plugin-SH3VGQEE.js.map} +0 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateCsrfRequest
|
|
3
|
+
} from "./chunk-P6OS7652.js";
|
|
4
|
+
import {
|
|
5
|
+
WEB_SHAPED_MIDDLEWARE,
|
|
6
|
+
evaluateRoutePolicy,
|
|
7
|
+
subjectFromContext
|
|
8
|
+
} from "./chunk-IDWN2OSG.js";
|
|
9
|
+
import {
|
|
10
|
+
RequestBodyConsumedError,
|
|
11
|
+
parseRequestBody
|
|
12
|
+
} from "./chunk-ZDCCILMP.js";
|
|
13
|
+
import {
|
|
14
|
+
scanMiddlewares
|
|
15
|
+
} from "./chunk-V6Y2SMRG.js";
|
|
16
|
+
import {
|
|
17
|
+
envelopeCodeToStatus
|
|
18
|
+
} from "./chunk-ACKBVBUI.js";
|
|
19
|
+
import {
|
|
20
|
+
TheoError,
|
|
21
|
+
serverErrorToEnvelope
|
|
22
|
+
} from "./chunk-IZ3TIL6K.js";
|
|
23
|
+
|
|
24
|
+
// src/core/contracts/client-safe-error.ts
|
|
25
|
+
var INTERNAL_CODES = /* @__PURE__ */ new Set(["INTERNAL_ERROR", "INTERNAL_SERVER_ERROR"]);
|
|
26
|
+
var GENERIC_INTERNAL_MESSAGE = "Internal server error";
|
|
27
|
+
function redacts(code) {
|
|
28
|
+
return INTERNAL_CODES.has(code) && process.env.NODE_ENV === "production";
|
|
29
|
+
}
|
|
30
|
+
function clientSafeErrorMessage(code, message) {
|
|
31
|
+
return redacts(code) ? GENERIC_INTERNAL_MESSAGE : message;
|
|
32
|
+
}
|
|
33
|
+
function clientSafeErrorEnvelope(envelope) {
|
|
34
|
+
if (!redacts(envelope.code)) return envelope;
|
|
35
|
+
return { code: envelope.code, message: GENERIC_INTERNAL_MESSAGE };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/server/http/send-response.ts
|
|
39
|
+
function sendJson(res, data, status = 200, transformer) {
|
|
40
|
+
const body = transformer ? transformer.serialize(data) : JSON.stringify(data);
|
|
41
|
+
res.writeHead(status, {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
"Content-Length": Buffer.byteLength(body)
|
|
44
|
+
});
|
|
45
|
+
res.end(body);
|
|
46
|
+
}
|
|
47
|
+
function oneLine(value) {
|
|
48
|
+
return value.replace(/[\r\n]/g, "\\n");
|
|
49
|
+
}
|
|
50
|
+
function sendError(res, codeOrInput, message, status, issues, requestId, options) {
|
|
51
|
+
let code;
|
|
52
|
+
if (typeof codeOrInput === "string") {
|
|
53
|
+
code = codeOrInput;
|
|
54
|
+
message = message ?? "";
|
|
55
|
+
status = status ?? 500;
|
|
56
|
+
} else {
|
|
57
|
+
code = codeOrInput.code;
|
|
58
|
+
message = codeOrInput.message;
|
|
59
|
+
status = codeOrInput.status;
|
|
60
|
+
issues = codeOrInput.issues;
|
|
61
|
+
requestId = codeOrInput.requestId;
|
|
62
|
+
options = codeOrInput.options;
|
|
63
|
+
}
|
|
64
|
+
const errorMessage = clientSafeErrorMessage(code, message);
|
|
65
|
+
if (code === "INTERNAL_ERROR") {
|
|
66
|
+
console.error(`[${oneLine(requestId ?? "no-id")}] ${oneLine(message)}`);
|
|
67
|
+
}
|
|
68
|
+
if (status === 404 && options?.custom404Html) {
|
|
69
|
+
const body = options.custom404Html;
|
|
70
|
+
res.writeHead(404, {
|
|
71
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
72
|
+
"Content-Length": Buffer.byteLength(body)
|
|
73
|
+
});
|
|
74
|
+
res.end(body);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (status === 500 && options?.custom500Html) {
|
|
78
|
+
const body = options.custom500Html;
|
|
79
|
+
res.writeHead(500, {
|
|
80
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
81
|
+
"Content-Length": Buffer.byteLength(body)
|
|
82
|
+
});
|
|
83
|
+
res.end(body);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
sendJson(
|
|
87
|
+
res,
|
|
88
|
+
{
|
|
89
|
+
error: {
|
|
90
|
+
code,
|
|
91
|
+
message: errorMessage,
|
|
92
|
+
...requestId ? { requestId } : {},
|
|
93
|
+
...issues ? { issues } : {}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
status
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/server/http/node-request.ts
|
|
101
|
+
import { Readable } from "node:stream";
|
|
102
|
+
function pickHeaderString(value) {
|
|
103
|
+
if (typeof value === "string") return value;
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
for (const v of value) if (typeof v === "string" && v.length > 0) return v;
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
function synthesizeAbsoluteUrl(req) {
|
|
110
|
+
const host = pickHeaderString(req.headers.host) ?? "localhost";
|
|
111
|
+
return `http://${host}${req.url ?? "/"}`;
|
|
112
|
+
}
|
|
113
|
+
function nodeHeadersToWeb(req) {
|
|
114
|
+
const headers = new Headers();
|
|
115
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
116
|
+
if (value === void 0) continue;
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
headers.set(key, value.join(", "));
|
|
119
|
+
} else {
|
|
120
|
+
headers.set(key, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return headers;
|
|
124
|
+
}
|
|
125
|
+
function incomingMessageToWebRequest(req) {
|
|
126
|
+
const url = synthesizeAbsoluteUrl(req);
|
|
127
|
+
const headers = nodeHeadersToWeb(req);
|
|
128
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
129
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
130
|
+
if (!hasBody) {
|
|
131
|
+
return new Request(url, { method, headers });
|
|
132
|
+
}
|
|
133
|
+
const webStream = Readable.toWeb(req);
|
|
134
|
+
return new Request(url, {
|
|
135
|
+
method,
|
|
136
|
+
headers,
|
|
137
|
+
body: webStream,
|
|
138
|
+
// EC-2: Node 18+ requires `duplex: 'half'` when body is a stream.
|
|
139
|
+
// The `RequestInit` type omits it (Web spec gap); cast accordingly.
|
|
140
|
+
...{ duplex: "half" }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function createWebRequestSource(req) {
|
|
144
|
+
let converted;
|
|
145
|
+
return {
|
|
146
|
+
method: (req.method ?? "GET").toUpperCase(),
|
|
147
|
+
toRequest: () => converted ??= incomingMessageToWebRequest(req)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function incomingMessageToHandlerRequest(req) {
|
|
151
|
+
return new Request(synthesizeAbsoluteUrl(req), {
|
|
152
|
+
method: (req.method ?? "GET").toUpperCase(),
|
|
153
|
+
headers: nodeHeadersToWeb(req)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/server/http/validate-route-input.ts
|
|
158
|
+
function validateRouteInput(config, raw) {
|
|
159
|
+
let query = raw.query;
|
|
160
|
+
if (config.query !== void 0) {
|
|
161
|
+
const parsed = config.query.safeParse(raw.query);
|
|
162
|
+
if (!parsed.success) return { ok: false, channel: "query", error: parsed.error };
|
|
163
|
+
query = parsed.data;
|
|
164
|
+
}
|
|
165
|
+
let body = raw.body;
|
|
166
|
+
if (config.body !== void 0) {
|
|
167
|
+
const parsed = config.body.safeParse(raw.body);
|
|
168
|
+
if (!parsed.success) return { ok: false, channel: "body", error: parsed.error };
|
|
169
|
+
body = parsed.data;
|
|
170
|
+
}
|
|
171
|
+
let params = raw.params;
|
|
172
|
+
if (config.params !== void 0) {
|
|
173
|
+
const parsed = config.params.safeParse(raw.params);
|
|
174
|
+
if (!parsed.success) return { ok: false, channel: "params", error: parsed.error };
|
|
175
|
+
params = parsed.data;
|
|
176
|
+
}
|
|
177
|
+
return { ok: true, query, body, params };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/server/http/execute-stages.ts
|
|
181
|
+
async function parseQueryAndBody(req, res, requestId) {
|
|
182
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
183
|
+
const query = Object.fromEntries(url.searchParams);
|
|
184
|
+
let body;
|
|
185
|
+
let raw;
|
|
186
|
+
try {
|
|
187
|
+
const parsed = await parseRequestBody(req);
|
|
188
|
+
raw = parsed.raw;
|
|
189
|
+
if (parsed.json !== void 0) {
|
|
190
|
+
body = parsed.json;
|
|
191
|
+
} else if (parsed.files.length > 0 || Object.keys(parsed.fields).length > 0) {
|
|
192
|
+
body = { ...parsed.fields, _files: parsed.files };
|
|
193
|
+
} else {
|
|
194
|
+
body = void 0;
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (err instanceof RequestBodyConsumedError) {
|
|
198
|
+
sendError(res, "INTERNAL_ERROR", err.message, err.status, void 0, requestId);
|
|
199
|
+
return { ok: false };
|
|
200
|
+
}
|
|
201
|
+
const message = err.message;
|
|
202
|
+
const status = message.includes("Unsupported Content-Type") ? 415 : 400;
|
|
203
|
+
sendError(res, "VALIDATION_ERROR", message, status, void 0, requestId);
|
|
204
|
+
return { ok: false };
|
|
205
|
+
}
|
|
206
|
+
return { ok: true, data: { query, body, raw } };
|
|
207
|
+
}
|
|
208
|
+
var isZodLike = (value) => typeof value === "object" && value !== null && typeof value.safeParse === "function";
|
|
209
|
+
function runZodValidation(routeConfig, res, requestId, input) {
|
|
210
|
+
const { query, params } = input;
|
|
211
|
+
let { body } = input;
|
|
212
|
+
if (isZodLike(routeConfig.query)) {
|
|
213
|
+
const result = routeConfig.query.safeParse(query);
|
|
214
|
+
if (!result.success) {
|
|
215
|
+
sendError(
|
|
216
|
+
res,
|
|
217
|
+
"VALIDATION_ERROR",
|
|
218
|
+
"Invalid query parameters",
|
|
219
|
+
400,
|
|
220
|
+
result.error?.issues,
|
|
221
|
+
requestId
|
|
222
|
+
);
|
|
223
|
+
return { ok: false };
|
|
224
|
+
}
|
|
225
|
+
Object.assign(query, result.data);
|
|
226
|
+
}
|
|
227
|
+
if (isZodLike(routeConfig.body)) {
|
|
228
|
+
const result = routeConfig.body.safeParse(body);
|
|
229
|
+
if (!result.success) {
|
|
230
|
+
sendError(
|
|
231
|
+
res,
|
|
232
|
+
"VALIDATION_ERROR",
|
|
233
|
+
"Invalid request body",
|
|
234
|
+
400,
|
|
235
|
+
result.error?.issues,
|
|
236
|
+
requestId
|
|
237
|
+
);
|
|
238
|
+
return { ok: false };
|
|
239
|
+
}
|
|
240
|
+
body = result.data;
|
|
241
|
+
}
|
|
242
|
+
if (isZodLike(routeConfig.params)) {
|
|
243
|
+
const result = routeConfig.params.safeParse(params);
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
sendError(
|
|
246
|
+
res,
|
|
247
|
+
"VALIDATION_ERROR",
|
|
248
|
+
"Invalid route parameters",
|
|
249
|
+
400,
|
|
250
|
+
result.error?.issues,
|
|
251
|
+
requestId
|
|
252
|
+
);
|
|
253
|
+
return { ok: false };
|
|
254
|
+
}
|
|
255
|
+
Object.assign(params, result.data);
|
|
256
|
+
}
|
|
257
|
+
return { ok: true, data: { query, body, params } };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/server/http/web-middleware-runner.ts
|
|
261
|
+
async function runWebMiddleware(request, middleware, context, downstream) {
|
|
262
|
+
const runFrom = async (index) => {
|
|
263
|
+
if (index >= middleware.length) {
|
|
264
|
+
return downstream === void 0 ? void 0 : await downstream(request, context);
|
|
265
|
+
}
|
|
266
|
+
const invocations = [];
|
|
267
|
+
const rejections = /* @__PURE__ */ new Map();
|
|
268
|
+
let frameSettled = false;
|
|
269
|
+
let yieldedInvocation;
|
|
270
|
+
const next = () => {
|
|
271
|
+
const started = runFrom(index + 1);
|
|
272
|
+
invocations.push(started);
|
|
273
|
+
started.catch((reason) => {
|
|
274
|
+
if (frameSettled) {
|
|
275
|
+
reportOne(started, reason);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
rejections.set(started, reason);
|
|
279
|
+
});
|
|
280
|
+
return started;
|
|
281
|
+
};
|
|
282
|
+
const reportOne = (invocation, reason) => {
|
|
283
|
+
if (invocation === yieldedInvocation) return;
|
|
284
|
+
const detail = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
285
|
+
console.warn(
|
|
286
|
+
`[theokit] a middleware invoked next() and the downstream rejected, but the frame did not yield that result, so this failure reached no client: ${detail}`
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
const report = (yielded) => {
|
|
290
|
+
yieldedInvocation = yielded;
|
|
291
|
+
frameSettled = true;
|
|
292
|
+
for (const [invocation, reason] of rejections) reportOne(invocation, reason);
|
|
293
|
+
if (invocations.length > 1) {
|
|
294
|
+
console.warn(
|
|
295
|
+
`[theokit] a middleware called next() ${String(invocations.length)} times, so the downstream ran that many times against the same context object, concurrently and with no synchronisation. Only the last result is used; the rest are discarded with their side effects already performed. Call next() once, or make the downstream idempotent.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
let own;
|
|
300
|
+
try {
|
|
301
|
+
own = await middleware[index](request, context, next);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
let held;
|
|
304
|
+
for (const [invocation, reason] of rejections) {
|
|
305
|
+
if (reason === error) held = invocation;
|
|
306
|
+
}
|
|
307
|
+
report(held);
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
if (own instanceof Response) {
|
|
311
|
+
report();
|
|
312
|
+
return own;
|
|
313
|
+
}
|
|
314
|
+
if (invocations.length > 0) {
|
|
315
|
+
const yielded = invocations[invocations.length - 1];
|
|
316
|
+
try {
|
|
317
|
+
return await yielded;
|
|
318
|
+
} finally {
|
|
319
|
+
report(yielded);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
report();
|
|
323
|
+
return await runFrom(index + 1);
|
|
324
|
+
};
|
|
325
|
+
return runFrom(0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/server/web-handler.ts
|
|
329
|
+
function searchParamsToObject(params) {
|
|
330
|
+
const out = {};
|
|
331
|
+
for (const [key, value] of params.entries()) {
|
|
332
|
+
if (!Object.hasOwn(out, key)) {
|
|
333
|
+
out[key] = value;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const existing = out[key];
|
|
337
|
+
if (Array.isArray(existing)) {
|
|
338
|
+
existing.push(value);
|
|
339
|
+
} else {
|
|
340
|
+
out[key] = [existing, value];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
async function parseBodyInline(request) {
|
|
346
|
+
if (request.method === "GET" || request.method === "HEAD") return void 0;
|
|
347
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
348
|
+
if (contentType.includes("application/json")) {
|
|
349
|
+
const text = await request.text();
|
|
350
|
+
if (text.length === 0) return void 0;
|
|
351
|
+
try {
|
|
352
|
+
return JSON.parse(text);
|
|
353
|
+
} catch {
|
|
354
|
+
return void 0;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (contentType.startsWith("text/")) {
|
|
358
|
+
const text = await request.text();
|
|
359
|
+
return text.length === 0 ? void 0 : text;
|
|
360
|
+
}
|
|
361
|
+
return void 0;
|
|
362
|
+
}
|
|
363
|
+
async function parseBodyFull(request) {
|
|
364
|
+
if (request.method === "GET" || request.method === "HEAD") return void 0;
|
|
365
|
+
const { parseWebRequestBody } = await import("./body-parser-web-ON5CUTEL.js");
|
|
366
|
+
try {
|
|
367
|
+
const parsed = await parseWebRequestBody(request);
|
|
368
|
+
if (parsed.json === void 0 && Object.keys(parsed.fields).length === 0 && parsed.files.length === 0) {
|
|
369
|
+
return void 0;
|
|
370
|
+
}
|
|
371
|
+
return parsed;
|
|
372
|
+
} catch {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function validationErrorResponse(zodError, field) {
|
|
377
|
+
const envelope = {
|
|
378
|
+
code: "BAD_REQUEST",
|
|
379
|
+
message: `Validation failed: ${field}`,
|
|
380
|
+
ext: {
|
|
381
|
+
fields: zodError.issues.map((issue) => ({
|
|
382
|
+
path: issue.path.join("."),
|
|
383
|
+
message: issue.message
|
|
384
|
+
}))
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
return new Response(JSON.stringify(envelope), {
|
|
388
|
+
status: 400,
|
|
389
|
+
headers: { "content-type": "application/json" }
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async function runHandler(config, request, bodyParser = "inline", paramsInput = {}, context = {}) {
|
|
393
|
+
const url = new URL(request.url);
|
|
394
|
+
const queryRaw = searchParamsToObject(url.searchParams);
|
|
395
|
+
const bodyRaw = bodyParser === "full" ? await parseBodyFull(request) : await parseBodyInline(request);
|
|
396
|
+
const paramsRaw = paramsInput;
|
|
397
|
+
const validated = validateRouteInput(config, {
|
|
398
|
+
query: queryRaw,
|
|
399
|
+
body: bodyRaw,
|
|
400
|
+
params: paramsRaw
|
|
401
|
+
});
|
|
402
|
+
if (!validated.ok) {
|
|
403
|
+
return { ok: false, response: validationErrorResponse(validated.error, validated.channel) };
|
|
404
|
+
}
|
|
405
|
+
const { query, body, params } = validated;
|
|
406
|
+
const decision = await evaluateRoutePolicy(config.policy, {
|
|
407
|
+
subject: subjectFromContext(context),
|
|
408
|
+
query,
|
|
409
|
+
body,
|
|
410
|
+
params
|
|
411
|
+
});
|
|
412
|
+
if (!decision.allowed) {
|
|
413
|
+
return { ok: false, response: accessDeniedResponse(decision.reason) };
|
|
414
|
+
}
|
|
415
|
+
const result = await config.handler({ query, body, params, request, context });
|
|
416
|
+
return validateResponseOutput(config.response, result) ?? { ok: true, result };
|
|
417
|
+
}
|
|
418
|
+
function validateResponseOutput(response, result) {
|
|
419
|
+
const validatable = result !== void 0 && result !== null && !(result instanceof Response);
|
|
420
|
+
if (!validatable || !isZodLike(response)) return void 0;
|
|
421
|
+
const parsed = response.safeParse(result);
|
|
422
|
+
if (parsed.success) return { ok: true, result: parsed.data };
|
|
423
|
+
const err = new TheoError({
|
|
424
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
425
|
+
message: "response validation failed",
|
|
426
|
+
ext: { issues: parsed.error?.issues }
|
|
427
|
+
});
|
|
428
|
+
return { ok: false, response: handlerErrorResponse(err) };
|
|
429
|
+
}
|
|
430
|
+
function toResponse(result, status) {
|
|
431
|
+
if (result === void 0) {
|
|
432
|
+
return new Response(null, { status: 204 });
|
|
433
|
+
}
|
|
434
|
+
if (result instanceof Response) {
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
return new Response(JSON.stringify(result), {
|
|
438
|
+
status: status ?? 200,
|
|
439
|
+
headers: { "content-type": "application/json" }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function handlerErrorResponse(err) {
|
|
443
|
+
const envelope = clientSafeErrorEnvelope(serverErrorToEnvelope(err));
|
|
444
|
+
const status = envelopeCodeToStatus(envelope.code);
|
|
445
|
+
return new Response(JSON.stringify(envelope), {
|
|
446
|
+
status,
|
|
447
|
+
headers: { "content-type": "application/json" }
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
var CSRF_PROTECTED_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
451
|
+
function mergeHookHeaders(response, hookHeaders) {
|
|
452
|
+
if ([...hookHeaders].length === 0) return response;
|
|
453
|
+
const merged = new Headers(response.headers);
|
|
454
|
+
for (const [k, v] of hookHeaders.entries()) {
|
|
455
|
+
if (k.toLowerCase() === "set-cookie") continue;
|
|
456
|
+
if (!merged.has(k)) merged.set(k, v);
|
|
457
|
+
}
|
|
458
|
+
for (const sc of hookHeaders.getSetCookie()) {
|
|
459
|
+
merged.append("Set-Cookie", sc);
|
|
460
|
+
}
|
|
461
|
+
return new Response(response.body, {
|
|
462
|
+
status: response.status,
|
|
463
|
+
statusText: response.statusText,
|
|
464
|
+
headers: merged
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function methodNotAllowedResponse(method) {
|
|
468
|
+
const envelope = {
|
|
469
|
+
code: "METHOD_NOT_ALLOWED",
|
|
470
|
+
message: `Method ${method} not allowed`
|
|
471
|
+
};
|
|
472
|
+
return new Response(JSON.stringify(envelope), {
|
|
473
|
+
status: 405,
|
|
474
|
+
headers: { "content-type": "application/json" }
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function shouldEnforceCsrf(method, config, csrfMode) {
|
|
478
|
+
if ((csrfMode ?? "strict") === "off") return false;
|
|
479
|
+
if (!CSRF_PROTECTED_METHODS.has(method)) return false;
|
|
480
|
+
return config?.csrf !== false;
|
|
481
|
+
}
|
|
482
|
+
function accessDeniedResponse(reason) {
|
|
483
|
+
const envelope = {
|
|
484
|
+
code: "FORBIDDEN",
|
|
485
|
+
message: `Access denied: ${reason}`
|
|
486
|
+
};
|
|
487
|
+
return new Response(JSON.stringify({ error: envelope }), {
|
|
488
|
+
status: 403,
|
|
489
|
+
headers: { "Content-Type": "application/json" }
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function csrfFailedResponse(reason) {
|
|
493
|
+
const envelope = {
|
|
494
|
+
code: "FORBIDDEN",
|
|
495
|
+
message: `CSRF check failed: ${reason}`
|
|
496
|
+
};
|
|
497
|
+
return new Response(JSON.stringify(envelope), {
|
|
498
|
+
status: 403,
|
|
499
|
+
headers: { "content-type": "application/json" }
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
async function executeWebRequest(request, routeModule, opts = {}) {
|
|
503
|
+
const method = request.method.toUpperCase();
|
|
504
|
+
const config = routeModule[method];
|
|
505
|
+
if (opts.hooks === void 0) {
|
|
506
|
+
if (config === void 0 || typeof config.handler !== "function") {
|
|
507
|
+
return methodNotAllowedResponse(method);
|
|
508
|
+
}
|
|
509
|
+
if (shouldEnforceCsrf(method, config, opts.csrfMode)) {
|
|
510
|
+
const csrfCheck = validateCsrfRequest(request);
|
|
511
|
+
if (!csrfCheck.valid) return csrfFailedResponse(csrfCheck.reason);
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
const context = {};
|
|
515
|
+
const runRoute = async () => {
|
|
516
|
+
const outcome = await runHandler(
|
|
517
|
+
config,
|
|
518
|
+
request,
|
|
519
|
+
opts.bodyParser ?? "inline",
|
|
520
|
+
opts.params ?? {},
|
|
521
|
+
context
|
|
522
|
+
);
|
|
523
|
+
return outcome.ok ? toResponse(outcome.result, config.status) : outcome.response;
|
|
524
|
+
};
|
|
525
|
+
if (opts.middleware?.length) {
|
|
526
|
+
const response = await runWebMiddleware(request, opts.middleware, context, runRoute);
|
|
527
|
+
if (response !== void 0) return response;
|
|
528
|
+
}
|
|
529
|
+
return await runRoute();
|
|
530
|
+
} catch (err) {
|
|
531
|
+
return handlerErrorResponse(err);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return runWithHooks(request, config, opts, opts.hooks);
|
|
535
|
+
}
|
|
536
|
+
async function runPreHandlerPipeline(hookCtx, request, config, opts, hooks) {
|
|
537
|
+
const method = request.method.toUpperCase();
|
|
538
|
+
const runList = async (list) => {
|
|
539
|
+
for (const hook of list) {
|
|
540
|
+
if (hookCtx.response !== void 0) return;
|
|
541
|
+
await hook(hookCtx);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
if (hooks.onRequest) await runList(hooks.onRequest);
|
|
545
|
+
if (hookCtx.response === void 0 && shouldEnforceCsrf(method, config, opts.csrfMode)) {
|
|
546
|
+
const csrfCheck = validateCsrfRequest(request);
|
|
547
|
+
if (!csrfCheck.valid) hookCtx.response = csrfFailedResponse(csrfCheck.reason);
|
|
548
|
+
}
|
|
549
|
+
if (hookCtx.response === void 0 && hooks.preHandler) {
|
|
550
|
+
await runList(hooks.preHandler);
|
|
551
|
+
}
|
|
552
|
+
if (hookCtx.response === void 0) {
|
|
553
|
+
await runHandlerStage(hookCtx, request, config, opts, method);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function runHandlerStage(hookCtx, request, config, opts, method) {
|
|
557
|
+
if (config === void 0 || typeof config.handler !== "function") {
|
|
558
|
+
hookCtx.response = methodNotAllowedResponse(method);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const runRoute = async () => {
|
|
562
|
+
const outcome = await runHandler(
|
|
563
|
+
config,
|
|
564
|
+
request,
|
|
565
|
+
opts.bodyParser ?? "inline",
|
|
566
|
+
opts.params ?? {},
|
|
567
|
+
hookCtx.ctx
|
|
568
|
+
);
|
|
569
|
+
return outcome.ok ? toResponse(outcome.result, config.status) : outcome.response;
|
|
570
|
+
};
|
|
571
|
+
if (opts.middleware?.length) {
|
|
572
|
+
const response = await runWebMiddleware(request, opts.middleware, hookCtx.ctx, runRoute);
|
|
573
|
+
if (response !== void 0) {
|
|
574
|
+
hookCtx.response = response;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
hookCtx.response = await runRoute();
|
|
579
|
+
}
|
|
580
|
+
async function runWithHooks(request, config, opts, hooks) {
|
|
581
|
+
const hookCtx = {
|
|
582
|
+
request,
|
|
583
|
+
responseHeaders: new Headers(),
|
|
584
|
+
ctx: {},
|
|
585
|
+
requestId: opts.requestId ?? globalThis.crypto.randomUUID()
|
|
586
|
+
};
|
|
587
|
+
try {
|
|
588
|
+
await runPreHandlerPipeline(hookCtx, request, config, opts, hooks);
|
|
589
|
+
if (hooks.onResponse) {
|
|
590
|
+
for (const hook of hooks.onResponse) {
|
|
591
|
+
await hook(hookCtx);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const finalResponse = hookCtx.response ?? new Response(
|
|
595
|
+
JSON.stringify({ code: "INTERNAL_SERVER_ERROR", message: "No response built" }),
|
|
596
|
+
{
|
|
597
|
+
status: 500,
|
|
598
|
+
headers: { "content-type": "application/json" }
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
return mergeHookHeaders(finalResponse, hookCtx.responseHeaders);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
return runErrorHooks(err, hookCtx, hooks.onError);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function runErrorHooks(err, hookCtx, onError) {
|
|
607
|
+
const errorResponse = handlerErrorResponse(err);
|
|
608
|
+
if (onError !== void 0) {
|
|
609
|
+
const errorCtx = {
|
|
610
|
+
...hookCtx,
|
|
611
|
+
response: errorResponse,
|
|
612
|
+
error: err
|
|
613
|
+
};
|
|
614
|
+
for (const [index, hook] of onError.entries()) {
|
|
615
|
+
try {
|
|
616
|
+
await hook(errorCtx);
|
|
617
|
+
} catch (hookError) {
|
|
618
|
+
try {
|
|
619
|
+
const reason = hookError instanceof Error ? hookError.stack ?? hookError.message : String(hookError);
|
|
620
|
+
const named = hook.name === "" ? "" : ` (${hook.name})`;
|
|
621
|
+
console.warn(
|
|
622
|
+
`[theokit] onError hook threw and was swallowed to avoid recursion: hook ${String(index)}${named} \u2014 the error it was given still produced a response, but whatever this hook reports to is NOT receiving it: ${reason}`
|
|
623
|
+
);
|
|
624
|
+
} catch {
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return mergeHookHeaders(errorResponse, hookCtx.responseHeaders);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/server/http/middleware-runner.ts
|
|
633
|
+
import { existsSync } from "node:fs";
|
|
634
|
+
import { join } from "node:path";
|
|
635
|
+
|
|
636
|
+
// src/server/http/node-web-adapter.ts
|
|
637
|
+
async function writeWebResponseToServerResponse(response, res) {
|
|
638
|
+
const setCookies = response.headers.getSetCookie();
|
|
639
|
+
if (setCookies.length > 0) {
|
|
640
|
+
res.setHeader("Set-Cookie", setCookies);
|
|
641
|
+
}
|
|
642
|
+
const otherHeaders = {};
|
|
643
|
+
for (const [key, value] of response.headers.entries()) {
|
|
644
|
+
if (key.toLowerCase() === "set-cookie") continue;
|
|
645
|
+
otherHeaders[key] = value;
|
|
646
|
+
}
|
|
647
|
+
res.writeHead(response.status, response.statusText, otherHeaders);
|
|
648
|
+
if (response.body === null) {
|
|
649
|
+
res.end();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const reader = response.body.getReader();
|
|
653
|
+
try {
|
|
654
|
+
for (; ; ) {
|
|
655
|
+
const { done, value } = await reader.read();
|
|
656
|
+
if (done) break;
|
|
657
|
+
res.write(value);
|
|
658
|
+
}
|
|
659
|
+
res.end();
|
|
660
|
+
} finally {
|
|
661
|
+
reader.releaseLock();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/server/http/middleware-runner.ts
|
|
666
|
+
var USER_MODULE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
667
|
+
function resolveUserModule(dir, base) {
|
|
668
|
+
for (const ext of USER_MODULE_EXTENSIONS) {
|
|
669
|
+
const candidate = join(dir, `${base}${ext}`);
|
|
670
|
+
if (existsSync(candidate)) return candidate;
|
|
671
|
+
}
|
|
672
|
+
return void 0;
|
|
673
|
+
}
|
|
674
|
+
var middlewareCache = /* @__PURE__ */ new Map();
|
|
675
|
+
function _resetMiddlewareCacheForTests() {
|
|
676
|
+
middlewareCache.clear();
|
|
677
|
+
}
|
|
678
|
+
function getCachedScan(serverDir) {
|
|
679
|
+
let cached = middlewareCache.get(serverDir);
|
|
680
|
+
if (!cached) {
|
|
681
|
+
const singleFilePath = resolveUserModule(serverDir, "middleware") ?? join(serverDir, "middleware.ts");
|
|
682
|
+
cached = {
|
|
683
|
+
singleFilePath,
|
|
684
|
+
singleFileExists: existsSync(singleFilePath),
|
|
685
|
+
dirMiddlewares: scanMiddlewares(serverDir)
|
|
686
|
+
};
|
|
687
|
+
middlewareCache.set(serverDir, cached);
|
|
688
|
+
}
|
|
689
|
+
return cached;
|
|
690
|
+
}
|
|
691
|
+
var MiddlewareNextUnavailableError = class extends Error {
|
|
692
|
+
constructor() {
|
|
693
|
+
super(
|
|
694
|
+
"`next` is not available in `server/middleware/*.ts`. That path runs BEFORE routing, so it has no downstream response to give you: there is nothing to await and nothing to observe afterwards. Return a `Response` to answer the request, or return nothing to continue. For code that must run AFTER the route, use the Web path \u2014 `executeWebRequest`, whose runner passes a `next` that reaches the route (`http/web-middleware-runner.ts`)."
|
|
695
|
+
);
|
|
696
|
+
this.name = "MiddlewareNextUnavailableError";
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
var nodePathHasNoDownstream = () => {
|
|
700
|
+
throw new MiddlewareNextUnavailableError();
|
|
701
|
+
};
|
|
702
|
+
async function runWebShapedMiddleware(mw, req, res, context) {
|
|
703
|
+
const request = createWebRequestSource(req).toRequest();
|
|
704
|
+
const result = await mw(request, context, nodePathHasNoDownstream);
|
|
705
|
+
if (!(result instanceof Response)) return { shortCircuited: false };
|
|
706
|
+
await writeWebResponseToServerResponse(result, res);
|
|
707
|
+
return { shortCircuited: true };
|
|
708
|
+
}
|
|
709
|
+
function isWebShaped(mw) {
|
|
710
|
+
return WEB_SHAPED_MIDDLEWARE in mw;
|
|
711
|
+
}
|
|
712
|
+
async function runScannedMiddleware(mw, req, res, context) {
|
|
713
|
+
if (isWebShaped(mw)) {
|
|
714
|
+
const { shortCircuited } = await runWebShapedMiddleware(mw, req, res, context);
|
|
715
|
+
return { aborted: shortCircuited };
|
|
716
|
+
}
|
|
717
|
+
const { nextCalled } = await runOneMiddleware(mw, req, res);
|
|
718
|
+
return { aborted: !nextCalled || res.writableEnded };
|
|
719
|
+
}
|
|
720
|
+
var SILENT_MIDDLEWARE_WARNING_MS = 1e4;
|
|
721
|
+
async function runOneMiddleware(mw, req, res) {
|
|
722
|
+
const state = { nextCalled: false };
|
|
723
|
+
let signalNext;
|
|
724
|
+
const nextCalled = new Promise((resolve) => {
|
|
725
|
+
signalNext = resolve;
|
|
726
|
+
});
|
|
727
|
+
await mw(req, res, () => {
|
|
728
|
+
state.nextCalled = true;
|
|
729
|
+
signalNext();
|
|
730
|
+
});
|
|
731
|
+
if (state.nextCalled || res.writableEnded) return state;
|
|
732
|
+
const signals = [nextCalled, warnAboutSilence(mw)];
|
|
733
|
+
const ended = responseEnded(res);
|
|
734
|
+
if (ended !== void 0) signals.push(ended);
|
|
735
|
+
await Promise.race(signals);
|
|
736
|
+
return state;
|
|
737
|
+
}
|
|
738
|
+
function responseEnded(res) {
|
|
739
|
+
if (typeof res.once !== "function") return void 0;
|
|
740
|
+
return new Promise((resolve) => {
|
|
741
|
+
res.once("finish", resolve);
|
|
742
|
+
res.once("close", resolve);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function warnAboutSilence(mw) {
|
|
746
|
+
return new Promise((resolve) => {
|
|
747
|
+
const timer = setTimeout(() => {
|
|
748
|
+
const named = mw.name === "" ? "an anonymous middleware" : `\`${mw.name}\``;
|
|
749
|
+
console.warn(
|
|
750
|
+
`[theokit] ${named} in server/middleware/ has neither called next() nor written a response after ${String(SILENT_MIDDLEWARE_WARNING_MS)}ms. The request is waiting on it. Call next() to continue, or write a response to answer \u2014 doing neither holds the socket open.`
|
|
751
|
+
);
|
|
752
|
+
resolve();
|
|
753
|
+
}, SILENT_MIDDLEWARE_WARNING_MS);
|
|
754
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
async function runMiddlewareAndContext(req, res, loadModule, serverDir) {
|
|
758
|
+
const { singleFilePath, singleFileExists, dirMiddlewares } = getCachedScan(serverDir);
|
|
759
|
+
const dirExists = dirMiddlewares.length > 0;
|
|
760
|
+
const middlewareCtx = {};
|
|
761
|
+
if (singleFileExists && dirExists) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
"Ambiguous middleware configuration: found both server/middleware.ts and server/middleware/ directory. Use one or the other, not both."
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (dirExists) {
|
|
767
|
+
for (const mwPath of dirMiddlewares) {
|
|
768
|
+
const mod = await loadModule(mwPath);
|
|
769
|
+
const mw = mod.default;
|
|
770
|
+
if (typeof mw !== "function") continue;
|
|
771
|
+
const { aborted } = await runScannedMiddleware(mw, req, res, middlewareCtx);
|
|
772
|
+
if (aborted) return { ctx: middlewareCtx, aborted: true };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (singleFileExists) {
|
|
776
|
+
const mod = await loadModule(singleFilePath);
|
|
777
|
+
const mw = mod.default;
|
|
778
|
+
if (typeof mw === "function") {
|
|
779
|
+
const { aborted } = await runScannedMiddleware(mw, req, res, middlewareCtx);
|
|
780
|
+
if (aborted) return { ctx: middlewareCtx, aborted: true };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const factoryCtx = await createServerContext(req, res, loadModule, serverDir);
|
|
784
|
+
if (factoryCtx === null || typeof factoryCtx !== "object") {
|
|
785
|
+
return { ctx: middlewareCtx, aborted: false };
|
|
786
|
+
}
|
|
787
|
+
return { ctx: { ...middlewareCtx, ...factoryCtx }, aborted: false };
|
|
788
|
+
}
|
|
789
|
+
async function createServerContext(req, res, loadModule, serverDir) {
|
|
790
|
+
const contextPath = resolveUserModule(serverDir, "context");
|
|
791
|
+
if (contextPath === void 0) return {};
|
|
792
|
+
const mod = await loadModule(contextPath);
|
|
793
|
+
const createContext = mod.createContext;
|
|
794
|
+
if (typeof createContext !== "function") return {};
|
|
795
|
+
return await createContext({ request: req, response: res });
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export {
|
|
799
|
+
sendJson,
|
|
800
|
+
sendError,
|
|
801
|
+
parseQueryAndBody,
|
|
802
|
+
isZodLike,
|
|
803
|
+
runZodValidation,
|
|
804
|
+
incomingMessageToWebRequest,
|
|
805
|
+
createWebRequestSource,
|
|
806
|
+
incomingMessageToHandlerRequest,
|
|
807
|
+
validateRouteInput,
|
|
808
|
+
executeWebRequest,
|
|
809
|
+
writeWebResponseToServerResponse,
|
|
810
|
+
resolveUserModule,
|
|
811
|
+
_resetMiddlewareCacheForTests,
|
|
812
|
+
MiddlewareNextUnavailableError,
|
|
813
|
+
runMiddlewareAndContext,
|
|
814
|
+
createServerContext
|
|
815
|
+
};
|
|
816
|
+
//# sourceMappingURL=chunk-4D4YVXHR.js.map
|