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.
@@ -0,0 +1,254 @@
1
+ import { test } from "bun:test";
2
+ import {
3
+ type AsyncResult,
4
+ type Fail,
5
+ type Failure,
6
+ type Ok,
7
+ type Result,
8
+ fail,
9
+ fn,
10
+ fromThrowable,
11
+ ok,
12
+ wrapThrowable,
13
+ } from "./index.ts";
14
+
15
+ // Type assertion helpers
16
+ type IsExact<T, U> = [T] extends [U] ? ([U] extends [T] ? true : false) : false;
17
+ function assertType<_T extends true>(_value: _T): void {}
18
+
19
+ // ok() returns Result<T, never>
20
+ test("ok() returns Result<T, never>", () => {
21
+ const result = ok(42);
22
+ type Expected = Result<number, never>;
23
+ assertType<IsExact<typeof result, Expected>>(true);
24
+ });
25
+
26
+ test("Ok.unwrap() returns T after isOk()", () => {
27
+ const result: Result<number, Failure<"ERR">> = ok(42);
28
+ if (result.isOk()) {
29
+ const value = result.unwrap();
30
+ assertType<IsExact<typeof value, number>>(true);
31
+ }
32
+ });
33
+
34
+ // fail() returns Result<never, Failure<Code>>
35
+ test("fail() returns Result<never, Failure<Code>>", () => {
36
+ const result = fail("ERR", "error message");
37
+ type Expected = Result<never, Failure<"ERR">>;
38
+ assertType<IsExact<typeof result, Expected>>(true);
39
+ });
40
+
41
+ test("Fail.unwrap() returns E after hasFailed()", () => {
42
+ const result: Result<number, Failure<"ERR">> = fail("ERR", "error");
43
+ if (result.hasFailed()) {
44
+ const error = result.unwrap();
45
+ assertType<IsExact<typeof error, Failure<"ERR">>>(true);
46
+ }
47
+ });
48
+
49
+ // Result union type tests
50
+ test("Result<T, E> is union of Ok<T, E> | Fail<T, E>", () => {
51
+ const result: Result<number, Failure<"ERR">> = ok(42);
52
+ assertType<
53
+ IsExact<
54
+ typeof result,
55
+ Ok<number, Failure<"ERR">> | Fail<number, Failure<"ERR">>
56
+ >
57
+ >(true);
58
+ });
59
+
60
+ test("Result.unwrap() returns T | E without narrowing", () => {
61
+ const result: Result<number, Failure<"ERR">> = ok(42);
62
+ const value = result.unwrap();
63
+ assertType<IsExact<typeof value, number | Failure<"ERR">>>(true);
64
+ });
65
+
66
+ // AsyncResult type tests
67
+ test("AsyncResult<T, E> wraps Promise<Result<T, E>>", () => {
68
+ const asyncResult = fromThrowable(() => Promise.resolve(42));
69
+ type Expected = AsyncResult<number, Failure<"UNKNOWN_FAILURE">>;
70
+ assertType<IsExact<typeof asyncResult, Expected>>(true);
71
+ });
72
+
73
+ test("AsyncResult.unwrap() returns Promise<T | E>", () => {
74
+ const asyncResult = fromThrowable(() => Promise.resolve(42));
75
+ const promise = asyncResult.unwrap();
76
+ assertType<
77
+ IsExact<typeof promise, Promise<number | Failure<"UNKNOWN_FAILURE">>>
78
+ >(true);
79
+ });
80
+
81
+ // Failure type tests
82
+ test("Failure<Code> has correct structure", () => {
83
+ type TestFailure = Failure<"TEST_ERR">;
84
+ const failure: TestFailure = { code: "TEST_ERR", message: "test" };
85
+ assertType<IsExact<typeof failure.code, "TEST_ERR">>(true);
86
+ assertType<IsExact<typeof failure.message, string>>(true);
87
+ });
88
+
89
+ test("Failure with cause is optional", () => {
90
+ type TestFailure = Failure<"TEST_ERR">;
91
+ const withCause: TestFailure = {
92
+ code: "TEST_ERR",
93
+ message: "test",
94
+ cause: "reason",
95
+ };
96
+ const withoutCause: TestFailure = { code: "TEST_ERR", message: "test" };
97
+ assertType<IsExact<typeof withCause, TestFailure>>(true);
98
+ assertType<IsExact<typeof withoutCause, TestFailure>>(true);
99
+ });
100
+
101
+ // ok() function type tests
102
+ test("ok() infers type from argument", () => {
103
+ const numResult = ok(42);
104
+ const strResult = ok("hello");
105
+ const objResult = ok({ foo: "bar" });
106
+
107
+ assertType<IsExact<typeof numResult, Result<number, never>>>(true);
108
+ assertType<IsExact<typeof strResult, Result<string, never>>>(true);
109
+ assertType<IsExact<typeof objResult, Result<{ foo: string }, never>>>(true);
110
+ });
111
+
112
+ // fail() function type tests
113
+ test("fail() curried form preserves code type", () => {
114
+ const NotFound = fail("NOT_FOUND");
115
+ const result = NotFound("Resource not found");
116
+
117
+ assertType<IsExact<typeof result, Result<never, Failure<"NOT_FOUND">>>>(true);
118
+ });
119
+
120
+ test("fail() immediate form creates correct type", () => {
121
+ const result = fail("ERR", "error message");
122
+ assertType<IsExact<typeof result, Result<never, Failure<"ERR">>>>(true);
123
+ });
124
+
125
+ // fromThrowable() type tests
126
+ test("fromThrowable() with sync function", () => {
127
+ const result = fromThrowable(() => 42);
128
+ assertType<
129
+ IsExact<typeof result, Result<number, Failure<"UNKNOWN_FAILURE">>>
130
+ >(true);
131
+ });
132
+
133
+ test("fromThrowable() with async function", () => {
134
+ const result = fromThrowable(() => Promise.resolve(42));
135
+ assertType<
136
+ IsExact<typeof result, AsyncResult<number, Failure<"UNKNOWN_FAILURE">>>
137
+ >(true);
138
+ });
139
+
140
+ test("fromThrowable() with custom error mapper", () => {
141
+ type CustomError = { code: number; msg: string };
142
+ const result = fromThrowable(
143
+ () => 42,
144
+ (_e): CustomError => ({ code: 500, msg: "error" }),
145
+ );
146
+ assertType<IsExact<typeof result, Result<number, CustomError>>>(true);
147
+ });
148
+
149
+ test("fromThrowable() async with custom error mapper", () => {
150
+ type CustomError = { code: number; msg: string };
151
+ const result = fromThrowable(
152
+ () => Promise.resolve(42),
153
+ (_e): CustomError => ({ code: 500, msg: "error" }),
154
+ );
155
+ assertType<IsExact<typeof result, AsyncResult<number, CustomError>>>(true);
156
+ });
157
+
158
+ // wrapThrowable() type tests
159
+ test("wrapThrowable() preserves function signature", () => {
160
+ const safeFn = wrapThrowable((x: number, y: string) => x + y.length);
161
+ type Expected = (
162
+ x: number,
163
+ y: string,
164
+ ) => Result<number, Failure<"UNKNOWN_FAILURE">>;
165
+ assertType<IsExact<typeof safeFn, Expected>>(true);
166
+ });
167
+
168
+ test("wrapThrowable() with async function", () => {
169
+ const safeFn = wrapThrowable((x: number) => Promise.resolve(x * 2));
170
+ type Expected = (
171
+ x: number,
172
+ ) => AsyncResult<number, Failure<"UNKNOWN_FAILURE">>;
173
+ assertType<IsExact<typeof safeFn, Expected>>(true);
174
+ });
175
+
176
+ // fn() type tests
177
+ test("fn() with generator function", () => {
178
+ const compute = fn(function* (x: number) {
179
+ const value = yield* ok(x * 2);
180
+ return value + 1;
181
+ });
182
+
183
+ const result = compute.run(5);
184
+ assertType<IsExact<typeof result, Result<number, never>>>(true);
185
+ });
186
+
187
+ test("fn() with async generator", () => {
188
+ const compute = fn(function* (x: number) {
189
+ const value = yield* fromThrowable(() => Promise.resolve(x * 2));
190
+ return value + 1;
191
+ });
192
+
193
+ const result = compute.run(5);
194
+ assertType<
195
+ IsExact<typeof result, AsyncResult<number, Failure<"UNKNOWN_FAILURE">>>
196
+ >(true);
197
+ });
198
+
199
+ test("fn() with dependencies", () => {
200
+ const Dep = fn.dependency<string>()("dep");
201
+
202
+ const compute = fn(function* (x: number) {
203
+ const dep = yield* fn.require(Dep);
204
+ return ok(dep + x);
205
+ });
206
+
207
+ // Before injection, should not have .run() available directly
208
+ // After injection, should have .run() available
209
+ const withDep = compute.inject(Dep.impl("test"));
210
+ const result = withDep.run(5);
211
+ assertType<IsExact<typeof result, Result<string, never>>>(true);
212
+ });
213
+
214
+ // Result method type tests
215
+ test("Result.map() transforms value type", () => {
216
+ const result = ok(42);
217
+ if (result.isOk()) {
218
+ const mapped = result.map((x: number) => x.toString());
219
+ assertType<IsExact<typeof mapped, Result<string, never>>>(true);
220
+ }
221
+ });
222
+
223
+ test("Result.mapFailure() returns Result with new error type", () => {
224
+ const result = fail("ERR", "error");
225
+ if (result.hasFailed()) {
226
+ const mapped = result.mapFailure((e: Failure<"ERR">) =>
227
+ fail("NEW_ERR", e.message),
228
+ );
229
+ // Check that mapped is a Result type
230
+ type CheckResult = typeof mapped extends Result<infer _T, infer _E>
231
+ ? true
232
+ : false;
233
+ assertType<IsExact<CheckResult, true>>(true);
234
+ }
235
+ });
236
+
237
+ test("AsyncResult.map() returns AsyncResult", () => {
238
+ const result = fromThrowable(() => Promise.resolve(42));
239
+ const mapped = result.map((x) => x * 2);
240
+ // Check that mapped is an AsyncResult (the exact type has unions that may not match exactly)
241
+ type CheckAsync = typeof mapped extends AsyncResult<infer _U, infer _E>
242
+ ? true
243
+ : false;
244
+ assertType<IsExact<CheckAsync, true>>(true);
245
+ });
246
+
247
+ test("Result.match() returns union of handler return types", () => {
248
+ const result: Result<number, Failure<"ERR">> = ok(42);
249
+ const matched = result.match({
250
+ ok: (v) => v * 2,
251
+ failed: (e) => e.code.length,
252
+ });
253
+ assertType<IsExact<typeof matched, number>>(true);
254
+ });