@fragno-dev/core 0.1.7 → 0.1.9

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 (183) hide show
  1. package/.turbo/turbo-build.log +131 -64
  2. package/CHANGELOG.md +19 -0
  3. package/dist/api/api.d.ts +38 -2
  4. package/dist/api/api.d.ts.map +1 -0
  5. package/dist/api/api.js +9 -2
  6. package/dist/api/api.js.map +1 -0
  7. package/dist/api/bind-services.d.ts +6 -0
  8. package/dist/api/bind-services.d.ts.map +1 -0
  9. package/dist/api/bind-services.js +20 -0
  10. package/dist/api/bind-services.js.map +1 -0
  11. package/dist/api/error.d.ts +26 -0
  12. package/dist/api/error.d.ts.map +1 -0
  13. package/dist/{api-DngJDcmO.js → api/error.js} +2 -8
  14. package/dist/api/error.js.map +1 -0
  15. package/dist/api/fragment-definition-builder.d.ts +313 -0
  16. package/dist/api/fragment-definition-builder.d.ts.map +1 -0
  17. package/dist/api/fragment-definition-builder.js +326 -0
  18. package/dist/api/fragment-definition-builder.js.map +1 -0
  19. package/dist/api/fragment-instantiator.d.ts +216 -0
  20. package/dist/api/fragment-instantiator.d.ts.map +1 -0
  21. package/dist/api/fragment-instantiator.js +487 -0
  22. package/dist/api/fragment-instantiator.js.map +1 -0
  23. package/dist/api/fragno-response.d.ts +30 -0
  24. package/dist/api/fragno-response.d.ts.map +1 -0
  25. package/dist/api/fragno-response.js +73 -0
  26. package/dist/api/fragno-response.js.map +1 -0
  27. package/dist/api/internal/path.d.ts +50 -0
  28. package/dist/api/internal/path.d.ts.map +1 -0
  29. package/dist/api/internal/path.js +76 -0
  30. package/dist/api/internal/path.js.map +1 -0
  31. package/dist/api/internal/response-stream.d.ts +43 -0
  32. package/dist/api/internal/response-stream.d.ts.map +1 -0
  33. package/dist/api/internal/response-stream.js +81 -0
  34. package/dist/api/internal/response-stream.js.map +1 -0
  35. package/dist/api/internal/route.js +10 -0
  36. package/dist/api/internal/route.js.map +1 -0
  37. package/dist/api/mutable-request-state.d.ts +82 -0
  38. package/dist/api/mutable-request-state.d.ts.map +1 -0
  39. package/dist/api/mutable-request-state.js +97 -0
  40. package/dist/api/mutable-request-state.js.map +1 -0
  41. package/dist/api/request-context-storage.d.ts +42 -0
  42. package/dist/api/request-context-storage.d.ts.map +1 -0
  43. package/dist/api/request-context-storage.js +43 -0
  44. package/dist/api/request-context-storage.js.map +1 -0
  45. package/dist/api/request-input-context.d.ts +89 -0
  46. package/dist/api/request-input-context.d.ts.map +1 -0
  47. package/dist/api/request-input-context.js +118 -0
  48. package/dist/api/request-input-context.js.map +1 -0
  49. package/dist/api/request-middleware.d.ts +50 -0
  50. package/dist/api/request-middleware.d.ts.map +1 -0
  51. package/dist/api/request-middleware.js +83 -0
  52. package/dist/api/request-middleware.js.map +1 -0
  53. package/dist/api/request-output-context.d.ts +41 -0
  54. package/dist/api/request-output-context.d.ts.map +1 -0
  55. package/dist/api/request-output-context.js +119 -0
  56. package/dist/api/request-output-context.js.map +1 -0
  57. package/dist/api/route-handler-input-options.d.ts +21 -0
  58. package/dist/api/route-handler-input-options.d.ts.map +1 -0
  59. package/dist/api/route.d.ts +54 -3
  60. package/dist/api/route.d.ts.map +1 -0
  61. package/dist/api/route.js +29 -2
  62. package/dist/api/route.js.map +1 -0
  63. package/dist/api/shared-types.d.ts +47 -0
  64. package/dist/api/shared-types.d.ts.map +1 -0
  65. package/dist/api/shared-types.js +1 -0
  66. package/dist/client/client-error.d.ts +60 -0
  67. package/dist/client/client-error.d.ts.map +1 -0
  68. package/dist/client/client-error.js +92 -0
  69. package/dist/client/client-error.js.map +1 -0
  70. package/dist/client/client.d.ts +210 -4
  71. package/dist/client/client.d.ts.map +1 -0
  72. package/dist/client/client.js +397 -6
  73. package/dist/client/client.js.map +1 -0
  74. package/dist/client/client.svelte.d.ts +5 -3
  75. package/dist/client/client.svelte.d.ts.map +1 -1
  76. package/dist/client/client.svelte.js +1 -5
  77. package/dist/client/client.svelte.js.map +1 -1
  78. package/dist/client/internal/fetcher-merge.js +36 -0
  79. package/dist/client/internal/fetcher-merge.js.map +1 -0
  80. package/dist/client/internal/ndjson-streaming.js +139 -0
  81. package/dist/client/internal/ndjson-streaming.js.map +1 -0
  82. package/dist/client/react.d.ts +5 -3
  83. package/dist/client/react.d.ts.map +1 -1
  84. package/dist/client/react.js +3 -5
  85. package/dist/client/react.js.map +1 -1
  86. package/dist/client/solid.d.ts +5 -3
  87. package/dist/client/solid.d.ts.map +1 -1
  88. package/dist/client/solid.js +2 -5
  89. package/dist/client/solid.js.map +1 -1
  90. package/dist/client/vanilla.d.ts +5 -3
  91. package/dist/client/vanilla.d.ts.map +1 -1
  92. package/dist/client/vanilla.js +2 -43
  93. package/dist/client/vanilla.js.map +1 -1
  94. package/dist/client/vue.d.ts +5 -3
  95. package/dist/client/vue.d.ts.map +1 -1
  96. package/dist/client/vue.js +1 -5
  97. package/dist/client/vue.js.map +1 -1
  98. package/dist/http/http-status.d.ts +26 -0
  99. package/dist/http/http-status.d.ts.map +1 -0
  100. package/dist/integrations/react-ssr.js +1 -1
  101. package/dist/internal/symbols.d.ts +9 -0
  102. package/dist/internal/symbols.d.ts.map +1 -0
  103. package/dist/internal/symbols.js +10 -0
  104. package/dist/internal/symbols.js.map +1 -0
  105. package/dist/mod-client.d.ts +36 -0
  106. package/dist/mod-client.d.ts.map +1 -0
  107. package/dist/mod-client.js +21 -0
  108. package/dist/mod-client.js.map +1 -0
  109. package/dist/mod.d.ts +7 -4
  110. package/dist/mod.js +4 -6
  111. package/dist/request/request.d.ts +4 -0
  112. package/dist/request/request.js +5 -0
  113. package/dist/test/test.d.ts +62 -35
  114. package/dist/test/test.d.ts.map +1 -1
  115. package/dist/test/test.js +75 -40
  116. package/dist/test/test.js.map +1 -1
  117. package/dist/util/async.js +40 -0
  118. package/dist/util/async.js.map +1 -0
  119. package/dist/util/content-type.js +49 -0
  120. package/dist/util/content-type.js.map +1 -0
  121. package/dist/util/nanostores.js +31 -0
  122. package/dist/util/nanostores.js.map +1 -0
  123. package/dist/{ssr-BByDVfFD.js → util/ssr.js} +2 -2
  124. package/dist/util/ssr.js.map +1 -0
  125. package/dist/util/types-util.d.ts +8 -0
  126. package/dist/util/types-util.d.ts.map +1 -0
  127. package/package.json +19 -12
  128. package/src/api/api.ts +41 -6
  129. package/src/api/bind-services.ts +42 -0
  130. package/src/api/fragment-definition-builder.extend.test.ts +810 -0
  131. package/src/api/fragment-definition-builder.test.ts +499 -0
  132. package/src/api/fragment-definition-builder.ts +1088 -0
  133. package/src/api/fragment-instantiator.test.ts +1488 -0
  134. package/src/api/fragment-instantiator.ts +1053 -0
  135. package/src/api/fragment-services.test.ts +727 -0
  136. package/src/api/request-context-storage.ts +64 -0
  137. package/src/api/request-middleware.test.ts +301 -225
  138. package/src/api/route.test.ts +87 -1
  139. package/src/api/route.ts +345 -24
  140. package/src/api/shared-types.ts +43 -0
  141. package/src/client/client-builder.test.ts +23 -23
  142. package/src/client/client.ssr.test.ts +3 -3
  143. package/src/client/client.svelte.test.ts +15 -15
  144. package/src/client/client.test.ts +22 -22
  145. package/src/client/client.ts +72 -12
  146. package/src/client/internal/fetcher-merge.ts +1 -1
  147. package/src/client/react.test.ts +2 -2
  148. package/src/client/solid.test.ts +2 -2
  149. package/src/client/vanilla.test.ts +2 -2
  150. package/src/client/vue.test.ts +2 -2
  151. package/src/internal/symbols.ts +5 -0
  152. package/src/mod-client.ts +59 -0
  153. package/src/mod.ts +26 -9
  154. package/src/request/request.ts +8 -0
  155. package/src/test/test.test.ts +200 -381
  156. package/src/test/test.ts +190 -117
  157. package/tsdown.config.ts +8 -5
  158. package/dist/api/fragment-builder.d.ts +0 -4
  159. package/dist/api/fragment-builder.js +0 -3
  160. package/dist/api/fragment-instantiation.d.ts +0 -4
  161. package/dist/api/fragment-instantiation.js +0 -6
  162. package/dist/api-BWN97TOr.d.ts +0 -377
  163. package/dist/api-BWN97TOr.d.ts.map +0 -1
  164. package/dist/api-DngJDcmO.js.map +0 -1
  165. package/dist/client-C5LsYHEI.js +0 -782
  166. package/dist/client-C5LsYHEI.js.map +0 -1
  167. package/dist/fragment-builder-DOnCVBqc.js +0 -47
  168. package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
  169. package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
  170. package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
  171. package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
  172. package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
  173. package/dist/request-output-context-CdIjwmEN.js +0 -320
  174. package/dist/request-output-context-CdIjwmEN.js.map +0 -1
  175. package/dist/route-Bl9Zr1Yv.d.ts +0 -26
  176. package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
  177. package/dist/route-C5Uryylh.js +0 -21
  178. package/dist/route-C5Uryylh.js.map +0 -1
  179. package/dist/ssr-BByDVfFD.js.map +0 -1
  180. package/src/api/fragment-builder.ts +0 -80
  181. package/src/api/fragment-instantiation.test.ts +0 -460
  182. package/src/api/fragment-instantiation.ts +0 -499
  183. package/src/api/fragment.test.ts +0 -537
