@faapi/faapi 0.0.0-canary.ede2e84 → 0.0.0-canary.f08a14e

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/index.d.ts CHANGED
@@ -54,6 +54,17 @@ interface SseEvent {
54
54
  interface SseWriter {
55
55
  /** 推送一个 SSE 事件 */
56
56
  send(event: SseEvent): void;
57
+ /**
58
+ * 直接写入原始字节/字符串,不做任何 SSE 序列化
59
+ *
60
+ * 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。
61
+ * 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,
62
+ * 不适用于原文透传场景。
63
+ *
64
+ * 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。
65
+ * 与 `send` 一致:close/aborted 后静默忽略,不抛错。
66
+ */
67
+ sendRaw(chunk: string | Uint8Array): void;
57
68
  /** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */
58
69
  sendError(error: unknown): void;
59
70
  /** 关闭流(多次调用安全) */
@@ -1064,6 +1075,30 @@ interface ValidationIssue {
1064
1075
  }
1065
1076
  type ValidationErrorCode = 'TYPE_MISMATCH' | 'MISSING_FIELD' | 'INVALID_FORMAT' | 'INVALID_VALUE' | 'COERCE_FAILED';
1066
1077
 
1078
+ /**
1079
+ * 从 Request 对象创建 FaapiContext
1080
+ * @param request Web Request 对象
1081
+ * @param params 动态路由参数
1082
+ * @param config 自定义业务配置(来自 faapi.config.ts)
1083
+ * @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)
1084
+ */
1085
+ declare function createContext(request: Request, params: Record<string, string>, config?: Record<string, unknown>, ip?: string): FaapiContext;
1086
+
1087
+ /**
1088
+ * 调用路由 handler 并将返回值转为 Response
1089
+ *
1090
+ * 流程(洋葱模型):
1091
+ * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after
1092
+ * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)
1093
+ * 3. 中间件可用 try/catch 捕获内层错误
1094
+ * 4. 最内层执行注入器(按需)→ handler
1095
+ *
1096
+ * 注入器与中间件解耦:
1097
+ * - 注入器按 handler 参数名匹配,只执行需要的
1098
+ * - 注入器可读取中间件塞进 ctx 的值
1099
+ */
1100
+ declare function invokeHandler(handler: (...args: unknown[]) => unknown, ctx: FaapiContext, body?: unknown, middlewares?: FaapiMiddleware[], injectors?: InjectorMap): Promise<Response>;
1101
+
1067
1102
  interface InjectOptions {
1068
1103
  method?: string;
1069
1104
  path?: string;
@@ -1157,4 +1192,4 @@ type ProdApp = AppBase;
1157
1192
  */
1158
1193
  declare function createProdApp(options?: CreateAppOptions): Promise<ProdApp>;
1159
1194
 
1160
- export { type ProdApp as App, type CorsOptions, type CreateAppOptions, type DevApp, type FaapiConfig, type FaapiContext, type FaapiContextConfig, FaapiError, type FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, type HelmetOptions, type InjectOptions, type InjectResponse, type Injector, type InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type RouteInfo, type RouteInputSchema, type RouteManifest, RouteNotFoundError, type RouteOutputSchema, type RouteParamSchema, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, type SseEvent, type SseWriter, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, collectRouteSchemaSources, cors, createProdApp as createApp, createDevApp, createProdApp, createProgram, extractTypeInfo, getInputTypeForMethod, helmet, invalidateProgramCache, loadConfig, logger, resolveTypeNode };
1195
+ export { type ProdApp as App, type CorsOptions, type CreateAppOptions, type DevApp, type FaapiConfig, type FaapiContext, type FaapiContextConfig, FaapiError, type FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, type HelmetOptions, type InjectOptions, type InjectResponse, type Injector, type InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type RouteInfo, type RouteInputSchema, type RouteManifest, RouteNotFoundError, type RouteOutputSchema, type RouteParamSchema, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, type SseEvent, type SseWriter, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, collectRouteSchemaSources, cors, createProdApp as createApp, createContext, createDevApp, createProdApp, createProgram, extractTypeInfo, getInputTypeForMethod, helmet, invalidateProgramCache, invokeHandler, loadConfig, logger, resolveTypeNode };
package/dist/index.js CHANGED
@@ -1095,182 +1095,6 @@ var ModuleLoadError = class extends FaapiError {
1095
1095
  }
1096
1096
  };
1097
1097
 
1098
- // src/cli/createAppCore.ts
1099
- import fs4 from "fs";
1100
- import path7 from "path";
1101
- import { PassThrough } from "stream";
1102
-
1103
- // src/router/sortRoutes.ts
1104
- function sortRoutes(routes) {
1105
- return [...routes].sort((a, b) => {
1106
- if (a.isDynamic !== b.isDynamic) {
1107
- return a.isDynamic ? 1 : -1;
1108
- }
1109
- if (a.isCatchAll !== b.isCatchAll) {
1110
- return a.isCatchAll ? 1 : -1;
1111
- }
1112
- const aSegments = a.urlPath.split("/").filter(Boolean).length;
1113
- const bSegments = b.urlPath.split("/").filter(Boolean).length;
1114
- if (aSegments !== bSegments) {
1115
- return aSegments - bSegments;
1116
- }
1117
- return a.urlPath.localeCompare(b.urlPath);
1118
- });
1119
- }
1120
-
1121
- // src/router/detectRouteConflicts.ts
1122
- function detectRouteConflicts(routes) {
1123
- const map = /* @__PURE__ */ new Map();
1124
- for (const route of routes) {
1125
- const key = `${route.method} ${route.urlPath}`;
1126
- const existing = map.get(key);
1127
- if (existing) {
1128
- existing.files.push(route.filePath);
1129
- } else {
1130
- map.set(key, {
1131
- method: route.method,
1132
- urlPath: route.urlPath,
1133
- files: [route.filePath]
1134
- });
1135
- }
1136
- }
1137
- const conflicts = [];
1138
- for (const conflict of map.values()) {
1139
- if (conflict.files.length > 1) {
1140
- conflicts.push(conflict);
1141
- }
1142
- }
1143
- return conflicts;
1144
- }
1145
-
1146
- // src/server/createServer.ts
1147
- import {
1148
- createServer as createHttpServer
1149
- } from "http";
1150
- import { createSecureServer as createHttp2SecureServer } from "http2";
1151
- import { readFileSync } from "fs";
1152
- import { Readable as Readable2 } from "stream";
1153
- import path5 from "path";
1154
-
1155
- // src/router/matchRoute.ts
1156
- function matchRoute(routes, method, path9) {
1157
- for (const route of routes) {
1158
- if (route.method !== method) {
1159
- continue;
1160
- }
1161
- if (!route.isDynamic) {
1162
- if (route.urlPath === path9) {
1163
- return { route, params: {} };
1164
- }
1165
- continue;
1166
- }
1167
- const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1168
- if (params !== null) {
1169
- return { route, params };
1170
- }
1171
- }
1172
- return null;
1173
- }
1174
- function matchWsRoute(wsRoutes, path9) {
1175
- for (const route of wsRoutes) {
1176
- if (!route.isDynamic) {
1177
- if (route.urlPath === path9) {
1178
- return { route, params: {} };
1179
- }
1180
- continue;
1181
- }
1182
- const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1183
- if (params !== null) {
1184
- return { route, params };
1185
- }
1186
- }
1187
- return null;
1188
- }
1189
- function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
1190
- const patternSegments = pattern.split("/").filter(Boolean);
1191
- const pathSegments = path9.split("/").filter(Boolean);
1192
- if (isCatchAll) {
1193
- const nonCatchAllCount = patternSegments.length - 1;
1194
- if (pathSegments.length <= nonCatchAllCount) {
1195
- return null;
1196
- }
1197
- const params2 = {};
1198
- for (let i = 0; i < nonCatchAllCount; i++) {
1199
- const patternSeg = patternSegments[i];
1200
- const pathSeg = pathSegments[i];
1201
- if (patternSeg.startsWith(":")) {
1202
- const paramName = patternSeg.slice(1);
1203
- params2[paramName] = pathSeg;
1204
- } else if (patternSeg !== pathSeg) {
1205
- return null;
1206
- }
1207
- }
1208
- const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
1209
- const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
1210
- params2[catchAllParamName] = catchAllValue;
1211
- if (Object.keys(params2).length !== paramNames.length) {
1212
- return null;
1213
- }
1214
- return params2;
1215
- }
1216
- if (patternSegments.length !== pathSegments.length) {
1217
- return null;
1218
- }
1219
- const params = {};
1220
- for (let i = 0; i < patternSegments.length; i++) {
1221
- const patternSeg = patternSegments[i];
1222
- const pathSeg = pathSegments[i];
1223
- if (patternSeg.startsWith(":")) {
1224
- const paramName = patternSeg.slice(1);
1225
- params[paramName] = pathSeg;
1226
- } else if (patternSeg !== pathSeg) {
1227
- return null;
1228
- }
1229
- }
1230
- if (Object.keys(params).length !== paramNames.length) {
1231
- return null;
1232
- }
1233
- return params;
1234
- }
1235
-
1236
- // src/loader/resolveExports.ts
1237
- function resolveExport(module, exportName) {
1238
- if (exportName in module && typeof module[exportName] !== "undefined") {
1239
- return module[exportName];
1240
- }
1241
- const defaultExport = module.default;
1242
- if (defaultExport !== null && typeof defaultExport === "object") {
1243
- const value = defaultExport[exportName];
1244
- if (value !== void 0) {
1245
- return value;
1246
- }
1247
- }
1248
- return void 0;
1249
- }
1250
-
1251
- // src/loader/validateRouteModule.ts
1252
- function validateRouteModule(value, method, filePath) {
1253
- if (typeof value !== "function") {
1254
- throw new Error(
1255
- `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
1256
- );
1257
- }
1258
- }
1259
-
1260
- // src/loader/loadRouteModule.ts
1261
- async function loadRouteModule(filePath, method) {
1262
- let module;
1263
- try {
1264
- module = await importWithCacheBust(filePath);
1265
- } catch (err) {
1266
- const reason = err instanceof Error ? err.message : String(err);
1267
- throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
1268
- }
1269
- const handler = resolveExport(module, method);
1270
- validateRouteModule(handler, method, filePath);
1271
- return { handler, method };
1272
- }
1273
-
1274
1098
  // src/runtime/sse.ts
1275
1099
  function encodeSseEvent(event) {
1276
1100
  let out = "";
@@ -1337,6 +1161,11 @@ function createSseWriter() {
1337
1161
  const text = encodeSseEvent(event);
1338
1162
  controller.enqueue(encoder.encode(text));
1339
1163
  },
1164
+ sendRaw(chunk) {
1165
+ if (closed || !controller) return;
1166
+ const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
1167
+ controller.enqueue(bytes);
1168
+ },
1340
1169
  sendError(error) {
1341
1170
  if (closed || !controller) return;
1342
1171
  const message = error instanceof Error ? error.message : String(error);
@@ -1475,119 +1304,30 @@ function createContext(request, params, config = {}, ip = "") {
1475
1304
  return ctx;
1476
1305
  }
1477
1306
 
1478
- // src/utils/queryToObject.ts
1479
- function queryToObject(params) {
1480
- const result = {};
1481
- for (const [key, value] of params) {
1482
- result[key] = value;
1307
+ // src/utils/isPlainObject.ts
1308
+ function isPlainObject(value) {
1309
+ if (value === null || typeof value !== "object") {
1310
+ return false;
1483
1311
  }
1484
- return result;
1485
- }
1486
-
1487
- // src/utils/parseJsonBody.ts
1488
- function parseJsonBody(text) {
1489
- try {
1490
- const data = JSON.parse(text);
1491
- return { success: true, data };
1492
- } catch {
1493
- return { success: false, error: "Invalid JSON body" };
1312
+ if (Array.isArray(value)) {
1313
+ return false;
1494
1314
  }
1315
+ const proto = Object.getPrototypeOf(value);
1316
+ return proto === null || proto === Object.prototype;
1495
1317
  }
1496
1318
 
1497
- // src/utils/parseMultipart.ts
1498
- async function parseMultipart(request) {
1499
- const formData = await request.formData();
1500
- const fields = {};
1501
- const files = [];
1502
- for (const [key, value] of formData.entries()) {
1503
- if (value instanceof File) {
1504
- files.push({
1505
- name: key,
1506
- filename: value.name,
1507
- type: value.type,
1508
- size: value.size,
1509
- arrayBuffer: () => value.arrayBuffer()
1510
- });
1511
- } else {
1512
- if (key in fields) {
1513
- const existing = fields[key];
1514
- if (Array.isArray(existing)) {
1515
- existing.push(value);
1516
- } else {
1517
- fields[key] = [existing, value];
1518
- }
1519
- } else {
1520
- fields[key] = value;
1521
- }
1522
- }
1319
+ // src/response/toResponse.ts
1320
+ async function toResponse(value, meta) {
1321
+ if (value instanceof Promise) {
1322
+ return toResponse(await value, meta);
1523
1323
  }
1524
- return { fields, files };
1525
- }
1526
-
1527
- // src/runtime/resolveInput.ts
1528
- async function resolveInput(method, request) {
1529
- const inputType = getInputTypeForMethod(method);
1530
- if (inputType === "body") {
1531
- const contentType = request.headers.get("content-type") ?? "";
1532
- if (contentType.includes("multipart/form-data")) {
1533
- return parseMultipart(request);
1324
+ const applyMeta = (headers2) => {
1325
+ if (!meta) return;
1326
+ for (const [key, val] of Object.entries(meta.headers)) {
1327
+ headers2.set(key, val);
1534
1328
  }
1535
- if (contentType.includes("application/x-www-form-urlencoded")) {
1536
- const text2 = await request.text();
1537
- if (text2.trim() === "") return null;
1538
- const params = new URLSearchParams(text2);
1539
- const obj = {};
1540
- for (const [key, value] of params) {
1541
- obj[key] = value;
1542
- }
1543
- return obj;
1544
- }
1545
- const text = await request.text();
1546
- if (text.trim() === "") {
1547
- return null;
1548
- }
1549
- const result = parseJsonBody(text);
1550
- if (!result.success) {
1551
- throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
1552
- {
1553
- path: "body",
1554
- code: "INVALID_FORMAT",
1555
- expected: "JSON",
1556
- received: "text",
1557
- message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
1558
- }
1559
- ]);
1560
- }
1561
- return result.data;
1562
- }
1563
- const url = new URL(request.url);
1564
- return queryToObject(url.searchParams);
1565
- }
1566
-
1567
- // src/utils/isPlainObject.ts
1568
- function isPlainObject(value) {
1569
- if (value === null || typeof value !== "object") {
1570
- return false;
1571
- }
1572
- if (Array.isArray(value)) {
1573
- return false;
1574
- }
1575
- const proto = Object.getPrototypeOf(value);
1576
- return proto === null || proto === Object.prototype;
1577
- }
1578
-
1579
- // src/response/toResponse.ts
1580
- async function toResponse(value, meta) {
1581
- if (value instanceof Promise) {
1582
- return toResponse(await value, meta);
1583
- }
1584
- const applyMeta = (headers2) => {
1585
- if (!meta) return;
1586
- for (const [key, val] of Object.entries(meta.headers)) {
1587
- headers2.set(key, val);
1588
- }
1589
- for (const cookie of meta.setCookies ?? []) {
1590
- headers2.append("set-cookie", cookie);
1329
+ for (const cookie of meta.setCookies ?? []) {
1330
+ headers2.append("set-cookie", cookie);
1591
1331
  }
1592
1332
  };
1593
1333
  if (value instanceof Response) {
@@ -1665,6 +1405,15 @@ async function toResponse(value, meta) {
1665
1405
  });
1666
1406
  }
1667
1407
 
1408
+ // src/utils/queryToObject.ts
1409
+ function queryToObject(params) {
1410
+ const result = {};
1411
+ for (const [key, value] of params) {
1412
+ result[key] = value;
1413
+ }
1414
+ return result;
1415
+ }
1416
+
1668
1417
  // src/injection/injectParams.ts
1669
1418
  function getBuiltinInjectionValue(type, ctx, body) {
1670
1419
  switch (type) {
@@ -1804,6 +1553,262 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1804
1553
  return await compose(middlewares, ctx, finalHandler);
1805
1554
  }
1806
1555
 
1556
+ // src/cli/createAppCore.ts
1557
+ import fs4 from "fs";
1558
+ import path7 from "path";
1559
+ import { PassThrough } from "stream";
1560
+
1561
+ // src/router/sortRoutes.ts
1562
+ function sortRoutes(routes) {
1563
+ return [...routes].sort((a, b) => {
1564
+ if (a.isDynamic !== b.isDynamic) {
1565
+ return a.isDynamic ? 1 : -1;
1566
+ }
1567
+ if (a.isCatchAll !== b.isCatchAll) {
1568
+ return a.isCatchAll ? 1 : -1;
1569
+ }
1570
+ const aSegments = a.urlPath.split("/").filter(Boolean).length;
1571
+ const bSegments = b.urlPath.split("/").filter(Boolean).length;
1572
+ if (aSegments !== bSegments) {
1573
+ return aSegments - bSegments;
1574
+ }
1575
+ return a.urlPath.localeCompare(b.urlPath);
1576
+ });
1577
+ }
1578
+
1579
+ // src/router/detectRouteConflicts.ts
1580
+ function detectRouteConflicts(routes) {
1581
+ const map = /* @__PURE__ */ new Map();
1582
+ for (const route of routes) {
1583
+ const key = `${route.method} ${route.urlPath}`;
1584
+ const existing = map.get(key);
1585
+ if (existing) {
1586
+ existing.files.push(route.filePath);
1587
+ } else {
1588
+ map.set(key, {
1589
+ method: route.method,
1590
+ urlPath: route.urlPath,
1591
+ files: [route.filePath]
1592
+ });
1593
+ }
1594
+ }
1595
+ const conflicts = [];
1596
+ for (const conflict of map.values()) {
1597
+ if (conflict.files.length > 1) {
1598
+ conflicts.push(conflict);
1599
+ }
1600
+ }
1601
+ return conflicts;
1602
+ }
1603
+
1604
+ // src/server/createServer.ts
1605
+ import {
1606
+ createServer as createHttpServer
1607
+ } from "http";
1608
+ import { createSecureServer as createHttp2SecureServer } from "http2";
1609
+ import { readFileSync } from "fs";
1610
+ import { Readable as Readable2 } from "stream";
1611
+ import path5 from "path";
1612
+
1613
+ // src/router/matchRoute.ts
1614
+ function matchRoute(routes, method, path9) {
1615
+ for (const route of routes) {
1616
+ if (route.method !== method) {
1617
+ continue;
1618
+ }
1619
+ if (!route.isDynamic) {
1620
+ if (route.urlPath === path9) {
1621
+ return { route, params: {} };
1622
+ }
1623
+ continue;
1624
+ }
1625
+ const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1626
+ if (params !== null) {
1627
+ return { route, params };
1628
+ }
1629
+ }
1630
+ return null;
1631
+ }
1632
+ function matchWsRoute(wsRoutes, path9) {
1633
+ for (const route of wsRoutes) {
1634
+ if (!route.isDynamic) {
1635
+ if (route.urlPath === path9) {
1636
+ return { route, params: {} };
1637
+ }
1638
+ continue;
1639
+ }
1640
+ const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1641
+ if (params !== null) {
1642
+ return { route, params };
1643
+ }
1644
+ }
1645
+ return null;
1646
+ }
1647
+ function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
1648
+ const patternSegments = pattern.split("/").filter(Boolean);
1649
+ const pathSegments = path9.split("/").filter(Boolean);
1650
+ if (isCatchAll) {
1651
+ const nonCatchAllCount = patternSegments.length - 1;
1652
+ if (pathSegments.length <= nonCatchAllCount) {
1653
+ return null;
1654
+ }
1655
+ const params2 = {};
1656
+ for (let i = 0; i < nonCatchAllCount; i++) {
1657
+ const patternSeg = patternSegments[i];
1658
+ const pathSeg = pathSegments[i];
1659
+ if (patternSeg.startsWith(":")) {
1660
+ const paramName = patternSeg.slice(1);
1661
+ params2[paramName] = pathSeg;
1662
+ } else if (patternSeg !== pathSeg) {
1663
+ return null;
1664
+ }
1665
+ }
1666
+ const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
1667
+ const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
1668
+ params2[catchAllParamName] = catchAllValue;
1669
+ if (Object.keys(params2).length !== paramNames.length) {
1670
+ return null;
1671
+ }
1672
+ return params2;
1673
+ }
1674
+ if (patternSegments.length !== pathSegments.length) {
1675
+ return null;
1676
+ }
1677
+ const params = {};
1678
+ for (let i = 0; i < patternSegments.length; i++) {
1679
+ const patternSeg = patternSegments[i];
1680
+ const pathSeg = pathSegments[i];
1681
+ if (patternSeg.startsWith(":")) {
1682
+ const paramName = patternSeg.slice(1);
1683
+ params[paramName] = pathSeg;
1684
+ } else if (patternSeg !== pathSeg) {
1685
+ return null;
1686
+ }
1687
+ }
1688
+ if (Object.keys(params).length !== paramNames.length) {
1689
+ return null;
1690
+ }
1691
+ return params;
1692
+ }
1693
+
1694
+ // src/loader/resolveExports.ts
1695
+ function resolveExport(module, exportName) {
1696
+ if (exportName in module && typeof module[exportName] !== "undefined") {
1697
+ return module[exportName];
1698
+ }
1699
+ const defaultExport = module.default;
1700
+ if (defaultExport !== null && typeof defaultExport === "object") {
1701
+ const value = defaultExport[exportName];
1702
+ if (value !== void 0) {
1703
+ return value;
1704
+ }
1705
+ }
1706
+ return void 0;
1707
+ }
1708
+
1709
+ // src/loader/validateRouteModule.ts
1710
+ function validateRouteModule(value, method, filePath) {
1711
+ if (typeof value !== "function") {
1712
+ throw new Error(
1713
+ `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
1714
+ );
1715
+ }
1716
+ }
1717
+
1718
+ // src/loader/loadRouteModule.ts
1719
+ async function loadRouteModule(filePath, method) {
1720
+ let module;
1721
+ try {
1722
+ module = await importWithCacheBust(filePath);
1723
+ } catch (err) {
1724
+ const reason = err instanceof Error ? err.message : String(err);
1725
+ throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
1726
+ }
1727
+ const handler = resolveExport(module, method);
1728
+ validateRouteModule(handler, method, filePath);
1729
+ return { handler, method };
1730
+ }
1731
+
1732
+ // src/utils/parseJsonBody.ts
1733
+ function parseJsonBody(text) {
1734
+ try {
1735
+ const data = JSON.parse(text);
1736
+ return { success: true, data };
1737
+ } catch {
1738
+ return { success: false, error: "Invalid JSON body" };
1739
+ }
1740
+ }
1741
+
1742
+ // src/utils/parseMultipart.ts
1743
+ async function parseMultipart(request) {
1744
+ const formData = await request.formData();
1745
+ const fields = {};
1746
+ const files = [];
1747
+ for (const [key, value] of formData.entries()) {
1748
+ if (value instanceof File) {
1749
+ files.push({
1750
+ name: key,
1751
+ filename: value.name,
1752
+ type: value.type,
1753
+ size: value.size,
1754
+ arrayBuffer: () => value.arrayBuffer()
1755
+ });
1756
+ } else {
1757
+ if (key in fields) {
1758
+ const existing = fields[key];
1759
+ if (Array.isArray(existing)) {
1760
+ existing.push(value);
1761
+ } else {
1762
+ fields[key] = [existing, value];
1763
+ }
1764
+ } else {
1765
+ fields[key] = value;
1766
+ }
1767
+ }
1768
+ }
1769
+ return { fields, files };
1770
+ }
1771
+
1772
+ // src/runtime/resolveInput.ts
1773
+ async function resolveInput(method, request) {
1774
+ const inputType = getInputTypeForMethod(method);
1775
+ if (inputType === "body") {
1776
+ const contentType = request.headers.get("content-type") ?? "";
1777
+ if (contentType.includes("multipart/form-data")) {
1778
+ return parseMultipart(request);
1779
+ }
1780
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1781
+ const text2 = await request.text();
1782
+ if (text2.trim() === "") return null;
1783
+ const params = new URLSearchParams(text2);
1784
+ const obj = {};
1785
+ for (const [key, value] of params) {
1786
+ obj[key] = value;
1787
+ }
1788
+ return obj;
1789
+ }
1790
+ const text = await request.text();
1791
+ if (text.trim() === "") {
1792
+ return null;
1793
+ }
1794
+ const result = parseJsonBody(text);
1795
+ if (!result.success) {
1796
+ throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
1797
+ {
1798
+ path: "body",
1799
+ code: "INVALID_FORMAT",
1800
+ expected: "JSON",
1801
+ received: "text",
1802
+ message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
1803
+ }
1804
+ ]);
1805
+ }
1806
+ return result.data;
1807
+ }
1808
+ const url = new URL(request.url);
1809
+ return queryToObject(url.searchParams);
1810
+ }
1811
+
1807
1812
  // src/response/sendNodeResponse.ts
1808
1813
  import { Readable } from "stream";
1809
1814
  async function sendNodeResponse(response, res) {
@@ -3373,6 +3378,7 @@ export {
3373
3378
  collectRouteSchemaSources,
3374
3379
  cors,
3375
3380
  createProdApp as createApp,
3381
+ createContext,
3376
3382
  createDevApp,
3377
3383
  createProdApp,
3378
3384
  createProgram,
@@ -3380,6 +3386,7 @@ export {
3380
3386
  getInputTypeForMethod,
3381
3387
  helmet,
3382
3388
  invalidateProgramCache,
3389
+ invokeHandler,
3383
3390
  loadConfig,
3384
3391
  logger,
3385
3392
  resolveTypeNode