@fragno-dev/core 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.turbo/turbo-build.log +49 -45
  2. package/CHANGELOG.md +53 -0
  3. package/dist/api/api.d.ts +2 -2
  4. package/dist/api/fragment-builder.d.ts +3 -2
  5. package/dist/api/fragment-instantiation.d.ts +4 -3
  6. package/dist/api/fragment-instantiation.js +3 -3
  7. package/dist/api/route.d.ts +3 -0
  8. package/dist/api/route.js +3 -0
  9. package/dist/{api-BX90b4-D.d.ts → api-CoCkNi6h.d.ts} +20 -7
  10. package/dist/api-CoCkNi6h.d.ts.map +1 -0
  11. package/dist/api-DngJDcmO.js.map +1 -1
  12. package/dist/client/client.d.ts +4 -3
  13. package/dist/client/client.js +3 -3
  14. package/dist/client/client.svelte.d.ts +3 -3
  15. package/dist/client/client.svelte.d.ts.map +1 -1
  16. package/dist/client/client.svelte.js +3 -3
  17. package/dist/client/react.d.ts +3 -3
  18. package/dist/client/react.d.ts.map +1 -1
  19. package/dist/client/react.js +3 -3
  20. package/dist/client/solid.d.ts +3 -3
  21. package/dist/client/solid.d.ts.map +1 -1
  22. package/dist/client/solid.js +3 -3
  23. package/dist/client/vanilla.d.ts +3 -3
  24. package/dist/client/vanilla.d.ts.map +1 -1
  25. package/dist/client/vanilla.js +3 -3
  26. package/dist/client/vue.d.ts +3 -3
  27. package/dist/client/vue.d.ts.map +1 -1
  28. package/dist/client/vue.js +3 -3
  29. package/dist/{client-C6LChM0Y.js → client-DJfCJiHK.js} +81 -7
  30. package/dist/client-DJfCJiHK.js.map +1 -0
  31. package/dist/{fragment-builder-BZr2JkuW.d.ts → fragment-builder-8-tiECi5.d.ts} +75 -38
  32. package/dist/fragment-builder-8-tiECi5.d.ts.map +1 -0
  33. package/dist/{fragment-instantiation-D74OQjbn.js → fragment-instantiation-C4wvwl6V.js} +129 -6
  34. package/dist/fragment-instantiation-C4wvwl6V.js.map +1 -0
  35. package/dist/mod.d.ts +3 -2
  36. package/dist/mod.js +3 -3
  37. package/dist/{route-D1MZR6JL.js → request-output-context-CdIjwmEN.js} +22 -33
  38. package/dist/request-output-context-CdIjwmEN.js.map +1 -0
  39. package/dist/route-C5Uryylh.js +21 -0
  40. package/dist/route-C5Uryylh.js.map +1 -0
  41. package/dist/route-mGLYSUvD.d.ts +26 -0
  42. package/dist/route-mGLYSUvD.d.ts.map +1 -0
  43. package/dist/test/test.d.ts +24 -70
  44. package/dist/test/test.d.ts.map +1 -1
  45. package/dist/test/test.js +27 -115
  46. package/dist/test/test.js.map +1 -1
  47. package/package.json +6 -1
  48. package/src/api/api.ts +1 -0
  49. package/src/api/fragment-instantiation.test.ts +460 -0
  50. package/src/api/fragment-instantiation.ts +157 -5
  51. package/src/api/fragno-response.ts +132 -0
  52. package/src/api/request-input-context.test.ts +37 -29
  53. package/src/api/request-input-context.ts +16 -14
  54. package/src/api/request-output-context.test.ts +10 -10
  55. package/src/api/request-output-context.ts +3 -3
  56. package/src/api/route-handler-input-options.ts +15 -0
  57. package/src/client/client.test.ts +264 -0
  58. package/src/client/client.ts +65 -3
  59. package/src/client/internal/fetcher-merge.ts +59 -0
  60. package/src/test/test.test.ts +110 -165
  61. package/src/test/test.ts +56 -266
  62. package/tsdown.config.ts +1 -0
  63. package/dist/api-BX90b4-D.d.ts.map +0 -1
  64. package/dist/client-C6LChM0Y.js.map +0 -1
  65. package/dist/fragment-builder-BZr2JkuW.d.ts.map +0 -1
  66. package/dist/fragment-instantiation-D74OQjbn.js.map +0 -1
  67. package/dist/route-CTxjMtGZ.js +0 -10
  68. package/dist/route-CTxjMtGZ.js.map +0 -1
  69. package/dist/route-D1MZR6JL.js.map +0 -1
