@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.
- package/.turbo/turbo-build.log +131 -56
- package/CHANGELOG.md +13 -0
- package/dist/api/api.d.ts +38 -2
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +9 -3
- 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/error.js +48 -0
- 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 -2
- 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 -2
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +397 -5
- package/dist/client/client.js.map +1 -0
- package/dist/client/client.svelte.d.ts +5 -2
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +1 -4
- 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 -2
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +3 -4
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +5 -2
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +2 -4
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +5 -2
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +2 -42
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +5 -2
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +1 -4
- 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 -2
- package/dist/mod.js +4 -4
- package/dist/request/request.d.ts +4 -0
- package/dist/request/request.js +5 -0
- package/dist/test/test.d.ts +62 -34
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js +75 -42
- 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-kyKI7pqH.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 +1 -5
- 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 +454 -189
- package/src/api/request-context-storage.ts +64 -0
- package/src/api/request-middleware.test.ts +301 -228
- package/src/api/route.test.ts +12 -36
- package/src/api/route.ts +167 -155
- 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 +22 -15
- package/src/request/request.ts +8 -0
- package/src/test/test.test.ts +189 -375
- package/src/test/test.ts +186 -152
- package/tsdown.config.ts +8 -5
- package/dist/api/fragment-builder.d.ts +0 -2
- package/dist/api/fragment-builder.js +0 -3
- package/dist/api/fragment-instantiation.d.ts +0 -2
- package/dist/api/fragment-instantiation.js +0 -4
- package/dist/api-BFrUCIsF.d.ts +0 -963
- package/dist/api-BFrUCIsF.d.ts.map +0 -1
- package/dist/client-DAFHcKqA.js +0 -782
- package/dist/client-DAFHcKqA.js.map +0 -1
- package/dist/fragment-builder-Boh2vNHq.js +0 -108
- package/dist/fragment-builder-Boh2vNHq.js.map +0 -1
- package/dist/fragment-instantiation-DUT-HLl1.js +0 -898
- package/dist/fragment-instantiation-DUT-HLl1.js.map +0 -1
- package/dist/route-C4CyNHkC.js +0 -26
- package/dist/route-C4CyNHkC.js.map +0 -1
- package/dist/ssr-kyKI7pqH.js.map +0 -1
- package/src/api/fragment-builder.ts +0 -518
- package/src/api/fragment-instantiation.test.ts +0 -702
- package/src/api/fragment-instantiation.ts +0 -766
- 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 {
|
|
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")
|
|
13
|
-
(
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
30
|
-
|
|
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 =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
if (result) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
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 =
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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 =
|
|
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 =
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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 =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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 =
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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 =
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
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 =
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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 =
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
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 =
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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 =
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
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 =
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
867
|
+
const body = await input.valid();
|
|
800
868
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
|
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 =
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
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
|
+
});
|
|
859
938
|
|
|
860
|
-
|
|
861
|
-
executionOrder.push("after-second-async");
|
|
939
|
+
executionOrder.push("end");
|
|
862
940
|
|
|
863
|
-
return
|
|
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
|
});
|