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