@@ -3,7 +3,7 @@ import { type FragnoRouteConfig, type HTTPMethod } from "./api";
3
3
  import { FragnoApiError } from "./error";
4
4
  import { getMountRoute } from "./internal/route";
5
5
  import { addRoute, createRouter, findRoute } from "rou3";
6
- import { RequestInputContext } from "./request-input-context";
6
+ import { RequestInputContext, type RequestBodyType } from "./request-input-context";
7
7
  import type { ExtractPathParams } from "./internal/path";
8
8
  import { RequestOutputContext } from "./request-output-context";
9
9
  import {
@@ -19,14 +19,23 @@ import {
19
19
  } from "./request-middleware";
20
20
  import type { FragmentDefinition } from "./fragment-builder";
21
21
  import { MutableRequestState } from "./mutable-request-state";
22
+ import type { RouteHandlerInputOptions } from "./route-handler-input-options";
23
+ import type { ExtractRouteByPath, ExtractRoutePath } from "../client/client";
24
+ import { type FragnoResponse, parseFragnoResponse } from "./fragno-response";
25
+ import type { InferOrUnknown } from "../util/types-util";
22
26
 
23
27
  export interface FragnoPublicConfig {
24
28
  mountRoute?: string;
25
29
  }
26
30
 
31
+ export type FetcherConfig =
32
+ | { type: "options"; options: RequestInit }
33
+ | { type: "function"; fetcher: typeof fetch };
34
+
27
35
  export interface FragnoPublicClientConfig {
28
36
  mountRoute?: string;
29
37
  baseUrl?: string;
38
+ fetcherConfig?: FetcherConfig;
30
39
  }
31
40
 
32
41
  type AstroHandlers = {
@@ -48,6 +57,8 @@ type SolidStartHandlers = {
48
57
  OPTIONS: (args: { request: Request }) => Promise<Response>;
49
58
  };
50
59
 
60
+ type TanStackStartHandlers = SolidStartHandlers;
61
+
51
62
  type StandardHandlers = {
52
63
  GET: (req: Request) => Promise<Response>;
53
64
  POST: (req: Request) => Promise<Response>;
@@ -64,6 +75,7 @@ type HandlersByFramework = {
64
75
  "next-js": StandardHandlers;
65
76
  "svelte-kit": StandardHandlers;
66
77
  "solid-start": SolidStartHandlers;
78
+ "tanstack-start": TanStackStartHandlers;
67
79
  };
68
80
 
69
81
  // Not actually a symbol, since we might be dealing with multiple instances of this code.
@@ -86,6 +98,26 @@ export interface FragnoInstantiatedFragment<
86
98
  handlersFor: <T extends FullstackFrameworks>(framework: T) => HandlersByFramework[T];
87
99
  handler: (req: Request) => Promise<Response>;
88
100
  mountRoute: string;
101
+ callRoute: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
102
+ method: TMethod,
103
+ path: TPath,
104
+ inputOptions?: RouteHandlerInputOptions<
105
+ TPath,
106
+ ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
107
+ >,
108
+ ) => Promise<
109
+ FragnoResponse<
110
+ InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>
111
+ >
112
+ >;
113
+ callRouteRaw: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
114
+ method: TMethod,
115
+ path: TPath,
116
+ inputOptions?: RouteHandlerInputOptions<
117
+ TPath,
118
+ ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
119
+ >,
120
+ ) => Promise<Response>;
89
121
  withMiddleware: (
90
122
  handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,
91
123
  ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TAdditionalContext>;
@@ -130,6 +162,8 @@ export function createFragment<
130
162
  TServices,
131
163
  TAdditionalContext
132
164
  > {
165
+ type TRoutes = FlattenRouteFactories<TRoutesOrFactories>;
166
+
133
167
  const definition = fragmentBuilder.definition;
134
168
 
135
169
  const dependencies = definition.dependencies?.(config, options) ?? ({} as TDeps);
@@ -190,6 +224,96 @@ export function createFragment<
190
224
 
191
225
  return fragment;
192
226
  },
227
+ callRoute: async <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
228
+ method: TMethod,
229
+ path: TPath,
230
+ inputOptions?: RouteHandlerInputOptions<
231
+ TPath,
232
+ ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
233
+ >,
234
+ ): Promise<
235
+ FragnoResponse<
236
+ InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>
237
+ >
238
+ > => {
239
+ const response = await fragment.callRouteRaw(method, path, inputOptions);
240
+ return parseFragnoResponse(response);
241
+ },
242
+ callRouteRaw: async <
243
+ TMethod extends HTTPMethod,
244
+ TPath extends ExtractRoutePath<TRoutes, TMethod>,
245
+ >(
246
+ method: TMethod,
247
+ path: TPath,
248
+ inputOptions?: RouteHandlerInputOptions<
249
+ TPath,
250
+ ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
251
+ >,
252
+ ): Promise<Response> => {
253
+ // Find the route configuration
254
+ const route = routes.find((r) => r.method === method && r.path === path);
255
+
256
+ if (!route) {
257
+ return Response.json(
258
+ {
259
+ error: `Route ${method} ${path} not found`,
260
+ code: "ROUTE_NOT_FOUND",
261
+ },
262
+ { status: 404 },
263
+ );
264
+ }
265
+
266
+ const {
267
+ pathParams = {} as ExtractPathParams<TPath>,
268
+ body,
269
+ query,
270
+ headers,
271
+ } = inputOptions || {};
272
+
273
+ // Convert query to URLSearchParams if needed
274
+ const searchParams =
275
+ query instanceof URLSearchParams
276
+ ? query
277
+ : query
278
+ ? new URLSearchParams(query)
279
+ : new URLSearchParams();
280
+
281
+ // Convert headers to Headers if needed
282
+ const requestHeaders =
283
+ headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
284
+
285
+ // Construct RequestInputContext
286
+ const inputContext = new RequestInputContext({
287
+ path: route.path,
288
+ method: route.method,
289
+ pathParams,
290
+ searchParams,
291
+ headers: requestHeaders,
292
+ parsedBody: body,
293
+ inputSchema: route.inputSchema,
294
+ shouldValidateInput: true, // Enable validation for production use
295
+ });
296
+
297
+ // Construct RequestOutputContext
298
+ const outputContext = new RequestOutputContext(route.outputSchema);
299
+
300
+ // Call the route handler
301
+ try {
302
+ const response = await route.handler(inputContext, outputContext);
303
+ return response;
304
+ } catch (error) {
305
+ console.error("Error in callRoute handler", error);
306
+
307
+ if (error instanceof FragnoApiError) {
308
+ return error.toResponse();
309
+ }
310
+
311
+ return Response.json(
312
+ { error: "Internal server error", code: "INTERNAL_SERVER_ERROR" },
313
+ { status: 500 },
314
+ );
315
+ }
316
+ },
193
317
  handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {
194
318
  const handler = fragment.handler;
195
319
 
@@ -233,6 +357,15 @@ export function createFragment<
233
357
  HEAD: ({ request }: { request: Request }) => handler(request),
234
358
  OPTIONS: ({ request }: { request: Request }) => handler(request),
235
359
  },
360
+ "tanstack-start": {
361
+ GET: ({ request }: { request: Request }) => handler(request),
362
+ POST: ({ request }: { request: Request }) => handler(request),
363
+ PUT: ({ request }: { request: Request }) => handler(request),
364
+ DELETE: ({ request }: { request: Request }) => handler(request),
365
+ PATCH: ({ request }: { request: Request }) => handler(request),
366
+ HEAD: ({ request }: { request: Request }) => handler(request),
367
+ OPTIONS: ({ request }: { request: Request }) => handler(request),
368
+ },
236
369
  } satisfies HandlersByFramework;
