@fragno-dev/core 0.1.8 → 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 (176) hide show
  1. package/.turbo/turbo-build.log +131 -56
  2. package/CHANGELOG.md +13 -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 -3
  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/error.js +48 -0
  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 -2
  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 -2
  71. package/dist/client/client.d.ts.map +1 -0
  72. package/dist/client/client.js +397 -5
  73. package/dist/client/client.js.map +1 -0
  74. package/dist/client/client.svelte.d.ts +5 -2
  75. package/dist/client/client.svelte.d.ts.map +1 -1
  76. package/dist/client/client.svelte.js +1 -4
  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 -2
  83. package/dist/client/react.d.ts.map +1 -1
  84. package/dist/client/react.js +3 -4
  85. package/dist/client/react.js.map +1 -1
  86. package/dist/client/solid.d.ts +5 -2
  87. package/dist/client/solid.d.ts.map +1 -1
  88. package/dist/client/solid.js +2 -4
  89. package/dist/client/solid.js.map +1 -1
  90. package/dist/client/vanilla.d.ts +5 -2
  91. package/dist/client/vanilla.d.ts.map +1 -1
  92. package/dist/client/vanilla.js +2 -42
  93. package/dist/client/vanilla.js.map +1 -1
  94. package/dist/client/vue.d.ts +5 -2
  95. package/dist/client/vue.d.ts.map +1 -1
  96. package/dist/client/vue.js +1 -4
  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 -2
  110. package/dist/mod.js +4 -4
  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 -34
  114. package/dist/test/test.d.ts.map +1 -1
  115. package/dist/test/test.js +75 -42
  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-kyKI7pqH.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 +1 -5
  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 +454 -189
  136. package/src/api/request-context-storage.ts +64 -0
  137. package/src/api/request-middleware.test.ts +301 -228
  138. package/src/api/route.test.ts +12 -36
  139. package/src/api/route.ts +167 -155
  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 +22 -15
  154. package/src/request/request.ts +8 -0
  155. package/src/test/test.test.ts +189 -375
  156. package/src/test/test.ts +186 -152
  157. package/tsdown.config.ts +8 -5
  158. package/dist/api/fragment-builder.d.ts +0 -2
  159. package/dist/api/fragment-builder.js +0 -3
  160. package/dist/api/fragment-instantiation.d.ts +0 -2
  161. package/dist/api/fragment-instantiation.js +0 -4
  162. package/dist/api-BFrUCIsF.d.ts +0 -963
  163. package/dist/api-BFrUCIsF.d.ts.map +0 -1
  164. package/dist/client-DAFHcKqA.js +0 -782
  165. package/dist/client-DAFHcKqA.js.map +0 -1
  166. package/dist/fragment-builder-Boh2vNHq.js +0 -108
  167. package/dist/fragment-builder-Boh2vNHq.js.map +0 -1
  168. package/dist/fragment-instantiation-DUT-HLl1.js +0 -898
  169. package/dist/fragment-instantiation-DUT-HLl1.js.map +0 -1
  170. package/dist/route-C4CyNHkC.js +0 -26
  171. package/dist/route-C4CyNHkC.js.map +0 -1
  172. package/dist/ssr-kyKI7pqH.js.map +0 -1
  173. package/src/api/fragment-builder.ts +0 -518
  174. package/src/api/fragment-instantiation.test.ts +0 -702
  175. package/src/api/fragment-instantiation.ts +0 -766
  176. package/src/api/fragment.test.ts +0 -585
@@ -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,12 +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").providesService(
13
- ({ defineService }) =>
14
- defineService({
15
- auth: { isAuthorized: (token?: string) => token === "valid-token" },
16
- }),
17
- );
12
+ const fragment = defineFragment<typeof config>("test-lib")
13
+ .providesBaseService(() => ({
14
+ auth: { isAuthorized: (token?: string) => token === "valid-token" },
15
+ }))
16
+ .build();
18
17
 
