@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.
- package/.turbo/turbo-build.log +131 -64
- package/CHANGELOG.md +19 -0
- package/dist/api/api.d.ts +38 -2
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +9 -2
- package/dist/api/api.js.map +1 -0
- package/dist/api/bind-services.d.ts +6 -0
- package/dist/api/bind-services.d.ts.map +1 -0
- package/dist/api/bind-services.js +20 -0
- package/dist/api/bind-services.js.map +1 -0
- package/dist/api/error.d.ts +26 -0
- package/dist/api/error.d.ts.map +1 -0
- package/dist/{api-DngJDcmO.js → api/error.js} +2 -8
- package/dist/api/error.js.map +1 -0
- package/dist/api/fragment-definition-builder.d.ts +313 -0
- package/dist/api/fragment-definition-builder.d.ts.map +1 -0
- package/dist/api/fragment-definition-builder.js +326 -0
- package/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/api/fragment-instantiator.d.ts +216 -0
- package/dist/api/fragment-instantiator.d.ts.map +1 -0
- package/dist/api/fragment-instantiator.js +487 -0
- package/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/api/fragno-response.d.ts +30 -0
- package/dist/api/fragno-response.d.ts.map +1 -0
- package/dist/api/fragno-response.js +73 -0
- package/dist/api/fragno-response.js.map +1 -0
- package/dist/api/internal/path.d.ts +50 -0
- package/dist/api/internal/path.d.ts.map +1 -0
- package/dist/api/internal/path.js +76 -0
- package/dist/api/internal/path.js.map +1 -0
- package/dist/api/internal/response-stream.d.ts +43 -0
- package/dist/api/internal/response-stream.d.ts.map +1 -0
- package/dist/api/internal/response-stream.js +81 -0
- package/dist/api/internal/response-stream.js.map +1 -0
- package/dist/api/internal/route.js +10 -0
- package/dist/api/internal/route.js.map +1 -0
- package/dist/api/mutable-request-state.d.ts +82 -0
- package/dist/api/mutable-request-state.d.ts.map +1 -0
- package/dist/api/mutable-request-state.js +97 -0
- package/dist/api/mutable-request-state.js.map +1 -0
- package/dist/api/request-context-storage.d.ts +42 -0
- package/dist/api/request-context-storage.d.ts.map +1 -0
- package/dist/api/request-context-storage.js +43 -0
- package/dist/api/request-context-storage.js.map +1 -0
- package/dist/api/request-input-context.d.ts +89 -0
- package/dist/api/request-input-context.d.ts.map +1 -0
- package/dist/api/request-input-context.js +118 -0
- package/dist/api/request-input-context.js.map +1 -0
- package/dist/api/request-middleware.d.ts +50 -0
- package/dist/api/request-middleware.d.ts.map +1 -0
- package/dist/api/request-middleware.js +83 -0
- package/dist/api/request-middleware.js.map +1 -0
- package/dist/api/request-output-context.d.ts +41 -0
- package/dist/api/request-output-context.d.ts.map +1 -0
- package/dist/api/request-output-context.js +119 -0
- package/dist/api/request-output-context.js.map +1 -0
- package/dist/api/route-handler-input-options.d.ts +21 -0
- package/dist/api/route-handler-input-options.d.ts.map +1 -0
- package/dist/api/route.d.ts +54 -3
- package/dist/api/route.d.ts.map +1 -0
- package/dist/api/route.js +29 -2
- package/dist/api/route.js.map +1 -0
- package/dist/api/shared-types.d.ts +47 -0
- package/dist/api/shared-types.d.ts.map +1 -0
- package/dist/api/shared-types.js +1 -0
- package/dist/client/client-error.d.ts +60 -0
- package/dist/client/client-error.d.ts.map +1 -0
- package/dist/client/client-error.js +92 -0
- package/dist/client/client-error.js.map +1 -0
- package/dist/client/client.d.ts +210 -4
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +397 -6
- package/dist/client/client.js.map +1 -0
- package/dist/client/client.svelte.d.ts +5 -3
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +1 -5
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/internal/fetcher-merge.js +36 -0
- package/dist/client/internal/fetcher-merge.js.map +1 -0
- package/dist/client/internal/ndjson-streaming.js +139 -0
- package/dist/client/internal/ndjson-streaming.js.map +1 -0
- package/dist/client/react.d.ts +5 -3
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +3 -5
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +5 -3
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +2 -5
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +5 -3
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +2 -43
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +5 -3
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +1 -5
- package/dist/client/vue.js.map +1 -1
- package/dist/http/http-status.d.ts +26 -0
- package/dist/http/http-status.d.ts.map +1 -0
- package/dist/integrations/react-ssr.js +1 -1
- package/dist/internal/symbols.d.ts +9 -0
- package/dist/internal/symbols.d.ts.map +1 -0
- package/dist/internal/symbols.js +10 -0
- package/dist/internal/symbols.js.map +1 -0
- package/dist/mod-client.d.ts +36 -0
- package/dist/mod-client.d.ts.map +1 -0
- package/dist/mod-client.js +21 -0
- package/dist/mod-client.js.map +1 -0
- package/dist/mod.d.ts +7 -4
- package/dist/mod.js +4 -6
- package/dist/request/request.d.ts +4 -0
- package/dist/request/request.js +5 -0
- package/dist/test/test.d.ts +62 -35
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js +75 -40
- package/dist/test/test.js.map +1 -1
- package/dist/util/async.js +40 -0
- package/dist/util/async.js.map +1 -0
- package/dist/util/content-type.js +49 -0
- package/dist/util/content-type.js.map +1 -0
- package/dist/util/nanostores.js +31 -0
- package/dist/util/nanostores.js.map +1 -0
- package/dist/{ssr-BByDVfFD.js → util/ssr.js} +2 -2
- package/dist/util/ssr.js.map +1 -0
- package/dist/util/types-util.d.ts +8 -0
- package/dist/util/types-util.d.ts.map +1 -0
- package/package.json +19 -12
- package/src/api/api.ts +41 -6
- package/src/api/bind-services.ts +42 -0
- package/src/api/fragment-definition-builder.extend.test.ts +810 -0
- package/src/api/fragment-definition-builder.test.ts +499 -0
- package/src/api/fragment-definition-builder.ts +1088 -0
- package/src/api/fragment-instantiator.test.ts +1488 -0
- package/src/api/fragment-instantiator.ts +1053 -0
- package/src/api/fragment-services.test.ts +727 -0
- package/src/api/request-context-storage.ts +64 -0
- package/src/api/request-middleware.test.ts +301 -225
- package/src/api/route.test.ts +87 -1
- package/src/api/route.ts +345 -24
- package/src/api/shared-types.ts +43 -0
- package/src/client/client-builder.test.ts +23 -23
- package/src/client/client.ssr.test.ts +3 -3
- package/src/client/client.svelte.test.ts +15 -15
- package/src/client/client.test.ts +22 -22
- package/src/client/client.ts +72 -12
- package/src/client/internal/fetcher-merge.ts +1 -1
- package/src/client/react.test.ts +2 -2
- package/src/client/solid.test.ts +2 -2
- package/src/client/vanilla.test.ts +2 -2
- package/src/client/vue.test.ts +2 -2
- package/src/internal/symbols.ts +5 -0
- package/src/mod-client.ts +59 -0
- package/src/mod.ts +26 -9
- package/src/request/request.ts +8 -0
- package/src/test/test.test.ts +200 -381
- package/src/test/test.ts +190 -117
- package/tsdown.config.ts +8 -5
- package/dist/api/fragment-builder.d.ts +0 -4
- package/dist/api/fragment-builder.js +0 -3
- package/dist/api/fragment-instantiation.d.ts +0 -4
- package/dist/api/fragment-instantiation.js +0 -6
- package/dist/api-BWN97TOr.d.ts +0 -377
- package/dist/api-BWN97TOr.d.ts.map +0 -1
- package/dist/api-DngJDcmO.js.map +0 -1
- package/dist/client-C5LsYHEI.js +0 -782
- package/dist/client-C5LsYHEI.js.map +0 -1
- package/dist/fragment-builder-DOnCVBqc.js +0 -47
- package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
- package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
- package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
- package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
- package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
- package/dist/request-output-context-CdIjwmEN.js +0 -320
- package/dist/request-output-context-CdIjwmEN.js.map +0 -1
- package/dist/route-Bl9Zr1Yv.d.ts +0 -26
- package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
- package/dist/route-C5Uryylh.js +0 -21
- package/dist/route-C5Uryylh.js.map +0 -1
- package/dist/ssr-BByDVfFD.js.map +0 -1
- package/src/api/fragment-builder.ts +0 -80
- package/src/api/fragment-instantiation.test.ts +0 -460
- package/src/api/fragment-instantiation.ts +0 -499
- 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 {
|
|
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")
|
|
13
|
-
|
|
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 =
|
|
27
|
-
|
|
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 =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
if (result) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 =
|
|
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 =
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
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 =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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 =
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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 =
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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 =
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
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 =
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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 =
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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 =
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
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 =
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
|
|
867
|
+
const body = await input.valid();
|
|
797
868
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
|
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 =
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
executionOrder.push("
|
|
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
|
-
|
|
858
|
-
executionOrder.push("after-second-async");
|
|
939
|
+
executionOrder.push("end");
|
|
859
940
|
|
|
860
|
-
return
|
|
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
|
});
|