237
370
 
238
371
  return allHandlers[framework];
@@ -269,10 +402,28 @@ export function createFragment<
269
402
  const outputContext = new RequestOutputContext(outputSchema);
270
403
 
271
404
  // Create mutable request state that can be modified by middleware
272
- // Clone the request to avoid consuming the body stream
273
- const clonedReq = req.clone();
274
- const requestBody =
275
- clonedReq.body instanceof ReadableStream ? await clonedReq.json() : undefined;
405
+ // Clone the request to read body as both text and JSON without consuming original stream
406
+ let requestBody: RequestBodyType = undefined;
407
+ let rawBody: string | undefined = undefined;
408
+
409
+ if (req.body instanceof ReadableStream) {
410
+ // Clone request to make sure we don't consume body stream
411
+ const clonedReq = req.clone();
412
+
413
+ // Get raw text
414
+ rawBody = await clonedReq.text();
415
+
416
+ // Parse JSON if body is not empty
417
+ if (rawBody) {
418
+ try {
419
+ requestBody = JSON.parse(rawBody);
420
+ } catch {
421
+ // If JSON parsing fails, keep body as undefined
422
+ // This handles cases where body is not JSON
423
+ requestBody = undefined;
424
+ }
425
+ }
426
+ }
276
427
 
277
428
  const requestState = new MutableRequestState({
278
429
  pathParams: route.params ?? {},
@@ -323,6 +474,7 @@ export function createFragment<
323
474
  pathParams: (route.params ?? {}) as ExtractPathParams<typeof path>,
324
475
  inputSchema,
325
476
  state: requestState,
477
+ rawBody,
326
478
  });
