@onrails/result 0.1.4 → 0.2.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/DESIGN.md +2 -2
- package/README.md +0 -3
- package/dist/$.cjs.map +1 -1
- package/dist/$.js.map +1 -1
- package/dist/_.d.cts +1 -1
- package/dist/_.d.ts +1 -1
- package/dist/async-1fIhfzFu.d.ts +290 -0
- package/dist/async-u8U5P2EH.d.cts +290 -0
- package/dist/compat/neverthrow.cjs +234 -50
- package/dist/compat/neverthrow.cjs.map +1 -1
- package/dist/compat/neverthrow.d.cts +2 -2
- package/dist/compat/neverthrow.d.ts +2 -2
- package/dist/compat/neverthrow.js +231 -47
- package/dist/compat/neverthrow.js.map +1 -1
- package/dist/extra.cjs +1 -24
- package/dist/extra.cjs.map +1 -1
- package/dist/extra.d.cts +3 -9
- package/dist/extra.d.ts +3 -9
- package/dist/extra.js +2 -24
- package/dist/extra.js.map +1 -1
- package/dist/fluent.cjs +0 -2
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +2 -6
- package/dist/fluent.d.ts +2 -6
- package/dist/fluent.js +0 -2
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +220 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +174 -51
- package/dist/index.d.ts +174 -51
- package/dist/index.js +221 -44
- package/dist/index.js.map +1 -1
- package/dist/interop.cjs +203 -23
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.d.cts +48 -8
- package/dist/interop.d.ts +48 -8
- package/dist/interop.js +203 -23
- package/dist/interop.js.map +1 -1
- package/dist/pipe.cjs.map +1 -1
- package/dist/pipe.d.cts +7 -5
- package/dist/pipe.d.ts +7 -5
- package/dist/pipe.js.map +1 -1
- package/dist/railway.cjs +203 -24
- package/dist/railway.cjs.map +1 -1
- package/dist/railway.d.cts +2 -2
- package/dist/railway.d.ts +2 -2
- package/dist/railway.js +203 -24
- package/dist/railway.js.map +1 -1
- package/dist/try-gen.cjs.map +1 -1
- package/dist/try-gen.d.cts +29 -3
- package/dist/try-gen.d.ts +29 -3
- package/dist/try-gen.js.map +1 -1
- package/dist/types-BQ9vv0nD.d.cts +55 -0
- package/dist/types-BQ9vv0nD.d.ts +55 -0
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js.map +1 -1
- package/package.json +5 -3
- package/skills/railway-do-notation/SKILL.md +436 -0
- package/skills/result-composition/SKILL.md +325 -0
- package/dist/async-CH8Cs8KE.d.cts +0 -161
- package/dist/async-DKISqIBc.d.ts +0 -161
- package/dist/types-C2Dp1d5J.d.cts +0 -21
- package/dist/types-C2Dp1d5J.d.ts +0 -21
package/dist/railway.cjs
CHANGED
|
@@ -71,9 +71,32 @@ var ResultAsync = class _ResultAsync {
|
|
|
71
71
|
}
|
|
72
72
|
run;
|
|
73
73
|
promise = null;
|
|
74
|
+
/**
|
|
75
|
+
* Lifts an already-settled sync {@link Result} into a {@link ResultAsync}.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const ra = ResultAsync.fromResult(ok(42)); // ResultAsync<number, never>
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
74
82
|
static fromResult(result) {
|
|
75
83
|
return new _ResultAsync(async () => result);
|
|
76
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Wraps a `PromiseLike<T>` that may reject. Rejections pass through `onReject`
|
|
87
|
+
* to become a typed `Err`; resolution becomes `Ok<T>`.
|
|
88
|
+
*
|
|
89
|
+
* @param promise - the promise to wrap
|
|
90
|
+
* @param onReject - maps a rejection reason to the `Err` channel
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const body = ResultAsync.fromPromise(
|
|
95
|
+
* fetch(url).then((r) => r.text()),
|
|
96
|
+
* (e): NetError => ({ kind: "net", cause: String(e) }),
|
|
97
|
+
* );
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
77
100
|
static fromPromise(promise, onReject) {
|
|
78
101
|
return new _ResultAsync(async () => {
|
|
79
102
|
try {
|
|
@@ -83,12 +106,29 @@ var ResultAsync = class _ResultAsync {
|
|
|
83
106
|
}
|
|
84
107
|
});
|
|
85
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Wraps a `PromiseLike<T>` that is **guaranteed not to reject**, skipping the
|
|
111
|
+
* `onReject` mapper. Use only when rejection is provably impossible.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const now = ResultAsync.fromSafePromise(Promise.resolve(Date.now()));
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
86
118
|
static fromSafePromise(promise) {
|
|
87
119
|
return new _ResultAsync(async () => ok(await promise));
|
|
88
120
|
}
|
|
89
121
|
/**
|
|
90
|
-
* Defers work until {@link resolve}. Unlike {@link fromPromise},
|
|
91
|
-
* until the `ResultAsync` is resolved (e.g. by `combineTuple` /
|
|
122
|
+
* Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
|
|
123
|
+
* does not run until the `ResultAsync` is resolved (e.g. by `combineTuple` /
|
|
124
|
+
* `combineTupleParallel`). The factory runs at most once — `resolve` memoizes.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const insert = ResultAsync.defer(() => db.orders.insert(row));
|
|
129
|
+
* // nothing has run yet
|
|
130
|
+
* const r = await insert; // factory runs exactly once here
|
|
131
|
+
* ```
|
|
92
132
|
*/
|
|
93
133
|
static defer(fn) {
|
|
94
134
|
return new _ResultAsync(fn);
|
|
@@ -96,14 +136,42 @@ var ResultAsync = class _ResultAsync {
|
|
|
96
136
|
static ok(value) {
|
|
97
137
|
return new _ResultAsync(async () => ok(value));
|
|
98
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Lifts an error into an `Err` async result.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const r = ResultAsync.err({ kind: "not_found" as const });
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
99
147
|
static err(error) {
|
|
100
148
|
return new _ResultAsync(async () => err(error));
|
|
101
149
|
}
|
|
102
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
|
|
152
|
+
* {@link ResultAsync}, auto-flattening one level of double-wrapping. A thrown
|
|
153
|
+
* defect is routed through `onDefect`, defaulting to {@link UnexpectedError}.
|
|
154
|
+
*
|
|
155
|
+
* @param promise - a promise that already yields a `Result`
|
|
156
|
+
* @param onDefect - maps an unexpected throw to the `Err` channel
|
|
157
|
+
* @see {@link fromAsync} from `@onrails/result/interop`
|
|
158
|
+
*/
|
|
103
159
|
static fromResultPromise(promise, onDefect) {
|
|
104
160
|
const mapDefect = onDefect ?? ((error) => new UnexpectedError("Unexpected async defect", error));
|
|
105
161
|
return new _ResultAsync(() => liftPromiseResult(promise, mapDefect));
|
|
106
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Combines a homogeneous array of async results into one. Resolves
|
|
165
|
+
* **sequentially** in input order, short-circuiting on the first `Err`.
|
|
166
|
+
* For heterogeneous tuples that preserve per-index types, use
|
|
167
|
+
* {@link combineTuple}; for wall-clock overlap, {@link combineTupleParallel}.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const all = ResultAsync.combine([loadA(), loadB(), loadC()]);
|
|
172
|
+
* // ResultAsync<Item[], LoadError>
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
107
175
|
static combine(results) {
|
|
108
176
|
return new _ResultAsync(async () => {
|
|
109
177
|
const values = [];
|
|
@@ -117,6 +185,23 @@ var ResultAsync = class _ResultAsync {
|
|
|
117
185
|
return ok(values);
|
|
118
186
|
});
|
|
119
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Heterogeneous async tuple combine — resolves branches **sequentially**
|
|
190
|
+
* (left-to-right), returning the first `Err` in input order. Preserves each
|
|
191
|
+
* branch's `Ok` type by position, so the result destructures type-safely.
|
|
192
|
+
* This is the canonical sequential async combine (replaces the former
|
|
193
|
+
* `sequenceTupleAsync`).
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
|
|
198
|
+
* // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
|
|
199
|
+
* const r = await combined;
|
|
200
|
+
* if (isOk(r)) {
|
|
201
|
+
* const [cfg, catalog] = r.value; // typed per position
|
|
202
|
+
* }
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
120
205
|
static combineTuple(results) {
|
|
121
206
|
return _ResultAsync.combine(
|
|
122
207
|
results
|
|
@@ -125,18 +210,55 @@ var ResultAsync = class _ResultAsync {
|
|
|
125
210
|
/**
|
|
126
211
|
* Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
|
|
127
212
|
* parallel for independent IO). On failure, returns the first `Err` in input order.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* // independent IO — overlap them
|
|
217
|
+
* const combined = ResultAsync.combineTupleParallel([
|
|
218
|
+
* loadProfile(id),
|
|
219
|
+
* loadMetrics(id),
|
|
220
|
+
* ] as const);
|
|
221
|
+
* ```
|
|
128
222
|
*/
|
|
129
223
|
static combineTupleParallel(results) {
|
|
130
224
|
return new _ResultAsync(
|
|
131
225
|
async () => sequenceSettled(await Promise.all(results.map((ra) => ra.resolve())))
|
|
132
226
|
);
|
|
133
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Transforms the `Ok` value, passing `Err` through unchanged.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* ResultAsync.ok(2).map((n) => n * 3); // ResultAsync<number> → Ok 6
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
134
236
|
map(fn) {
|
|
135
237
|
return new _ResultAsync(async () => map(await this.resolve(), fn));
|
|
136
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Transforms the `Err` value, passing `Ok` through unchanged — useful for
|
|
241
|
+
* unifying heterogeneous failures into one app-level union.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* load(id).mapErr((e): AppError => ({ kind: "load", cause: e }));
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
137
248
|
mapErr(fn) {
|
|
138
249
|
return new _ResultAsync(async () => mapErr(await this.resolve(), fn));
|
|
139
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Canonical bind — chains a step that itself returns a `ResultAsync` or sync
|
|
253
|
+
* `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* authenticate(req)
|
|
258
|
+
* .flatMap((user) => loadProfile(user.id)) // ResultAsync
|
|
259
|
+
* .flatMap((p) => validate(p)); // sync Result also accepted
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
140
262
|
flatMap(fn) {
|
|
141
263
|
return new _ResultAsync(async () => {
|
|
142
264
|
const first = await this.resolve();
|
|
@@ -153,16 +275,28 @@ var ResultAsync = class _ResultAsync {
|
|
|
153
275
|
return isCompatLike(next) ? next.inner : next;
|
|
154
276
|
});
|
|
155
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
|
|
280
|
+
* tier; prefer `flatMap` in new code.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* authenticate(req).andThen((user) => loadProfile(user.id));
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
156
287
|
andThen(fn) {
|
|
157
288
|
return this.flatMap(fn);
|
|
158
289
|
}
|
|
159
290
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
291
|
+
* Error-channel bind — runs `fn` only on `Err`, swapping in a recovery
|
|
292
|
+
* `ResultAsync` or sync `Result`. `Ok` passes through. The mirror of
|
|
293
|
+
* {@link flatMap} on the error track.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* loadFromCache(id).recover(() => loadFromOrigin(id));
|
|
298
|
+
* ```
|
|
162
299
|
*/
|
|
163
|
-
chain(fn) {
|
|
164
|
-
return this.flatMap(fn);
|
|
165
|
-
}
|
|
166
300
|
recover(fn) {
|
|
167
301
|
return new _ResultAsync(async () => {
|
|
168
302
|
const first = await this.resolve();
|
|
@@ -173,9 +307,27 @@ var ResultAsync = class _ResultAsync {
|
|
|
173
307
|
return next instanceof _ResultAsync ? next.resolve() : next;
|
|
174
308
|
});
|
|
175
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* neverthrow-compat alias of {@link recover}. Kept as the documented compat
|
|
312
|
+
* tier; prefer `recover` in new code.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```ts
|
|
316
|
+
* loadFromCache(id).orElse(() => loadFromOrigin(id));
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
176
319
|
orElse(fn) {
|
|
177
320
|
return this.recover(fn);
|
|
178
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Runs a side effect on the `Ok` value and passes the result through
|
|
324
|
+
* unchanged; a no-op on `Err`. Mirrors {@link tapErr}.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* saveUser(u).tap((saved) => analytics.track("user_saved", saved.id));
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
179
331
|
tap(fn) {
|
|
180
332
|
return new _ResultAsync(async () => {
|
|
181
333
|
const result = await this.resolve();
|
|
@@ -185,6 +337,15 @@ var ResultAsync = class _ResultAsync {
|
|
|
185
337
|
return result;
|
|
186
338
|
});
|
|
187
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Runs a side effect on the `Err` value and passes the result through
|
|
342
|
+
* unchanged; a no-op on `Ok`. The error-track mirror of {@link tap}.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* saveUser(u).tapErr((e) => logger.warn("save failed", e));
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
188
349
|
tapErr(fn) {
|
|
189
350
|
return new _ResultAsync(async () => {
|
|
190
351
|
const result = await this.resolve();
|
|
@@ -194,28 +355,49 @@ var ResultAsync = class _ResultAsync {
|
|
|
194
355
|
return result;
|
|
195
356
|
});
|
|
196
357
|
}
|
|
197
|
-
unwrapOr(defaultValue) {
|
|
198
|
-
return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
|
|
199
|
-
}
|
|
200
358
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
359
|
+
* Settles to the `Ok` value, or `defaultValue` if `Err`. Terminal — returns a
|
|
360
|
+
* plain `Promise`, not a `ResultAsync`.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```ts
|
|
364
|
+
* const profile = await loadProfile(id).unwrapOr(guestProfile);
|
|
365
|
+
* ```
|
|
203
366
|
*/
|
|
204
|
-
|
|
205
|
-
return this.resolve().then((result) =>
|
|
367
|
+
unwrapOr(defaultValue) {
|
|
368
|
+
return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
|
|
206
369
|
}
|
|
207
370
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
371
|
+
* Terminal collapse — folds both tracks into a single awaited value. Returns
|
|
372
|
+
* a plain `Promise`, settling the carrier exactly once.
|
|
373
|
+
*
|
|
374
|
+
* @param onOk - handles the `Ok` value
|
|
375
|
+
* @param onErr - handles the `Err` value
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* const status = await save(row).match(
|
|
380
|
+
* () => 200,
|
|
381
|
+
* (e) => (e.kind === "conflict" ? 409 : 500),
|
|
382
|
+
* );
|
|
383
|
+
* ```
|
|
210
384
|
*/
|
|
211
|
-
isErr() {
|
|
212
|
-
return this.resolve().then((result) => isErr(result));
|
|
213
|
-
}
|
|
214
385
|
match(onOk, onErr) {
|
|
215
386
|
return this.resolve().then(
|
|
216
387
|
(result) => isErr(result) ? onErr(result.error) : onOk(result.value)
|
|
217
388
|
);
|
|
218
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Settles the carrier to a bare sync {@link Result}, memoizing so the
|
|
392
|
+
* underlying factory runs at most once. Prefer `await ra` (the thenable) or
|
|
393
|
+
* {@link match} in app code; use `resolve` when you need the tagged union back.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* const r = await load(id).resolve(); // Result<Data, LoadError>
|
|
398
|
+
* if (isOk(r)) use(r.value);
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
219
401
|
resolve() {
|
|
220
402
|
if (!this.promise) {
|
|
221
403
|
this.promise = this.run();
|
|
@@ -302,10 +484,7 @@ var Railway = class _Railway {
|
|
|
302
484
|
return new _Railway({ mode: "sync", result: err(current.error) });
|
|
303
485
|
}
|
|
304
486
|
const next = fn(current.value);
|
|
305
|
-
|
|
306
|
-
return new _Railway({ mode: "sync", result: err(next.error) });
|
|
307
|
-
}
|
|
308
|
-
return new _Railway({
|
|
487
|
+
return isErr(next) ? new _Railway({ mode: "sync", result: err(next.error) }) : new _Railway({
|
|
309
488
|
mode: "sync",
|
|
310
489
|
result: ok(addField(current.value, key, next.value))
|
|
311
490
|
});
|