skyr 0.1.0
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/README.md +513 -0
- package/package.json +19 -0
- package/src/di.test.ts +299 -0
- package/src/di.ts +624 -0
- package/src/index.ts +11 -0
- package/src/result.test.ts +1052 -0
- package/src/result.ts +663 -0
- package/src/types.test.ts +254 -0
package/src/di.test.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { fn } from "./di.ts";
|
|
3
|
+
import { fail, fromThrowable, ok } from "./result.ts";
|
|
4
|
+
|
|
5
|
+
test("fn with no dependencies", () => {
|
|
6
|
+
const add = fn(function* (a: number, b: number) {
|
|
7
|
+
const n1 = yield* ok(a);
|
|
8
|
+
const n2 = yield* ok(b);
|
|
9
|
+
return n1 + n2;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const result = add.run(2, 3);
|
|
13
|
+
expect(result.unwrap()).toBe(5);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("fn with single dependency", () => {
|
|
17
|
+
const PrefixService = fn.dependency<string>()("prefix");
|
|
18
|
+
|
|
19
|
+
const greet = fn(function* (name: string) {
|
|
20
|
+
const prefix = yield* fn.require(PrefixService);
|
|
21
|
+
return ok(`${prefix} ${name}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const result = greet.inject(PrefixService.impl("Hello")).run("World");
|
|
25
|
+
expect(result.unwrap()).toBe("Hello World");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("fn with multiple dependencies", () => {
|
|
29
|
+
const MultiplierService = fn.dependency<number>()("multiplier");
|
|
30
|
+
const OffsetService = fn.dependency<number>()("offset");
|
|
31
|
+
|
|
32
|
+
const calculate = fn(function* (x: number) {
|
|
33
|
+
const multiplier = yield* fn.require(MultiplierService);
|
|
34
|
+
const offset = yield* fn.require(OffsetService);
|
|
35
|
+
return ok(x * multiplier + offset);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = calculate
|
|
39
|
+
.inject(MultiplierService.impl(2), OffsetService.impl(10))
|
|
40
|
+
.run(5);
|
|
41
|
+
expect(result.unwrap()).toBe(20);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("fn throws on missing dependency", () => {
|
|
45
|
+
const MissingService = fn.dependency<number>()("missing");
|
|
46
|
+
|
|
47
|
+
const needsDep = fn(function* () {
|
|
48
|
+
const value = yield* fn.require(MissingService);
|
|
49
|
+
return ok(value);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(() => needsDep.run()).toThrow("Missing dependency: missing");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("yield* unwraps ok results", () => {
|
|
56
|
+
const double = fn(function* (n: number) {
|
|
57
|
+
const result = yield* ok(n * 2);
|
|
58
|
+
return ok(result + 1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = double.run(5);
|
|
62
|
+
expect(result.unwrap()).toBe(11);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("yield* short-circuits on err", () => {
|
|
66
|
+
const failing = fn(function* (n: number) {
|
|
67
|
+
const _result = yield* fail("FAILED", "Operation failed");
|
|
68
|
+
return ok(n * 2); // Should not reach here
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = failing.run(5);
|
|
72
|
+
expect(result.hasFailed()).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("async result handling", async () => {
|
|
76
|
+
const fetchValue = fn(function* () {
|
|
77
|
+
yield Promise.resolve(42);
|
|
78
|
+
return ok(10);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await fetchValue.run();
|
|
82
|
+
expect(result.unwrap()).toBe(10);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("auto-wraps plain return values in ok", () => {
|
|
86
|
+
const add = fn(function* (a: number, b: number) {
|
|
87
|
+
return a + b;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const result = add.run(2, 3);
|
|
91
|
+
expect(result.isOk()).toBe(true);
|
|
92
|
+
expect(result.unwrap()).toBe(5);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("auto-wrapping preserves explicit Result returns", () => {
|
|
96
|
+
const mayFail = fn(function* (n: number) {
|
|
97
|
+
if (n < 0) return fail("NEGATIVE", "Number is negative");
|
|
98
|
+
return ok(n * 2);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const okResult = mayFail.run(5);
|
|
102
|
+
expect(okResult.isOk()).toBe(true);
|
|
103
|
+
expect(okResult.unwrap()).toBe(10);
|
|
104
|
+
|
|
105
|
+
const errResult = mayFail.run(-5);
|
|
106
|
+
expect(errResult.hasFailed()).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("async generator switches to async mode", async () => {
|
|
110
|
+
const fetchValue = fn(function* (n: number) {
|
|
111
|
+
yield fromThrowable(() => Promise.resolve(n));
|
|
112
|
+
return ok(10);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await fetchValue.run(5);
|
|
116
|
+
expect(result.unwrap()).toBe(10);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("async generator with early error from AsyncResult", async () => {
|
|
120
|
+
const fetchValue = fn(function* () {
|
|
121
|
+
yield fromThrowable(() => Promise.reject("failed"));
|
|
122
|
+
return ok(10);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await fetchValue.run();
|
|
126
|
+
expect(result.hasFailed()).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("async generator with dependency after async", async () => {
|
|
130
|
+
const MultiplierService = fn.dependency<number>()("multiplier");
|
|
131
|
+
|
|
132
|
+
const compute = fn(function* (n: number) {
|
|
133
|
+
yield Promise.resolve(n);
|
|
134
|
+
const multiplier = yield* fn.require(MultiplierService);
|
|
135
|
+
return ok(n * multiplier);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = await compute.inject(MultiplierService.impl(3)).run(5);
|
|
139
|
+
expect(result.unwrap()).toBe(15);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("async generator with error after dependency", async () => {
|
|
143
|
+
const MultiplierService = fn.dependency<number>()("multiplier");
|
|
144
|
+
|
|
145
|
+
const compute = fn(function* (n: number) {
|
|
146
|
+
yield Promise.resolve(42);
|
|
147
|
+
const multiplier = yield* fn.require(MultiplierService);
|
|
148
|
+
if (multiplier < 0) {
|
|
149
|
+
return fail("NEGATIVE_MULTIPLIER", "Multiplier is negative");
|
|
150
|
+
}
|
|
151
|
+
return ok(n * multiplier);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const result = await compute.inject(MultiplierService.impl(-1)).run(5);
|
|
155
|
+
expect(result.hasFailed()).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("async generator with plain Promise", async () => {
|
|
159
|
+
const compute = fn(function* (n: number) {
|
|
160
|
+
yield Promise.resolve(100);
|
|
161
|
+
return ok(n * 2);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const result = await compute.run(5);
|
|
165
|
+
expect(result.unwrap()).toBe(10);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("async generator with multiple async yields", async () => {
|
|
169
|
+
const compute = fn(function* (n: number) {
|
|
170
|
+
yield fromThrowable(() => Promise.resolve(10));
|
|
171
|
+
yield fromThrowable(() => Promise.resolve(20));
|
|
172
|
+
return ok(n * 2);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const result = await compute.run(5);
|
|
176
|
+
expect(result.unwrap()).toBe(10);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("async generator with error in middle", async () => {
|
|
180
|
+
const compute = fn(function* () {
|
|
181
|
+
yield fromThrowable(() => Promise.resolve(10));
|
|
182
|
+
yield fromThrowable(() => Promise.reject("failed"));
|
|
183
|
+
return ok(30);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const result = await compute.run();
|
|
187
|
+
expect(result.hasFailed()).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("async generator returning Result", async () => {
|
|
191
|
+
const compute = fn(function* (n: number) {
|
|
192
|
+
yield Promise.resolve(42);
|
|
193
|
+
return ok(n * 2);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const result = await compute.run(5);
|
|
197
|
+
expect(result.isOk()).toBe(true);
|
|
198
|
+
expect(result.unwrap()).toBe(10);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("async generator returning plain value", async () => {
|
|
202
|
+
const compute = fn(function* (n: number) {
|
|
203
|
+
yield Promise.resolve(42);
|
|
204
|
+
return n * 2;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const result = await compute.run(5);
|
|
208
|
+
expect(result.isOk()).toBe(true);
|
|
209
|
+
expect(result.unwrap()).toBe(10);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("async generator with err after async", async () => {
|
|
213
|
+
const compute = fn(function* () {
|
|
214
|
+
yield Promise.resolve(42);
|
|
215
|
+
return fail("FAILED", "Operation failed");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const result = await compute.run();
|
|
219
|
+
expect(result.hasFailed()).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("sync generator with plain Ok yield", () => {
|
|
223
|
+
const compute = fn(function* () {
|
|
224
|
+
yield ok(42);
|
|
225
|
+
return ok(10);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const result = compute.run();
|
|
229
|
+
expect(result.unwrap()).toBe(10);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("yield* unwraps AsyncResult", async () => {
|
|
233
|
+
const compute = fn(function* (n: number) {
|
|
234
|
+
const value = yield* fromThrowable(() => Promise.resolve(n * 2));
|
|
235
|
+
return ok(value + 10);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const result = await compute.run(5);
|
|
239
|
+
expect(result.unwrap()).toBe(20);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("yield* short-circuits on AsyncResult error", async () => {
|
|
243
|
+
const compute = fn(function* () {
|
|
244
|
+
const _value = yield* fromThrowable(() => Promise.reject("failed"));
|
|
245
|
+
return ok(10); // Should not reach here
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const result = await compute.run();
|
|
249
|
+
expect(result.hasFailed()).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("yield() inherits dependencies from child function", () => {
|
|
253
|
+
const MultiplierService = fn.dependency<number>()("multiplier");
|
|
254
|
+
|
|
255
|
+
const child = fn(function* (n: number) {
|
|
256
|
+
const multiplier = yield* fn.require(MultiplierService);
|
|
257
|
+
return ok(n * multiplier);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const parent = fn(function* (n: number) {
|
|
261
|
+
const result = yield* child.yield(n);
|
|
262
|
+
return ok(result + 10);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const output = parent.inject(MultiplierService.impl(3)).run(5);
|
|
266
|
+
expect(output.unwrap()).toBe(25);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("yield() with child that fails", () => {
|
|
270
|
+
const child = fn(function* () {
|
|
271
|
+
yield* fail("CHILD_ERROR", "Child failed");
|
|
272
|
+
return ok("should not reach");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const parent = fn(function* () {
|
|
276
|
+
const result = yield* child.yield();
|
|
277
|
+
return ok(result);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const output = parent.run();
|
|
281
|
+
expect(output.hasFailed()).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("yield() with pre-injected dependency in child", () => {
|
|
285
|
+
const DepService = fn.dependency<string>()("service");
|
|
286
|
+
|
|
287
|
+
const child = fn(function* () {
|
|
288
|
+
const dep = yield* fn.require(DepService);
|
|
289
|
+
return ok(dep);
|
|
290
|
+
}).inject(DepService.impl("injected"));
|
|
291
|
+
|
|
292
|
+
const parent = fn(function* () {
|
|
293
|
+
const result = yield* child.yield();
|
|
294
|
+
return ok(result);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const output = parent.run();
|
|
298
|
+
expect(output.unwrap()).toBe("injected");
|
|
299
|
+
});
|