theokit 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-virtual-module-SQDY3V5X.js → actions-virtual-module-3CDQTWOC.js} +6 -6
- package/dist/{actions-virtual-module-PNPRCEOS.js → actions-virtual-module-EIPXX4ZB.js} +3 -3
- package/dist/agents-typed-client-SAWAAH7K.js +142 -0
- package/dist/agents-typed-client-SAWAAH7K.js.map +1 -0
- package/dist/agents-typed-client-UTEQUA63.js +143 -0
- package/dist/agents-typed-client-UTEQUA63.js.map +1 -0
- package/dist/{app-typed-client-5GYEOYP3.js → app-typed-client-7PBFWZUE.js} +3 -3
- package/dist/{app-typed-client-QG7BVZYW.js → app-typed-client-CSOK7NPC.js} +6 -6
- package/dist/body-parser-web-FV5HWCY3.js +71 -0
- package/dist/body-parser-web-FV5HWCY3.js.map +1 -0
- package/dist/{build-QFRLSEZ4.js → build-HXND27XG.js} +11 -11
- package/dist/{chunk-223EFY5X.js → chunk-2J7XU3PW.js} +68 -27
- package/dist/chunk-2J7XU3PW.js.map +1 -0
- package/dist/{chunk-RESN62GB.js → chunk-2KZQPDYR.js} +5 -48
- package/dist/chunk-2KZQPDYR.js.map +1 -0
- package/dist/chunk-3S3BNW5K.js +445 -0
- package/dist/chunk-3S3BNW5K.js.map +1 -0
- package/dist/{chunk-6FYD34NX.js → chunk-BQDGES7C.js} +28 -28
- package/dist/{chunk-6FYD34NX.js.map → chunk-BQDGES7C.js.map} +1 -1
- package/dist/chunk-EXP56GFQ.js +52 -0
- package/dist/chunk-EXP56GFQ.js.map +1 -0
- package/dist/chunk-F4YUPDJ2.js +115 -0
- package/dist/chunk-F4YUPDJ2.js.map +1 -0
- package/dist/{chunk-NAZ4E2GT.js → chunk-KXA37ONC.js} +2 -2
- package/dist/chunk-NHJMZCAS.js +32 -0
- package/dist/chunk-NHJMZCAS.js.map +1 -0
- package/dist/{chunk-43D6XNDR.js → chunk-O62MW4MT.js} +91 -18
- package/dist/chunk-O62MW4MT.js.map +1 -0
- package/dist/chunk-RSVN727G.js +1 -0
- package/dist/{chunk-7CBRKNQA.js → chunk-RYTZYFSD.js} +198 -6
- package/dist/chunk-RYTZYFSD.js.map +1 -0
- package/dist/chunk-UNLA45FY.js +235 -0
- package/dist/chunk-UNLA45FY.js.map +1 -0
- package/dist/{chunk-GFMQJHXX.js → chunk-WR4F4EEZ.js} +1082 -1074
- package/dist/chunk-WR4F4EEZ.js.map +1 -0
- package/dist/{chunk-AD74EAK3.js → chunk-ZSTZXR2D.js} +1 -30
- package/dist/chunk-ZSTZXR2D.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/client/index.d.ts +57 -1
- package/dist/client/index.js +84 -3
- package/dist/client/index.js.map +1 -1
- package/dist/{dev-GBXOTXUP.js → dev-OWW4XVIH.js} +10 -10
- package/dist/{dev-emit-FEFEDLZF.js → dev-emit-5MDSBP5D.js} +3 -3
- package/dist/{dev-emit-O4EGOSNV.js → dev-emit-QH2YGZXN.js} +2 -2
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/internal-api-4YTJDITC.js +83 -0
- package/dist/internal-api-EFKZWIYZ.js +66 -0
- package/dist/internal-api-EFKZWIYZ.js.map +1 -0
- package/dist/{openapi-VR6AFBLJ.js → openapi-FHY6HC6I.js} +7 -7
- package/dist/{registry-Q2TZQLUH.js → registry-34LL7NF4.js} +1 -1
- package/dist/{routes-LRYOIIAI.js → routes-EW7TP7NJ.js} +2 -2
- package/dist/server/agent/index.js +2 -1
- package/dist/server/define/index.js +4 -2
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +9 -294
- package/dist/server/index.js.map +1 -1
- package/dist/server/scan/index.d.ts +22 -2
- package/dist/server/scan/index.js +1 -1
- package/dist/{start-3ZHAXSJE.js → start-KIQ5TTLR.js} +76 -13
- package/dist/start-KIQ5TTLR.js.map +1 -0
- package/dist/vite-plugin/index.js +6 -4
- package/dist/{vite-plugin-WO72VLYR.js → vite-plugin-RK66K26Z.js} +7 -7
- package/dist/vite-plugin-RK66K26Z.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-223EFY5X.js.map +0 -1
- package/dist/chunk-3LVRAGAZ.js +0 -73
- package/dist/chunk-3LVRAGAZ.js.map +0 -1
- package/dist/chunk-43D6XNDR.js.map +0 -1
- package/dist/chunk-7CBRKNQA.js.map +0 -1
- package/dist/chunk-AD74EAK3.js.map +0 -1
- package/dist/chunk-GFMQJHXX.js.map +0 -1
- package/dist/chunk-PBEH6NXR.js +0 -44
- package/dist/chunk-PBEH6NXR.js.map +0 -1
- package/dist/chunk-PIVX3DYW.js +0 -142
- package/dist/chunk-PIVX3DYW.js.map +0 -1
- package/dist/chunk-PPPR5DGR.js +0 -1
- package/dist/chunk-RESN62GB.js.map +0 -1
- package/dist/start-3ZHAXSJE.js.map +0 -1
- /package/dist/{actions-virtual-module-SQDY3V5X.js.map → actions-virtual-module-3CDQTWOC.js.map} +0 -0
- /package/dist/{actions-virtual-module-PNPRCEOS.js.map → actions-virtual-module-EIPXX4ZB.js.map} +0 -0
- /package/dist/{app-typed-client-5GYEOYP3.js.map → app-typed-client-7PBFWZUE.js.map} +0 -0
- /package/dist/{app-typed-client-QG7BVZYW.js.map → app-typed-client-CSOK7NPC.js.map} +0 -0
- /package/dist/{build-QFRLSEZ4.js.map → build-HXND27XG.js.map} +0 -0
- /package/dist/{chunk-NAZ4E2GT.js.map → chunk-KXA37ONC.js.map} +0 -0
- /package/dist/{chunk-PPPR5DGR.js.map → chunk-RSVN727G.js.map} +0 -0
- /package/dist/{dev-GBXOTXUP.js.map → dev-OWW4XVIH.js.map} +0 -0
- /package/dist/{dev-emit-FEFEDLZF.js.map → dev-emit-5MDSBP5D.js.map} +0 -0
- /package/dist/{dev-emit-O4EGOSNV.js.map → dev-emit-QH2YGZXN.js.map} +0 -0
- /package/dist/{vite-plugin-WO72VLYR.js.map → internal-api-4YTJDITC.js.map} +0 -0
- /package/dist/{openapi-VR6AFBLJ.js.map → openapi-FHY6HC6I.js.map} +0 -0
- /package/dist/{registry-Q2TZQLUH.js.map → registry-34LL7NF4.js.map} +0 -0
- /package/dist/{routes-LRYOIIAI.js.map → routes-EW7TP7NJ.js.map} +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isZodLike
|
|
3
|
+
} from "./chunk-TGTNRUH3.js";
|
|
4
|
+
import {
|
|
5
|
+
validateCsrfRequest
|
|
6
|
+
} from "./chunk-TSLSIRBR.js";
|
|
7
|
+
import {
|
|
8
|
+
envelopeCodeToStatus
|
|
9
|
+
} from "./chunk-X32XEJXR.js";
|
|
10
|
+
import {
|
|
11
|
+
TheoError,
|
|
12
|
+
serverErrorToEnvelope
|
|
13
|
+
} from "./chunk-UVXB2ER7.js";
|
|
14
|
+
|
|
15
|
+
// src/config/load-env.ts
|
|
16
|
+
import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
|
|
17
|
+
import { resolve } from "path";
|
|
18
|
+
import dotenv from "dotenv";
|
|
19
|
+
import { expand } from "dotenv-expand";
|
|
20
|
+
var MAX_ENV_FILE_BYTES = 1048576;
|
|
21
|
+
var cache = /* @__PURE__ */ new Map();
|
|
22
|
+
function _resetEnvCache() {
|
|
23
|
+
cache.clear();
|
|
24
|
+
}
|
|
25
|
+
function envFilesInPriorityOrder(mode) {
|
|
26
|
+
const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`];
|
|
27
|
+
if (mode === "test") {
|
|
28
|
+
return files.filter((f) => f !== ".env.local");
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
function readEnvFile(path) {
|
|
33
|
+
let stat;
|
|
34
|
+
try {
|
|
35
|
+
stat = statSync(path);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (!stat.isFile() && !stat.isFIFO()) return null;
|
|
40
|
+
if (stat.size > MAX_ENV_FILE_BYTES) {
|
|
41
|
+
console.warn(
|
|
42
|
+
`[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes \u2014 skipping (likely a generated artifact, not a real env file)`
|
|
43
|
+
);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const lstat = lstatSync(path);
|
|
48
|
+
if (lstat.isSymbolicLink()) {
|
|
49
|
+
const real = realpathSync(path);
|
|
50
|
+
console.info(`[theokit] .env at ${path} is a symlink \u2192 ${real}`);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const content = readFileSync(path, "utf-8");
|
|
56
|
+
return dotenv.parse(content);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function loadEnv(options = {}) {
|
|
62
|
+
const cwd = options.cwd ?? process.cwd();
|
|
63
|
+
const mode = options.mode ?? process.env.NODE_ENV ?? "development";
|
|
64
|
+
const cacheKey = `${cwd}:${mode}`;
|
|
65
|
+
if (!options.forceReload) {
|
|
66
|
+
const cached = cache.get(cacheKey);
|
|
67
|
+
if (cached) return cached;
|
|
68
|
+
}
|
|
69
|
+
const fileNames = envFilesInPriorityOrder(mode);
|
|
70
|
+
const filePaths = fileNames.map((f) => resolve(cwd, f));
|
|
71
|
+
const merged = {};
|
|
72
|
+
const loadedFromFiles = [];
|
|
73
|
+
for (const path of [...filePaths].reverse()) {
|
|
74
|
+
const parsed = readEnvFile(path);
|
|
75
|
+
if (parsed === null) continue;
|
|
76
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
77
|
+
merged[k] = v;
|
|
78
|
+
}
|
|
79
|
+
loadedFromFiles.unshift(path);
|
|
80
|
+
}
|
|
81
|
+
if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === void 0) {
|
|
82
|
+
process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV;
|
|
83
|
+
}
|
|
84
|
+
delete merged.NODE_ENV;
|
|
85
|
+
const processEnvClone = {};
|
|
86
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
87
|
+
if (typeof v === "string") processEnvClone[k] = v;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
expand({ parsed: merged, processEnv: processEnvClone });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof RangeError) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`[theokit] .env expansion overflow (likely circular reference like A=\${B}, B=\${A}). Leaving values as literals.`
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const loaded = {};
|
|
101
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
102
|
+
if (process.env[k] !== void 0) continue;
|
|
103
|
+
process.env[k] = v;
|
|
104
|
+
loaded[k] = v;
|
|
105
|
+
}
|
|
106
|
+
process.env.__THEOKIT_PROCESSED_ENV = "true";
|
|
107
|
+
const result = { loaded, loadedFromFiles };
|
|
108
|
+
cache.set(cacheKey, result);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/server/transformer.ts
|
|
113
|
+
import superjson from "superjson";
|
|
114
|
+
var superjsonTransformer = {
|
|
115
|
+
name: "superjson",
|
|
116
|
+
serialize: (v) => JSON.stringify(superjson.serialize(v)),
|
|
117
|
+
deserialize: (raw) => {
|
|
118
|
+
const parsed = JSON.parse(raw);
|
|
119
|
+
return superjson.deserialize(parsed);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var jsonTransformer = {
|
|
123
|
+
name: "json",
|
|
124
|
+
serialize: (v) => JSON.stringify(v),
|
|
125
|
+
deserialize: (raw) => JSON.parse(raw)
|
|
126
|
+
};
|
|
127
|
+
var BUILT_INS = {
|
|
128
|
+
superjson: superjsonTransformer,
|
|
129
|
+
json: jsonTransformer
|
|
130
|
+
};
|
|
131
|
+
function resolveTransformer(selector) {
|
|
132
|
+
if (typeof selector === "string") {
|
|
133
|
+
const built = BUILT_INS[selector];
|
|
134
|
+
if (built === void 0) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Unknown transformer "${selector}". Built-in options: ${Object.keys(BUILT_INS).join(", ")}.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return built;
|
|
140
|
+
}
|
|
141
|
+
if (typeof selector !== "object" || typeof selector.serialize !== "function" || typeof selector.deserialize !== "function") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return selector;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/server/http/web-middleware-runner.ts
|
|
150
|
+
async function runWebMiddleware(request, middleware, context) {
|
|
151
|
+
for (const mw of middleware) {
|
|
152
|
+
const result = await mw(request, context);
|
|
153
|
+
if (result instanceof Response) return result;
|
|
154
|
+
}
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/server/web-handler.ts
|
|
159
|
+
function searchParamsToObject(params) {
|
|
160
|
+
const out = {};
|
|
161
|
+
for (const [key, value] of params.entries()) {
|
|
162
|
+
if (!Object.hasOwn(out, key)) {
|
|
163
|
+
out[key] = value;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const existing = out[key];
|
|
167
|
+
if (Array.isArray(existing)) {
|
|
168
|
+
existing.push(value);
|
|
169
|
+
} else {
|
|
170
|
+
out[key] = [existing, value];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
async function parseBodyInline(request) {
|
|
176
|
+
if (request.method === "GET" || request.method === "HEAD") return void 0;
|
|
177
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
178
|
+
if (contentType.includes("application/json")) {
|
|
179
|
+
const text = await request.text();
|
|
180
|
+
if (text.length === 0) return void 0;
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(text);
|
|
183
|
+
} catch {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (contentType.startsWith("text/")) {
|
|
188
|
+
const text = await request.text();
|
|
189
|
+
return text.length === 0 ? void 0 : text;
|
|
190
|
+
}
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
async function parseBodyFull(request) {
|
|
194
|
+
if (request.method === "GET" || request.method === "HEAD") return void 0;
|
|
195
|
+
const { parseWebRequestBody } = await import("./body-parser-web-MUWBKZ3F.js");
|
|
196
|
+
try {
|
|
197
|
+
const parsed = await parseWebRequestBody(request);
|
|
198
|
+
if (parsed.json === void 0 && Object.keys(parsed.fields).length === 0 && parsed.files.length === 0) {
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
return parsed;
|
|
202
|
+
} catch {
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function validationErrorResponse(zodError, field) {
|
|
207
|
+
const envelope = {
|
|
208
|
+
code: "BAD_REQUEST",
|
|
209
|
+
message: `Validation failed: ${field}`,
|
|
210
|
+
ext: {
|
|
211
|
+
fields: zodError.issues.map((issue) => ({
|
|
212
|
+
path: issue.path.join("."),
|
|
213
|
+
message: issue.message
|
|
214
|
+
}))
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return new Response(JSON.stringify(envelope), {
|
|
218
|
+
status: 400,
|
|
219
|
+
headers: { "content-type": "application/json" }
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async function runHandler(config, request, bodyParser = "inline", paramsInput = {}, context = {}) {
|
|
223
|
+
const url = new URL(request.url);
|
|
224
|
+
const queryRaw = searchParamsToObject(url.searchParams);
|
|
225
|
+
const bodyRaw = bodyParser === "full" ? await parseBodyFull(request) : await parseBodyInline(request);
|
|
226
|
+
const paramsRaw = paramsInput;
|
|
227
|
+
let query = queryRaw;
|
|
228
|
+
if (config.query !== void 0) {
|
|
229
|
+
const parsed = config.query.safeParse(queryRaw);
|
|
230
|
+
if (!parsed.success)
|
|
231
|
+
return { ok: false, response: validationErrorResponse(parsed.error, "query") };
|
|
232
|
+
query = parsed.data;
|
|
233
|
+
}
|
|
234
|
+
let body = bodyRaw;
|
|
235
|
+
if (config.body !== void 0) {
|
|
236
|
+
const parsed = config.body.safeParse(bodyRaw);
|
|
237
|
+
if (!parsed.success)
|
|
238
|
+
return { ok: false, response: validationErrorResponse(parsed.error, "body") };
|
|
239
|
+
body = parsed.data;
|
|
240
|
+
}
|
|
241
|
+
let params = paramsRaw;
|
|
242
|
+
if (config.params !== void 0) {
|
|
243
|
+
const parsed = config.params.safeParse(paramsRaw);
|
|
244
|
+
if (!parsed.success)
|
|
245
|
+
return { ok: false, response: validationErrorResponse(parsed.error, "params") };
|
|
246
|
+
params = parsed.data;
|
|
247
|
+
}
|
|
248
|
+
const result = await config.handler({ query, body, params, request, context });
|
|
249
|
+
return validateResponseOutput(config.response, result) ?? { ok: true, result };
|
|
250
|
+
}
|
|
251
|
+
function validateResponseOutput(response, result) {
|
|
252
|
+
const validatable = result !== void 0 && result !== null && !(result instanceof Response);
|
|
253
|
+
if (!validatable || !isZodLike(response)) return void 0;
|
|
254
|
+
const parsed = response.safeParse(result);
|
|
255
|
+
if (parsed.success) return { ok: true, result: parsed.data };
|
|
256
|
+
const err = new TheoError({
|
|
257
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
258
|
+
message: "response validation failed",
|
|
259
|
+
ext: { issues: parsed.error?.issues }
|
|
260
|
+
});
|
|
261
|
+
return { ok: false, response: handlerErrorResponse(err) };
|
|
262
|
+
}
|
|
263
|
+
function toResponse(result, status) {
|
|
264
|
+
if (result === void 0) {
|
|
265
|
+
return new Response(null, { status: 204 });
|
|
266
|
+
}
|
|
267
|
+
if (result instanceof Response) {
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
return new Response(JSON.stringify(result), {
|
|
271
|
+
status: status ?? 200,
|
|
272
|
+
headers: { "content-type": "application/json" }
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function handlerErrorResponse(err) {
|
|
276
|
+
const envelope = serverErrorToEnvelope(err);
|
|
277
|
+
const status = envelopeCodeToStatus(envelope.code);
|
|
278
|
+
return new Response(JSON.stringify(envelope), {
|
|
279
|
+
status,
|
|
280
|
+
headers: { "content-type": "application/json" }
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
var CSRF_PROTECTED_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
284
|
+
function mergeHookHeaders(response, hookHeaders) {
|
|
285
|
+
if ([...hookHeaders].length === 0) return response;
|
|
286
|
+
const merged = new Headers(response.headers);
|
|
287
|
+
for (const [k, v] of hookHeaders.entries()) {
|
|
288
|
+
if (k.toLowerCase() === "set-cookie") continue;
|
|
289
|
+
if (!merged.has(k)) merged.set(k, v);
|
|
290
|
+
}
|
|
291
|
+
for (const sc of hookHeaders.getSetCookie()) {
|
|
292
|
+
merged.append("Set-Cookie", sc);
|
|
293
|
+
}
|
|
294
|
+
return new Response(response.body, {
|
|
295
|
+
status: response.status,
|
|
296
|
+
statusText: response.statusText,
|
|
297
|
+
headers: merged
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function methodNotAllowedResponse(method) {
|
|
301
|
+
const envelope = {
|
|
302
|
+
code: "METHOD_NOT_ALLOWED",
|
|
303
|
+
message: `Method ${method} not allowed`
|
|
304
|
+
};
|
|
305
|
+
return new Response(JSON.stringify(envelope), {
|
|
306
|
+
status: 405,
|
|
307
|
+
headers: { "content-type": "application/json" }
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function csrfFailedResponse(reason) {
|
|
311
|
+
const envelope = {
|
|
312
|
+
code: "FORBIDDEN",
|
|
313
|
+
message: `CSRF check failed: ${reason}`
|
|
314
|
+
};
|
|
315
|
+
return new Response(JSON.stringify(envelope), {
|
|
316
|
+
status: 403,
|
|
317
|
+
headers: { "content-type": "application/json" }
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async function executeWebRequest(request, routeModule, opts = {}) {
|
|
321
|
+
const method = request.method.toUpperCase();
|
|
322
|
+
const config = routeModule[method];
|
|
323
|
+
if (opts.hooks === void 0) {
|
|
324
|
+
if (config === void 0 || typeof config.handler !== "function") {
|
|
325
|
+
return methodNotAllowedResponse(method);
|
|
326
|
+
}
|
|
327
|
+
if (opts.csrfMode === "strict" && CSRF_PROTECTED_METHODS.has(method)) {
|
|
328
|
+
const csrfCheck = validateCsrfRequest(request);
|
|
329
|
+
if (!csrfCheck.valid) return csrfFailedResponse(csrfCheck.reason);
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const context = {};
|
|
333
|
+
if (opts.middleware?.length) {
|
|
334
|
+
const shortCircuit = await runWebMiddleware(request, opts.middleware, context);
|
|
335
|
+
if (shortCircuit) return shortCircuit;
|
|
336
|
+
}
|
|
337
|
+
const outcome = await runHandler(
|
|
338
|
+
config,
|
|
339
|
+
request,
|
|
340
|
+
opts.bodyParser ?? "inline",
|
|
341
|
+
opts.params ?? {},
|
|
342
|
+
context
|
|
343
|
+
);
|
|
344
|
+
if (!outcome.ok) return outcome.response;
|
|
345
|
+
return toResponse(outcome.result, config.status);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
return handlerErrorResponse(err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return runWithHooks(request, config, opts, opts.hooks);
|
|
351
|
+
}
|
|
352
|
+
async function runPreHandlerPipeline(hookCtx, request, config, opts, hooks) {
|
|
353
|
+
const method = request.method.toUpperCase();
|
|
354
|
+
const runList = async (list) => {
|
|
355
|
+
for (const hook of list) {
|
|
356
|
+
if (hookCtx.response !== void 0) return;
|
|
357
|
+
await hook(hookCtx);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
if (hooks.onRequest) await runList(hooks.onRequest);
|
|
361
|
+
if (hookCtx.response === void 0 && opts.csrfMode === "strict" && CSRF_PROTECTED_METHODS.has(method)) {
|
|
362
|
+
const csrfCheck = validateCsrfRequest(request);
|
|
363
|
+
if (!csrfCheck.valid) hookCtx.response = csrfFailedResponse(csrfCheck.reason);
|
|
364
|
+
}
|
|
365
|
+
if (hookCtx.response === void 0 && hooks.preHandler) {
|
|
366
|
+
await runList(hooks.preHandler);
|
|
367
|
+
}
|
|
368
|
+
if (hookCtx.response === void 0) {
|
|
369
|
+
await runHandlerStage(hookCtx, request, config, opts, method);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function runHandlerStage(hookCtx, request, config, opts, method) {
|
|
373
|
+
if (config === void 0 || typeof config.handler !== "function") {
|
|
374
|
+
hookCtx.response = methodNotAllowedResponse(method);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (opts.middleware?.length) {
|
|
378
|
+
const shortCircuit = await runWebMiddleware(request, opts.middleware, hookCtx.ctx);
|
|
379
|
+
if (shortCircuit) {
|
|
380
|
+
hookCtx.response = shortCircuit;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const outcome = await runHandler(
|
|
385
|
+
config,
|
|
386
|
+
request,
|
|
387
|
+
opts.bodyParser ?? "inline",
|
|
388
|
+
opts.params ?? {},
|
|
389
|
+
hookCtx.ctx
|
|
390
|
+
);
|
|
391
|
+
hookCtx.response = outcome.ok ? toResponse(outcome.result, config.status) : outcome.response;
|
|
392
|
+
}
|
|
393
|
+
async function runWithHooks(request, config, opts, hooks) {
|
|
394
|
+
const hookCtx = {
|
|
395
|
+
request,
|
|
396
|
+
responseHeaders: new Headers(),
|
|
397
|
+
ctx: {},
|
|
398
|
+
requestId: opts.requestId ?? globalThis.crypto.randomUUID()
|
|
399
|
+
};
|
|
400
|
+
try {
|
|
401
|
+
await runPreHandlerPipeline(hookCtx, request, config, opts, hooks);
|
|
402
|
+
if (hooks.onResponse) {
|
|
403
|
+
for (const hook of hooks.onResponse) {
|
|
404
|
+
await hook(hookCtx);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const finalResponse = hookCtx.response ?? new Response(
|
|
408
|
+
JSON.stringify({ code: "INTERNAL_SERVER_ERROR", message: "No response built" }),
|
|
409
|
+
{
|
|
410
|
+
status: 500,
|
|
411
|
+
headers: { "content-type": "application/json" }
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
return mergeHookHeaders(finalResponse, hookCtx.responseHeaders);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
return runErrorHooks(err, hookCtx, hooks.onError);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function runErrorHooks(err, hookCtx, onError) {
|
|
420
|
+
const errorResponse = handlerErrorResponse(err);
|
|
421
|
+
if (onError !== void 0) {
|
|
422
|
+
const errorCtx = {
|
|
423
|
+
...hookCtx,
|
|
424
|
+
response: errorResponse,
|
|
425
|
+
error: err
|
|
426
|
+
};
|
|
427
|
+
for (const hook of onError) {
|
|
428
|
+
try {
|
|
429
|
+
await hook(errorCtx);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return mergeHookHeaders(errorResponse, hookCtx.responseHeaders);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export {
|
|
438
|
+
_resetEnvCache,
|
|
439
|
+
loadEnv,
|
|
440
|
+
superjsonTransformer,
|
|
441
|
+
jsonTransformer,
|
|
442
|
+
resolveTransformer,
|
|
443
|
+
executeWebRequest
|
|
444
|
+
};
|
|
445
|
+
//# sourceMappingURL=chunk-3S3BNW5K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/load-env.ts","../src/server/transformer.ts","../src/server/http/web-middleware-runner.ts","../src/server/web-handler.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Reads `.env` files from a CLI-controlled `cwd`. File names are a fixed\n * literal set. Build-time + CLI tool; no HTTP input.\n */\n/**\n * `loadEnv()` — auto-loads `.env` files into `process.env` for server code.\n *\n * Implements the Next.js `loadEnvConfig` algorithm\n * (`referencias/next.js/packages/next-env/index.ts:114-180`) with TheoKit\n * adaptations:\n *\n * - EC-1: 1MB file-size cap (anti-OOM, anti-supply-chain).\n * - EC-2: `_resetEnvCache()` test-only side-door.\n * - EC-8: `dotenv-expand` circular-ref safe (lib-provided).\n * - EC-13: log when `.env` is a symlink (transparency).\n * - D6: real `process.env` wins over `.env`-file values.\n * - NODE_ENV stash: `.env`-set NODE_ENV NEVER overrides the real\n * `process.env.NODE_ENV`. Stashed in `__THEOKIT_USER_NODE_ENV` instead.\n */\n\nimport { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport dotenv from 'dotenv'\nimport { expand, type DotenvPopulateInput } from 'dotenv-expand'\n\nimport type { LoadEnvOptions, LoadEnvResult } from './load-env-types.js'\n\n/** EC-1: 1MB cap. Real `.env`s are < 10KB. */\nconst MAX_ENV_FILE_BYTES = 1_048_576\n\nconst cache = new Map<string, LoadEnvResult>()\n\n/** Test-only side-door for EC-2 — clear the module-level cache between vitest test files. */\nexport function _resetEnvCache(): void {\n cache.clear()\n}\n\nfunction envFilesInPriorityOrder(mode: string): string[] {\n // Top of list = highest priority. Will be read in REVERSE so first-in\n // wins via overwrite during merge.\n const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`]\n // Test mode skips `.env.local` per dotenv convention (avoid leaking dev\n // secrets into the test suite).\n if (mode === 'test') {\n return files.filter((f) => f !== '.env.local')\n }\n return files\n}\n\nfunction readEnvFile(path: string): Record<string, string> | null {\n let stat\n try {\n stat = statSync(path)\n } catch {\n return null // ENOENT / EACCES → skip\n }\n\n // Allow files + FIFOs (1Password/SOPS pipe support).\n if (!stat.isFile() && !stat.isFIFO()) return null\n\n // EC-1 — anti-OOM cap.\n if (stat.size > MAX_ENV_FILE_BYTES) {\n console.warn(\n `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes — skipping (likely a generated artifact, not a real env file)`,\n )\n return null\n }\n\n // EC-13 — symlink transparency. Don't refuse; just log.\n try {\n const lstat = lstatSync(path)\n if (lstat.isSymbolicLink()) {\n const real = realpathSync(path)\n // eslint-disable-next-line no-console -- intentional transparency log on a build-time tool\n console.info(`[theokit] .env at ${path} is a symlink → ${real}`)\n }\n } catch {\n // lstat failure is non-fatal — fall through to read.\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return dotenv.parse(content)\n } catch {\n return null\n }\n}\n\n// eslint-disable-next-line complexity -- canonical env-load algorithm (priority → expand → guards → apply); inlining branches is clearer than extracting micro-helpers\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult {\n const cwd = options.cwd ?? process.cwd()\n const mode = options.mode ?? process.env.NODE_ENV ?? 'development'\n const cacheKey = `${cwd}:${mode}`\n\n if (!options.forceReload) {\n const cached = cache.get(cacheKey)\n if (cached) return cached\n }\n\n const fileNames = envFilesInPriorityOrder(mode)\n const filePaths = fileNames.map((f) => resolve(cwd, f))\n\n // Read in REVERSE — lower-priority files first, so higher-priority\n // (lower-index) overwrites during merge.\n const merged: Record<string, string> = {}\n const loadedFromFiles: string[] = []\n for (const path of [...filePaths].reverse()) {\n const parsed = readEnvFile(path)\n if (parsed === null) continue\n for (const [k, v] of Object.entries(parsed)) {\n merged[k] = v\n }\n loadedFromFiles.unshift(path) // keep priority order in result\n }\n\n // NODE_ENV stash — `.env`-set NODE_ENV never overrides real process.env.NODE_ENV.\n if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === undefined) {\n process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV\n }\n delete merged.NODE_ENV // never propagate\n\n // EC-8 — dotenv-expand@11 stack-overflows on circular refs (A=${B}, B=${A}).\n // Wrap in try/catch — on overflow, leave the parsed map untouched (literal\n // values survive). Tested via test_loadEnv_circular_expand_no_loop_EC8.\n // Pass a CLONE of process.env (string-only, filtered) so expand can\n // reference real env without mutating it.\n const processEnvClone: DotenvPopulateInput = {}\n for (const [k, v] of Object.entries(process.env)) {\n if (typeof v === 'string') processEnvClone[k] = v\n }\n try {\n expand({ parsed: merged, processEnv: processEnvClone })\n } catch (err) {\n if (err instanceof RangeError) {\n console.warn(\n `[theokit] .env expansion overflow (likely circular reference like A=\\${B}, B=\\${A}). Leaving values as literals.`,\n )\n } else {\n throw err\n }\n }\n\n // Apply: D6 — real process.env wins over file values.\n const loaded: Record<string, string> = {}\n for (const [k, v] of Object.entries(merged)) {\n if (process.env[k] !== undefined) continue // real env wins\n process.env[k] = v\n loaded[k] = v\n }\n\n // Sentinel\n process.env.__THEOKIT_PROCESSED_ENV = 'true'\n\n const result: LoadEnvResult = { loaded, loadedFromFiles }\n cache.set(cacheKey, result)\n return result\n}\n","import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * T3.2 — Web-Standards middleware runner for the `executeWebRequest` path.\n *\n * The Node request path (`http/execute.ts`) runs middleware via\n * `runMiddlewareAndContext`, which is coupled to Node `req`/`res`. Per G8\n * (Web Standards over Node APIs) the Web path needs a `Request`-shaped runner;\n * this is it. Extracted to a sibling so `web-handler.ts` stays under its LoC\n * budget (G6).\n *\n * Ordering invariant (EC-3): the caller runs the CSRF gate BEFORE invoking this\n * runner, mirroring the Node path (CSRF stage precedes user middleware).\n *\n * NOTE (D4 / EC-12): this is its own middleware contract — `(request, context)`.\n * Full semantic parity with the Node `defineMiddleware` contract is part of the\n * deferred Node→Web pipeline convergence, not this slice.\n */\n\n/** A Web-path middleware: mutate `context`, or return a `Response` to short-circuit. */\nexport type WebMiddleware = (\n request: Request,\n context: Record<string, unknown>,\n // A middleware may mutate `context` and return nothing (void) OR return a\n // `Response` to short-circuit (Express/Koa-style). `void` in this union is\n // intentional — `runWebMiddleware` only inspects `instanceof Response`, so a\n // void return means \"continue to the next middleware / handler\".\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n) => Promise<Response | undefined> | Response | undefined | void\n\n/**\n * Run middleware in order against a shared mutable `context`. The first\n * middleware that returns a `Response` short-circuits the chain (the handler\n * is not reached); its `Response` is returned verbatim so headers like\n * `Set-Cookie` are preserved (EC-11). Returns `undefined` when every\n * middleware passes (the handler should run next).\n */\nexport async function runWebMiddleware(\n request: Request,\n middleware: readonly WebMiddleware[],\n context: Record<string, unknown>,\n): Promise<Response | undefined> {\n for (const mw of middleware) {\n const result = await mw(request, context)\n if (result instanceof Response) return result\n }\n return undefined\n}\n","/**\n * T5a.2 Phase A — Foundation: Web-Standards request handler entry-point.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase A. Bridges the gap between Web `Request`/`Response` and the\n * existing `defineRoute` config shape (which is consumed today by the Node-\n * shaped `executeRoute`).\n *\n * This is the entry-point T1.2 RED tests expect:\n * `executeWebRequest(request: Request, routeModule: { GET?, POST?, ... }): Promise<Response>`\n *\n * Architecture per ADR-0028 (R3a) — this function accepts a native Web\n * `Request` and returns a native Web `Response`. No `node:*` is required in\n * the implementation (Zod + native Headers/URL/Response cover the surface).\n *\n * **Scope (intentionally narrow):**\n * - Method dispatch (GET/POST/PUT/PATCH/DELETE).\n * - Zod validation for `query` (from URL.searchParams), `body` (from\n * request.json() OR request.text()), `params` (consumer-supplied OR\n * parsed from path).\n * - Handler invocation.\n * - Result → JSON Response (200 default, 204 for void return).\n * - Error → envelope-shaped JSON Response (400 for validation, 500 for\n * handler throws) per G5 boundary translation (serverErrorToEnvelope).\n *\n * **NOT included (deferred to T5a.2 Phase B-G):**\n * - Plugin runner integration (onRequest/preHandler/onResponse hooks).\n * - CSRF / CORS / security headers / rate limiting / cookies / auth.\n * - Middleware chain. SSR rendering. WebSocket upgrade. File upload.\n * - File-system routing scan; consumer provides the route module\n * explicitly (Web Request entry doesn't yet know about scan results).\n *\n * The narrow scope makes this a viable Phase A landing zone — turns the\n * 7 T1.2 RED tests GREEN without prematurely refactoring the full\n * IncomingMessage→Request boundary across server/http/ files.\n */\nimport type { z } from 'zod'\n\nimport { envelopeCodeToStatus } from '../core/contracts/envelope-code-to-status.js'\nimport type { TheoErrorEnvelope } from '../core/contracts/error-envelope.js'\nimport { serverErrorToEnvelope } from '../core/contracts/server-error-to-envelope.js'\nimport { TheoError } from '../core/contracts/theo-error.js'\n\nimport { isZodLike } from './http/execute-stages.js'\nimport { runWebMiddleware, type WebMiddleware } from './http/web-middleware-runner.js'\nimport type {\n WebOnErrorHook,\n WebOnRequestHook,\n WebOnResponseHook,\n WebPluginContext,\n WebPluginErrorContext,\n WebPreHandlerHook,\n} from './plugin-types.js'\nimport { validateCsrfRequest } from './security/csrf.js'\n\nexport type { WebMiddleware }\n\n/**\n * Minimal structural type for a `defineRoute` config. Mirrors\n * `core/contracts/route-config.ts` shape but only the fields this entry\n * point needs at runtime (Zod schemas + handler). Kept structural to\n * avoid a hard dep on the full RouteConfig generic chain.\n */\ninterface WebRouteHandlerConfig {\n query?: z.ZodType\n body?: z.ZodType\n params?: z.ZodType\n /** Optional Zod schema validating a plain-object handler return (D1/D2). */\n response?: z.ZodType\n /** Honored for plain-object returns to match the Node runner (D3). */\n status?: number\n handler: (ctx: {\n query: unknown\n body: unknown\n params: unknown\n request: Request\n /** Mutable per-request context populated by the Web middleware chain (T3.2). */\n context: Record<string, unknown>\n }) => unknown\n}\n\n/**\n * The route module exported by a `server/routes/*.ts` file (after T2.6 G6\n * router lockdown). Method-named exports map to `defineRoute` results.\n */\ntype WebRouteModule = Partial<Record<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', unknown>>\n\n/**\n * Coerce URL.searchParams into a plain `{ key: value }` object so Zod\n * `z.object({...})` schemas work without per-route adapters. Repeated keys\n * become arrays (most browsers serialize multi-select that way).\n */\nfunction searchParamsToObject(params: URLSearchParams): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {}\n for (const [key, value] of params.entries()) {\n if (!Object.hasOwn(out, key)) {\n out[key] = value\n continue\n }\n const existing = out[key]\n if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n out[key] = [existing, value]\n }\n }\n return out\n}\n\n/**\n * Parse request body based on Content-Type. JSON is the canonical path;\n * empty body returns `undefined` (the handler treats as \"no body\").\n *\n * **Inline-only mode (default):** handles `application/json` and `text/*`.\n * Other content-types return `undefined`. Multipart/form-data requires\n * the opt-in `bodyParser: 'full'` mode (T5a.2 Phase E).\n */\nasync function parseBodyInline(request: Request): Promise<unknown> {\n // GET/HEAD have no body by spec; even if the consumer sets one, ignore it\n // (matches Web Request semantics — `body` is null for GET/HEAD anyway).\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const contentType = request.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const text = await request.text()\n if (text.length === 0) return undefined\n try {\n return JSON.parse(text)\n } catch {\n // Malformed JSON — return undefined; Zod schema (if any) will fail with\n // a more useful \"required\" message than a raw SyntaxError.\n return undefined\n }\n }\n // text/plain or no body: return raw text or undefined\n if (contentType.startsWith('text/')) {\n const text = await request.text()\n return text.length === 0 ? undefined : text\n }\n return undefined\n}\n\n/**\n * T5a.2 Phase E — \"full\" body parser delegating to `parseWebRequestBody`.\n * Returns a `ParsedWebBody` (`{ json?, fields, files }`) for JSON OR\n * multipart, or `undefined` when the body is empty / unparseable.\n *\n * Handler downstream pattern:\n * `body?.json` (JSON requests)\n * `body?.fields` + `body?.files` (multipart upload requests)\n *\n * Limits enforced by parseWebRequestBody:\n * - declared Content-Length cap (default 10MB × maxFiles + 1MB margin)\n * - per-file size cap (default 10MB)\n * - max files (default 10)\n */\nasync function parseBodyFull(request: Request): Promise<unknown> {\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const { parseWebRequestBody } = await import('./body-parser-web.js')\n try {\n const parsed = await parseWebRequestBody(request)\n // If nothing parsed (empty body OR unhandled content-type), return undefined\n // so Zod schemas treat it as missing — same semantics as inline parser.\n if (\n parsed.json === undefined &&\n Object.keys(parsed.fields).length === 0 &&\n parsed.files.length === 0\n ) {\n return undefined\n }\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Build an envelope-shaped 400 Response for a Zod validation failure. Uses\n * the canonical `TheoErrorEnvelope` shape per G5 D3 (boundary translation).\n */\nfunction validationErrorResponse(zodError: z.ZodError, field: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'BAD_REQUEST',\n message: `Validation failed: ${field}`,\n ext: {\n fields: zodError.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n })),\n },\n }\n return new Response(JSON.stringify(envelope), {\n status: 400,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Run a `defineRoute` config against a parsed Web Request. Returns the raw\n * handler result; serialization to Response happens in the caller.\n *\n * Throws if Zod validation fails (caller catches and emits 400).\n */\nasync function runHandler(\n config: WebRouteHandlerConfig,\n request: Request,\n bodyParser: 'inline' | 'full' = 'inline',\n paramsInput: Record<string, string> = {},\n context: Record<string, unknown> = {},\n): Promise<{ ok: true; result: unknown } | { ok: false; response: Response }> {\n const url = new URL(request.url)\n const queryRaw = searchParamsToObject(url.searchParams)\n const bodyRaw =\n bodyParser === 'full' ? await parseBodyFull(request) : await parseBodyInline(request)\n // T3.1 — route params resolved upstream (matchRoute) and threaded via\n // opts.params. Defaults to `{}` so callers that don't supply params keep the\n // prior behavior (a route declaring config.params then fails validation).\n const paramsRaw = paramsInput\n\n // Validate input via Zod schemas when present.\n let query: unknown = queryRaw\n if (config.query !== undefined) {\n const parsed = config.query.safeParse(queryRaw)\n if (!parsed.success)\n return { ok: false, response: validationErrorResponse(parsed.error, 'query') }\n query = parsed.data\n }\n let body: unknown = bodyRaw\n if (config.body !== undefined) {\n const parsed = config.body.safeParse(bodyRaw)\n if (!parsed.success)\n return { ok: false, response: validationErrorResponse(parsed.error, 'body') }\n body = parsed.data\n }\n let params: unknown = paramsRaw\n if (config.params !== undefined) {\n const parsed = config.params.safeParse(paramsRaw)\n if (!parsed.success)\n return { ok: false, response: validationErrorResponse(parsed.error, 'params') }\n params = parsed.data\n }\n\n const result = await config.handler({ query, body, params, request, context })\n return validateResponseOutput(config.response, result) ?? { ok: true, result }\n}\n\n/**\n * Validate a plain-object handler return against `config.response` (D1/D2). A\n * mismatch is a SERVER fault → 500 envelope. `Response`-instance returns and\n * `undefined`/`null` (→ 204) skip validation — parity with the Node runner,\n * which validates only in its plain-object branch. Returns `undefined` when no\n * validation applies (caller forwards the raw result).\n */\nfunction validateResponseOutput(\n response: unknown,\n result: unknown,\n): { ok: true; result: unknown } | { ok: false; response: Response } | undefined {\n const validatable = result !== undefined && result !== null && !(result instanceof Response)\n if (!validatable || !isZodLike(response)) return undefined\n const parsed = response.safeParse(result)\n if (parsed.success) return { ok: true, result: parsed.data }\n const err = new TheoError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'response validation failed',\n ext: { issues: parsed.error?.issues },\n })\n return { ok: false, response: handlerErrorResponse(err) }\n}\n\n/**\n * Serialize a handler return value into a native Web `Response`. Conventions:\n * - `undefined` / `void` → 204 No Content.\n * - existing `Response` instance → pass through unchanged.\n * - everything else → 200 JSON.\n */\nfunction toResponse(result: unknown, status?: number): Response {\n if (result === undefined) {\n return new Response(null, { status: 204 })\n }\n if (result instanceof Response) {\n return result\n }\n return new Response(JSON.stringify(result), {\n status: status ?? 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Build an envelope-shaped 500 Response when a handler throws. Uses\n * `serverErrorToEnvelope` so existing ad-hoc Error classes (AuthRequired,\n * FileTooLarge, etc.) get mapped to canonical envelope codes.\n */\nfunction handlerErrorResponse(err: unknown): Response {\n const envelope = serverErrorToEnvelope(err)\n // Map envelope code → HTTP status when possible. Default to 500 for\n // INTERNAL_SERVER_ERROR. Other codes map per HTTP semantics.\n const status = envelopeCodeToStatus(envelope.code)\n return new Response(JSON.stringify(envelope), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n// envelopeCodeToStatus extracted to core/contracts/envelope-code-to-status.ts\n// (architecture-remediation T1.2, 2026-06-12)\n\n/**\n * Optional behavior knobs for `executeWebRequest`. Each knob defaults to\n * the safe \"no-op\" stance so existing Phase A consumers (T1.2 fixture\n * tests) keep working unchanged.\n *\n * **`csrfMode`** (T5a.2 Phase B slice 1/6) — when set to `'strict'`, the\n * Web-Standards request gate runs `validateCsrfRequest` BEFORE method\n * dispatch on state-changing methods (POST/PUT/PATCH/DELETE) and emits a\n * `403 FORBIDDEN` envelope when the check fails. Default: `'off'` (no\n * CSRF enforcement) to preserve Phase A backward compat. Production\n * consumers SHOULD pass `csrfMode: 'strict'`.\n *\n * Per the T5a.2 plan v1.0 § Phase B header-only leaves: csrf.ts is the\n * first leaf to be migrated (slice 1/6); 5 more sibling leaves remain\n * (csrf-multi-header, csrf-readiness-endpoint, csp-report, cors, cookies).\n */\nexport interface ExecuteWebRequestOptions {\n csrfMode?: 'off' | 'strict'\n /**\n * T3.1 — route params resolved upstream by `matchRoute` (e.g. `{ id: '42' }`\n * for `/users/:id`). Threaded to the handler's `params` input and validated\n * against `config.params` (Zod) when declared. Defaults to `{}` — callers\n * that don't supply params keep the prior behavior (additive, backward-compat).\n */\n params?: Record<string, string>\n /**\n * T3.2 — Web-Standards middleware chain. Runs in order AFTER the CSRF gate\n * (EC-3) and BEFORE the handler. A middleware returning a `Response`\n * short-circuits (handler not reached); mutating `context` passes data to\n * the handler. Omitted → zero overhead (handler runs directly).\n */\n middleware?: readonly WebMiddleware[]\n /**\n * T5a.2 Phase E — body parser strategy.\n *\n * - `'inline'` (default): handle `application/json` + `text/*` only.\n * Returns the parsed value (object for JSON, string for text). Other\n * content-types (e.g., multipart) return `undefined`.\n * - `'full'`: delegate to `parseWebRequestBody` for multipart support\n * + per-file size caps + max-files cap (Web Standards `request.formData()`\n * under the hood). Returns a `ParsedWebBody` object:\n * `{ json?, fields, files }`. Multipart consumers MUST opt into this\n * mode; JSON-only routes pay zero cost staying on `'inline'`.\n */\n bodyParser?: 'inline' | 'full'\n /**\n * T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n *\n * Adapters (Node, CF Workers, Bun, Deno) wire `WebPluginContext`-shaped\n * hooks at the executeWebRequest lifecycle. Lifecycle order mirrors the\n * Fastify / Hono convention:\n *\n * 1. CSRF check (when opts.csrfMode === 'strict')\n * 2. onRequest — earliest, before body parsing. Plugins can short-circuit\n * by setting `ctx.response` (handler skipped; subsequent hooks see\n * the short-circuit response).\n * 3. body parse (inline OR full per opts.bodyParser)\n * 4. preHandler — after body parsed, before handler runs. Same\n * short-circuit semantic.\n * 5. handler invocation\n * 6. onResponse — after handler returns OR after a hook short-circuit.\n * `ctx.response` is populated.\n * 7. onError — fires if any of (handler, onRequest, preHandler) throws.\n * `ctx.response` is the envelope-shaped error response built via\n * serverErrorToEnvelope.\n *\n * `responseHeaders` is shared across hooks; the final Response merges\n * them with the handler's Response headers (handler headers win on\n * conflict; hook headers add new ones). Decorations made via\n * `ctx.ctx[key] = value` persist across hooks (request-scoped state).\n *\n * `hooks: undefined` (default) → no plugin lifecycle, Phase A behavior\n * preserved. Production consumers wire via the WebPluginRunner facade\n * (a future Phase G slice).\n */\n hooks?: {\n onRequest?: readonly WebOnRequestHook[]\n preHandler?: readonly WebPreHandlerHook[]\n onResponse?: readonly WebOnResponseHook[]\n onError?: readonly WebOnErrorHook[]\n }\n /**\n * Stable request identifier propagated into hook contexts. Adapters\n * resolve via traceparent / x-request-id / generated UUID (see\n * `extractTraceIdFromRequest` from Phase C slice 1/2). Default:\n * `globalThis.crypto.randomUUID()`.\n */\n requestId?: string\n}\n\n/**\n * Methods that require CSRF enforcement when `csrfMode: 'strict'`. GET\n * and HEAD are read-only per HTTP semantics and bypass CSRF (the threat\n * model is state-changing requests forged via cross-origin POSTs); OPTIONS\n * is the CORS preflight and also bypasses.\n */\nexport const CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/**\n * Web-Standards entry-point for executing a route module against a native\n * Web `Request`. Returns a native Web `Response`.\n *\n * Method dispatch: looks up the handler keyed by `request.method`\n * (uppercase). Returns 405 if the method isn't exported.\n *\n * @example\n * import * as users from './app/users/route.js'\n * import { executeWebRequest } from 'theokit/server'\n *\n * const response = await executeWebRequest(\n * new Request('http://localhost/api/users', { method: 'GET' }),\n * users,\n * { csrfMode: 'strict' },\n * )\n */\n/**\n * T5a.2 Phase G slice 1/N — merge hook-mutated response headers into the\n * handler's Response. Handler-set headers win on conflict (the handler\n * has the most context about its own response); hook headers add new\n * entries (e.g., CORS, Set-Cookie). Returns a new Response with the\n * merged headers + same body/status as the source.\n *\n * Set-Cookie is multi-value at the Web spec layer — `getSetCookie()` is\n * the only way to retrieve multiple values. The merge appends ALL hook-\n * set Set-Cookie values to the response (multiple Set-Cookie headers is\n * spec-correct and how browsers expect cookie issuance).\n */\nfunction mergeHookHeaders(response: Response, hookHeaders: Headers): Response {\n if ([...hookHeaders].length === 0) return response\n const merged = new Headers(response.headers)\n for (const [k, v] of hookHeaders.entries()) {\n if (k.toLowerCase() === 'set-cookie') continue // handled separately\n if (!merged.has(k)) merged.set(k, v)\n }\n for (const sc of hookHeaders.getSetCookie()) {\n merged.append('Set-Cookie', sc)\n }\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n })\n}\n\n/** Build a 405 envelope Response. */\nfunction methodNotAllowedResponse(method: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'METHOD_NOT_ALLOWED',\n message: `Method ${method} not allowed`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 405,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/** Build a 403 CSRF envelope Response. */\nfunction csrfFailedResponse(reason: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'FORBIDDEN',\n message: `CSRF check failed: ${reason}`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 403,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport async function executeWebRequest(\n request: Request,\n routeModule: WebRouteModule,\n opts: ExecuteWebRequestOptions = {},\n): Promise<Response> {\n const method = request.method.toUpperCase() as keyof WebRouteModule\n const config = routeModule[method] as WebRouteHandlerConfig | undefined\n\n // T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n // When opts.hooks is undefined, skip the entire hook orchestration to\n // preserve Phase A backward compat (zero overhead for consumers not\n // wiring hooks). In the no-hooks branch, dispatch + CSRF gates run\n // FIRST and 405 short-circuits before the handler runs.\n if (opts.hooks === undefined) {\n if (config === undefined || typeof config.handler !== 'function') {\n return methodNotAllowedResponse(method)\n }\n if (opts.csrfMode === 'strict' && CSRF_PROTECTED_METHODS.has(method)) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) return csrfFailedResponse(csrfCheck.reason)\n }\n try {\n // T3.2 — middleware runs AFTER the CSRF gate (EC-3), BEFORE the handler.\n const context: Record<string, unknown> = {}\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, context)\n if (shortCircuit) return shortCircuit\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n context,\n )\n if (!outcome.ok) return outcome.response\n return toResponse(outcome.result, config.status)\n } catch (err) {\n return handlerErrorResponse(err)\n }\n }\n\n // With hooks: onRequest hooks run BEFORE method dispatch (Hono /\n // Fastify convention) so CORS preflight plugins can intercept OPTIONS\n // requests regardless of route shape. If no hook short-circuits, then\n // 405 fires when the method isn't exported by the route module.\n return runWithHooks(request, config, opts, opts.hooks)\n}\n\n/**\n * Run the request through the full plugin lifecycle. Extracted to keep\n * `executeWebRequest`'s cyclomatic + cognitive complexity under the lint\n * caps (15/20). Pure side-effect surface — same return type as the\n * no-hooks branch.\n */\n/**\n * Pre-handler pipeline: onRequest hooks → CSRF gate → preHandler hooks →\n * method dispatch + handler. Mutates `hookCtx.response` on short-circuit\n * OR on handler completion. Extracted from `runWithHooks` to keep the\n * latter's cyclomatic complexity under the lint cap (15).\n */\nasync function runPreHandlerPipeline(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<void> {\n const method = request.method.toUpperCase()\n const runList = async (list: readonly WebOnRequestHook[]): Promise<void> => {\n for (const hook of list) {\n if (hookCtx.response !== undefined) return\n await hook(hookCtx)\n }\n }\n // onRequest first (CORS preflight + auth gate intercept before dispatch).\n if (hooks.onRequest) await runList(hooks.onRequest)\n // CSRF gate AFTER onRequest (auth-short-circuit avoids CSRF cost) but\n // BEFORE the handler.\n if (\n hookCtx.response === undefined &&\n opts.csrfMode === 'strict' &&\n CSRF_PROTECTED_METHODS.has(method)\n ) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) hookCtx.response = csrfFailedResponse(csrfCheck.reason)\n }\n if (hookCtx.response === undefined && hooks.preHandler) {\n await runList(hooks.preHandler)\n }\n if (hookCtx.response === undefined) {\n await runHandlerStage(hookCtx, request, config, opts, method)\n }\n}\n\n/**\n * T3.2 — the middleware + handler stage of the hook pipeline. Extracted from\n * `runPreHandlerPipeline` to keep its cyclomatic complexity under the lint cap.\n * Order: 405 check → user middleware (after CSRF + preHandler) → handler.\n */\nasync function runHandlerStage(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n method: string,\n): Promise<void> {\n // 405 if route module doesn't export this method.\n if (config === undefined || typeof config.handler !== 'function') {\n hookCtx.response = methodNotAllowedResponse(method)\n return\n }\n // hookCtx.ctx is the shared per-request context the middleware mutates.\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, hookCtx.ctx)\n if (shortCircuit) {\n hookCtx.response = shortCircuit\n return\n }\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n hookCtx.ctx,\n )\n hookCtx.response = outcome.ok ? toResponse(outcome.result, config.status) : outcome.response\n}\n\nasync function runWithHooks(\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<Response> {\n const hookCtx: WebPluginContext = {\n request,\n responseHeaders: new Headers(),\n ctx: {},\n requestId: opts.requestId ?? globalThis.crypto.randomUUID(),\n }\n try {\n await runPreHandlerPipeline(hookCtx, request, config, opts, hooks)\n if (hooks.onResponse) {\n for (const hook of hooks.onResponse) {\n await hook(hookCtx)\n }\n }\n // runPreHandlerPipeline guarantees hookCtx.response is set (via either\n // a hook short-circuit OR handler outcome OR 405). TS doesn't infer this\n // post-mutation; the assertion is safe per the function's contract.\n const finalResponse =\n hookCtx.response ??\n new Response(\n JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'No response built' }),\n {\n status: 500,\n headers: { 'content-type': 'application/json' },\n },\n )\n return mergeHookHeaders(finalResponse, hookCtx.responseHeaders)\n } catch (err) {\n return runErrorHooks(err, hookCtx, hooks.onError)\n }\n}\n\n/**\n * Run onError hooks against an envelope-shaped error response. EC-9 —\n * each hook throw is swallowed to avoid error-in-error-handler recursion.\n * Returns the merged final Response.\n */\nasync function runErrorHooks(\n err: unknown,\n hookCtx: WebPluginContext,\n onError: readonly WebOnErrorHook[] | undefined,\n): Promise<Response> {\n const errorResponse = handlerErrorResponse(err)\n if (onError !== undefined) {\n const errorCtx: WebPluginErrorContext = {\n ...hookCtx,\n response: errorResponse,\n error: err,\n }\n for (const hook of onError) {\n try {\n await hook(errorCtx)\n } catch {\n // EC-9: error in error handler — swallow to avoid recursion.\n }\n }\n }\n return mergeHookHeaders(errorResponse, hookCtx.responseHeaders)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -173,6 +173,33 @@ function stripComments(source) {
|
|
|
173
173
|
return out;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// src/server/scan/match.ts
|
|
177
|
+
function compilePattern(routePath) {
|
|
178
|
+
const paramNames = [];
|
|
179
|
+
const regexStr = routePath.replace(/:(?:\.\.\.)?([^/]+)/g, (match, name) => {
|
|
180
|
+
paramNames.push(name);
|
|
181
|
+
return match.startsWith(":...") ? "(.+)" : "([^/]+)";
|
|
182
|
+
});
|
|
183
|
+
return { pattern: new RegExp(`^${regexStr}$`), paramNames };
|
|
184
|
+
}
|
|
185
|
+
function matchRoute(url, routes) {
|
|
186
|
+
let path = url.split("?")[0];
|
|
187
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
188
|
+
path = path.slice(0, -1);
|
|
189
|
+
}
|
|
190
|
+
for (const route of routes) {
|
|
191
|
+
const match = route.pattern.exec(path);
|
|
192
|
+
if (match) {
|
|
193
|
+
const params = {};
|
|
194
|
+
route.paramNames.forEach((name, i) => {
|
|
195
|
+
params[name] = match[i + 1];
|
|
196
|
+
});
|
|
197
|
+
return { route, params };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
176
203
|
// src/core/contracts/http-methods.ts
|
|
177
204
|
var HTTP_METHODS = [
|
|
178
205
|
"GET",
|
|
@@ -267,33 +294,6 @@ var RouterConventionError = class extends Error {
|
|
|
267
294
|
}
|
|
268
295
|
};
|
|
269
296
|
|
|
270
|
-
// src/server/scan/match.ts
|
|
271
|
-
function compilePattern(routePath) {
|
|
272
|
-
const paramNames = [];
|
|
273
|
-
const regexStr = routePath.replace(/:(?:\.\.\.)?([^/]+)/g, (match, name) => {
|
|
274
|
-
paramNames.push(name);
|
|
275
|
-
return match.startsWith(":...") ? "(.+)" : "([^/]+)";
|
|
276
|
-
});
|
|
277
|
-
return { pattern: new RegExp(`^${regexStr}$`), paramNames };
|
|
278
|
-
}
|
|
279
|
-
function matchRoute(url, routes) {
|
|
280
|
-
let path = url.split("?")[0];
|
|
281
|
-
if (path.length > 1 && path.endsWith("/")) {
|
|
282
|
-
path = path.slice(0, -1);
|
|
283
|
-
}
|
|
284
|
-
for (const route of routes) {
|
|
285
|
-
const match = route.pattern.exec(path);
|
|
286
|
-
if (match) {
|
|
287
|
-
const params = {};
|
|
288
|
-
route.paramNames.forEach((name, i) => {
|
|
289
|
-
params[name] = match[i + 1];
|
|
290
|
-
});
|
|
291
|
-
return { route, params };
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
297
|
// src/server/scan/scan.ts
|
|
298
298
|
var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
299
299
|
var TEST_OR_SPEC_RE = /\.(test|spec)\.[jt]sx?$/;
|
|
@@ -440,4 +440,4 @@ export {
|
|
|
440
440
|
scanServerRoutes,
|
|
441
441
|
scanWebSocketRoutes
|
|
442
442
|
};
|
|
443
|
-
//# sourceMappingURL=chunk-
|
|
443
|
+
//# sourceMappingURL=chunk-BQDGES7C.js.map
|