@fragno-dev/core 0.1.1 → 0.1.3

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 (71) hide show
  1. package/.turbo/turbo-build.log +41 -32
  2. package/CHANGELOG.md +15 -0
  3. package/LICENSE.md +16 -0
  4. package/dist/api/api.d.ts +1 -1
  5. package/dist/api/fragment-builder.d.ts +2 -2
  6. package/dist/api/fragment-instantiation.d.ts +2 -2
  7. package/dist/api/fragment-instantiation.js +3 -2
  8. package/dist/{api-Dcr4_-3g.d.ts → api-BX90b4-D.d.ts} +92 -6
  9. package/dist/api-BX90b4-D.d.ts.map +1 -0
  10. package/dist/client/client.d.ts +2 -2
  11. package/dist/client/client.js +4 -3
  12. package/dist/client/client.svelte.d.ts +2 -2
  13. package/dist/client/client.svelte.js +4 -3
  14. package/dist/client/client.svelte.js.map +1 -1
  15. package/dist/client/react.d.ts +2 -2
  16. package/dist/client/react.d.ts.map +1 -1
  17. package/dist/client/react.js +4 -3
  18. package/dist/client/react.js.map +1 -1
  19. package/dist/client/solid.d.ts +2 -2
  20. package/dist/client/solid.js +4 -3
  21. package/dist/client/solid.js.map +1 -1
  22. package/dist/client/vanilla.d.ts +2 -2
  23. package/dist/client/vanilla.js +4 -3
  24. package/dist/client/vanilla.js.map +1 -1
  25. package/dist/client/vue.d.ts +2 -2
  26. package/dist/client/vue.js +4 -3
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/{client-D5ORmjBP.js → client-C6LChM0Y.js} +4 -3
  29. package/dist/client-C6LChM0Y.js.map +1 -0
  30. package/dist/{fragment-builder-D6-oLYnH.d.ts → fragment-builder-BZr2JkuW.d.ts} +51 -38
  31. package/dist/fragment-builder-BZr2JkuW.d.ts.map +1 -0
  32. package/dist/fragment-builder-DOnCVBqc.js.map +1 -1
  33. package/dist/{fragment-instantiation-f4AhwQss.js → fragment-instantiation-DMw8OKMC.js} +137 -11
  34. package/dist/fragment-instantiation-DMw8OKMC.js.map +1 -0
  35. package/dist/integrations/react-ssr.js +1 -1
  36. package/dist/mod.d.ts +2 -2
  37. package/dist/mod.js +3 -2
  38. package/dist/route-CTxjMtGZ.js +10 -0
  39. package/dist/route-CTxjMtGZ.js.map +1 -0
  40. package/dist/{route-B4RbOWjd.js → route-D1MZR6JL.js} +22 -22
  41. package/dist/route-D1MZR6JL.js.map +1 -0
  42. package/dist/{ssr-CamRrMc0.js → ssr-BByDVfFD.js} +1 -1
  43. package/dist/{ssr-CamRrMc0.js.map → ssr-BByDVfFD.js.map} +1 -1
  44. package/dist/test/test.d.ts +112 -0
  45. package/dist/test/test.d.ts.map +1 -0
  46. package/dist/test/test.js +155 -0
  47. package/dist/test/test.js.map +1 -0
  48. package/package.json +18 -24
  49. package/src/api/fragment-builder.ts +0 -1
  50. package/src/api/fragment-instantiation.ts +16 -3
  51. package/src/api/mutable-request-state.ts +107 -0
  52. package/src/api/request-input-context.test.ts +51 -0
  53. package/src/api/request-input-context.ts +20 -13
  54. package/src/api/request-middleware.test.ts +88 -2
  55. package/src/api/request-middleware.ts +28 -6
  56. package/src/api/request-output-context.test.ts +6 -2
  57. package/src/api/request-output-context.ts +15 -9
  58. package/src/client/component.test.svelte +2 -0
  59. package/src/client/internal/ndjson-streaming.ts +6 -2
  60. package/src/client/react.ts +3 -1
  61. package/src/test/test.test.ts +449 -0
  62. package/src/test/test.ts +379 -0
  63. package/src/util/async.test.ts +6 -2
  64. package/tsdown.config.ts +1 -0
  65. package/.turbo/turbo-test.log +0 -297
  66. package/.turbo/turbo-types$colon$check.log +0 -1
  67. package/dist/api-Dcr4_-3g.d.ts.map +0 -1
  68. package/dist/client-D5ORmjBP.js.map +0 -1
  69. package/dist/fragment-builder-D6-oLYnH.d.ts.map +0 -1
  70. package/dist/fragment-instantiation-f4AhwQss.js.map +0 -1
  71. package/dist/route-B4RbOWjd.js.map +0 -1