@@ -1,6 +1,6 @@
1
1
  import { test, expect, describe, expectTypeOf } from "vitest";
2
- import { defineFragment } from "./fragment-builder";
3
- import { createFragment } from "./fragment-instantiation";
2
+ import { defineFragment } from "./fragment-definition-builder";
3
+ import { instantiate } from "./fragment-instantiator";
4
4
  import { defineRoute } from "./route";
5
5
  import { z } from "zod";
6
6
  import { FragnoApiValidationError } from "./error";
@@ -9,9 +9,11 @@ describe("Request Middleware", () => {
9
9
  test("middleware can intercept and return early", async () => {
10
10
  const config = { apiKey: "test" };
11
11
 
12
- const fragment = defineFragment<typeof config>("test-lib").withServices(() => ({
13
- auth: { isAuthorized: (token?: string) => token === "valid-token" },
14
- }));
12
+ const fragment = defineFragment<typeof config>("test-lib")
13
+ .providesBaseService(() => ({
14
+ auth: { isAuthorized: (token?: string) => token === "valid-token" },
15
+ }))
16
+ .build();
15
17
 
16
18
  const routes = [
17
19
  defineRoute({
@@ -23,9 +25,11 @@ describe("Request Middleware", () => {
23
25
  }),
24
26
  ] as const;
25
27
 
26
- const instance = createFragment(fragment, config, routes, {
27
- mountRoute: "/api",
28
- });
28
+ const instance = instantiate(fragment)
29
+ .withConfig(config)
30
+ .withRoutes(routes)
31
+ .withOptions({ mountRoute: "/api" })
32
+ .build();
29
33
 
30
34
  // Add middleware that checks authorization
31
35
  const withAuth = instance.withMiddleware(async ({ queryParams }, { services, error }) => {
@@ -65,7 +69,7 @@ describe("Request Middleware", () => {
65
69
  test("ifMatchesRoute - middleware has access to matched route information", async () => {
66
70
  const config = {};
67
71
 
68
- const fragment = defineFragment<typeof config>("test-lib");
72
+ const fragment = defineFragment<typeof config>("test-lib").build();
69
73
 
70
74
  const routes = [
71
75
  defineRoute({
@@ -94,35 +98,38 @@ describe("Request Middleware", () => {
94
98
  }),
95
99
  ] as const;
96
100
 
97
- const instance = createFragment(fragment, config, routes, {
98
- mountRoute: "/api",
99
- }).withMiddleware(async ({ ifMatchesRoute }) => {
100
- const result = await ifMatchesRoute(
101
- "POST",
102
- "/users/:id",
103
- async ({ path, pathParams, input }, { error }) => {
104
- expectTypeOf(path).toEqualTypeOf<"/users/:id">();
105
- expectTypeOf(pathParams).toEqualTypeOf<{ id: string }>();
106
-
107
- expectTypeOf(input.schema).toEqualTypeOf<z.ZodObject<{ name: z.ZodString }>>();
108
-
109
- return error(
110
- {
111
- message: "Creating users has been disabled.",
112
- code: "CREATE_USERS_DISABLED",
113
- },
114
- 403,
115
- );
116
- },
117
- );
101
+ const instance = instantiate(fragment)
102
+ .withConfig(config)
103
+ .withRoutes(routes)
104
+ .withOptions({ mountRoute: "/api" })
105
+ .build()
106
+ .withMiddleware(async ({ ifMatchesRoute }) => {
107
+ const result = await ifMatchesRoute(
108
+ "POST",
109
+ "/users/:id",
110
+ async ({ path, pathParams, input }, { error }) => {
111
+ expectTypeOf(path).toEqualTypeOf<"/users/:id">();
112
+ expectTypeOf(pathParams).toEqualTypeOf<{ id: string }>();
113
+
114
+ expectTypeOf(input.schema).toEqualTypeOf<z.ZodObject<{ name: z.ZodString }>>();
115
+
116
+ return error(
117
+ {
118
+ message: "Creating users has been disabled.",
119
+ code: "CREATE_USERS_DISABLED",
120
+ },
121
+ 403,
122
+ );
123
+ },
124
+ );
118
125
 
119
- if (result) {
120
- return result;
121
- }
126
+ if (result) {
127
+ return result;
128
+ }
122
129
 
123
- // This was a request to any other route
124
- return undefined;
125
- });
130
+ // This was a request to any other route
131
+ return undefined;
132
+ });
126
133
 
127
134
  // Request to POST, should be disabled.
128
135
  const req = new Request("http://localhost/api/users/123", {
@@ -154,7 +161,7 @@ describe("Request Middleware", () => {
154
161
  test("ifMatchesRoute - not called for other routes", async () => {
155
162
  const config = {};
156
163
 
157
- const fragment = defineFragment<typeof config>("test-lib");
164
+ const fragment = defineFragment<typeof config>("test-lib").build();
158
165
 
159
166
  const routes = [
160
167
  defineRoute({
@@ -185,13 +192,18 @@ describe("Request Middleware", () => {
185
192
 
186
193
  let middlewareCalled = false;
187
194
 
188
- const instance = createFragment(fragment, config, routes, {
189
- mountRoute: "/api",
190
- }).withMiddleware(async ({ ifMatchesRoute }) => {
191
- return ifMatchesRoute("GET", "/users", () => {
192
- middlewareCalled = true;
195
+ const instance = instantiate(fragment)
196
+ .withConfig(config)
197
+ .withRoutes(routes)
198
+ .withOptions({
199
+ mountRoute: "/api",
200
+ })
201
+ .build()
202
+ .withMiddleware(async ({ ifMatchesRoute }) => {
203
+ return ifMatchesRoute("GET", "/users", () => {
204
+ middlewareCalled = true;
205
+ });
193
206
  });
194
- });
195
207
 
196
208
  // Request to POST, should be disabled.
197
209
  const req = new Request("http://localhost/api/users/123", {
@@ -214,7 +226,7 @@ describe("Request Middleware", () => {
214
226
  test("ifMatchesRoute - can return undefined", async () => {
215
227
  const config = {};
216
228
 
217
- const fragment = defineFragment<typeof config>("test-lib");
229
+ const fragment = defineFragment<typeof config>("test-lib").build();
218
230
 
219
231
  const routes = [
220
232
  defineRoute({
@@ -231,22 +243,27 @@ describe("Request Middleware", () => {
231
243
 
232
244
  let middlewareCalled = false;
233
245
 
234
- const instance = createFragment(fragment, config, routes, {
235
- mountRoute: "/api",
236
- }).withMiddleware(async ({ ifMatchesRoute }) => {
237
- await ifMatchesRoute("GET", "/users", async ({ path, pathParams, input }) => {
238
- expectTypeOf(path).toEqualTypeOf<"/users">();
239
- expectTypeOf(pathParams).toEqualTypeOf<{ [x: string]: never }>();
240
- expectTypeOf(input).toEqualTypeOf<undefined>();
241
-
242
- middlewareCalled = true;
246
+ const instance = instantiate(fragment)
247
+ .withConfig(config)
248
+ .withRoutes(routes)
249
+ .withOptions({
250
+ mountRoute: "/api",
251
+ })
252
+ .build()
253
+ .withMiddleware(async ({ ifMatchesRoute }) => {
254
+ await ifMatchesRoute("GET", "/users", async ({ path, pathParams, input }) => {
255
+ expectTypeOf(path).toEqualTypeOf<"/users">();
256
+ expectTypeOf(pathParams).toEqualTypeOf<{ [x: string]: never }>();
257
+ expectTypeOf(input).toEqualTypeOf<undefined>();
258
+
259
+ middlewareCalled = true;
260
+
261
+ return undefined;
262
+ });
243
263
 
244
264
  return undefined;
245
265
  });
246
266
 
247
- return undefined;
248
- });
249
-
250
267
  const getReq = new Request("http://localhost/api/users", {
251
268
  method: "GET",
252
269
  });
@@ -262,7 +279,7 @@ describe("Request Middleware", () => {
262
279
  test("only one middleware is supported", async () => {
263
280
  const config = {};
264
281
 
265
- const fragment = defineFragment<typeof config>("test-lib");
282
+ const fragment = defineFragment<typeof config>("test-lib").build();
266
283
 
267
284
  const routes = [
268
285
  defineRoute({
@@ -274,7 +291,11 @@ describe("Request Middleware", () => {
274
291
  }),
275
292
  ] as const;
276
293
 
277
- const instance = createFragment(fragment, config, routes, {});
294
+ const instance = instantiate(fragment)
295
+ .withConfig(config)
296
+ .withRoutes(routes)
297
+ .withOptions({})
298
+ .build();
278
299
 
279
300
  const withMiddleware = instance.withMiddleware(async () => {
280
301
  return undefined;
@@ -291,7 +312,7 @@ describe("Request Middleware", () => {
291
312
  test("middleware and handler can both consume request body without double consumption", async () => {
292
313
  const config = {};
293
314
 
294
- const fragment = defineFragment<typeof config>("test-lib");
315
+ const fragment = defineFragment<typeof config>("test-lib").build();
295
316
 
296
317
  const routes = [
297
318
  defineRoute({
@@ -309,19 +330,24 @@ describe("Request Middleware", () => {
309
330
  }),
310
331
  ] as const;
311
332
 
312
- const instance = createFragment(fragment, config, routes, {
313
- mountRoute: "/api",
314
- }).withMiddleware(async ({ ifMatchesRoute }) => {
315
- // Middleware consumes the request body
316
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
317
- const body = await input.valid();
318
- // Middleware can read the body
319
- expect(body).toEqual({ name: "John Doe" });
320
- return undefined; // Continue to handler
321
- });
333
+ const instance = instantiate(fragment)
334
+ .withConfig(config)
335
+ .withRoutes(routes)
336
+ .withOptions({
337
+ mountRoute: "/api",
338
+ })
339
+ .build()
340
+ .withMiddleware(async ({ ifMatchesRoute }) => {
341
+ // Middleware consumes the request body
342
+ const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
343
+ const body = await input.valid();
344
+ // Middleware can read the body
345
+ expect(body).toEqual({ name: "John Doe" });
346
+ return undefined; // Continue to handler
347
+ });
322
348
 
323
- return result;
324
- });
349
+ return result;
350
+ });
325
351
 
326
352
  const req = new Request("http://localhost/api/users", {
327
353
  method: "POST",
@@ -338,7 +364,7 @@ describe("Request Middleware", () => {
338
364
  });
339
365
 
340
366
  test("middleware can modify path parameters", async () => {
341
- const fragment = defineFragment("test-lib");
367
+ const fragment = defineFragment("test-lib").build();
342
368
 
343
369
  const routes = [
344
370
  defineRoute({
@@ -355,16 +381,21 @@ describe("Request Middleware", () => {
355
381
  }),
356
382
  ] as const;
357
383
 
358
- const instance = createFragment(fragment, {}, routes, {
359
- mountRoute: "/api",
360
- }).withMiddleware(async ({ ifMatchesRoute }) => {
361
- // Middleware can read path and query parameters
362
- const result = await ifMatchesRoute("GET", "/users/:id", async ({ pathParams }) => {
363
- pathParams.id = "9999";
364
- });
384
+ const instance = instantiate(fragment)
385
+ .withConfig({})
386
+ .withRoutes(routes)
387
+ .withOptions({
388
+ mountRoute: "/api",
389
+ })
390
+ .build()
391
+ .withMiddleware(async ({ ifMatchesRoute }) => {
392
+ // Middleware can read path and query parameters
393
+ const result = await ifMatchesRoute("GET", "/users/:id", async ({ pathParams }) => {
394
+ pathParams.id = "9999";
395
+ });
365
396
 
366
- return result;
367
- });
397
+ return result;
398
+ });
368
399
 
369
400
  // Test with admin role
370
401
  const adminReq = new Request("http://localhost/api/users/123?role=admin", {
@@ -382,7 +413,7 @@ describe("Request Middleware", () => {
382
413
  test("middleware calling input.valid() can catch validation error", async () => {
383
414
  const config = {};
384
415
 
385
- const fragment = defineFragment<typeof config>("test-lib");
416
+ const fragment = defineFragment<typeof config>("test-lib").build();
386
417
 
387
418
  const routes = [
388
419
  defineRoute({
@@ -404,30 +435,35 @@ describe("Request Middleware", () => {
404
435
  }),
405
436
  ] as const;
406
437
 
407
- const instance = createFragment(fragment, config, routes, {
408
- mountRoute: "/api",
409
- }).withMiddleware(async ({ ifMatchesRoute }) => {
410
- // Middleware tries to validate the input
411
- const result = await ifMatchesRoute("POST", "/users", async ({ input }, { error }) => {
412
- try {
413
- await input.valid();
414
- return undefined; // Continue to handler if valid
415
- } catch (validationError) {
416
- expect(validationError).toBeInstanceOf(FragnoApiValidationError);
438
+ const instance = instantiate(fragment)
439
+ .withConfig(config)
440
+ .withRoutes(routes)
441
+ .withOptions({
442
+ mountRoute: "/api",
443
+ })
444
+ .build()
445
+ .withMiddleware(async ({ ifMatchesRoute }) => {
446
+ // Middleware tries to validate the input
447
+ const result = await ifMatchesRoute("POST", "/users", async ({ input }, { error }) => {
448
+ try {
449
+ await input.valid();
450
+ return undefined; // Continue to handler if valid
451
+ } catch (validationError) {
452
+ expect(validationError).toBeInstanceOf(FragnoApiValidationError);
453
+
454
+ return error(
455
+ {
456
+ message: "Request validation failed in middleware",
457
+ code: "MIDDLEWARE_VALIDATION_ERROR",
458
+ },
459
+ 400,
460
+ );
461
+ }
462
+ });
417
463
 
418
- return error(
419
- {
420
- message: "Request validation failed in middleware",
421
- code: "MIDDLEWARE_VALIDATION_ERROR",
422
- },
423
- 400,
424
- );
425
- }
464
+ return result;
426
465
  });
427
466
 
428
- return result;
429
- });
430
-
431
467
  // Test with invalid request (missing required fields)
432
468
  const invalidReq = new Request("http://localhost/api/users", {
433
469
  method: "POST",
@@ -448,7 +484,7 @@ describe("Request Middleware", () => {
448
484
  test("middleware calling input.valid() can ignore validation error", async () => {
449
485
  const config = {};
450
486
 
451
- const fragment = defineFragment<typeof config>("test-lib");
487
+ const fragment = defineFragment<typeof config>("test-lib").build();
452
488
 
453
489
  const routes = [
454
490
  defineRoute({
@@ -471,16 +507,21 @@ describe("Request Middleware", () => {
471
507
  }),
472
508
  ] as const;
473
509
 
474
- const instance = createFragment(fragment, config, routes, {
475
- mountRoute: "/api",
476
- }).withMiddleware(async ({ ifMatchesRoute }) => {
477
- // Middleware tries to validate the input
478
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
479
- await input.valid();
480
- });
510
+ const instance = instantiate(fragment)
511
+ .withConfig(config)
512
+ .withRoutes(routes)
513
+ .withOptions({
514
+ mountRoute: "/api",
515
+ })
516
+ .build()
517
+ .withMiddleware(async ({ ifMatchesRoute }) => {
518
+ // Middleware tries to validate the input
519
+ const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
520
+ await input.valid();
521
+ });
481
522
 
482
- return result;
483
- });
523
+ return result;
524
+ });
484
525
 
485
526
  // Test with invalid request (missing required fields)
486
527
  const invalidReq = new Request("http://localhost/api/users", {
@@ -501,7 +542,7 @@ describe("Request Middleware", () => {
501
542
  });
502
543
 
503
544
  test("middleware can modify query parameters", async () => {
504
- const fragment = defineFragment("test-lib");
545
+ const fragment = defineFragment("test-lib").build();
505
546
 
506
547
  const routes = [
507
548
  defineRoute({
@@ -518,16 +559,21 @@ describe("Request Middleware", () => {
518
559
  }),
519
560
  ] as const;
520
561
 
521
- const instance = createFragment(fragment, {}, routes, {
522
- mountRoute: "/api",
523
- }).withMiddleware(async ({ ifMatchesRoute }) => {
524
- // Middleware can read path and query parameters
525
- const result = await ifMatchesRoute("GET", "/users/:id", async ({ query }) => {
526
- query.set("role", "some-other-role-defined-in-middleware");
527
- });
562
+ const instance = instantiate(fragment)
563
+ .withConfig({})
564
+ .withRoutes(routes)
565
+ .withOptions({
566
+ mountRoute: "/api",
567
+ })
568
+ .build()
569
+ .withMiddleware(async ({ ifMatchesRoute }) => {
570
+ // Middleware can read path and query parameters
571
+ const result = await ifMatchesRoute("GET", "/users/:id", async ({ query }) => {
572
+ query.set("role", "some-other-role-defined-in-middleware");
573
+ });
528
574
 
529
- return result;
530
- });
575
+ return result;
576
+ });
531
577
 
532
578
  // Test with admin role
533
579
  const adminReq = new Request("http://localhost/api/users/123?role=admin", {
@@ -543,7 +589,7 @@ describe("Request Middleware", () => {
543
589
  });
544
590
 
545
591
  test("middleware can modify request body", async () => {
546
- const fragment = defineFragment("test-lib");
592
+ const fragment = defineFragment("test-lib").build();
547
593
 
548
594
  const routes = [
549
595
  defineRoute({
@@ -561,21 +607,26 @@ describe("Request Middleware", () => {
561
607
  }),
562
608
  ] as const;
563
609
 
564
- const instance = createFragment(fragment, {}, routes, {
565
- mountRoute: "/api",
566
- }).withMiddleware(async ({ ifMatchesRoute, requestState }) => {
567
- // Middleware modifies the request body
568
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
569
- const body = await input.valid();
570
- // Modify the body by adding a role field
571
- requestState.setBody({
572
- ...body,
573
- role: "admin-from-middleware",
610
+ const instance = instantiate(fragment)
611
+ .withConfig({})
612
+ .withRoutes(routes)
613
+ .withOptions({
614
+ mountRoute: "/api",
615
+ })
616
+ .build()
617
+ .withMiddleware(async ({ ifMatchesRoute, requestState }) => {
618
+ // Middleware modifies the request body
619
+ const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
620
+ const body = await input.valid();
621
+ // Modify the body by adding a role field
622
+ requestState.setBody({
623
+ ...body,
624
+ role: "admin-from-middleware",
625
+ });
574
626
  });
575
- });
576
627
 
577
- return result;
578
- });
628
+ return result;
629
+ });
579
630
 
580
631
  const req = new Request("http://localhost/api/users", {
581
632
  method: "POST",
@@ -592,7 +643,7 @@ describe("Request Middleware", () => {
592
643
  });
593
644
 
594
645
  test("middleware can modify request headers", async () => {
595
- const fragment = defineFragment("test-lib");
646
+ const fragment = defineFragment("test-lib").build();
596
647
 
597
648
  const routes = [
598
649
  defineRoute({
@@ -608,14 +659,19 @@ describe("Request Middleware", () => {
608
659
  }),
609
660
  ] as const;
610
661
 
611
- const instance = createFragment(fragment, {}, routes, {
612
- mountRoute: "/api",
613
- }).withMiddleware(async ({ headers }) => {
614
- // Middleware modifies headers
615
- headers.set("Authorization", "Bearer middleware-token");
616
- headers.set("X-Custom-Header", "middleware-value");
617
- return undefined;
618
- });
662
+ const instance = instantiate(fragment)
663
+ .withConfig({})
664
+ .withRoutes(routes)
665
+ .withOptions({
666
+ mountRoute: "/api",
667
+ })
668
+ .build()
669
+ .withMiddleware(async ({ headers }) => {
670
+ // Middleware modifies headers
671
+ headers.set("Authorization", "Bearer middleware-token");
672
+ headers.set("X-Custom-Header", "middleware-value");
673
+ return undefined;
674
+ });
619
675
 
620
676
  const req = new Request("http://localhost/api/data", {
621
677
  method: "GET",
@@ -630,7 +686,7 @@ describe("Request Middleware", () => {
630
686
  });
631
687
 
632
688
  test("ifMatchesRoute properly awaits async handlers", async () => {
633
- const fragment = defineFragment("test-lib");
689
+ const fragment = defineFragment("test-lib").build();
634
690
 
635
691
  const routes = [
636
692
  defineRoute({
@@ -651,33 +707,38 @@ describe("Request Middleware", () => {
651
707
 
652
708
  let asyncOperationCompleted = false;
653
709
 
654
- const instance = createFragment(fragment, {}, routes, {
655
- mountRoute: "/api",
656
- }).withMiddleware(async ({ ifMatchesRoute }) => {
657
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
658
- // Simulate async operation (e.g., database lookup)
659
- await new Promise((resolve) => setTimeout(resolve, 10));
660
- const body = await input.valid();
661
-
662
- // Verify the async operation completed
663
- asyncOperationCompleted = true;
664
-
665
- // Check if user exists
666
- if (body.name === "existing-user") {
667
- return new Response(
668
- JSON.stringify({
669
- message: "User already exists",
670
- code: "USER_EXISTS",
671
- }),
672
- { status: 409, headers: { "Content-Type": "application/json" } },
673
- );
674
- }
710
+ const instance = instantiate(fragment)
711
+ .withConfig({})
712
+ .withRoutes(routes)
713
+ .withOptions({
714
+ mountRoute: "/api",
715
+ })
716
+ .build()
717
+ .withMiddleware(async ({ ifMatchesRoute }) => {
718
+ const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
719
+ // Simulate async operation (e.g., database lookup)
720
+ await new Promise((resolve) => setTimeout(resolve, 10));
721
+ const body = await input.valid();
675
722
 
676
- return undefined;
677
- });
723
+ // Verify the async operation completed
724
+ asyncOperationCompleted = true;
725
+
726
+ // Check if user exists
727
+ if (body.name === "existing-user") {
728
+ return new Response(
729
+ JSON.stringify({
730
+ message: "User already exists",
731
+ code: "USER_EXISTS",
732
+ }),
733
+ { status: 409, headers: { "Content-Type": "application/json" } },
734
+ );
735
+ }
736
+
737
+ return undefined;
738
+ });
678
739
 
679
- return result;
680
- });
740
+ return result;
741
+ });
681
742
 
682
743
  // Test with existing user
683
744
  const existingUserReq = new Request("http://localhost/api/users", {
@@ -715,7 +776,7 @@ describe("Request Middleware", () => {
715
776
  });
716
777
 
717
778
  test("ifMatchesRoute handles async errors properly", async () => {
718
- const fragment = defineFragment("test-lib");
779
+ const fragment = defineFragment("test-lib").build();
719
780
 
720
781
  const routes = [
721
782
  defineRoute({
@@ -728,25 +789,30 @@ describe("Request Middleware", () => {
728
789
  }),
729
790
  ] as const;
730
791
 
731
- const instance = createFragment(fragment, {}, routes, {
732
- mountRoute: "/api",
733
- }).withMiddleware(async ({ ifMatchesRoute }) => {
734
- const result = await ifMatchesRoute("GET", "/data", async (_, { error }) => {
735
- // Simulate async operation that fails
736
- await new Promise((resolve) => setTimeout(resolve, 5));
737
-
738
- // Simulate an error condition (e.g., database unavailable)
739
- return error(
740
- {
741
- message: "Service temporarily unavailable",
742
- code: "SERVICE_UNAVAILABLE",
743
- },
744
- 503,
745
- );
746
- });
792
+ const instance = instantiate(fragment)
793
+ .withConfig({})
794
+ .withRoutes(routes)
795
+ .withOptions({
796
+ mountRoute: "/api",
797
+ })
798
+ .build()
799
+ .withMiddleware(async ({ ifMatchesRoute }) => {
800
+ const result = await ifMatchesRoute("GET", "/data", async (_, { error }) => {
801
+ // Simulate async operation that fails
802
+ await new Promise((resolve) => setTimeout(resolve, 5));
803
+
804
+ // Simulate an error condition (e.g., database unavailable)
805
+ return error(
806
+ {
807
+ message: "Service temporarily unavailable",
808
+ code: "SERVICE_UNAVAILABLE",
809
+ },
810
+ 503,
811
+ );
812
+ });
747
813
 
748
- return result;
749
- });
814
+ return result;
815
+ });
750
816
 
751
817
  const req = new Request("http://localhost/api/data", {
752
818
  method: "GET",
@@ -761,7 +827,7 @@ describe("Request Middleware", () => {
761
827
  });
762
828
 
763
829
  test("ifMatchesRoute with async body modification", async () => {
764
- const fragment = defineFragment("test-lib");
830
+ const fragment = defineFragment("test-lib").build();
765
831
 
766
832
  const routes = [
767
833
  defineRoute({
@@ -786,27 +852,32 @@ describe("Request Middleware", () => {
786
852
  }),
787
853
  ] as const;
788
854
 
789
- const instance = createFragment(fragment, {}, routes, {
790
- mountRoute: "/api",
791
- }).withMiddleware(async ({ ifMatchesRoute, requestState }) => {
792
- const result = await ifMatchesRoute("POST", "/posts", async ({ input }) => {
793
- // Simulate async operation to enrich the body (e.g., fetching user data)
794
- await new Promise((resolve) => setTimeout(resolve, 5));
855
+ const instance = instantiate(fragment)
856
+ .withConfig({})
857
+ .withRoutes(routes)
858
+ .withOptions({
859
+ mountRoute: "/api",
860
+ })
861
+ .build()
862
+ .withMiddleware(async ({ ifMatchesRoute, requestState }) => {
863
+ const result = await ifMatchesRoute("POST", "/posts", async ({ input }) => {
864
+ // Simulate async operation to enrich the body (e.g., fetching user data)
865
+ await new Promise((resolve) => setTimeout(resolve, 5));
795
866
 
796
- const body = await input.valid();
867
+ const body = await input.valid();
797
868
 
798
- // Enrich the body with additional data
799
- requestState.setBody({
800
- ...body,
801
- content: `${body.content}\n\n[Enhanced by middleware]`,
869
+ // Enrich the body with additional data
870
+ requestState.setBody({
871
+ ...body,
872
+ content: `${body.content}\n\n[Enhanced by middleware]`,
873
+ });
874
+
875
+ return undefined;
802
876
  });
803
877
 
804
- return undefined;
878
+ return result;
805
879
  });
806
880
 
807
- return result;
808
- });
809
-
810
881
  const req = new Request("http://localhost/api/posts", {
811
882
  method: "POST",
812
883
  headers: { "Content-Type": "application/json" },
@@ -829,7 +900,7 @@ describe("Request Middleware", () => {
829
900
  });
830
901
 
831
902
  test("multiple async operations in ifMatchesRoute complete in order", async () => {
832
- const fragment = defineFragment("test-lib");
903
+ const fragment = defineFragment("test-lib").build();
833
904
 
834
905
  const routes = [
835
906
  defineRoute({
@@ -844,27 +915,32 @@ describe("Request Middleware", () => {
844
915
 
845
916
  const executionOrder: string[] = [];
846
917
 
847
- const instance = createFragment(fragment, {}, routes, {
848
- mountRoute: "/api",
849
- }).withMiddleware(async ({ ifMatchesRoute }) => {
850
- executionOrder.push("start");
851
-
852
- const result = await ifMatchesRoute("GET", "/status", async () => {
853
- executionOrder.push("before-first-async");
854
- await new Promise((resolve) => setTimeout(resolve, 5));
855
- executionOrder.push("after-first-async");
918
+ const instance = instantiate(fragment)
919
+ .withConfig({})
920
+ .withRoutes(routes)
921
+ .withOptions({
922
+ mountRoute: "/api",
923
+ })
924
+ .build()
925
+ .withMiddleware(async ({ ifMatchesRoute }) => {
926
+ executionOrder.push("start");
927
+
928
+ const result = await ifMatchesRoute("GET", "/status", async () => {
929
+ executionOrder.push("before-first-async");
930
+ await new Promise((resolve) => setTimeout(resolve, 5));
931
+ executionOrder.push("after-first-async");
932
+
933
+ await new Promise((resolve) => setTimeout(resolve, 5));
934
+ executionOrder.push("after-second-async");
935
+
936
+ return undefined;
937
+ });
856
938
 
857
- await new Promise((resolve) => setTimeout(resolve, 5));
858
- executionOrder.push("after-second-async");
939
+ executionOrder.push("end");
859
940
 
860
- return undefined;
941
+ return result;
861
942
  });
862
943
 
863
- executionOrder.push("end");
864
-
865
- return result;
866
- });
867
-
868
944
  const req = new Request("http://localhost/api/status", {
869
945
  method: "GET",
870
946
  });