@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 { describe, test, expect, 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
|
|
|
5
5
|
// Test service interface definitions
|
|
6
6
|
interface IEmailService {
|
|
@@ -14,75 +14,69 @@ interface ILogger {
|
|
|
14
14
|
describe("Fragment Service System", () => {
|
|
15
15
|
describe("usesService", () => {
|
|
16
16
|
test("should declare required service by default", () => {
|
|
17
|
-
const
|
|
18
|
-
"email",
|
|
19
|
-
|
|
17
|
+
const definition = defineFragment("test-fragment")
|
|
18
|
+
.usesService<"email", IEmailService>("email")
|
|
19
|
+
.build();
|
|
20
20
|
|
|
21
|
-
expect(
|
|
22
|
-
expect(
|
|
21
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
22
|
+
expect(definition.serviceDependencies?.email).toEqual({ name: "email", required: true });
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test("should declare optional service with
|
|
26
|
-
const
|
|
27
|
-
"email",
|
|
28
|
-
|
|
29
|
-
);
|
|
25
|
+
test("should declare optional service with usesOptionalService", () => {
|
|
26
|
+
const definition = defineFragment("test-fragment")
|
|
27
|
+
.usesOptionalService<"email", IEmailService>("email")
|
|
28
|
+
.build();
|
|
30
29
|
|
|
31
|
-
expect(
|
|
32
|
-
expect(
|
|
30
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
31
|
+
expect(definition.serviceDependencies?.email).toEqual({ name: "email", required: false });
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
test("should support multiple required services", () => {
|
|
36
|
-
const
|
|
35
|
+
const definition = defineFragment("test-fragment")
|
|
37
36
|
.usesService<"email", IEmailService>("email")
|
|
38
|
-
.usesService<"logger", ILogger>("logger")
|
|
37
|
+
.usesService<"logger", ILogger>("logger")
|
|
38
|
+
.build();
|
|
39
39
|
|
|
40
|
-
expect(
|
|
41
|
-
expect(
|
|
40
|
+
expect(definition.serviceDependencies?.email).toEqual({ name: "email", required: true });
|
|
41
|
+
expect(definition.serviceDependencies?.logger).toEqual({ name: "logger", required: true });
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("should support mixing required and optional services", () => {
|
|
45
|
-
const
|
|
45
|
+
const definition = defineFragment("test-fragment")
|
|
46
46
|
.usesService<"email", IEmailService>("email")
|
|
47
|
-
.
|
|
47
|
+
.usesOptionalService<"logger", ILogger>("logger")
|
|
48
|
+
.build();
|
|
48
49
|
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
50
|
+
expect(definition.serviceDependencies?.email).toEqual({ name: "email", required: true });
|
|
51
|
+
expect(definition.serviceDependencies?.logger).toEqual({ name: "logger", required: false });
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
test("should preserve other fragment properties", () => {
|
|
54
|
-
const
|
|
55
|
+
const definition = defineFragment<{ apiKey: string }>("test-fragment")
|
|
55
56
|
.withDependencies(() => ({ dep: "value" }))
|
|
56
|
-
.usesService<"email", IEmailService>("email")
|
|
57
|
+
.usesService<"email", IEmailService>("email")
|
|
58
|
+
.build();
|
|
57
59
|
|
|
58
|
-
expect(
|
|
59
|
-
expect(
|
|
60
|
+
expect(definition.name).toBe("test-fragment");
|
|
61
|
+
expect(definition.serviceDependencies?.email).toBeDefined();
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
test("should have correct type inference for required service", () => {
|
|
63
|
-
const
|
|
65
|
+
const definition = defineFragment("test")
|
|
66
|
+
.usesService<"email", IEmailService>("email")
|
|
67
|
+
.build();
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
usedServices?: {
|
|
68
|
-
email: { name: string; required: boolean };
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
}>();
|
|
69
|
+
expect(definition.serviceDependencies?.email).toBeDefined();
|
|
70
|
+
expect(definition.serviceDependencies?.email?.required).toBe(true);
|
|
72
71
|
});
|
|
73
72
|
|
|
74
73
|
test("should have correct type inference for optional service", () => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
usedServices?: {
|
|
82
|
-
logger: { name: string; required: boolean };
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
}>();
|
|
74
|
+
const definition = defineFragment("test")
|
|
75
|
+
.usesOptionalService<"logger", ILogger>("logger")
|
|
76
|
+
.build();
|
|
77
|
+
|
|
78
|
+
expect(definition.serviceDependencies?.logger).toBeDefined();
|
|
79
|
+
expect(definition.serviceDependencies?.logger?.required).toBe(false);
|
|
86
80
|
});
|
|
87
81
|
});
|
|
88
82
|
|
|
@@ -92,12 +86,11 @@ describe("Fragment Service System", () => {
|
|
|
92
86
|
sendEmail: async () => {},
|
|
93
87
|
};
|
|
94
88
|
|
|
95
|
-
const
|
|
96
|
-
"email",
|
|
97
|
-
(
|
|
98
|
-
);
|
|
89
|
+
const definition = defineFragment("test-fragment")
|
|
90
|
+
.providesService("email", () => emailImpl)
|
|
91
|
+
.build();
|
|
99
92
|
|
|
100
|
-
expect(
|
|
93
|
+
expect(definition.namedServices).toBeDefined();
|
|
101
94
|
});
|
|
102
95
|
|
|
103
96
|
test("should support multiple provided services", () => {
|
|
@@ -109,20 +102,22 @@ describe("Fragment Service System", () => {
|
|
|
109
102
|
log: () => {},
|
|
110
103
|
};
|
|
111
104
|
|
|
112
|
-
const
|
|
113
|
-
.providesService("email", (
|
|
114
|
-
.providesService("logger", (
|
|
105
|
+
const _definition = defineFragment("test-fragment")
|
|
106
|
+
.providesService("email", () => emailImpl)
|
|
107
|
+
.providesService("logger", () => loggerImpl)
|
|
108
|
+
.build();
|
|
115
109
|
});
|
|
116
110
|
});
|
|
117
111
|
|
|
118
112
|
describe("Service metadata", () => {
|
|
119
113
|
test("should store service metadata in definition", () => {
|
|
120
|
-
const
|
|
114
|
+
const definition = defineFragment("test")
|
|
121
115
|
.usesService<"email", IEmailService>("email")
|
|
122
|
-
.
|
|
116
|
+
.usesOptionalService<"logger", ILogger>("logger")
|
|
117
|
+
.build();
|
|
123
118
|
|
|
124
|
-
expect(
|
|
125
|
-
expect(
|
|
119
|
+
expect(definition.serviceDependencies?.email?.required).toBe(true);
|
|
120
|
+
expect(definition.serviceDependencies?.logger?.required).toBe(false);
|
|
126
121
|
});
|
|
127
122
|
|
|
128
123
|
test("should store provided services in definition", () => {
|
|
@@ -130,139 +125,159 @@ describe("Fragment Service System", () => {
|
|
|
130
125
|
sendEmail: async () => {},
|
|
131
126
|
};
|
|
132
127
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
const definition = defineFragment("test")
|
|
129
|
+
.providesService("email", () => emailImpl)
|
|
130
|
+
.build();
|
|
136
131
|
|
|
137
|
-
expect(typeof
|
|
132
|
+
expect(typeof definition.namedServices).toBe("object");
|
|
138
133
|
});
|
|
139
134
|
|
|
140
135
|
test("should allow fragments without any services", () => {
|
|
141
|
-
const
|
|
136
|
+
const definition = defineFragment("test").build();
|
|
142
137
|
|
|
143
|
-
expect(
|
|
144
|
-
expect(
|
|
138
|
+
expect(definition.serviceDependencies).toBeUndefined();
|
|
139
|
+
expect(definition.namedServices).toBeUndefined();
|
|
145
140
|
});
|
|
146
141
|
});
|
|
147
142
|
|
|
148
143
|
describe("Type safety", () => {
|
|
149
|
-
test("Unnamed services should have correct types (using
|
|
150
|
-
const
|
|
151
|
-
|
|
144
|
+
test("Unnamed services should have correct types (using providesBaseService)", () => {
|
|
145
|
+
const definition = defineFragment("test")
|
|
146
|
+
.providesBaseService(() => ({
|
|
152
147
|
sendEmail: async () => {},
|
|
153
|
-
})
|
|
154
|
-
|
|
148
|
+
}))
|
|
149
|
+
.build();
|
|
155
150
|
|
|
156
|
-
const instance =
|
|
151
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
157
152
|
expect(instance.services.sendEmail).toBeDefined();
|
|
158
153
|
expectTypeOf<typeof instance.services.sendEmail>().toExtend<() => Promise<void>>();
|
|
159
154
|
});
|
|
160
155
|
|
|
161
|
-
test("Named services should have correct types
|
|
162
|
-
const
|
|
163
|
-
|
|
156
|
+
test("Named services should have correct types", () => {
|
|
157
|
+
const definition = defineFragment("test")
|
|
158
|
+
.providesService("email", () => ({
|
|
164
159
|
sendEmail: async () => {},
|
|
165
|
-
})
|
|
166
|
-
|
|
160
|
+
}))
|
|
161
|
+
.build();
|
|
167
162
|
|
|
168
|
-
const instance =
|
|
163
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
169
164
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
170
165
|
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<() => Promise<void>>();
|
|
171
166
|
});
|
|
172
167
|
|
|
173
|
-
test("Unnamed services should have correct types (using
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
test("Unnamed services should have correct types (using factory)", () => {
|
|
169
|
+
const definition = defineFragment("test")
|
|
170
|
+
.providesBaseService(() => ({
|
|
171
|
+
sendEmail: async () => {},
|
|
172
|
+
}))
|
|
173
|
+
.build();
|
|
177
174
|
|
|
178
|
-
const instance =
|
|
175
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
179
176
|
expect(instance.services.sendEmail).toBeDefined();
|
|
180
177
|
expectTypeOf<typeof instance.services.sendEmail>().toExtend<() => Promise<void>>();
|
|
181
178
|
});
|
|
182
179
|
|
|
183
180
|
test("Unnamed services should have correct types (using callback with context)", () => {
|
|
184
|
-
const
|
|
185
|
-
|
|
181
|
+
const definition = defineFragment("test")
|
|
182
|
+
.providesBaseService(() => ({
|
|
186
183
|
sendEmail: async () => {},
|
|
187
|
-
})
|
|
188
|
-
|
|
184
|
+
}))
|
|
185
|
+
.build();
|
|
189
186
|
|
|
190
|
-
const instance =
|
|
187
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
191
188
|
expect(instance.services.sendEmail).toBeDefined();
|
|
192
189
|
expectTypeOf<typeof instance.services.sendEmail>().toExtend<() => Promise<void>>();
|
|
193
190
|
});
|
|
194
191
|
|
|
195
192
|
test("Unnamed services should have correct types (using 0-arity factory)", () => {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
const definition = defineFragment("test")
|
|
194
|
+
.providesBaseService(() => ({
|
|
195
|
+
sendEmail: async () => {},
|
|
196
|
+
}))
|
|
197
|
+
.build();
|
|
199
198
|
|
|
200
|
-
const instance =
|
|
199
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
201
200
|
expect(instance.services.sendEmail).toBeDefined();
|
|
202
201
|
expectTypeOf<typeof instance.services.sendEmail>().toExtend<() => Promise<void>>();
|
|
203
202
|
});
|
|
204
203
|
|
|
205
|
-
test("Named services should have correct types (using
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
test("Named services should have correct types (using factory)", () => {
|
|
205
|
+
const definition = defineFragment("test")
|
|
206
|
+
.providesService("email", () => ({
|
|
207
|
+
sendEmail: async () => {},
|
|
208
|
+
}))
|
|
209
|
+
.build();
|
|
209
210
|
|
|
210
|
-
const instance =
|
|
211
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
211
212
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
212
213
|
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<() => Promise<void>>();
|
|
213
214
|
});
|
|
214
215
|
|
|
215
216
|
test("usesService (required)", () => {
|
|
216
|
-
const
|
|
217
|
+
const definition = defineFragment("test")
|
|
218
|
+
.usesService<"email", IEmailService>("email")
|
|
219
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
220
|
+
sendEmail: (to: string, subject: string, body: string) => {
|
|
221
|
+
return serviceDeps.email.sendEmail(to, subject, body);
|
|
222
|
+
},
|
|
223
|
+
}))
|
|
224
|
+
.build();
|
|
217
225
|
|
|
218
226
|
const emailImpl: IEmailService = {
|
|
219
227
|
sendEmail: async () => {},
|
|
220
228
|
};
|
|
221
229
|
|
|
222
|
-
const instance =
|
|
223
|
-
|
|
224
|
-
{}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
},
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<
|
|
230
|
+
const instance = instantiate(definition)
|
|
231
|
+
.withOptions({})
|
|
232
|
+
.withServices({ email: emailImpl })
|
|
233
|
+
.build();
|
|
234
|
+
|
|
235
|
+
expect(instance.services.sendEmail).toBeDefined();
|
|
236
|
+
expectTypeOf<typeof instance.services.sendEmail>().toExtend<
|
|
233
237
|
(to: string, subject: string, body: string) => void
|
|
234
238
|
>();
|
|
235
239
|
});
|
|
236
240
|
|
|
237
241
|
test("usesService (required) - builder style", () => {
|
|
238
|
-
const
|
|
242
|
+
const definition = defineFragment("test")
|
|
243
|
+
.usesService<"email", IEmailService>("email")
|
|
244
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
245
|
+
sendEmail: (to: string, subject: string, body: string) => {
|
|
246
|
+
return serviceDeps.email.sendEmail(to, subject, body);
|
|
247
|
+
},
|
|
248
|
+
}))
|
|
249
|
+
.build();
|
|
239
250
|
|
|
240
251
|
const emailImpl: IEmailService = {
|
|
241
252
|
sendEmail: async () => {},
|
|
242
253
|
};
|
|
243
254
|
|
|
244
|
-
const instance =
|
|
255
|
+
const instance = instantiate(definition)
|
|
256
|
+
.withServices({ email: emailImpl })
|
|
257
|
+
.withOptions({})
|
|
258
|
+
.build();
|
|
245
259
|
|
|
246
|
-
|
|
260
|
+
expect(instance.services.sendEmail).toBeDefined();
|
|
261
|
+
expectTypeOf<typeof instance.services.sendEmail>().toExtend<
|
|
247
262
|
(to: string, subject: string, body: string) => void
|
|
248
263
|
>();
|
|
249
264
|
});
|
|
250
265
|
|
|
251
|
-
test("
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
266
|
+
test("usesOptionalService", () => {
|
|
267
|
+
const definition = defineFragment("test")
|
|
268
|
+
.usesOptionalService<"email", IEmailService>("email")
|
|
269
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
270
|
+
sendEmail: serviceDeps.email
|
|
271
|
+
? (to: string, subject: string, body: string) =>
|
|
272
|
+
serviceDeps.email!.sendEmail(to, subject, body)
|
|
273
|
+
: undefined,
|
|
274
|
+
}))
|
|
275
|
+
.build();
|
|
255
276
|
|
|
256
|
-
const instance =
|
|
257
|
-
// For optional services, the service itself might be undefined
|
|
258
|
-
expectTypeOf<typeof instance.services.email>().toExtend<IEmailService | undefined>();
|
|
277
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
259
278
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<
|
|
263
|
-
(to: string, subject: string, body: string) => Promise<void>
|
|
264
|
-
>();
|
|
265
|
-
}
|
|
279
|
+
// For optional services, the wrapped service method might be undefined
|
|
280
|
+
expect(instance.services.sendEmail).toBeUndefined();
|
|
266
281
|
});
|
|
267
282
|
|
|
268
283
|
test("provided services should have correct types", () => {
|
|
@@ -270,54 +285,89 @@ describe("Fragment Service System", () => {
|
|
|
270
285
|
sendEmail: async () => {},
|
|
271
286
|
};
|
|
272
287
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
const definition = defineFragment("test")
|
|
289
|
+
.providesService("email", () => emailImpl)
|
|
290
|
+
.build();
|
|
276
291
|
|
|
277
|
-
//
|
|
278
|
-
expect(
|
|
279
|
-
expect(typeof
|
|
292
|
+
// namedServices stores an object with service names as keys and factory functions as values
|
|
293
|
+
expect(definition.namedServices).toBeDefined();
|
|
294
|
+
expect(typeof definition.namedServices).toBe("object");
|
|
280
295
|
});
|
|
281
296
|
|
|
282
297
|
test("Named services should have correct types (using callback with context)", () => {
|
|
283
|
-
const
|
|
284
|
-
|
|
298
|
+
const definition = defineFragment("test")
|
|
299
|
+
.providesService("email", () => ({
|
|
285
300
|
sendEmail: async () => {},
|
|
286
|
-
})
|
|
287
|
-
|
|
301
|
+
}))
|
|
302
|
+
.build();
|
|
288
303
|
|
|
289
|
-
const instance =
|
|
304
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
290
305
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
291
306
|
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<() => Promise<void>>();
|
|
292
307
|
});
|
|
293
308
|
|
|
294
309
|
test("Named services should have correct types (using 0-arity factory)", () => {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
const definition = defineFragment("test")
|
|
311
|
+
.providesService("email", () => ({
|
|
312
|
+
sendEmail: async () => {},
|
|
313
|
+
}))
|
|
314
|
+
.build();
|
|
298
315
|
|
|
299
|
-
const instance =
|
|
316
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
300
317
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
301
318
|
expectTypeOf<typeof instance.services.email.sendEmail>().toExtend<() => Promise<void>>();
|
|
302
319
|
});
|
|
320
|
+
|
|
321
|
+
test("Type mismatch when using a service", () => {
|
|
322
|
+
interface ExpectedService {
|
|
323
|
+
throwDice: () => 1 | 2 | 3 | 4 | 5 | 6;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const definition = defineFragment("test")
|
|
327
|
+
.usesService<"expected", ExpectedService>("expected")
|
|
328
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
329
|
+
throwDice: () => serviceDeps.expected.throwDice(),
|
|
330
|
+
}))
|
|
331
|
+
.build();
|
|
332
|
+
|
|
333
|
+
interface ActualService {
|
|
334
|
+
throwDice: () => number;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const actualService: ActualService = {
|
|
338
|
+
throwDice: () => 1,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const instance = instantiate(definition)
|
|
342
|
+
// @ts-expect-error - Type mismatch
|
|
343
|
+
.withServices({ expected: actualService })
|
|
344
|
+
.withOptions({})
|
|
345
|
+
.build();
|
|
346
|
+
|
|
347
|
+
// The wrapped service on the instance has the correct type based on the declared service
|
|
348
|
+
expect(instance.services.throwDice).toBeDefined();
|
|
349
|
+
expectTypeOf<typeof instance.services.throwDice>().toExtend<() => 1 | 2 | 3 | 4 | 5 | 6>();
|
|
350
|
+
});
|
|
303
351
|
});
|
|
304
352
|
|
|
305
353
|
describe("Error handling", () => {
|
|
306
354
|
test("should throw error when required service is not provided", () => {
|
|
307
|
-
const
|
|
355
|
+
const definition = defineFragment("test")
|
|
356
|
+
.usesService<"email", IEmailService>("email")
|
|
357
|
+
.build();
|
|
308
358
|
|
|
309
359
|
expect(() => {
|
|
310
|
-
|
|
360
|
+
instantiate(definition).withOptions({}).build();
|
|
311
361
|
}).toThrow("Fragment 'test' requires service 'email' but it was not provided");
|
|
312
362
|
});
|
|
313
363
|
|
|
314
364
|
test("should not throw when optional service is not provided", () => {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
365
|
+
const definition = defineFragment("test")
|
|
366
|
+
.usesOptionalService<"email", IEmailService>("email")
|
|
367
|
+
.build();
|
|
318
368
|
|
|
319
369
|
expect(() => {
|
|
320
|
-
|
|
370
|
+
instantiate(definition).withOptions({}).build();
|
|
321
371
|
}).not.toThrow();
|
|
322
372
|
});
|
|
323
373
|
});
|
|
@@ -328,15 +378,19 @@ describe("Fragment Service System", () => {
|
|
|
328
378
|
sendEmail: async () => {},
|
|
329
379
|
};
|
|
330
380
|
|
|
331
|
-
const
|
|
381
|
+
const definition = defineFragment("test")
|
|
332
382
|
.usesService<"email", IEmailService>("email")
|
|
333
|
-
.
|
|
383
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
334
384
|
sendWelcomeEmail: async (to: string) => {
|
|
335
|
-
await
|
|
385
|
+
await serviceDeps.email.sendEmail(to, "Welcome", "Welcome to our service!");
|
|
336
386
|
},
|
|
337
|
-
}))
|
|
387
|
+
}))
|
|
388
|
+
.build();
|
|
338
389
|
|
|
339
|
-
const instance =
|
|
390
|
+
const instance = instantiate(definition)
|
|
391
|
+
.withOptions({})
|
|
392
|
+
.withServices({ email: emailImpl })
|
|
393
|
+
.build();
|
|
340
394
|
|
|
341
395
|
expect(instance.services.sendWelcomeEmail).toBeDefined();
|
|
342
396
|
expect(typeof instance.services.sendWelcomeEmail).toBe("function");
|
|
@@ -347,40 +401,53 @@ describe("Fragment Service System", () => {
|
|
|
347
401
|
sendEmail: async () => {},
|
|
348
402
|
};
|
|
349
403
|
|
|
350
|
-
const
|
|
404
|
+
const definition = defineFragment("test")
|
|
351
405
|
.usesService<"email", IEmailService>("email")
|
|
352
|
-
.
|
|
406
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
353
407
|
sendWelcomeEmail: async (to: string) => {
|
|
354
|
-
await
|
|
408
|
+
await serviceDeps.email.sendEmail(to, "Welcome", "Welcome to our service!");
|
|
355
409
|
},
|
|
356
|
-
}))
|
|
410
|
+
}))
|
|
411
|
+
.build();
|
|
357
412
|
|
|
358
|
-
const instance =
|
|
413
|
+
const instance = instantiate(definition)
|
|
414
|
+
.withServices({ email: emailImpl })
|
|
415
|
+
.withOptions({})
|
|
416
|
+
.build();
|
|
359
417
|
|
|
360
418
|
expect(instance.services.sendWelcomeEmail).toBeDefined();
|
|
361
419
|
expect(typeof instance.services.sendWelcomeEmail).toBe("function");
|
|
362
420
|
});
|
|
363
421
|
|
|
364
422
|
test("provided service can access config", () => {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
423
|
+
const definition = defineFragment<{ apiKey: string }>("test")
|
|
424
|
+
.providesBaseService(({ config }) => ({
|
|
425
|
+
getApiKey: () => config.apiKey,
|
|
426
|
+
}))
|
|
427
|
+
.build();
|
|
368
428
|
|
|
369
|
-
const instance =
|
|
429
|
+
const instance = instantiate(definition)
|
|
430
|
+
.withConfig({ apiKey: "test-key" })
|
|
431
|
+
.withOptions({})
|
|
432
|
+
.build();
|
|
370
433
|
|
|
371
434
|
expect(instance.services.getApiKey()).toBe("test-key");
|
|
372
435
|
});
|
|
373
436
|
|
|
374
437
|
test("provided service can access deps from withDependencies", () => {
|
|
375
|
-
const
|
|
438
|
+
const definition = defineFragment<{ apiKey: string }>("test")
|
|
376
439
|
.withDependencies(({ config }) => ({
|
|
377
440
|
client: { key: config.apiKey },
|
|
378
441
|
}))
|
|
379
|
-
.
|
|
442
|
+
.providesBaseService(({ deps }) => ({
|
|
380
443
|
getClient: () => deps.client,
|
|
381
|
-
}))
|
|
444
|
+
}))
|
|
445
|
+
.build();
|
|
382
446
|
|
|
383
|
-
const instance =
|
|
447
|
+
const instance = instantiate(definition)
|
|
448
|
+
.withConfig({ apiKey: "test-key" })
|
|
449
|
+
.withOptions({})
|
|
450
|
+
.build();
|
|
384
451
|
|
|
385
452
|
expect(instance.services.getClient()).toEqual({ key: "test-key" });
|
|
386
453
|
});
|
|
@@ -388,29 +455,31 @@ describe("Fragment Service System", () => {
|
|
|
388
455
|
|
|
389
456
|
describe("Service chaining and multiple services", () => {
|
|
390
457
|
test("should support chaining multiple provided services", () => {
|
|
391
|
-
const
|
|
392
|
-
.providesService("email", {
|
|
458
|
+
const definition = defineFragment("test")
|
|
459
|
+
.providesService("email", () => ({
|
|
393
460
|
sendEmail: async () => {},
|
|
394
|
-
})
|
|
395
|
-
.providesService("logger", {
|
|
461
|
+
}))
|
|
462
|
+
.providesService("logger", () => ({
|
|
396
463
|
log: () => {},
|
|
397
|
-
})
|
|
464
|
+
}))
|
|
465
|
+
.build();
|
|
398
466
|
|
|
399
|
-
const instance =
|
|
467
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
400
468
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
401
469
|
expect(instance.services.logger.log).toBeDefined();
|
|
402
470
|
});
|
|
403
471
|
|
|
404
472
|
test("should support mixing unnamed and named provided services", () => {
|
|
405
|
-
const
|
|
406
|
-
.
|
|
473
|
+
const definition = defineFragment("test")
|
|
474
|
+
.providesBaseService(() => ({
|
|
407
475
|
helper: () => "help",
|
|
408
|
-
})
|
|
409
|
-
.providesService("email", {
|
|
476
|
+
}))
|
|
477
|
+
.providesService("email", () => ({
|
|
410
478
|
sendEmail: async () => {},
|
|
411
|
-
})
|
|
479
|
+
}))
|
|
480
|
+
.build();
|
|
412
481
|
|
|
413
|
-
const instance =
|
|
482
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
414
483
|
expect(instance.services.helper).toBeDefined();
|
|
415
484
|
expect(instance.services.email.sendEmail).toBeDefined();
|
|
416
485
|
});
|
|
@@ -418,19 +487,20 @@ describe("Fragment Service System", () => {
|
|
|
418
487
|
|
|
419
488
|
describe("Optional service runtime behavior", () => {
|
|
420
489
|
test("should handle optional service when not provided", () => {
|
|
421
|
-
const
|
|
422
|
-
.
|
|
423
|
-
.
|
|
490
|
+
const definition = defineFragment("test")
|
|
491
|
+
.usesOptionalService<"email", IEmailService>("email")
|
|
492
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
424
493
|
maybeSendEmail: async (to: string) => {
|
|
425
|
-
if (
|
|
426
|
-
await
|
|
494
|
+
if (serviceDeps.email) {
|
|
495
|
+
await serviceDeps.email.sendEmail(to, "Subject", "Body");
|
|
427
496
|
return true;
|
|
428
497
|
}
|
|
429
498
|
return false;
|
|
430
499
|
},
|
|
431
|
-
}))
|
|
500
|
+
}))
|
|
501
|
+
.build();
|
|
432
502
|
|
|
433
|
-
const instance =
|
|
503
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
434
504
|
|
|
435
505
|
expect(instance.services.maybeSendEmail).toBeDefined();
|
|
436
506
|
// Should not throw when optional service is not provided
|
|
@@ -441,22 +511,217 @@ describe("Fragment Service System", () => {
|
|
|
441
511
|
sendEmail: async () => {},
|
|
442
512
|
};
|
|
443
513
|
|
|
444
|
-
const
|
|
445
|
-
.
|
|
446
|
-
.
|
|
514
|
+
const definition = defineFragment("test")
|
|
515
|
+
.usesOptionalService<"email", IEmailService>("email")
|
|
516
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
447
517
|
maybeSendEmail: async (to: string) => {
|
|
448
|
-
if (
|
|
449
|
-
await
|
|
518
|
+
if (serviceDeps.email) {
|
|
519
|
+
await serviceDeps.email.sendEmail(to, "Subject", "Body");
|
|
450
520
|
return true;
|
|
451
521
|
}
|
|
452
522
|
return false;
|
|
453
523
|
},
|
|
454
|
-
}))
|
|
524
|
+
}))
|
|
525
|
+
.build();
|
|
455
526
|
|
|
456
|
-
const instance =
|
|
527
|
+
const instance = instantiate(definition)
|
|
528
|
+
.withOptions({})
|
|
529
|
+
.withServices({ email: emailImpl })
|
|
530
|
+
.build();
|
|
457
531
|
|
|
458
|
-
expect(instance.services.email).toBeDefined();
|
|
459
532
|
expect(instance.services.maybeSendEmail).toBeDefined();
|
|
533
|
+
// When the optional service is provided, the wrapped method should work
|
|
534
|
+
expect(typeof instance.services.maybeSendEmail).toBe("function");
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe("Private Services", () => {
|
|
539
|
+
test("private service should be accessible when defining other services", () => {
|
|
540
|
+
interface IDataStore {
|
|
541
|
+
get(key: string): string | undefined;
|
|
542
|
+
set(key: string, value: string): void;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const dataStoreImpl: IDataStore = {
|
|
546
|
+
get: () => "cached-value",
|
|
547
|
+
set: () => {},
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const definition = defineFragment("test")
|
|
551
|
+
.providesPrivateService("dataStore", () => dataStoreImpl)
|
|
552
|
+
.providesBaseService(({ privateServices }) => ({
|
|
553
|
+
getValue: (key: string) => {
|
|
554
|
+
// Private service is accessible here
|
|
555
|
+
return privateServices.dataStore.get(key);
|
|
556
|
+
},
|
|
557
|
+
}))
|
|
558
|
+
.build();
|
|
559
|
+
|
|
560
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
561
|
+
|
|
562
|
+
// Private service should NOT be accessible on the instance
|
|
563
|
+
expectTypeOf<typeof instance.services>().not.toMatchTypeOf<{ dataStore: IDataStore }>();
|
|
564
|
+
|
|
565
|
+
// But the public service that uses it should work
|
|
566
|
+
expect(instance.services.getValue).toBeDefined();
|
|
567
|
+
expect(instance.services.getValue("test")).toBe("cached-value");
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test("private service should NOT be exposed on fragment instance", () => {
|
|
571
|
+
interface IInternalCache {
|
|
572
|
+
cache: Map<string, unknown>;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const definition = defineFragment("test")
|
|
576
|
+
.providesPrivateService<"cache", IInternalCache>("cache", () => ({
|
|
577
|
+
cache: new Map(),
|
|
578
|
+
}))
|
|
579
|
+
.providesBaseService(({ privateServices }) => ({
|
|
580
|
+
getCacheSize: () => privateServices.cache.cache.size,
|
|
581
|
+
}))
|
|
582
|
+
.build();
|
|
583
|
+
|
|
584
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
585
|
+
|
|
586
|
+
// @ts-expect-error - Private service should not be accessible
|
|
587
|
+
expect(instance.services.cache).toBeUndefined();
|
|
588
|
+
|
|
589
|
+
// Only the public service should be accessible
|
|
590
|
+
expect(instance.services.getCacheSize).toBeDefined();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("multiple private services should work together", () => {
|
|
594
|
+
interface ILogger {
|
|
595
|
+
log(msg: string): void;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
interface ICache {
|
|
599
|
+
get(key: string): unknown;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const logger: ILogger = {
|
|
603
|
+
log: () => {},
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const cache: ICache = {
|
|
607
|
+
get: () => "cached",
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const definition = defineFragment("test")
|
|
611
|
+
.providesPrivateService("logger", () => logger)
|
|
612
|
+
.providesPrivateService("cache", () => cache)
|
|
613
|
+
.providesBaseService(({ privateServices }) => ({
|
|
614
|
+
getCachedValue: (key: string) => {
|
|
615
|
+
privateServices.logger.log(`Getting ${key}`);
|
|
616
|
+
return privateServices.cache.get(key);
|
|
617
|
+
},
|
|
618
|
+
}))
|
|
619
|
+
.build();
|
|
620
|
+
|
|
621
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
622
|
+
|
|
623
|
+
expect(instance.services.getCachedValue).toBeDefined();
|
|
624
|
+
expect(instance.services.getCachedValue("test")).toBe("cached");
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("private services can access config and deps", () => {
|
|
628
|
+
const definition = defineFragment<{ apiKey: string }>("test")
|
|
629
|
+
.withDependencies(({ config }) => ({
|
|
630
|
+
endpoint: `https://api.example.com/${config.apiKey}`,
|
|
631
|
+
}))
|
|
632
|
+
.providesPrivateService("internalApi", ({ deps }) => ({
|
|
633
|
+
makeRequest: () => `${deps.endpoint}/request`,
|
|
634
|
+
}))
|
|
635
|
+
.providesBaseService(({ privateServices }) => ({
|
|
636
|
+
doRequest: () => privateServices.internalApi.makeRequest(),
|
|
637
|
+
}))
|
|
638
|
+
.build();
|
|
639
|
+
|
|
640
|
+
const instance = instantiate(definition)
|
|
641
|
+
.withConfig({ apiKey: "test-key" })
|
|
642
|
+
.withOptions({})
|
|
643
|
+
.build();
|
|
644
|
+
|
|
645
|
+
expect(instance.services.doRequest()).toBe("https://api.example.com/test-key/request");
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test("private services can access serviceDeps", () => {
|
|
649
|
+
interface IEmailService {
|
|
650
|
+
sendEmail(to: string): Promise<void>;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const emailImpl: IEmailService = {
|
|
654
|
+
sendEmail: async () => {},
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const definition = defineFragment("test")
|
|
658
|
+
.usesService<"email", IEmailService>("email")
|
|
659
|
+
.providesPrivateService("emailHelper", ({ serviceDeps }) => ({
|
|
660
|
+
sendWelcomeEmail: (to: string) => serviceDeps.email.sendEmail(to),
|
|
661
|
+
}))
|
|
662
|
+
.providesBaseService(({ privateServices }) => ({
|
|
663
|
+
welcomeUser: (email: string) => privateServices.emailHelper.sendWelcomeEmail(email),
|
|
664
|
+
}))
|
|
665
|
+
.build();
|
|
666
|
+
|
|
667
|
+
const instance = instantiate(definition)
|
|
668
|
+
.withServices({ email: emailImpl })
|
|
669
|
+
.withOptions({})
|
|
670
|
+
.build();
|
|
671
|
+
|
|
672
|
+
expect(instance.services.welcomeUser).toBeDefined();
|
|
673
|
+
expectTypeOf<typeof instance.services.welcomeUser>().toExtend<
|
|
674
|
+
(email: string) => Promise<void>
|
|
675
|
+
>();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("named services can also access private services", () => {
|
|
679
|
+
const definition = defineFragment("test")
|
|
680
|
+
.providesPrivateService("helper", () => ({
|
|
681
|
+
multiply: (a: number, b: number) => a * b,
|
|
682
|
+
}))
|
|
683
|
+
.providesService("calculator", ({ privateServices }) => ({
|
|
684
|
+
square: (n: number) => privateServices.helper.multiply(n, n),
|
|
685
|
+
}))
|
|
686
|
+
.build();
|
|
687
|
+
|
|
688
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
689
|
+
|
|
690
|
+
expect(instance.services.calculator.square(5)).toBe(25);
|
|
691
|
+
// @ts-expect-error - Private service should not be accessible
|
|
692
|
+
expect(instance.services.helper).toBeUndefined();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("private services can access other private services (in order)", () => {
|
|
696
|
+
const definition = defineFragment("test")
|
|
697
|
+
.providesPrivateService("math", () => ({
|
|
698
|
+
add: (a: number, b: number) => a + b,
|
|
699
|
+
multiply: (a: number, b: number) => a * b,
|
|
700
|
+
}))
|
|
701
|
+
.providesPrivateService("calculator", ({ privateServices }) => ({
|
|
702
|
+
// This private service can access the earlier private service
|
|
703
|
+
square: (n: number) => privateServices.math.multiply(n, n),
|
|
704
|
+
addTen: (n: number) => privateServices.math.add(n, 10),
|
|
705
|
+
}))
|
|
706
|
+
.providesBaseService(({ privateServices }) => ({
|
|
707
|
+
compute: (n: number) => {
|
|
708
|
+
// Public service can access both private services
|
|
709
|
+
const squared = privateServices.calculator.square(n);
|
|
710
|
+
return privateServices.calculator.addTen(squared);
|
|
711
|
+
},
|
|
712
|
+
}))
|
|
713
|
+
.build();
|
|
714
|
+
|
|
715
|
+
const instance = instantiate(definition).withOptions({}).build();
|
|
716
|
+
|
|
717
|
+
// 5^2 = 25, 25 + 10 = 35
|
|
718
|
+
expect(instance.services.compute(5)).toBe(35);
|
|
719
|
+
|
|
720
|
+
// Private services should not be accessible on the instance
|
|
721
|
+
// @ts-expect-error - Private service should not be accessible
|
|
722
|
+
expect(instance.services.math).toBeUndefined();
|
|
723
|
+
// @ts-expect-error - Private service should not be accessible
|
|
724
|
+
expect(instance.services.calculator).toBeUndefined();
|
|
460
725
|
});
|
|
461
726
|
});
|
|
462
727
|
});
|