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