327
479
 
328
480
  try {
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Discriminated union representing all possible Fragno response types
3
+ */
4
+ export type FragnoResponse<T> =
5
+ | {
6
+ type: "empty";
7
+ status: number;
8
+ headers: Headers;
9
+ }
10
+ | {
11
+ type: "error";
12
+ status: number;
13
+ headers: Headers;
14
+ error: { message: string; code: string };
15
+ }
16
+ | {
17
+ type: "json";
18
+ status: number;
19
+ headers: Headers;
20
+ data: T;
21
+ }
22
+ | {
23
+ type: "jsonStream";
24
+ status: number;
25
+ headers: Headers;
26
+ stream: AsyncGenerator<T extends unknown[] ? T[number] : T>;
27
+ };
28
+
29
+ /**
30
+ * Parse a Response object into a FragnoResponse discriminated union
31
+ */
32
+ export async function parseFragnoResponse<T>(response: Response): Promise<FragnoResponse<T>> {
33
+ const status = response.status;
34
+ const headers = response.headers;
35
+ const contentType = headers.get("content-type") || "";
36
+
37
+ // Check for streaming response
38
+ if (contentType.includes("application/x-ndjson")) {
39
+ return {
40
+ type: "jsonStream",
41
+ status,
42
+ headers,
43
+ stream: parseNDJSONStream<T>(response),
44
+ };
45
+ }
46
+
47
+ // Parse JSON body
48
+ const text = await response.text();
49
+
50
+ // Empty response
51
+ if (!text || text === "null") {
52
+ return {
53
+ type: "empty",
54
+ status,
55
+ headers,
56
+ };
57
+ }
58
+
59
+ const data = JSON.parse(text);
60
+
61
+ // Error response (has message and code, or error and code)
62
+ if (data && typeof data === "object" && "code" in data) {
63
+ if ("message" in data) {
64
+ return {
65
+ type: "error",
66
+ status,
67
+ headers,
68
+ error: { message: data.message, code: data.code },
69
+ };
70
+ }
71
+ if ("error" in data) {
72
+ return {
73
+ type: "error",
74
+ status,
75
+ headers,
76
+ error: { message: data.error, code: data.code },
77
+ };
78
+ }
79
+ }
80
+
81
+ // JSON response
82
+ return {
83
+ type: "json",
84
+ status,
85
+ headers,
86
+ data: data as T,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Parse an NDJSON stream into an async generator
92
+ */
93
+ async function* parseNDJSONStream<T>(
94
+ response: Response,
95
+ ): AsyncGenerator<T extends unknown[] ? T[number] : T> {
96
+ if (!response.body) {
97
+ return;
98
+ }
99
+
100
+ const reader = response.body.getReader();
101
+ const decoder = new TextDecoder();
102
+ let buffer = "";
103
+
104
+ try {
105
+ while (true) {
106
+ const { done, value } = await reader.read();
107
+
108
+ if (done) {
109
+ break;
110
+ }
111
+
112
+ buffer += decoder.decode(value, { stream: true });
113
+ const lines = buffer.split("\n");
114
+
115
+ // Keep the last incomplete line in the buffer
116
+ buffer = lines.pop() || "";
117
+
118
+ for (const line of lines) {
119
+ if (line.trim()) {
120
+ yield JSON.parse(line) as T extends unknown[] ? T[number] : T;
121
+ }
122
+ }
123
+ }
124
+
125
+ // Process any remaining data in the buffer
126
+ if (buffer.trim()) {
127
+ yield JSON.parse(buffer) as T extends unknown[] ? T[number] : T;
128
+ }
129
+ } finally {
130
+ reader.releaseLock();
131
+ }
132
+ }
@@ -42,61 +42,67 @@ describe("RequestContext", () => {
42
42
  pathParams: {},
43
43
  searchParams: new URLSearchParams(),
44
44
  headers: new Headers(),
45
- body: undefined,
45
+ parsedBody: undefined,
46
46
  });
47
47
 
48
- const { path, pathParams, query: searchParams, input, rawBody: body } = ctx;
48
+ const { path, pathParams, query: searchParams, input, rawBody } = ctx;
49
49
 
50
50
  expect(path).toBe("/");
51
51
  expect(pathParams).toEqual({});
52
52
  expect(searchParams).toBeInstanceOf(URLSearchParams);
53
53
  expect(input).toBeUndefined();
54
- expect(body).toBeUndefined();
54
+ expect(rawBody).toBeUndefined();
55
55
  });
56
56
 
57
57
  test("Should support body in constructor", () => {
58
58
  const jsonBody = { test: "data" };
59
+ const rawBodyText = JSON.stringify(jsonBody);
59
60
  const ctx = new RequestInputContext({
60
61
  method: "POST",
61
62
  path: "/api/test",
62
63
  pathParams: {},
63
64
  searchParams: new URLSearchParams(),
64
65
  headers: new Headers(),
65
- body: jsonBody,
66
+ parsedBody: jsonBody,
67
+ rawBody: rawBodyText,
66
68
  });
67
69
 
68
- expect(ctx.rawBody).toEqual(jsonBody);
70
+ expect(ctx.rawBody).toEqual(rawBodyText);
69
71
  });
70
72
 
71
73
  test("Should support FormData body", () => {
72
74
  const formData = new FormData();
73
75
  formData.append("key", "value");
76
+ const rawBodyText = "form-data-as-text";
74
77
 
75
78
  const ctx = new RequestInputContext({
76
79
  path: "/api/form",
77
80
  pathParams: {},
78
81
  searchParams: new URLSearchParams(),
79
82
  headers: new Headers(),
80
- body: formData,
83
+ parsedBody: formData,
84
+ rawBody: rawBodyText,
81
85
  method: "POST",
82
86
  });
83
87
 
84
- expect(ctx.rawBody).toBe(formData);
88
+ expect(ctx.rawBody).toBe(rawBodyText);
85
89
  });
86
90
 
87
91
  test("Should support Blob body", () => {
88
92
  const blob = new Blob(["test content"], { type: "text/plain" });
93
+ const rawBodyText = "test content";
89
94
 
90
95
  const ctx = new RequestInputContext({
91
96
  path: "/api/upload",
92
97
  pathParams: {},
93
98
  searchParams: new URLSearchParams(),
94
99
  headers: new Headers(),
95
- body: blob,
100
+ parsedBody: blob,
101
+ rawBody: rawBodyText,
96
102
  method: "POST",
97
103
  });
98
104
 
99
- expect(ctx.rawBody).toBe(blob);
105
+ expect(ctx.rawBody).toBe(rawBodyText);
100
106
  });
101
107
 
102
108
  test("Should create RequestContext with fromRequest static method", async () => {
@@ -105,10 +111,10 @@ describe("RequestContext", () => {
105
111
  body: JSON.stringify({ test: "data" }),
106
112
  });
107
113
 
108
- const bodyData = { test: "data" };
109
114
  const url = new URL(request.url);
110
115
  const clonedReq = request.clone();
111
116
  const body = clonedReq.body instanceof ReadableStream ? await clonedReq.json() : undefined;
117
+ const rawBodyText = JSON.stringify({ test: "data" });
112
118
 
113
119
  const state = new MutableRequestState({
114
120
  pathParams: {},
@@ -123,10 +129,11 @@ describe("RequestContext", () => {
123
129
  path: "/api/test",
124
130
  pathParams: {},
125
131
  state,
132
+ rawBody: rawBodyText,
126
133
  });
127
134
 
128
135
  expect(ctx.path).toBe("/api/test");
129
- expect(ctx.rawBody).toEqual(bodyData);
136
+ expect(ctx.rawBody).toEqual(rawBodyText);
130
137
  });
131
138
 
132
139
  test("Should create RequestContext with fromSSRContext static method", () => {
@@ -139,7 +146,8 @@ describe("RequestContext", () => {
139
146
  });
140
147
 
141
148
  expect(ctx.path).toBe("/api/ssr");
142
- expect(ctx.rawBody).toEqual(bodyData);
149
+ // rawBody is not set in fromSSRContext
150
+ expect(ctx.rawBody).toBeUndefined();
143
151
  });
144
152
 
145
153
  describe("Input handling", () => {
@@ -149,7 +157,7 @@ describe("RequestContext", () => {
149
157
  pathParams: {},
150
158
  searchParams: new URLSearchParams(),
151
159
  headers: new Headers(),
152
- body: { test: "data" },
160
+ parsedBody: { test: "data" },
153
161
  method: "POST",
154
162
  });
155
163
 
@@ -162,7 +170,7 @@ describe("RequestContext", () => {
162
170
  pathParams: {},
163
171
  searchParams: new URLSearchParams(),
164
172
  headers: new Headers(),
165
- body: { test: "data" },
173
+ parsedBody: { test: "data" },
166
174
  inputSchema: validStringSchema,
167
175
  method: "POST",
168
176
  });
@@ -178,7 +186,7 @@ describe("RequestContext", () => {
178
186
  pathParams: {},
179
187
  searchParams: new URLSearchParams(),
180
188
  headers: new Headers(),
181
- body: "test string",
189
+ parsedBody: "test string",
182
190
  inputSchema: validStringSchema,
183
191
  method: "POST",
184
192
  });
@@ -193,7 +201,7 @@ describe("RequestContext", () => {
193
201
  pathParams: {},
194
202
  searchParams: new URLSearchParams(),
195
203
  headers: new Headers(),
196
- body: 123, // Invalid for string schema
204
+ parsedBody: 123, // Invalid for string schema
197
205
  inputSchema: invalidSchema,
198
206
  method: "POST",
199
207
  });
@@ -207,7 +215,7 @@ describe("RequestContext", () => {
207
215
  pathParams: {},
208
216
  searchParams: new URLSearchParams(),
209
217
  headers: new Headers(),
210
- body: 123,
218
+ parsedBody: 123,
211
219
  inputSchema: invalidSchema,
212
220
  method: "POST",
213
221
  });
@@ -236,13 +244,13 @@ describe("RequestContext", () => {
236
244
  pathParams: {},
237
245
  searchParams: new URLSearchParams(),
238
246
  headers: new Headers(),
239
- body: 123,
247
+ parsedBody: 123,
240
248
  inputSchema: invalidSchema,
241
249
  shouldValidateInput: false,
242
250
  method: "POST",
243
251
  });
244
252
 
245
- // Should return the raw body without validation when validation is disabled
253
+ // Should return the parsed body without validation when validation is disabled
246
254
  const result = await ctx.input?.valid();
247
255
  expect(result).toBe(123);
248
256
  });
@@ -256,7 +264,7 @@ describe("RequestContext", () => {
256
264
  pathParams: {},
257
265
  searchParams: new URLSearchParams(),
258
266
  headers: new Headers(),
259
- body: formData,
267
+ parsedBody: formData,
260
268
  inputSchema: validStringSchema,
261
269
  method: "POST",
262
270
  });
@@ -274,7 +282,7 @@ describe("RequestContext", () => {
274
282
  pathParams: {},
275
283
  searchParams: new URLSearchParams(),
276
284
  headers: new Headers(),
277
- body: blob,
285
+ parsedBody: blob,
278
286
  inputSchema: validStringSchema,
279
287
  method: "POST",
280
288
  });
@@ -290,7 +298,7 @@ describe("RequestContext", () => {
290
298
  pathParams: {},
291
299
  searchParams: new URLSearchParams(),
292
300
  headers: new Headers(),
293
- body: null,
301
+ parsedBody: null,
294
302
  inputSchema: validStringSchema,
295
303
  method: "POST",
296
304
  });
@@ -305,7 +313,7 @@ describe("RequestContext", () => {
305
313
  pathParams: {},
306
314
  searchParams: new URLSearchParams(),
307
315
  headers: new Headers(),
308
- body: undefined,
316
+ parsedBody: undefined,
309
317
  inputSchema: validStringSchema,
310
318
  method: "POST",
311
319
  });
@@ -324,7 +332,7 @@ describe("RequestContext", () => {
324
332
  headers: new Headers(),
325
333
  inputSchema: validStringSchema,
326
334
  method: "POST",
327
- body: undefined,
335
+ parsedBody: undefined,
328
336
  });
329
337
 
330
338
  // We can't directly access the private field, but we can test the behavior
@@ -340,7 +348,7 @@ describe("RequestContext", () => {
340
348
  inputSchema: validStringSchema,
341
349
  shouldValidateInput: true,
342
350
  method: "POST",
343
- body: undefined,
351
+ parsedBody: undefined,
344
352
  });
345
353
 
346
354
  expect(ctx.input).toBeDefined();
@@ -355,7 +363,7 @@ describe("RequestContext", () => {
355
363
  inputSchema: validStringSchema,
356
364
  shouldValidateInput: false,
357
365
  method: "POST",
358
- body: undefined,
366
+ parsedBody: undefined,
359
367
  });
360
368
 
361
369
  expect(ctx.input).toBeDefined();
@@ -406,7 +414,7 @@ describe("RequestContext", () => {
406
414
  searchParams: new URLSearchParams(),
407
415
  headers: new Headers(),
408
416
  method: "POST",
409
- body: undefined,
417
+ parsedBody: undefined,
410
418
  });
411
419
 
412
420
  expect(ctx.pathParams).toEqual({});
@@ -419,7 +427,7 @@ describe("RequestContext", () => {
419
427
  searchParams: new URLSearchParams(),
420
428
  headers: new Headers(),
421
429
  method: "POST",
422
- body: undefined,
430
+ parsedBody: undefined,
423
431
  });
424
432
 
425
433
  expect(ctx.query.toString()).toBe("");
@@ -433,7 +441,7 @@ describe("RequestContext", () => {
433
441
  searchParams,
434
442
  headers: new Headers(),
435
443
  method: "POST",
436
- body: undefined,
444
+ parsedBody: undefined,
437
445
  });
438
446
 
439
447
  expect(ctx.query.get("key")).toBe("value");