@@ -19,7 +19,9 @@ function mergeHeaders(...headerSources: (HeadersInit | undefined)[]): HeadersIni
19
19
  const mergedHeaders = new Headers();
20
20
 
21
21
  for (const headerSource of headerSources) {
22
- if (!headerSource) continue;
22
+ if (!headerSource) {
23
+ continue;
24
+ }
23
25
 
24
26
  if (headerSource instanceof Headers) {
25
27
  for (const [key, value] of headerSource.entries()) {
@@ -45,11 +47,11 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
45
47
  *
46
48
  * Shortcut for `throw new FragnoApiError(...)`
47
49
  */
48
- error(
50
+ error = (
49
51
  { message, code }: { message: string; code: TErrorCode },
50
52
  initOrStatus?: ResponseInit | StatusCode,
51
53
  headers?: HeadersInit,
52
- ): Response {
54
+ ): Response => {
53
55
  if (typeof initOrStatus === "undefined") {
54
56
  return Response.json({ message: message, code }, { status: 500, headers });
55
57
  }
@@ -63,12 +65,12 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
63
65
  { message: message, code },
64
66
  { status: initOrStatus.status, headers: mergedHeaders },
65
67
  );
66
- }
68
+ };
67
69
 
68
- empty(
70
+ empty = (
69
71
  initOrStatus?: ResponseInit<ContentlessStatusCode> | ContentlessStatusCode,
70
72
  headers?: HeadersInit,
71
- ): Response {
73
+ ): Response => {
72
74
  const defaultHeaders = {};
73
75
 
74
76
  if (typeof initOrStatus === "undefined") {
@@ -92,9 +94,13 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
92
94
  status: initOrStatus.status,
93
95
  headers: mergedHeaders,
94
96
  });
95
- }
97
+ };
96
98
 
97
- json(object: TOutput, initOrStatus?: ResponseInit | StatusCode, headers?: HeadersInit): Response {
99
+ json = (
100
+ object: TOutput,
101
+ initOrStatus?: ResponseInit | StatusCode,
102
+ headers?: HeadersInit,
103
+ ): Response => {
98
104
  if (typeof initOrStatus === "undefined") {
99
105
  return Response.json(object, {
100
106
  status: 200,
@@ -114,7 +120,7 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
114
120
  status: initOrStatus.status,
115
121
  headers: mergedHeaders,
116
122
  });
117
- }
123
+ };
118
124
 
119
125
  jsonStream = (
120
126
  cb: (stream: ResponseStream<TOutput>) => void | Promise<void>,
@@ -2,7 +2,9 @@
2
2
  import type { Readable } from "svelte/store";
3
3
  import { useFragno } from "./client.svelte";
4
4
 
5
+ // oxlint-disable-next-line no-unassigned-vars
5
6
  export let clientObj: Record<string, unknown>;
7
+ // oxlint-disable-next-line no-unassigned-vars
6
8
  export let hookName: string;
7
9
  export let args: Record<string, unknown> = {};
8
10
 
@@ -191,7 +191,9 @@ async function continueStreaming<TOutputSchema extends StandardSchemaV1, TErrorC
191
191
  if (buffer.trim()) {
192
192
  const lines = buffer.split("\n");
193
193
  for (const line of lines) {
194
- if (!line.trim()) continue;
194
+ if (!line.trim()) {
195
+ continue;
196
+ }
195
197
 
196
198
  try {
197
199
  const jsonObject = JSON.parse(line) as StandardSchemaV1.InferOutput<TOutputSchema>;
@@ -215,7 +217,9 @@ async function continueStreaming<TOutputSchema extends StandardSchemaV1, TErrorC
215
217
  buffer = lines.pop() || ""; // Keep incomplete line in buffer
216
218
 
217
219
  for (const line of lines) {
218
- if (!line.trim()) continue;
220
+ if (!line.trim()) {
221
+ continue;
222
+ }
219
223
 
220
224
  try {
221
225
  const jsonObject = JSON.parse(line) as StandardSchemaV1.InferOutput<TOutputSchema>;
@@ -210,7 +210,9 @@ export function useStore<SomeStore extends Store>(
210
210
 
211
211
  const subscribe = useCallback((onChange: () => void) => {
212
212
  const emitChange = (value: StoreValue<SomeStore>) => {
213
- if (snapshotRef.current === value) return;
213
+ if (snapshotRef.current === value) {
214
+ return;
215
+ }
214
216
  snapshotRef.current = value;
215
217
  onChange();
216
218
  };
@@ -0,0 +1,449 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createFragmentForTest } from "./test";
3
+ import { defineFragment } from "../api/fragment-builder";
4
+ import { defineRoute } from "../api/route";
5
+ import { z } from "zod";
6
+
7
+ describe("createFragmentForTest", () => {
8
+ it("should create a test fragment with config only", () => {
9
+ const fragment = defineFragment<{ apiKey: string }>("test");
10
+ const testFragment = createFragmentForTest(fragment, {
11
+ config: { apiKey: "test-key" },
12
+ });
13
+
14
+ expect(testFragment.config).toEqual({ apiKey: "test-key" });
15
+ expect(testFragment.deps).toEqual({});
16
+ expect(testFragment.services).toEqual({});
17
+ });
18
+
19
+ it("should create deps from fragment definition", () => {
20
+ const fragment = defineFragment<{ apiKey: string }>("test").withDependencies(({ config }) => ({
21
+ client: { apiKey: config.apiKey },
22
+ }));
23
+
24
+ const testFragment = createFragmentForTest(fragment, {
25
+ config: { apiKey: "test-key" },
26
+ });
27
+
28
+ expect(testFragment.deps).toEqual({ client: { apiKey: "test-key" } });
29
+ });
30
+
31
+ it("should override deps when provided", () => {
32
+ const fragment = defineFragment<{ apiKey: string }>("test").withDependencies(({ config }) => ({
33
+ client: { apiKey: config.apiKey },
34
+ }));
35
+
36
+ const testFragment = createFragmentForTest(fragment, {
37
+ config: { apiKey: "test-key" },
38
+ deps: { client: { apiKey: "override-key" } },
39
+ });
40
+
41
+ expect(testFragment.deps).toEqual({ client: { apiKey: "override-key" } });
42
+ });
43
+
44
+ it("should create services from fragment definition", () => {
45
+ const fragment = defineFragment<{ apiKey: string }>("test")
46
+ .withDependencies(({ config }) => ({
47
+ client: { apiKey: config.apiKey },
48
+ }))
49
+ .withServices(({ deps }) => ({
50
+ getApiKey: () => deps.client.apiKey,
51
+ }));
52
+
53
+ const testFragment = createFragmentForTest(fragment, {
54
+ config: { apiKey: "test-key" },
55
+ });
56
+
57
+ expect(testFragment.services.getApiKey()).toBe("test-key");
58
+ });
59
+
60
+ it("should override services when provided", () => {
61
+ const fragment = defineFragment<{ apiKey: string }>("test")
62
+ .withDependencies(({ config }) => ({
63
+ client: { apiKey: config.apiKey },
64
+ }))
65
+ .withServices(({ deps }) => ({
66
+ getApiKey: () => deps.client.apiKey,
67
+ }));
68
+
69
+ const testFragment = createFragmentForTest(fragment, {
70
+ config: { apiKey: "test-key" },
71
+ services: { getApiKey: () => "override-key" },
72
+ });
73
+
74
+ expect(testFragment.services.getApiKey()).toBe("override-key");
75
+ });
76
+
77
+ it("should initialize routes with fragment context", () => {
78
+ const fragment = defineFragment<{ multiplier: number }>("test")
79
+ .withDependencies(() => ({ dep: "value" }))
80
+ .withServices(({ config }) => ({
81
+ multiply: (x: number) => x * config.multiplier,
82
+ }));
83
+
84
+ const testFragment = createFragmentForTest(fragment, {
85
+ config: { multiplier: 2 },
86
+ });
87
+
88
+ const route = defineRoute({
89
+ method: "GET",
90
+ path: "/test",
91
+ outputSchema: z.object({ result: z.number() }),
92
+ handler: async (_ctx, { json }) => {
93
+ return json({ result: 42 });
94
+ },
95
+ });
96
+
97
+ const routes = [route] as const;
98
+ const [initializedRoute] = testFragment.initRoutes(routes);
99
+
100
+ expect(initializedRoute).toBe(route);
101
+ expect(initializedRoute.method).toBe("GET");
102
+ expect(initializedRoute.path).toBe("/test");
103
+ });
104
+
105
+ it("should initialize route factories with fragment context", async () => {
106
+ const fragment = defineFragment<{ multiplier: number }>("test")
107
+ .withDependencies(() => ({ dep: "value" }))
108
+ .withServices(({ config }) => ({
109
+ multiply: (x: number) => x * config.multiplier,
110
+ }));
111
+
112
+ const testFragment = createFragmentForTest(fragment, {
113
+ config: { multiplier: 3 },
114
+ });
115
+
116
+ const routeFactory = ({ services }: { services: { multiply: (x: number) => number } }) => {
117
+ return [
118
+ defineRoute({
119
+ method: "GET",
120
+ path: "/multiply",
121
+ outputSchema: z.object({ result: z.number() }),
122
+ handler: async (_ctx, { json }) => {
123
+ return json({ result: services.multiply(5) });
124
+ },
125
+ }),
126
+ ];
127
+ };
128
+
129
+ const routes = [routeFactory] as const;
130
+ const [multiplyRoute] = testFragment.initRoutes(routes);
131
+
132
+ expect(multiplyRoute.method).toBe("GET");
133
+ expect(multiplyRoute.path).toBe("/multiply");
134
+
135
+ // Test that the route was initialized with the correct services
136
+ const response = await testFragment.handler(multiplyRoute);
137
+ expect(response.type).toBe("json");
138
+ if (response.type === "json") {
139
+ expect(response.data).toEqual({ result: 15 }); // 5 * 3
140
+ }
141
+ });
142
+
143
+ it("should allow overriding config/deps/services for specific route initialization", async () => {
144
+ const fragment = defineFragment<{ multiplier: number }>("test")
145
+ .withDependencies(() => ({ baseUrl: "https://api.example.com" }))
146
+ .withServices(({ config }) => ({
147
+ multiply: (x: number) => x * config.multiplier,
148
+ getMessage: (): string => "original message",
149
+ }));
150
+
151
+ const testFragment = createFragmentForTest(fragment, {
152
+ config: { multiplier: 2 },
153
+ });
154
+
155
+ const routeFactory = ({
156
+ config,
157
+ services,
158
+ }: {
159
+ config: { multiplier: number };
160
+ services: { multiply: (x: number) => number; getMessage: () => string };
161
+ }) => {
162
+ return [
163
+ defineRoute({
164
+ method: "GET",
165
+ path: "/test",
166
+ outputSchema: z.object({
167
+ result: z.number(),
168
+ message: z.string(),
169
+ multiplier: z.number(),
170
+ }),
171
+ handler: async (_ctx, { json }) => {
172
+ return json({
173
+ result: services.multiply(10),
174
+ message: services.getMessage(),
175
+ multiplier: config.multiplier,
176
+ });
177
+ },
178
+ }),
179
+ ];
180
+ };
181
+
182
+ const routes = [routeFactory] as const;
183
+
184
+ // Initialize with overrides - completely replace the multiply service
185
+ const [overriddenRoute] = testFragment.initRoutes(routes, {
186
+ config: { multiplier: 5 },
187
+ services: {
188
+ multiply: (x: number) => x * 5, // Mock implementation uses hardcoded multiplier
189
+ getMessage: (): string => "mocked message",
190
+ },
191
+ });
192
+
193
+ const response = await testFragment.handler(overriddenRoute);
194
+ expect(response.type).toBe("json");
195
+ if (response.type === "json") {
196
+ expect(response.data).toEqual({
197
+ result: 50, // 10 * 5 (mocked multiply service)
198
+ message: "mocked message", // overridden service
199
+ multiplier: 5, // overridden config
200
+ });
201
+ }
202
+
203
+ // Verify original fragment config/services are unchanged
204
+ expect(testFragment.config.multiplier).toBe(2);
205
+ expect(testFragment.services.multiply(10)).toBe(20); // Original multiplier is 2
206
+ expect(testFragment.services.getMessage()).toBe("original message");
207
+ });
208
+ });
209
+
210
+ describe("fragment.handler", () => {
211
+ it("should handle JSON response", async () => {
212
+ const fragment = defineFragment<{ apiKey: string }>("test");
213
+ const testFragment = createFragmentForTest(fragment, {
214
+ config: { apiKey: "test-key" },
215
+ });
216
+
217
+ const route = defineRoute({
218
+ method: "GET",
219
+ path: "/test",
220
+ outputSchema: z.object({ message: z.string() }),
221
+ handler: async (_ctx, { json }) => {
222
+ return json({ message: "hello" });
223
+ },
224
+ });
225
+
226
+ const response = await testFragment.handler(route);
227
+
228
+ expect(response.type).toBe("json");
229
+ if (response.type === "json") {
230
+ expect(response.status).toBe(200);
231
+ expect(response.data).toEqual({ message: "hello" });
232
+ expect(response.headers).toBeInstanceOf(Headers);
233
+ }
234
+ });
235
+
236
+ it("should handle empty response", async () => {
237
+ const fragment = defineFragment<{ apiKey: string }>("test");
238
+ const testFragment = createFragmentForTest(fragment, {
239
+ config: { apiKey: "test-key" },
240
+ });
241
+
242
+ const route = defineRoute({
243
+ method: "DELETE",
244
+ path: "/test",
245
+ handler: async (_ctx, { empty }) => {
246
+ return empty(204);
247
+ },
248
+ });
249
+
250
+ const response = await testFragment.handler(route);
251
+
252
+ expect(response.type).toBe("empty");
253
+ if (response.type === "empty") {
254
+ expect(response.status).toBe(204);
255
+ expect(response.headers).toBeInstanceOf(Headers);
256
+ }
257
+ });
258
+
259
+ it("should handle error response", async () => {
260
+ const fragment = defineFragment<{ apiKey: string }>("test");
261
+ const testFragment = createFragmentForTest(fragment, {
262
+ config: { apiKey: "test-key" },
263
+ });
264
+
265
+ const route = defineRoute({
266
+ method: "GET",
267
+ path: "/test",
268
+ errorCodes: ["NOT_FOUND"] as const,
269
+ handler: async (_ctx, { error }) => {
270
+ return error({ message: "Not found", code: "NOT_FOUND" }, 404);
271
+ },
272
+ });
273
+
274
+ const response = await testFragment.handler(route);
275
+
276
+ expect(response.type).toBe("error");
277
+ if (response.type === "error") {
278
+ expect(response.status).toBe(404);
279
+ expect(response.error).toEqual({ message: "Not found", code: "NOT_FOUND" });
280
+ expect(response.headers).toBeInstanceOf(Headers);
281
+ }
282
+ });
283
+
284
+ it("should handle JSON stream response", async () => {
285
+ const fragment = defineFragment<{ apiKey: string }>("test");
286
+ const testFragment = createFragmentForTest(fragment, {
287
+ config: { apiKey: "test-key" },
288
+ });
289
+
290
+ const route = defineRoute({
291
+ method: "GET",
292
+ path: "/test/stream",
293
+ outputSchema: z.array(z.object({ value: z.number() })),
294
+ handler: async (_ctx, { jsonStream }) => {
295
+ return jsonStream(async (stream) => {
296
+ for (let i = 1; i <= 5; i++) {
297
+ await stream.write({ value: i });
298
+ }
299
+ });
300
+ },
301
+ });
302
+
303
+ const response = await testFragment.handler(route);
304
+
305
+ expect(response.type).toBe("jsonStream");
306
+ if (response.type === "jsonStream") {
307
+ expect(response.status).toBe(200);
308
+ expect(response.headers).toBeInstanceOf(Headers);
309
+ expect(response.headers.get("content-type")).toContain("application/x-ndjson");
310
+
311
+ const items = [];
312
+ for await (const item of response.stream) {
313
+ items.push(item);
314
+ }
315
+
316
+ expect(items).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }, { value: 5 }]);
317
+ }
318
+ });
319
+
320
+ it("should handle path parameters", async () => {
321
+ const fragment = defineFragment<{}>("test");
322
+ const testFragment = createFragmentForTest(fragment, {
323
+ config: {},
324
+ });
325
+
326
+ const route = defineRoute({
327
+ method: "GET",
328
+ path: "/users/:id",
329
+ outputSchema: z.object({ userId: z.string() }),
330
+ handler: async ({ pathParams }, { json }) => {
331
+ return json({ userId: pathParams.id });
332
+ },
333
+ });
334
+
335
+ const response = await testFragment.handler(route, {
336
+ pathParams: { id: "123" },
337
+ });
338
+
339
+ expect(response.type).toBe("json");
340
+ if (response.type === "json") {
341
+ expect(response.data).toEqual({ userId: "123" });
342
+ }
343
+ });
344
+
345
+ it("should handle query parameters", async () => {
346
+ const fragment = defineFragment<{}>("test");
347
+ const testFragment = createFragmentForTest(fragment, {
348
+ config: {},
349
+ });
350
+
351
+ const route = defineRoute({
352
+ method: "GET",
353
+ path: "/search",
354
+ outputSchema: z.object({ query: z.string() }),
355
+ handler: async ({ query }, { json }) => {
356
+ return json({ query: query.get("q") || "" });
357
+ },
358
+ });
359
+
360
+ const response = await testFragment.handler(route, {
361
+ query: { q: "test" },
362
+ });
363
+
364
+ expect(response.type).toBe("json");
365
+ if (response.type === "json") {
366
+ expect(response.data).toEqual({ query: "test" });
367
+ }
368
+ });
369
+
370
+ it("should handle request body", async () => {
371
+ const fragment = defineFragment<{}>("test");
372
+ const testFragment = createFragmentForTest(fragment, {
373
+ config: {},
374
+ });
375
+
376
+ const route = defineRoute({
377
+ method: "POST",
378
+ path: "/users",
379
+ inputSchema: z.object({ name: z.string(), email: z.string() }),
380
+ outputSchema: z.object({ id: z.number(), name: z.string(), email: z.string() }),
381
+ handler: async ({ input }, { json }) => {
382
+ if (input) {
383
+ const data = await input.valid();
384
+ return json({ id: 1, name: data.name, email: data.email });
385
+ }
386
+ return json({ id: 1, name: "", email: "" });
387
+ },
388
+ });
389
+
390
+ const response = await testFragment.handler(route, {
391
+ body: { name: "John", email: "john@example.com" },
392
+ });
393
+
394
+ expect(response.type).toBe("json");
395
+ if (response.type === "json") {
396
+ expect(response.data).toEqual({ id: 1, name: "John", email: "john@example.com" });
397
+ }
398
+ });
399
+
400
+ it("should handle custom headers", async () => {
401
+ const fragment = defineFragment<{}>("test");
402
+ const testFragment = createFragmentForTest(fragment, {
403
+ config: {},
404
+ });
405
+
406
+ const route = defineRoute({
407
+ method: "GET",
408
+ path: "/test",
409
+ outputSchema: z.object({ authHeader: z.string() }),
410
+ handler: async ({ headers }, { json }) => {
411
+ return json({ authHeader: headers.get("authorization") || "" });
412
+ },
413
+ });
414
+
415
+ const response = await testFragment.handler(route, {
416
+ headers: { authorization: "Bearer token" },
417
+ });
418
+
419
+ expect(response.type).toBe("json");
420
+ if (response.type === "json") {
421
+ expect(response.data).toEqual({ authHeader: "Bearer token" });
422
+ }
423
+ });
424
+
425
+ it("should properly type path params", async () => {
426
+ const fragment = defineFragment<{}>("test");
427
+ const testFragment = createFragmentForTest(fragment, {
428
+ config: {},
429
+ });
430
+
431
+ const route = defineRoute({
432
+ method: "GET",
433
+ path: "/orgs/:orgId/users/:userId",
434
+ outputSchema: z.object({ orgId: z.string(), userId: z.string() }),
435
+ handler: async ({ pathParams }, { json }) => {
436
+ return json({ orgId: pathParams.orgId, userId: pathParams.userId });
437
+ },
438
+ });
439
+
440
+ const response = await testFragment.handler(route, {
441
+ pathParams: { orgId: "123", userId: "456" },
442
+ });
443
+
444
+ expect(response.type).toBe("json");
445
+ if (response.type === "json") {
446
+ expect(response.data).toEqual({ orgId: "123", userId: "456" });
447
+ }
448
+ });
449
+ });