19
18
  const routes = [
20
19
  defineRoute({
@@ -26,9 +25,11 @@ describe("Request Middleware", () => {
26
25
  }),
27
26
  ] as const;
28
27
 
29
- const instance = createFragment(fragment, config, routes, {
30
- mountRoute: "/api",
31
- });
28
+ const instance = instantiate(fragment)
29
+ .withConfig(config)
30
+ .withRoutes(routes)
31
+ .withOptions({ mountRoute: "/api" })
32
+ .build();
32
33
 
33
34
  // Add middleware that checks authorization
34
35
  const withAuth = instance.withMiddleware(async ({ queryParams }, { services, error }) => {
@@ -68,7 +69,7 @@ describe("Request Middleware", () => {
68
69
  test("ifMatchesRoute - middleware has access to matched route information", async () => {
69
70
  const config = {};
70
71
 
71
- const fragment = defineFragment<typeof config>("test-lib");
72
+ const fragment = defineFragment<typeof config>("test-lib").build();
72
73
 
73
74
  const routes = [
74
75
  defineRoute({
@@ -97,35 +98,38 @@ describe("Request Middleware", () => {
97
98
  }),
98
99
  ] as const;
99
100
 
100
- const instance = createFragment(fragment, config, routes, {
101
- mountRoute: "/api",
102
- }).withMiddleware(async ({ ifMatchesRoute }) => {
103
- const result = await ifMatchesRoute(
104
- "POST",
105
- "/users/:id",
106
- async ({ path, pathParams, input }, { error }) => {
107
- expectTypeOf(path).toEqualTypeOf<"/users/:id">();
108
- expectTypeOf(pathParams).toEqualTypeOf<{ id: string }>();
109
-
110
- expectTypeOf(input.schema).toEqualTypeOf<z.ZodObject<{ name: z.ZodString }>>();
111
-
112
- return error(
113
- {
114
- message: "Creating users has been disabled.",
115
- code: "CREATE_USERS_DISABLED",
116
- },
117
- 403,
118
- );
119
- },
120
- );
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
+ );
121
125
 
122
- if (result) {
123
- return result;
124
- }
126
+ if (result) {
127
+ return result;
128
+ }
125
129
 
126
- // This was a request to any other route
127
- return undefined;
128
- });
130
+ // This was a request to any other route
131
+ return undefined;
132
+ });
129
133
 
130
134
  // Request to POST, should be disabled.
131
135
  const req = new Request("http://localhost/api/users/123", {
@@ -157,7 +161,7 @@ describe("Request Middleware", () => {
157
161
  test("ifMatchesRoute - not called for other routes", async () => {
158
162
  const config = {};
159
163
 
160
- const fragment = defineFragment<typeof config>("test-lib");
164
+ const fragment = defineFragment<typeof config>("test-lib").build();
161
165
 
162
166
  const routes = [
163
167
  defineRoute({
@@ -188,13 +192,18 @@ describe("Request Middleware", () => {
188
192
 
189
193
  let middlewareCalled = false;
190
194
 
191
- const instance = createFragment(fragment, config, routes, {
192
- mountRoute: "/api",
193
- }).withMiddleware(async ({ ifMatchesRoute }) => {
194
- return ifMatchesRoute("GET", "/users", () => {
195
- 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
+ });
196
206
  });
197
- });
198
207
 
199
208
  // Request to POST, should be disabled.
200
209
  const req = new Request("http://localhost/api/users/123", {
@@ -217,7 +226,7 @@ describe("Request Middleware", () => {
217
226
  test("ifMatchesRoute - can return undefined", async () => {
218
227
  const config = {};
219
228
 
220
- const fragment = defineFragment<typeof config>("test-lib");
229
+ const fragment = defineFragment<typeof config>("test-lib").build();
221
230
 
222
231
  const routes = [
223
232
  defineRoute({
@@ -234,22 +243,27 @@ describe("Request Middleware", () => {
234
243
 
235
244
  let middlewareCalled = false;
236
245
 
237
- const instance = createFragment(fragment, config, routes, {
238
- mountRoute: "/api",
239
- }).withMiddleware(async ({ ifMatchesRoute }) => {
240
- await ifMatchesRoute("GET", "/users", async ({ path, pathParams, input }) => {
241
- expectTypeOf(path).toEqualTypeOf<"/users">();
242
- expectTypeOf(pathParams).toEqualTypeOf<{ [x: string]: never }>();
243
- expectTypeOf(input).toEqualTypeOf<undefined>();
244
-
245
- 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
+ });
246
263
 
247
264
  return undefined;
248
265
  });
249
266
 
250
- return undefined;
251
- });
252
-
253
267
  const getReq = new Request("http://localhost/api/users", {
254
268
  method: "GET",
255
269
  });
@@ -265,7 +279,7 @@ describe("Request Middleware", () => {
265
279
  test("only one middleware is supported", async () => {
266
280
  const config = {};
267
281
 
268
- const fragment = defineFragment<typeof config>("test-lib");
282
+ const fragment = defineFragment<typeof config>("test-lib").build();
269
283
 
270
284
  const routes = [
271
285
  defineRoute({
@@ -277,7 +291,11 @@ describe("Request Middleware", () => {
277
291
  }),
278
292
  ] as const;
279
293
 
280
- const instance = createFragment(fragment, config, routes, {});
294
+ const instance = instantiate(fragment)
295
+ .withConfig(config)
296
+ .withRoutes(routes)
297
+ .withOptions({})
298
+ .build();
281
299
 
282
300
  const withMiddleware = instance.withMiddleware(async () => {
283
301
  return undefined;
@@ -294,7 +312,7 @@ describe("Request Middleware", () => {
294
312
  test("middleware and handler can both consume request body without double consumption", async () => {
295
313
  const config = {};
296
314
 
297
- const fragment = defineFragment<typeof config>("test-lib");
315
+ const fragment = defineFragment<typeof config>("test-lib").build();
298
316
 
299
317
  const routes = [
300
318
  defineRoute({
@@ -312,19 +330,24 @@ describe("Request Middleware", () => {
312
330
  }),
313
331
  ] as const;
314
332
 
315
- const instance = createFragment(fragment, config, routes, {
316
- mountRoute: "/api",
317
- }).withMiddleware(async ({ ifMatchesRoute }) => {
318
- // Middleware consumes the request body
319
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
320
- const body = await input.valid();
321
- // Middleware can read the body
322
- expect(body).toEqual({ name: "John Doe" });
323
- return undefined; // Continue to handler
324
- });
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
+ });
325
348
 
326
- return result;
327
- });
349
+ return result;
350
+ });
328
351
 
329
352
  const req = new Request("http://localhost/api/users", {
330
353
  method: "POST",
@@ -341,7 +364,7 @@ describe("Request Middleware", () => {
341
364
  });
342
365
 
343
366
  test("middleware can modify path parameters", async () => {
344
- const fragment = defineFragment("test-lib");
367
+ const fragment = defineFragment("test-lib").build();
345
368
 
346
369
  const routes = [
347
370
  defineRoute({
@@ -358,16 +381,21 @@ describe("Request Middleware", () => {
358
381
  }),
359
382
  ] as const;
360
383
 
361
- const instance = createFragment(fragment, {}, routes, {
362
- mountRoute: "/api",
363
- }).withMiddleware(async ({ ifMatchesRoute }) => {
364
- // Middleware can read path and query parameters
365
- const result = await ifMatchesRoute("GET", "/users/:id", async ({ pathParams }) => {
366
- pathParams.id = "9999";
367
- });
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
+ });
368
396
 
369
- return result;
370
- });
397
+ return result;
398
+ });
371
399
 
372
400
  // Test with admin role
373
401
  const adminReq = new Request("http://localhost/api/users/123?role=admin", {
@@ -385,7 +413,7 @@ describe("Request Middleware", () => {
385
413
  test("middleware calling input.valid() can catch validation error", async () => {
386
414
  const config = {};
387
415
 
388
- const fragment = defineFragment<typeof config>("test-lib");
416
+ const fragment = defineFragment<typeof config>("test-lib").build();
389
417
 
390
418
  const routes = [
391
419
  defineRoute({
@@ -407,30 +435,35 @@ describe("Request Middleware", () => {
407
435
  }),
408
436
  ] as const;
409
437
 
410
- const instance = createFragment(fragment, config, routes, {
411
- mountRoute: "/api",
412
- }).withMiddleware(async ({ ifMatchesRoute }) => {
413
- // Middleware tries to validate the input
414
- const result = await ifMatchesRoute("POST", "/users", async ({ input }, { error }) => {
415
- try {
416
- await input.valid();
417
- return undefined; // Continue to handler if valid
418
- } catch (validationError) {
419
- 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
+ });
420
463
 
421
- return error(
422
- {
423
- message: "Request validation failed in middleware",
424
- code: "MIDDLEWARE_VALIDATION_ERROR",
425
- },
426
- 400,
427
- );
428
- }
464
+ return result;
429
465
  });
430
466
 
431
- return result;
432
- });
433
-
434
467
  // Test with invalid request (missing required fields)
435
468
  const invalidReq = new Request("http://localhost/api/users", {
436
469
  method: "POST",
@@ -451,7 +484,7 @@ describe("Request Middleware", () => {
451
484
  test("middleware calling input.valid() can ignore validation error", async () => {
452
485
  const config = {};
453
486
 
454
- const fragment = defineFragment<typeof config>("test-lib");
487
+ const fragment = defineFragment<typeof config>("test-lib").build();
455
488
 
456
489
  const routes = [
457
490
  defineRoute({
@@ -474,16 +507,21 @@ describe("Request Middleware", () => {
474
507
  }),
475
508
  ] as const;
476
509
 
477
- const instance = createFragment(fragment, config, routes, {
478
- mountRoute: "/api",
479
- }).withMiddleware(async ({ ifMatchesRoute }) => {
480
- // Middleware tries to validate the input
481
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
482
- await input.valid();
483
- });
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
+ });
484
522
 
485
- return result;
486
- });
523
+ return result;
524
+ });
487
525
 
488
526
  // Test with invalid request (missing required fields)
489
527
  const invalidReq = new Request("http://localhost/api/users", {
@@ -504,7 +542,7 @@ describe("Request Middleware", () => {
504
542
  });
505
543
 
506
544
  test("middleware can modify query parameters", async () => {
507
- const fragment = defineFragment("test-lib");
545
+ const fragment = defineFragment("test-lib").build();
508
546
 
509
547
  const routes = [
510
548
  defineRoute({
@@ -521,16 +559,21 @@ describe("Request Middleware", () => {
521
559
  }),
522
560
  ] as const;
523
561
 
524
- const instance = createFragment(fragment, {}, routes, {
525
- mountRoute: "/api",
526
- }).withMiddleware(async ({ ifMatchesRoute }) => {
527
- // Middleware can read path and query parameters
528
- const result = await ifMatchesRoute("GET", "/users/:id", async ({ query }) => {
529
- query.set("role", "some-other-role-defined-in-middleware");
530
- });
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
+ });
531
574
 
532
- return result;
533
- });
575
+ return result;
576
+ });
534
577
 
535
578
  // Test with admin role
536
579
  const adminReq = new Request("http://localhost/api/users/123?role=admin", {
@@ -546,7 +589,7 @@ describe("Request Middleware", () => {
546
589
  });
547
590
 
548
591
  test("middleware can modify request body", async () => {
549
- const fragment = defineFragment("test-lib");
592
+ const fragment = defineFragment("test-lib").build();
550
593
 
551
594
  const routes = [
552
595
  defineRoute({
@@ -564,21 +607,26 @@ describe("Request Middleware", () => {
564
607
  }),
565
608
  ] as const;
566
609
 
567
- const instance = createFragment(fragment, {}, routes, {
568
- mountRoute: "/api",
569
- }).withMiddleware(async ({ ifMatchesRoute, requestState }) => {
570
- // Middleware modifies the request body
571
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
572
- const body = await input.valid();
573
- // Modify the body by adding a role field
574
- requestState.setBody({
575
- ...body,
576
- 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
+ });
577
626
  });
578
- });
579
627
 
580
- return result;
581
- });
628
+ return result;
629
+ });
582
630
 
583
631
  const req = new Request("http://localhost/api/users", {
584
632
  method: "POST",
@@ -595,7 +643,7 @@ describe("Request Middleware", () => {
595
643
  });
596
644
 
597
645
  test("middleware can modify request headers", async () => {
598
- const fragment = defineFragment("test-lib");
646
+ const fragment = defineFragment("test-lib").build();
599
647
 
600
648
  const routes = [
601
649
  defineRoute({
@@ -611,14 +659,19 @@ describe("Request Middleware", () => {
611
659
  }),
612
660
  ] as const;
613
661
 
614
- const instance = createFragment(fragment, {}, routes, {
615
- mountRoute: "/api",
616
- }).withMiddleware(async ({ headers }) => {
617
- // Middleware modifies headers
618
- headers.set("Authorization", "Bearer middleware-token");
619
- headers.set("X-Custom-Header", "middleware-value");
620
- return undefined;
621
- });
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
+ });
622
675
 
623
676
  const req = new Request("http://localhost/api/data", {
624
677
  method: "GET",
@@ -633,7 +686,7 @@ describe("Request Middleware", () => {
633
686
  });
634
687
 
635
688
  test("ifMatchesRoute properly awaits async handlers", async () => {
636
- const fragment = defineFragment("test-lib");
689
+ const fragment = defineFragment("test-lib").build();
637
690
 
638
691
  const routes = [
639
692
  defineRoute({
@@ -654,33 +707,38 @@ describe("Request Middleware", () => {
654
707
 
655
708
  let asyncOperationCompleted = false;
656
709
 
657
- const instance = createFragment(fragment, {}, routes, {
658
- mountRoute: "/api",
659
- }).withMiddleware(async ({ ifMatchesRoute }) => {
660
- const result = await ifMatchesRoute("POST", "/users", async ({ input }) => {
661
- // Simulate async operation (e.g., database lookup)
662
- await new Promise((resolve) => setTimeout(resolve, 10));
663
- const body = await input.valid();
664
-
665
- // Verify the async operation completed
666
- asyncOperationCompleted = true;
667
-
668
- // Check if user exists
669
- if (body.name === "existing-user") {
670
- return new Response(
671
- JSON.stringify({
672
- message: "User already exists",
673
- code: "USER_EXISTS",
674
- }),
675
- { status: 409, headers: { "Content-Type": "application/json" } },
676
- );
677
- }
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();
678
722
 
679
- return undefined;
680
- });
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
+ });
681
739
 
682
- return result;
683
- });
740
+ return result;
741
+ });
684
742
 
685
743
  // Test with existing user
686
744
  const existingUserReq = new Request("http://localhost/api/users", {
@@ -718,7 +776,7 @@ describe("Request Middleware", () => {
718
776
  });
719
777
 
720
778
  test("ifMatchesRoute handles async errors properly", async () => {
721
- const fragment = defineFragment("test-lib");
779
+ const fragment = defineFragment("test-lib").build();
722
780
 
723
781
  const routes = [
724
782
  defineRoute({
@@ -731,25 +789,30 @@ describe("Request Middleware", () => {
731
789
  }),
732
790
  ] as const;
733
791
 
734
- const instance = createFragment(fragment, {}, routes, {
735
- mountRoute: "/api",
736
- }).withMiddleware(async ({ ifMatchesRoute }) => {
737
- const result = await ifMatchesRoute("GET", "/data", async (_, { error }) => {
738
- // Simulate async operation that fails
739
- await new Promise((resolve) => setTimeout(resolve, 5));
740
-
741
- // Simulate an error condition (e.g., database unavailable)
742
- return error(
743
- {
744
- message: "Service temporarily unavailable",
745
- code: "SERVICE_UNAVAILABLE",
746
- },
747
- 503,
748
- );
749
- });
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
+ });
750
813
 
751
- return result;
752
- });
814
+ return result;
815
+ });
753
816
 
754
817
  const req = new Request("http://localhost/api/data", {
755
818
  method: "GET",
@@ -764,7 +827,7 @@ describe("Request Middleware", () => {
764
827
  });
765
828
 
766
829
  test("ifMatchesRoute with async body modification", async () => {
767
- const fragment = defineFragment("test-lib");
830
+ const fragment = defineFragment("test-lib").build();
768
831
 
769
832
  const routes = [
770
833
  defineRoute({
@@ -789,27 +852,32 @@ describe("Request Middleware", () => {
789
852
  }),
790
853
  ] as const;
791
854
 
792
- const instance = createFragment(fragment, {}, routes, {
793
- mountRoute: "/api",
794
- }).withMiddleware(async ({ ifMatchesRoute, requestState }) => {
795
- const result = await ifMatchesRoute("POST", "/posts", async ({ input }) => {
796
- // Simulate async operation to enrich the body (e.g., fetching user data)
797
- 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));
798
866
 
799
- const body = await input.valid();
867
+ const body = await input.valid();
800
868
 
801
- // Enrich the body with additional data
802
- requestState.setBody({
803
- ...body,
804
- 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;
805
876
  });
806
877
 
807
- return undefined;
878
+ return result;
808
879
  });
809
880
 
810
- return result;
811
- });
812
-
813
881
  const req = new Request("http://localhost/api/posts", {
814
882
  method: "POST",
815
883
  headers: { "Content-Type": "application/json" },
@@ -832,7 +900,7 @@ describe("Request Middleware", () => {
832
900
  });
833
901
 
834
902
  test("multiple async operations in ifMatchesRoute complete in order", async () => {
835
- const fragment = defineFragment("test-lib");
903
+ const fragment = defineFragment("test-lib").build();
836
904
 
837
905
  const routes = [
838
906
  defineRoute({
@@ -847,27 +915,32 @@ describe("Request Middleware", () => {
847
915
 
848
916
  const executionOrder: string[] = [];
849
917
 
850
- const instance = createFragment(fragment, {}, routes, {
851
- mountRoute: "/api",
852
- }).withMiddleware(async ({ ifMatchesRoute }) => {
853
- executionOrder.push("start");
854
-
855
- const result = await ifMatchesRoute("GET", "/status", async () => {
856
- executionOrder.push("before-first-async");
857
- await new Promise((resolve) => setTimeout(resolve, 5));
858
- 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
+ });
859
938
 
860
- await new Promise((resolve) => setTimeout(resolve, 5));
861
- executionOrder.push("after-second-async");
939
+ executionOrder.push("end");
862
940
 
863
- return undefined;
941
+ return result;
864
942
  });
865
943
 
866
- executionOrder.push("end");
867
-
868
- return result;
869
- });
870
-
871
944
  const req = new Request("http://localhost/api/status", {
872
945
  method: "GET",
873
946
  });