@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
package/src/test/test.test.ts
CHANGED
|
@@ -1,449 +1,268 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
2
|
-
import { createFragmentForTest } from "./test";
|
|
3
|
-
import { defineFragment } from "../api/fragment-builder";
|
|
4
|
-
import {
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createFragmentForTest, withTestUtils } from "./test";
|
|
3
|
+
import { defineFragment } from "../api/fragment-definition-builder";
|
|
4
|
+
import { defineRoutes } from "../api/route";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
|
-
describe("
|
|
8
|
-
it("should
|
|
9
|
-
const
|
|
10
|
-
|
|
7
|
+
describe("withTestUtils extension", () => {
|
|
8
|
+
it("should expose deps via services.deps", () => {
|
|
9
|
+
const definition = defineFragment<{ apiKey: string }>("test")
|
|
10
|
+
.withDependencies(({ config }) => ({
|
|
11
|
+
client: { apiKey: config.apiKey },
|
|
12
|
+
}))
|
|
13
|
+
.extend(withTestUtils())
|
|
14
|
+
.build();
|
|
15
|
+
|
|
16
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
11
17
|
config: { apiKey: "test-key" },
|
|
12
18
|
});
|
|
13
19
|
|
|
14
|
-
expect(
|
|
15
|
-
expect(testFragment.deps).toEqual({});
|
|
16
|
-
expect(testFragment.services).toEqual({});
|
|
20
|
+
expect(fragment.services.deps).toEqual({ client: { apiKey: "test-key" } });
|
|
17
21
|
});
|
|
18
22
|
|
|
19
|
-
it("should
|
|
20
|
-
const
|
|
21
|
-
client: { apiKey: config.apiKey },
|
|
22
|
-
}));
|
|
23
|
+
it("should work with empty deps", () => {
|
|
24
|
+
const definition = defineFragment<{ value: number }>("test").extend(withTestUtils()).build();
|
|
23
25
|
|
|
24
|
-
const
|
|
25
|
-
config: {
|
|
26
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
27
|
+
config: { value: 5 },
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
expect(
|
|
30
|
+
expect(fragment.services.deps).toEqual({});
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
it("should
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
it("should preserve existing base services", () => {
|
|
34
|
+
const definition = defineFragment<{ x: number; y: number }>("test")
|
|
35
|
+
.withDependencies(({ config }) => ({
|
|
36
|
+
x: config.x,
|
|
37
|
+
y: config.y,
|
|
38
|
+
}))
|
|
39
|
+
.providesBaseService(({ deps, defineService }) =>
|
|
40
|
+
defineService({
|
|
41
|
+
add: () => deps.x + deps.y,
|
|
42
|
+
multiply: () => deps.x * deps.y,
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
.extend(withTestUtils())
|
|
46
|
+
.build();
|
|
47
|
+
|
|
48
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
49
|
+
config: { x: 3, y: 4 },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Both existing services and deps should be available
|
|
53
|
+
expect(fragment.services.add()).toBe(7);
|
|
54
|
+
expect(fragment.services.multiply()).toBe(12);
|
|
55
|
+
expect(fragment.services.deps).toEqual({ x: 3, y: 4 });
|
|
42
56
|
});
|
|
43
57
|
|
|
44
|
-
it("should
|
|
45
|
-
const
|
|
58
|
+
it("should work with named services", () => {
|
|
59
|
+
const definition = defineFragment<{ value: number }>("test")
|
|
46
60
|
.withDependencies(({ config }) => ({
|
|
47
|
-
|
|
61
|
+
value: config.value,
|
|
48
62
|
}))
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
.providesService("math", ({ deps, defineService }) =>
|
|
64
|
+
defineService({
|
|
65
|
+
double: () => deps.value * 2,
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
68
|
+
.extend(withTestUtils())
|
|
69
|
+
.build();
|
|
52
70
|
|
|
53
|
-
const
|
|
54
|
-
config: {
|
|
71
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
72
|
+
config: { value: 21 },
|
|
55
73
|
});
|
|
56
74
|
|
|
57
|
-
expect(
|
|
75
|
+
expect(fragment.services.deps).toEqual({ value: 21 });
|
|
76
|
+
expect(fragment.services.math.double()).toBe(42);
|
|
58
77
|
});
|
|
59
78
|
|
|
60
|
-
it("should
|
|
61
|
-
|
|
79
|
+
it("should work with service dependencies", () => {
|
|
80
|
+
type Logger = { log: (msg: string) => void };
|
|
81
|
+
|
|
82
|
+
const logs: string[] = [];
|
|
83
|
+
const mockLogger: Logger = {
|
|
84
|
+
log: (msg: string) => logs.push(msg),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const definition = defineFragment<{ name: string }>("test")
|
|
62
88
|
.withDependencies(({ config }) => ({
|
|
63
|
-
|
|
89
|
+
name: config.name,
|
|
64
90
|
}))
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
.usesService<"logger", Logger>("logger")
|
|
92
|
+
.providesBaseService(({ deps, serviceDeps, defineService }) =>
|
|
93
|
+
defineService({
|
|
94
|
+
greet: () => {
|
|
95
|
+
serviceDeps.logger.log(`Hello, ${deps.name}!`);
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
.extend(withTestUtils())
|
|
100
|
+
.build();
|
|
101
|
+
|
|
102
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
103
|
+
config: { name: "World" },
|
|
104
|
+
serviceImplementations: {
|
|
105
|
+
logger: mockLogger,
|
|
106
|
+
},
|
|
72
107
|
});
|
|
73
108
|
|
|
74
|
-
expect(
|
|
109
|
+
expect(fragment.services.deps).toEqual({ name: "World" });
|
|
110
|
+
fragment.services.greet();
|
|
111
|
+
expect(logs).toEqual(["Hello, World!"]);
|
|
75
112
|
});
|
|
76
113
|
|
|
77
|
-
it("should
|
|
78
|
-
type
|
|
79
|
-
|
|
80
|
-
|
|
114
|
+
it("should work with request storage and context", () => {
|
|
115
|
+
type RequestStorage = { counter: number };
|
|
116
|
+
|
|
117
|
+
const definition = defineFragment<{ initialValue: number }>("test")
|
|
118
|
+
.withDependencies(({ config }) => ({
|
|
119
|
+
initialValue: config.initialValue,
|
|
120
|
+
}))
|
|
121
|
+
.withRequestStorage(
|
|
122
|
+
({ deps }): RequestStorage => ({
|
|
123
|
+
counter: deps.initialValue,
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
.withThisContext(({ storage }) => {
|
|
127
|
+
const ctx = {
|
|
128
|
+
getCounter: () => storage.getStore()?.counter ?? 0,
|
|
129
|
+
};
|
|
130
|
+
return { serviceContext: ctx, handlerContext: ctx };
|
|
131
|
+
})
|
|
132
|
+
.extend(withTestUtils())
|
|
133
|
+
.build();
|
|
134
|
+
|
|
135
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
136
|
+
config: { initialValue: 10 },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(fragment.services.deps).toEqual({ initialValue: 10 });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
81
142
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
multiply: (x: number) => x * config.multiplier,
|
|
86
|
-
}));
|
|
143
|
+
describe("createFragmentForTest", () => {
|
|
144
|
+
it("should instantiate fragments with routes", async () => {
|
|
145
|
+
type Config = { multiplier: number };
|
|
87
146
|
|
|
88
|
-
const
|
|
147
|
+
const definition = defineFragment<Config>("test")
|
|
148
|
+
.withDependencies(({ config }) => ({ multiplier: config.multiplier }))
|
|
149
|
+
.providesService("math", ({ deps, defineService }) =>
|
|
150
|
+
defineService({
|
|
151
|
+
multiply: (x: number) => x * deps.multiplier,
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
154
|
+
.extend(withTestUtils())
|
|
155
|
+
.build();
|
|
156
|
+
|
|
157
|
+
const routeFactory = defineRoutes(definition).create(({ services, defineRoute }) => [
|
|
89
158
|
defineRoute({
|
|
90
159
|
method: "GET",
|
|
91
160
|
path: "/multiply/:num",
|
|
92
|
-
outputSchema: z.object({ result: z.number() }),
|
|
161
|
+
outputSchema: z.object({ result: z.number(), deps: z.object({ multiplier: z.number() }) }),
|
|
93
162
|
handler: async ({ pathParams }, { json }) => {
|
|
94
163
|
const { num } = pathParams;
|
|
95
|
-
return json({
|
|
164
|
+
return json({
|
|
165
|
+
result: services.math.multiply(Number(num)),
|
|
166
|
+
deps: services.deps,
|
|
167
|
+
});
|
|
96
168
|
},
|
|
97
169
|
}),
|
|
98
170
|
]);
|
|
99
171
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
const testFragment = createFragmentForTest(fragment, routes, {
|
|
172
|
+
const fragment = createFragmentForTest(definition, [routeFactory], {
|
|
103
173
|
config: { multiplier: 3 },
|
|
104
174
|
});
|
|
105
175
|
|
|
106
|
-
|
|
107
|
-
const response = await testFragment.callRoute("GET", "/multiply/:num", {
|
|
108
|
-
// ^?
|
|
176
|
+
const response = await fragment.callRoute("GET", "/multiply/:num", {
|
|
109
177
|
pathParams: { num: "5" },
|
|
110
178
|
});
|
|
111
|
-
expect(response.type).toBe("json");
|
|
112
|
-
if (response.type === "json") {
|
|
113
|
-
expect(response.data).toEqual({ result: 15 }); // 5 * 3
|
|
114
|
-
expectTypeOf(response.data).toMatchObjectType<{ result: number }>();
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe("fragment.callRoute", () => {
|
|
120
|
-
it("should handle JSON response", async () => {
|
|
121
|
-
const fragment = defineFragment<{ apiKey: string }>("test");
|
|
122
|
-
|
|
123
|
-
const route = defineRoute({
|
|
124
|
-
method: "GET",
|
|
125
|
-
path: "/test",
|
|
126
|
-
outputSchema: z.object({ message: z.string() }),
|
|
127
|
-
handler: async (_ctx, { json }) => {
|
|
128
|
-
return json({ message: "hello" });
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
133
|
-
config: { apiKey: "test-key" },
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const response = await testFragment.callRoute("GET", "/test");
|
|
137
179
|
|
|
138
180
|
expect(response.type).toBe("json");
|
|
139
181
|
if (response.type === "json") {
|
|
140
|
-
expect(response.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should handle empty response", async () => {
|
|
148
|
-
const fragment = defineFragment<{ apiKey: string }>("test");
|
|
149
|
-
|
|
150
|
-
const route = defineRoute({
|
|
151
|
-
method: "DELETE",
|
|
152
|
-
path: "/test",
|
|
153
|
-
handler: async (_ctx, { empty }) => {
|
|
154
|
-
return empty(204);
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
159
|
-
config: { apiKey: "test-key" },
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const response = await testFragment.callRoute("DELETE", "/test");
|
|
163
|
-
|
|
164
|
-
expect(response.type).toBe("empty");
|
|
165
|
-
if (response.type === "empty") {
|
|
166
|
-
expect(response.status).toBe(204);
|
|
167
|
-
expect(response.headers).toBeInstanceOf(Headers);
|
|
182
|
+
expect(response.data).toEqual({
|
|
183
|
+
result: 15,
|
|
184
|
+
deps: { multiplier: 3 },
|
|
185
|
+
});
|
|
168
186
|
}
|
|
169
187
|
});
|
|
170
188
|
|
|
171
|
-
it("should
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const route = defineRoute({
|
|
175
|
-
method: "GET",
|
|
176
|
-
path: "/test",
|
|
177
|
-
errorCodes: ["NOT_FOUND"] as const,
|
|
178
|
-
handler: async (_ctx, { error }) => {
|
|
179
|
-
return error({ message: "Not found", code: "NOT_FOUND" }, 404);
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
184
|
-
config: { apiKey: "test-key" },
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const response = await testFragment.callRoute("GET", "/test");
|
|
189
|
+
it("should support request context in routes", async () => {
|
|
190
|
+
type RequestStorage = { counter: number };
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
config: { apiKey: "test-key" },
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const response = await testFragment.callRoute("GET", "/test/stream");
|
|
218
|
-
|
|
219
|
-
expect(response.type).toBe("jsonStream");
|
|
220
|
-
if (response.type === "jsonStream") {
|
|
221
|
-
expect(response.status).toBe(200);
|
|
222
|
-
expect(response.headers).toBeInstanceOf(Headers);
|
|
223
|
-
expect(response.headers.get("content-type")).toContain("application/x-ndjson");
|
|
224
|
-
|
|
225
|
-
const items = [];
|
|
226
|
-
for await (const item of response.stream) {
|
|
227
|
-
items.push(item);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
expect(items).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }, { value: 5 }]);
|
|
231
|
-
expectTypeOf(items[0]).toMatchObjectType<{ value: number }>();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("should handle route factory created with defineRoutes", async () => {
|
|
236
|
-
const fragment = defineFragment<{ apiKey: string }>("test").withServices(() => ({
|
|
237
|
-
getGreeting: (name: string) => `Hello, ${name}!`,
|
|
238
|
-
getCount: () => 42,
|
|
239
|
-
}));
|
|
240
|
-
|
|
241
|
-
type Config = { apiKey: string };
|
|
242
|
-
type Deps = {};
|
|
243
|
-
type Services = { getGreeting: (name: string) => string; getCount: () => number };
|
|
244
|
-
|
|
245
|
-
const routeFactory = defineRoutes<Config, Deps, Services>().create(({ services }) => [
|
|
246
|
-
defineRoute({
|
|
247
|
-
method: "GET",
|
|
248
|
-
path: "/greeting/:name",
|
|
249
|
-
outputSchema: z.object({ message: z.string() }),
|
|
250
|
-
handler: async ({ pathParams }, { json }) => {
|
|
251
|
-
return json({ message: services.getGreeting(pathParams.name) });
|
|
252
|
-
},
|
|
253
|
-
}),
|
|
192
|
+
const definition = defineFragment<{ initialValue: number }>("test")
|
|
193
|
+
.withDependencies(({ config }) => ({
|
|
194
|
+
initialValue: config.initialValue,
|
|
195
|
+
}))
|
|
196
|
+
.extend(withTestUtils())
|
|
197
|
+
.withRequestStorage(
|
|
198
|
+
({ deps }): RequestStorage => ({
|
|
199
|
+
counter: deps.initialValue,
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
.withThisContext(({ storage }) => {
|
|
203
|
+
const ctx = {
|
|
204
|
+
getCounter: () => storage.getStore()?.counter ?? 0,
|
|
205
|
+
incrementCounter: () => {
|
|
206
|
+
const store = storage.getStore();
|
|
207
|
+
if (store) {
|
|
208
|
+
store.counter++;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return { serviceContext: ctx, handlerContext: ctx };
|
|
213
|
+
})
|
|
214
|
+
.build();
|
|
215
|
+
|
|
216
|
+
const routeFactory = defineRoutes(definition).create(({ defineRoute }) => [
|
|
254
217
|
defineRoute({
|
|
255
|
-
method: "
|
|
256
|
-
path: "/
|
|
218
|
+
method: "POST",
|
|
219
|
+
path: "/increment",
|
|
257
220
|
outputSchema: z.object({ count: z.number() }),
|
|
258
|
-
handler: async (_ctx, { json })
|
|
259
|
-
|
|
221
|
+
handler: async function (_ctx, { json }) {
|
|
222
|
+
this.incrementCounter();
|
|
223
|
+
return json({ count: this.getCounter() });
|
|
260
224
|
},
|
|
261
225
|
}),
|
|
262
226
|
]);
|
|
263
227
|
|
|
264
|
-
const
|
|
265
|
-
config: {
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Test first route
|
|
269
|
-
const greetingResponse = await testFragment.callRoute("GET", "/greeting/:name", {
|
|
270
|
-
pathParams: { name: "World" },
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
expect(greetingResponse.type).toBe("json");
|
|
274
|
-
if (greetingResponse.type === "json") {
|
|
275
|
-
expect(greetingResponse.data).toEqual({ message: "Hello, World!" });
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Test second route
|
|
279
|
-
const countResponse = await testFragment.callRoute("GET", "/count");
|
|
280
|
-
|
|
281
|
-
expect(countResponse.type).toBe("json");
|
|
282
|
-
if (countResponse.type === "json") {
|
|
283
|
-
expect(countResponse.data).toEqual({ count: 42 });
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("should handle path parameters", async () => {
|
|
288
|
-
const fragment = defineFragment<{}>("test");
|
|
289
|
-
|
|
290
|
-
const route = defineRoute({
|
|
291
|
-
method: "GET",
|
|
292
|
-
path: "/users/:id",
|
|
293
|
-
outputSchema: z.object({ userId: z.string() }),
|
|
294
|
-
handler: async ({ pathParams }, { json }) => {
|
|
295
|
-
return json({ userId: pathParams.id });
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
300
|
-
config: {},
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const response = await testFragment.callRoute("GET", "/users/:id", {
|
|
304
|
-
pathParams: { id: "123" },
|
|
228
|
+
const fragment = createFragmentForTest(definition, [routeFactory], {
|
|
229
|
+
config: { initialValue: 10 },
|
|
305
230
|
});
|
|
306
231
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
232
|
+
// Each request should have its own isolated storage
|
|
233
|
+
const response1 = await fragment.callRoute("POST", "/increment");
|
|
234
|
+
expect(response1.type).toBe("json");
|
|
235
|
+
if (response1.type === "json") {
|
|
236
|
+
expect(response1.data).toEqual({ count: 11 });
|
|
310
237
|
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it("should handle query parameters", async () => {
|
|
314
|
-
const fragment = defineFragment<{}>("test");
|
|
315
|
-
|
|
316
|
-
const route = defineRoute({
|
|
317
|
-
method: "GET",
|
|
318
|
-
path: "/search",
|
|
319
|
-
outputSchema: z.object({ query: z.string() }),
|
|
320
|
-
handler: async ({ query }, { json }) => {
|
|
321
|
-
return json({ query: query.get("q") || "" });
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
238
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
query: { q: "test" },
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
expect(response.type).toBe("json");
|
|
334
|
-
if (response.type === "json") {
|
|
335
|
-
expect(response.data).toEqual({ query: "test" });
|
|
239
|
+
// New request should start fresh
|
|
240
|
+
const response2 = await fragment.callRoute("POST", "/increment");
|
|
241
|
+
expect(response2.type).toBe("json");
|
|
242
|
+
if (response2.type === "json") {
|
|
243
|
+
expect(response2.data).toEqual({ count: 11 }); // Not 12!
|
|
336
244
|
}
|
|
337
245
|
});
|
|
338
246
|
|
|
339
|
-
it("should
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return json({ id: 1, name: data.name, email: data.email });
|
|
351
|
-
}
|
|
352
|
-
return json({ id: 1, name: "", email: "" });
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
357
|
-
config: {},
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const response = await testFragment.callRoute("POST", "/users", {
|
|
361
|
-
body: { name: "John", email: "john@example.com" },
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
expect(response.type).toBe("json");
|
|
365
|
-
if (response.type === "json") {
|
|
366
|
-
expect(response.data).toEqual({ id: 1, name: "John", email: "john@example.com" });
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("should have the right types", () => {
|
|
371
|
-
const fragment = defineFragment<{}>("test");
|
|
372
|
-
|
|
373
|
-
const route = defineRoute({
|
|
374
|
-
method: "POST",
|
|
375
|
-
path: "/users",
|
|
376
|
-
inputSchema: z.object({ name: z.string(), email: z.string() }),
|
|
377
|
-
outputSchema: z.object({ id: z.number(), name: z.string(), email: z.string() }),
|
|
378
|
-
handler: async ({ input }, { json }) => {
|
|
379
|
-
if (input) {
|
|
380
|
-
const data = await input.valid();
|
|
381
|
-
return json({ id: 1, name: data.name, email: data.email });
|
|
382
|
-
}
|
|
383
|
-
return json({ id: 1, name: "", email: "" });
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
388
|
-
config: {},
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Check what type body is expected to have
|
|
392
|
-
type InputOptions = Parameters<typeof testFragment.callRoute<"POST", "/users">>[2];
|
|
393
|
-
type BodyType = NonNullable<NonNullable<InputOptions>["body"]>;
|
|
394
|
-
|
|
395
|
-
expectTypeOf<BodyType>().toMatchObjectType<{ name: string; email: string }>();
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("should handle custom headers", async () => {
|
|
399
|
-
const fragment = defineFragment<{}>("test");
|
|
400
|
-
|
|
401
|
-
const route = defineRoute({
|
|
402
|
-
method: "GET",
|
|
403
|
-
path: "/test",
|
|
404
|
-
outputSchema: z.object({ authHeader: z.string() }),
|
|
405
|
-
handler: async ({ headers }, { json }) => {
|
|
406
|
-
return json({ authHeader: headers.get("authorization") || "" });
|
|
407
|
-
},
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
411
|
-
config: {},
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
const response = await testFragment.callRoute("GET", "/test", {
|
|
415
|
-
headers: { authorization: "Bearer token" },
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
expect(response.type).toBe("json");
|
|
419
|
-
if (response.type === "json") {
|
|
420
|
-
expect(response.data).toEqual({ authHeader: "Bearer token" });
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("should properly type path params", async () => {
|
|
425
|
-
const fragment = defineFragment<{}>("test");
|
|
426
|
-
|
|
427
|
-
const route = defineRoute({
|
|
428
|
-
method: "GET",
|
|
429
|
-
path: "/orgs/:orgId/users/:userId",
|
|
430
|
-
outputSchema: z.object({ orgId: z.string(), userId: z.string() }),
|
|
431
|
-
handler: async ({ pathParams }, { json }) => {
|
|
432
|
-
return json({ orgId: pathParams.orgId, userId: pathParams.userId });
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const testFragment = createFragmentForTest(fragment, [route], {
|
|
437
|
-
config: {},
|
|
438
|
-
});
|
|
247
|
+
it("should work without withTestUtils (no deps exposed)", () => {
|
|
248
|
+
const definition = defineFragment<{ value: number }>("test")
|
|
249
|
+
.withDependencies(({ config }) => ({
|
|
250
|
+
value: config.value,
|
|
251
|
+
}))
|
|
252
|
+
.providesBaseService(({ deps, defineService }) =>
|
|
253
|
+
defineService({
|
|
254
|
+
getValue: () => deps.value,
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
.build();
|
|
439
258
|
|
|
440
|
-
const
|
|
441
|
-
|
|
259
|
+
const fragment = createFragmentForTest(definition, [], {
|
|
260
|
+
config: { value: 42 },
|
|
442
261
|
});
|
|
443
262
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
263
|
+
// Services should work
|
|
264
|
+
expect(fragment.services.getValue()).toBe(42);
|
|
265
|
+
// But deps should not be exposed
|
|
266
|
+
expect(fragment.services).not.toHaveProperty("deps");
|
|
448
267
|
});
|
|
449
268
|
});
|