@onrails/maybe 0.2.0 → 0.2.2
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 +1 -0
- package/dist/fluent.cjs +35 -48
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +17 -2
- package/dist/fluent.d.ts +17 -2
- package/dist/fluent.js +35 -48
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +27 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -24
- package/dist/index.d.ts +80 -24
- package/dist/index.js +26 -27
- package/dist/index.js.map +1 -1
- package/dist/interop.cjs +15 -3
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.d.cts +14 -4
- package/dist/interop.d.ts +14 -4
- package/dist/interop.js +16 -4
- package/dist/interop.js.map +1 -1
- package/package.json +3 -3
- package/dist/types-CDCo2pLN.d.cts +0 -29
- package/dist/types-CDCo2pLN.d.ts +0 -29
package/DESIGN.md
CHANGED
|
@@ -5,6 +5,7 @@ Expected absence as a tagged union — not failure. Pairs with `@onrails/result`
|
|
|
5
5
|
## Goals
|
|
6
6
|
|
|
7
7
|
- Same runtime model as `@onrails/result`: `{ _tag }` variants, dual-form module functions, `sideEffects: false`.
|
|
8
|
+
- Cross-carrier symmetry is spec-enforced (`test/symmetry.spec.ts`): shared ops keep the same name and dual arity as result's; channels mirror (`tapErr` ↔ `tapNone`); deliberate gaps are declared there. `of` = FL `pure` (alias of `some`); `show(m)` prints `Some(…)` / `None`.
|
|
8
9
|
- **Some / None** — not Ok/Err. Do not use Maybe for parse/IO/auth failures.
|
|
9
10
|
- Optional `@onrails/maybe/interop` bridge (`toResult`, `fromResult`) keyed on `@onrails/result`.
|
|
10
11
|
|
package/dist/fluent.cjs
CHANGED
|
@@ -1,72 +1,59 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/internal/dual.ts
|
|
4
|
+
function dual(arity, body) {
|
|
5
|
+
const dispatch = (...args) => {
|
|
6
|
+
if (args.length >= arity) return body(...args);
|
|
7
|
+
return (self) => body(self, ...args);
|
|
8
|
+
};
|
|
9
|
+
return dispatch;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
// src/maybe.ts
|
|
4
13
|
var some = (value) => ({ _tag: "Some", value });
|
|
5
14
|
var none = () => ({ _tag: "None" });
|
|
6
15
|
var isSome = (maybe) => maybe._tag === "Some";
|
|
7
16
|
var isNone = (maybe) => maybe._tag === "None";
|
|
8
17
|
var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
|
|
9
|
-
|
|
10
|
-
if (args.length === 2) return mapImpl(args[0], args[1]);
|
|
11
|
-
const fn = args[0];
|
|
12
|
-
return (maybe) => mapImpl(maybe, fn);
|
|
13
|
-
}
|
|
18
|
+
var map = dual(2, mapImpl);
|
|
14
19
|
var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
|
|
15
|
-
|
|
16
|
-
if (args.length === 2) return flatMapImpl(args[0], args[1]);
|
|
17
|
-
const fn = args[0];
|
|
18
|
-
return (maybe) => flatMapImpl(maybe, fn);
|
|
19
|
-
}
|
|
20
|
+
var flatMap = dual(2, flatMapImpl);
|
|
20
21
|
var andThen = flatMap;
|
|
21
22
|
var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return (maybe) => matchImpl(maybe, onSome, onNone);
|
|
26
|
-
}
|
|
27
|
-
var unwrapOr = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
23
|
+
var match = dual(3, matchImpl);
|
|
24
|
+
var unwrapOrImpl = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
25
|
+
var unwrapOr = dual(2, unwrapOrImpl);
|
|
28
26
|
var tapImpl = (maybe, fn) => {
|
|
29
27
|
if (isSome(maybe)) fn(maybe.value);
|
|
30
28
|
return maybe;
|
|
31
29
|
};
|
|
32
|
-
|
|
33
|
-
if (args.length === 2) return tapImpl(args[0], args[1]);
|
|
34
|
-
const fn = args[0];
|
|
35
|
-
return (maybe) => tapImpl(maybe, fn);
|
|
36
|
-
}
|
|
30
|
+
var tap = dual(2, tapImpl);
|
|
37
31
|
var tapNoneImpl = (maybe, fn) => {
|
|
38
32
|
if (isNone(maybe)) fn();
|
|
39
33
|
return maybe;
|
|
40
34
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
35
|
+
var tapNone = dual(2, tapNoneImpl);
|
|
36
|
+
var printPayload = (payload) => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(payload) ?? String(payload);
|
|
39
|
+
} catch {
|
|
40
|
+
return String(payload);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var show = (maybe) => isSome(maybe) ? `Some(${printPayload(maybe.value)})` : "None";
|
|
46
44
|
|
|
47
45
|
// src/fluent.ts
|
|
48
|
-
var fluent = (maybe) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return fluent(tapNone(maybe, fn));
|
|
60
|
-
},
|
|
61
|
-
andThen(fn) {
|
|
62
|
-
return fluent(andThen(maybe, fn));
|
|
63
|
-
},
|
|
64
|
-
match(onSome, onNone) {
|
|
65
|
-
return match(maybe, onSome, onNone);
|
|
66
|
-
},
|
|
67
|
-
unwrapOr(defaultValue) {
|
|
68
|
-
return unwrapOr(maybe, defaultValue);
|
|
69
|
-
}
|
|
46
|
+
var fluent = (maybe) => ({
|
|
47
|
+
maybe,
|
|
48
|
+
map: (fn) => fluent(map(maybe, fn)),
|
|
49
|
+
flatMap: (fn) => fluent(flatMap(maybe, fn)),
|
|
50
|
+
tap: (fn) => fluent(tap(maybe, fn)),
|
|
51
|
+
tapNone: (fn) => fluent(tapNone(maybe, fn)),
|
|
52
|
+
andThen: (fn) => fluent(andThen(maybe, fn)),
|
|
53
|
+
match: (onSome, onNone) => match(maybe, onSome, onNone),
|
|
54
|
+
unwrapOr: (defaultValue) => unwrapOr(maybe, defaultValue),
|
|
55
|
+
toMaybe: () => maybe,
|
|
56
|
+
toString: () => show(maybe)
|
|
70
57
|
});
|
|
71
58
|
|
|
72
59
|
exports.fluent = fluent;
|
package/dist/fluent.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":"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","file":"fluent.cjs","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * unwrapOr(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const unwrapOr = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport function tap<T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\nexport function tap<T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tap(\n ...args: [Maybe<unknown>, (value: unknown) => void] | [(value: unknown) => void]\n): unknown {\n if (args.length === 2) return tapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapImpl(maybe, fn);\n}\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport function tapNone<T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\nexport function tapNone<T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tapNone(...args: [Maybe<unknown>, () => void] | [() => void]): unknown {\n if (args.length === 2) return tapNoneImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapNoneImpl(maybe, fn);\n}\n","import { andThen, flatMap, type Maybe, map, match, tap, tapNone, unwrapOr } from \"./maybe.js\";\n\nexport type FluentMaybe<T> = Maybe<T> & {\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n tap(fn: (value: T) => void): FluentMaybe<T>;\n tapNone(fn: () => void): FluentMaybe<T>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n\n unwrapOr(defaultValue: T): T;\n};\n\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> =>\n Object.assign(maybe, {\n map<U>(fn: (value: T) => U) {\n return fluent(map(maybe, fn));\n },\n flatMap<U>(fn: (value: T) => Maybe<U>) {\n return fluent(flatMap(maybe, fn));\n },\n tap(fn: (value: T) => void) {\n return fluent(tap(maybe, fn));\n },\n tapNone(fn: () => void) {\n return fluent(tapNone(maybe, fn));\n },\n andThen<U>(fn: (value: T) => Maybe<U>) {\n return fluent(andThen(maybe, fn));\n },\n match<U>(onSome: (value: T) => U, onNone: () => U) {\n return match(maybe, onSome, onNone);\n },\n\n unwrapOr(defaultValue: T) {\n return unwrapOr(maybe, defaultValue);\n },\n });\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":"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","file":"fluent.cjs","sourcesContent":["/**\n * Owning module for the dual-form call idiom used across `@onrails/maybe`:\n * a function accepts either data-first (`map(m, fn)`) or curried data-last\n * (`map(fn)(m)`) invocation, dispatching on argument count against a fixed\n * `arity`.\n *\n * This is a deliberate twin of `packages/result/src/internal/dual.ts` — keep\n * the two in sync. No cross-package imports are allowed in this repo, so the\n * helper is vendored independently in each package rather than shared.\n */\nexport function dual<F>(arity: number, body: (...args: never[]) => unknown): F {\n const dispatch = (...args: unknown[]): unknown => {\n if (args.length >= arity) return (body as (...a: unknown[]) => unknown)(...args);\n return (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n };\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import { dual } from \"./internal/dual.js\";\n\n/**\n * Tagged optional value — a discriminated union over `_tag` where absence\n * (`None`) is an expected outcome, not a failure. Construct with {@link some} /\n * {@link none}, narrow with {@link isSome} / {@link isNone}, and collapse with\n * {@link match}.\n *\n * @example\n * ```ts\n * function findUser(id: string): Maybe<User> {\n * return fromNullable(db.users.get(id));\n * }\n * ```\n */\nexport type Maybe<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/** The `Some` branch of a {@link Maybe} — a present value tagged `\"Some\"`. */\nexport type Some<T> = Extract<Maybe<T>, { _tag: \"Some\" }>;\n\n/** The `None` branch of a {@link Maybe} — expected absence, tagged `\"None\"`. */\nexport type None = Extract<Maybe<never>, { _tag: \"None\" }>;\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * Fantasy Land `pure` — alias of {@link some}. One lift name shared across\n * the trio (`of` / `Result.of` / `ResultAsync.of`) for generic and FL-style\n * code.\n *\n * @example\n * ```ts\n * const m = of(1); // Maybe<number> — identical to some(1)\n * ```\n */\nexport const of = some;\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(cache.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport const map: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\n <T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, mapImpl);\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const flatMap: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\n <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, flatMapImpl);\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport const match: {\n <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\n <T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\n} = dual(3, matchImpl);\n\nconst unwrapOrImpl = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T>(maybe: Maybe<T>, defaultValue: T): T;\n <T>(defaultValue: T): (maybe: Maybe<T>) => T;\n} = dual(2, unwrapOrImpl);\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport const tap: {\n <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\n <T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapImpl);\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport const tapNone: {\n <T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\n <T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapNoneImpl);\n\nconst printPayload = (payload: unknown): string => {\n try {\n return JSON.stringify(payload) ?? String(payload);\n } catch {\n // cyclic or non-JSON payload — a debug printer must not throw\n return String(payload);\n }\n};\n\n/**\n * Debug printer — renders a maybe as `Some(…)` / `None` for logs. Payloads\n * print as JSON (values are plain data by design); non-JSON payloads fall\n * back to `String(...)`. Mirror of the result package's `show`.\n *\n * @example\n * ```ts\n * show(some(1)); // 'Some(1)'\n * show(none()); // 'None'\n * ```\n */\nexport const show = <T>(maybe: Maybe<T>): string =>\n isSome(maybe) ? `Some(${printPayload(maybe.value)})` : \"None\";\n","import { andThen, flatMap, type Maybe, map, match, show, tap, tapNone, unwrapOr } from \"./maybe.js\";\n\n/** Opt-in dot chaining over a {@link Maybe}. */\nexport type FluentMaybe<T> = {\n readonly maybe: Maybe<T>;\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n tap(fn: (value: T) => void): FluentMaybe<T>;\n tapNone(fn: () => void): FluentMaybe<T>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n\n unwrapOr(defaultValue: T): T;\n /** Exit the wrapper — hand back the canonical plain-data maybe. */\n toMaybe(): Maybe<T>;\n /** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */\n toString(): string;\n};\n\n/**\n * Wrap a {@link Maybe} for opt-in dot chaining — app-edge sugar. Keep the\n * data-first helpers ({@link map}, {@link flatMap}) in library internals.\n *\n * @example\n * ```ts\n * fluent(some(2)).map((n) => n * 3).unwrapOr(0); // 6\n * ```\n */\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> => ({\n maybe,\n map: (fn) => fluent(map(maybe, fn)),\n flatMap: (fn) => fluent(flatMap(maybe, fn)),\n tap: (fn) => fluent(tap(maybe, fn)),\n tapNone: (fn) => fluent(tapNone(maybe, fn)),\n andThen: (fn) => fluent(andThen(maybe, fn)),\n match: (onSome, onNone) => match(maybe, onSome, onNone),\n\n unwrapOr: (defaultValue) => unwrapOr(maybe, defaultValue),\n toMaybe: () => maybe,\n toString: () => show(maybe),\n});\n"]}
|
package/dist/fluent.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Maybe } from './index.cjs';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Opt-in dot chaining over a {@link Maybe}. */
|
|
4
|
+
type FluentMaybe<T> = {
|
|
5
|
+
readonly maybe: Maybe<T>;
|
|
4
6
|
map<U>(fn: (value: T) => U): FluentMaybe<U>;
|
|
5
7
|
flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
|
|
6
8
|
andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
|
|
@@ -8,7 +10,20 @@ type FluentMaybe<T> = Maybe<T> & {
|
|
|
8
10
|
tapNone(fn: () => void): FluentMaybe<T>;
|
|
9
11
|
match<U>(onSome: (value: T) => U, onNone: () => U): U;
|
|
10
12
|
unwrapOr(defaultValue: T): T;
|
|
13
|
+
/** Exit the wrapper — hand back the canonical plain-data maybe. */
|
|
14
|
+
toMaybe(): Maybe<T>;
|
|
15
|
+
/** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */
|
|
16
|
+
toString(): string;
|
|
11
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Wrap a {@link Maybe} for opt-in dot chaining — app-edge sugar. Keep the
|
|
20
|
+
* data-first helpers ({@link map}, {@link flatMap}) in library internals.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* fluent(some(2)).map((n) => n * 3).unwrapOr(0); // 6
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
12
27
|
declare const fluent: <T>(maybe: Maybe<T>) => FluentMaybe<T>;
|
|
13
28
|
|
|
14
29
|
export { type FluentMaybe, fluent };
|
package/dist/fluent.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Maybe } from './index.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Opt-in dot chaining over a {@link Maybe}. */
|
|
4
|
+
type FluentMaybe<T> = {
|
|
5
|
+
readonly maybe: Maybe<T>;
|
|
4
6
|
map<U>(fn: (value: T) => U): FluentMaybe<U>;
|
|
5
7
|
flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
|
|
6
8
|
andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
|
|
@@ -8,7 +10,20 @@ type FluentMaybe<T> = Maybe<T> & {
|
|
|
8
10
|
tapNone(fn: () => void): FluentMaybe<T>;
|
|
9
11
|
match<U>(onSome: (value: T) => U, onNone: () => U): U;
|
|
10
12
|
unwrapOr(defaultValue: T): T;
|
|
13
|
+
/** Exit the wrapper — hand back the canonical plain-data maybe. */
|
|
14
|
+
toMaybe(): Maybe<T>;
|
|
15
|
+
/** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */
|
|
16
|
+
toString(): string;
|
|
11
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Wrap a {@link Maybe} for opt-in dot chaining — app-edge sugar. Keep the
|
|
20
|
+
* data-first helpers ({@link map}, {@link flatMap}) in library internals.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* fluent(some(2)).map((n) => n * 3).unwrapOr(0); // 6
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
12
27
|
declare const fluent: <T>(maybe: Maybe<T>) => FluentMaybe<T>;
|
|
13
28
|
|
|
14
29
|
export { type FluentMaybe, fluent };
|
package/dist/fluent.js
CHANGED
|
@@ -1,70 +1,57 @@
|
|
|
1
|
+
// src/internal/dual.ts
|
|
2
|
+
function dual(arity, body) {
|
|
3
|
+
const dispatch = (...args) => {
|
|
4
|
+
if (args.length >= arity) return body(...args);
|
|
5
|
+
return (self) => body(self, ...args);
|
|
6
|
+
};
|
|
7
|
+
return dispatch;
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
// src/maybe.ts
|
|
2
11
|
var some = (value) => ({ _tag: "Some", value });
|
|
3
12
|
var none = () => ({ _tag: "None" });
|
|
4
13
|
var isSome = (maybe) => maybe._tag === "Some";
|
|
5
14
|
var isNone = (maybe) => maybe._tag === "None";
|
|
6
15
|
var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
|
|
7
|
-
|
|
8
|
-
if (args.length === 2) return mapImpl(args[0], args[1]);
|
|
9
|
-
const fn = args[0];
|
|
10
|
-
return (maybe) => mapImpl(maybe, fn);
|
|
11
|
-
}
|
|
16
|
+
var map = dual(2, mapImpl);
|
|
12
17
|
var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
|
|
13
|
-
|
|
14
|
-
if (args.length === 2) return flatMapImpl(args[0], args[1]);
|
|
15
|
-
const fn = args[0];
|
|
16
|
-
return (maybe) => flatMapImpl(maybe, fn);
|
|
17
|
-
}
|
|
18
|
+
var flatMap = dual(2, flatMapImpl);
|
|
18
19
|
var andThen = flatMap;
|
|
19
20
|
var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return (maybe) => matchImpl(maybe, onSome, onNone);
|
|
24
|
-
}
|
|
25
|
-
var unwrapOr = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
21
|
+
var match = dual(3, matchImpl);
|
|
22
|
+
var unwrapOrImpl = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
23
|
+
var unwrapOr = dual(2, unwrapOrImpl);
|
|
26
24
|
var tapImpl = (maybe, fn) => {
|
|
27
25
|
if (isSome(maybe)) fn(maybe.value);
|
|
28
26
|
return maybe;
|
|
29
27
|
};
|
|
30
|
-
|
|
31
|
-
if (args.length === 2) return tapImpl(args[0], args[1]);
|
|
32
|
-
const fn = args[0];
|
|
33
|
-
return (maybe) => tapImpl(maybe, fn);
|
|
34
|
-
}
|
|
28
|
+
var tap = dual(2, tapImpl);
|
|
35
29
|
var tapNoneImpl = (maybe, fn) => {
|
|
36
30
|
if (isNone(maybe)) fn();
|
|
37
31
|
return maybe;
|
|
38
32
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
33
|
+
var tapNone = dual(2, tapNoneImpl);
|
|
34
|
+
var printPayload = (payload) => {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(payload) ?? String(payload);
|
|
37
|
+
} catch {
|
|
38
|
+
return String(payload);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var show = (maybe) => isSome(maybe) ? `Some(${printPayload(maybe.value)})` : "None";
|
|
44
42
|
|
|
45
43
|
// src/fluent.ts
|
|
46
|
-
var fluent = (maybe) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return fluent(tapNone(maybe, fn));
|
|
58
|
-
},
|
|
59
|
-
andThen(fn) {
|
|
60
|
-
return fluent(andThen(maybe, fn));
|
|
61
|
-
},
|
|
62
|
-
match(onSome, onNone) {
|
|
63
|
-
return match(maybe, onSome, onNone);
|
|
64
|
-
},
|
|
65
|
-
unwrapOr(defaultValue) {
|
|
66
|
-
return unwrapOr(maybe, defaultValue);
|
|
67
|
-
}
|
|
44
|
+
var fluent = (maybe) => ({
|
|
45
|
+
maybe,
|
|
46
|
+
map: (fn) => fluent(map(maybe, fn)),
|
|
47
|
+
flatMap: (fn) => fluent(flatMap(maybe, fn)),
|
|
48
|
+
tap: (fn) => fluent(tap(maybe, fn)),
|
|
49
|
+
tapNone: (fn) => fluent(tapNone(maybe, fn)),
|
|
50
|
+
andThen: (fn) => fluent(andThen(maybe, fn)),
|
|
51
|
+
match: (onSome, onNone) => match(maybe, onSome, onNone),
|
|
52
|
+
unwrapOr: (defaultValue) => unwrapOr(maybe, defaultValue),
|
|
53
|
+
toMaybe: () => maybe,
|
|
54
|
+
toString: () => show(maybe)
|
|
68
55
|
});
|
|
69
56
|
|
|
70
57
|
export { fluent };
|
package/dist/fluent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":"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","file":"fluent.js","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * unwrapOr(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const unwrapOr = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport function tap<T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\nexport function tap<T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tap(\n ...args: [Maybe<unknown>, (value: unknown) => void] | [(value: unknown) => void]\n): unknown {\n if (args.length === 2) return tapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapImpl(maybe, fn);\n}\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport function tapNone<T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\nexport function tapNone<T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tapNone(...args: [Maybe<unknown>, () => void] | [() => void]): unknown {\n if (args.length === 2) return tapNoneImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapNoneImpl(maybe, fn);\n}\n","import { andThen, flatMap, type Maybe, map, match, tap, tapNone, unwrapOr } from \"./maybe.js\";\n\nexport type FluentMaybe<T> = Maybe<T> & {\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n tap(fn: (value: T) => void): FluentMaybe<T>;\n tapNone(fn: () => void): FluentMaybe<T>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n\n unwrapOr(defaultValue: T): T;\n};\n\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> =>\n Object.assign(maybe, {\n map<U>(fn: (value: T) => U) {\n return fluent(map(maybe, fn));\n },\n flatMap<U>(fn: (value: T) => Maybe<U>) {\n return fluent(flatMap(maybe, fn));\n },\n tap(fn: (value: T) => void) {\n return fluent(tap(maybe, fn));\n },\n tapNone(fn: () => void) {\n return fluent(tapNone(maybe, fn));\n },\n andThen<U>(fn: (value: T) => Maybe<U>) {\n return fluent(andThen(maybe, fn));\n },\n match<U>(onSome: (value: T) => U, onNone: () => U) {\n return match(maybe, onSome, onNone);\n },\n\n unwrapOr(defaultValue: T) {\n return unwrapOr(maybe, defaultValue);\n },\n });\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":"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","file":"fluent.js","sourcesContent":["/**\n * Owning module for the dual-form call idiom used across `@onrails/maybe`:\n * a function accepts either data-first (`map(m, fn)`) or curried data-last\n * (`map(fn)(m)`) invocation, dispatching on argument count against a fixed\n * `arity`.\n *\n * This is a deliberate twin of `packages/result/src/internal/dual.ts` — keep\n * the two in sync. No cross-package imports are allowed in this repo, so the\n * helper is vendored independently in each package rather than shared.\n */\nexport function dual<F>(arity: number, body: (...args: never[]) => unknown): F {\n const dispatch = (...args: unknown[]): unknown => {\n if (args.length >= arity) return (body as (...a: unknown[]) => unknown)(...args);\n return (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n };\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import { dual } from \"./internal/dual.js\";\n\n/**\n * Tagged optional value — a discriminated union over `_tag` where absence\n * (`None`) is an expected outcome, not a failure. Construct with {@link some} /\n * {@link none}, narrow with {@link isSome} / {@link isNone}, and collapse with\n * {@link match}.\n *\n * @example\n * ```ts\n * function findUser(id: string): Maybe<User> {\n * return fromNullable(db.users.get(id));\n * }\n * ```\n */\nexport type Maybe<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/** The `Some` branch of a {@link Maybe} — a present value tagged `\"Some\"`. */\nexport type Some<T> = Extract<Maybe<T>, { _tag: \"Some\" }>;\n\n/** The `None` branch of a {@link Maybe} — expected absence, tagged `\"None\"`. */\nexport type None = Extract<Maybe<never>, { _tag: \"None\" }>;\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * Fantasy Land `pure` — alias of {@link some}. One lift name shared across\n * the trio (`of` / `Result.of` / `ResultAsync.of`) for generic and FL-style\n * code.\n *\n * @example\n * ```ts\n * const m = of(1); // Maybe<number> — identical to some(1)\n * ```\n */\nexport const of = some;\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(cache.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport const map: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\n <T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, mapImpl);\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const flatMap: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\n <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, flatMapImpl);\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport const match: {\n <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\n <T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\n} = dual(3, matchImpl);\n\nconst unwrapOrImpl = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T>(maybe: Maybe<T>, defaultValue: T): T;\n <T>(defaultValue: T): (maybe: Maybe<T>) => T;\n} = dual(2, unwrapOrImpl);\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport const tap: {\n <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\n <T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapImpl);\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport const tapNone: {\n <T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\n <T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapNoneImpl);\n\nconst printPayload = (payload: unknown): string => {\n try {\n return JSON.stringify(payload) ?? String(payload);\n } catch {\n // cyclic or non-JSON payload — a debug printer must not throw\n return String(payload);\n }\n};\n\n/**\n * Debug printer — renders a maybe as `Some(…)` / `None` for logs. Payloads\n * print as JSON (values are plain data by design); non-JSON payloads fall\n * back to `String(...)`. Mirror of the result package's `show`.\n *\n * @example\n * ```ts\n * show(some(1)); // 'Some(1)'\n * show(none()); // 'None'\n * ```\n */\nexport const show = <T>(maybe: Maybe<T>): string =>\n isSome(maybe) ? `Some(${printPayload(maybe.value)})` : \"None\";\n","import { andThen, flatMap, type Maybe, map, match, show, tap, tapNone, unwrapOr } from \"./maybe.js\";\n\n/** Opt-in dot chaining over a {@link Maybe}. */\nexport type FluentMaybe<T> = {\n readonly maybe: Maybe<T>;\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n tap(fn: (value: T) => void): FluentMaybe<T>;\n tapNone(fn: () => void): FluentMaybe<T>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n\n unwrapOr(defaultValue: T): T;\n /** Exit the wrapper — hand back the canonical plain-data maybe. */\n toMaybe(): Maybe<T>;\n /** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */\n toString(): string;\n};\n\n/**\n * Wrap a {@link Maybe} for opt-in dot chaining — app-edge sugar. Keep the\n * data-first helpers ({@link map}, {@link flatMap}) in library internals.\n *\n * @example\n * ```ts\n * fluent(some(2)).map((n) => n * 3).unwrapOr(0); // 6\n * ```\n */\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> => ({\n maybe,\n map: (fn) => fluent(map(maybe, fn)),\n flatMap: (fn) => fluent(flatMap(maybe, fn)),\n tap: (fn) => fluent(tap(maybe, fn)),\n tapNone: (fn) => fluent(tapNone(maybe, fn)),\n andThen: (fn) => fluent(andThen(maybe, fn)),\n match: (onSome, onNone) => match(maybe, onSome, onNone),\n\n unwrapOr: (defaultValue) => unwrapOr(maybe, defaultValue),\n toMaybe: () => maybe,\n toString: () => show(maybe),\n});\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/internal/dual.ts
|
|
4
|
+
function dual(arity, body) {
|
|
5
|
+
const dispatch = (...args) => {
|
|
6
|
+
if (args.length >= arity) return body(...args);
|
|
7
|
+
return (self) => body(self, ...args);
|
|
8
|
+
};
|
|
9
|
+
return dispatch;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
// src/maybe.ts
|
|
4
13
|
var some = (value) => ({ _tag: "Some", value });
|
|
14
|
+
var of = some;
|
|
5
15
|
var none = () => ({ _tag: "None" });
|
|
6
16
|
var isSome = (maybe) => maybe._tag === "Some";
|
|
7
17
|
var isNone = (maybe) => maybe._tag === "None";
|
|
8
18
|
var fromNullable = (value) => value == null ? none() : some(value);
|
|
9
19
|
var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
|
|
10
|
-
|
|
11
|
-
if (args.length === 2) return mapImpl(args[0], args[1]);
|
|
12
|
-
const fn = args[0];
|
|
13
|
-
return (maybe) => mapImpl(maybe, fn);
|
|
14
|
-
}
|
|
20
|
+
var map = dual(2, mapImpl);
|
|
15
21
|
var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
|
|
16
|
-
|
|
17
|
-
if (args.length === 2) return flatMapImpl(args[0], args[1]);
|
|
18
|
-
const fn = args[0];
|
|
19
|
-
return (maybe) => flatMapImpl(maybe, fn);
|
|
20
|
-
}
|
|
22
|
+
var flatMap = dual(2, flatMapImpl);
|
|
21
23
|
var andThen = flatMap;
|
|
22
24
|
var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return (maybe) => matchImpl(maybe, onSome, onNone);
|
|
27
|
-
}
|
|
28
|
-
var unwrapOr = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
25
|
+
var match = dual(3, matchImpl);
|
|
26
|
+
var unwrapOrImpl = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
27
|
+
var unwrapOr = dual(2, unwrapOrImpl);
|
|
29
28
|
var unwrap = (maybe) => {
|
|
30
29
|
if (isNone(maybe)) {
|
|
31
30
|
throw new Error("Called unwrap on None");
|
|
@@ -47,20 +46,20 @@ var tapImpl = (maybe, fn) => {
|
|
|
47
46
|
if (isSome(maybe)) fn(maybe.value);
|
|
48
47
|
return maybe;
|
|
49
48
|
};
|
|
50
|
-
|
|
51
|
-
if (args.length === 2) return tapImpl(args[0], args[1]);
|
|
52
|
-
const fn = args[0];
|
|
53
|
-
return (maybe) => tapImpl(maybe, fn);
|
|
54
|
-
}
|
|
49
|
+
var tap = dual(2, tapImpl);
|
|
55
50
|
var tapNoneImpl = (maybe, fn) => {
|
|
56
51
|
if (isNone(maybe)) fn();
|
|
57
52
|
return maybe;
|
|
58
53
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
54
|
+
var tapNone = dual(2, tapNoneImpl);
|
|
55
|
+
var printPayload = (payload) => {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(payload) ?? String(payload);
|
|
58
|
+
} catch {
|
|
59
|
+
return String(payload);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var show = (maybe) => isSome(maybe) ? `Some(${printPayload(maybe.value)})` : "None";
|
|
64
63
|
|
|
65
64
|
exports.andThen = andThen;
|
|
66
65
|
exports.compact = compact;
|
|
@@ -72,7 +71,9 @@ exports.isSome = isSome;
|
|
|
72
71
|
exports.map = map;
|
|
73
72
|
exports.match = match;
|
|
74
73
|
exports.none = none;
|
|
74
|
+
exports.of = of;
|
|
75
75
|
exports.optional = optional;
|
|
76
|
+
exports.show = show;
|
|
76
77
|
exports.some = some;
|
|
77
78
|
exports.tap = tap;
|
|
78
79
|
exports.tapNone = tapNone;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/maybe.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * unwrapOr(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const unwrapOr = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport function tap<T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\nexport function tap<T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tap(\n ...args: [Maybe<unknown>, (value: unknown) => void] | [(value: unknown) => void]\n): unknown {\n if (args.length === 2) return tapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapImpl(maybe, fn);\n}\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport function tapNone<T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\nexport function tapNone<T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\nexport function tapNone(...args: [Maybe<unknown>, () => void] | [() => void]): unknown {\n if (args.length === 2) return tapNoneImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => tapNoneImpl(maybe, fn);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts"],"names":[],"mappings":";;;AAUO,SAAS,IAAA,CAAQ,OAAe,IAAA,EAAwC;AAC7E,EAAA,MAAM,QAAA,GAAW,IAAI,IAAA,KAA6B;AAChD,IAAA,IAAI,KAAK,MAAA,IAAU,KAAA,EAAO,OAAQ,IAAA,CAAsC,GAAG,IAAI,CAAA;AAC/E,IAAA,OAAO,CAAC,IAAA,KAAmB,IAAA,CAAsC,IAAA,EAAM,GAAG,IAAI,CAAA;AAAA,EAChF,CAAA;AAEA,EAAA,OAAO,QAAA;AACT;;;ACcO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM;AAY/D,IAAM,EAAA,GAAK;AAUX,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS;AAYxE,IAAM,MAAA,GAAS,CAAI,KAAA,KAAmC,KAAA,CAAM,IAAA,KAAS;AAYrE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK;AAQxC,IAAM,OAAA,GAAU,CAAO,KAAA,EAAiB,EAAA,KACtC,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA,CAAK,EAAA,CAAG,KAAA,CAAM,KAAK,CAAC,IAAI,IAAA,EAAQ;AAW3C,IAAM,GAAA,GAGT,IAAA,CAAK,CAAA,EAAG,OAAO;AAEnB,IAAM,WAAA,GAAc,CAAO,KAAA,EAAiB,EAAA,KAC1C,MAAA,CAAO,KAAK,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAI,IAAA,EAAQ;AAarC,IAAM,OAAA,GAGT,IAAA,CAAK,CAAA,EAAG,WAAW;AAchB,IAAM,OAAA,GAAU;AAEvB,IAAM,SAAA,GAAY,CAAO,KAAA,EAAiB,MAAA,EAAyB,MAAA,KACjE,MAAA,CAAO,KAAK,CAAA,GAAI,MAAA,CAAO,KAAA,CAAM,KAAK,CAAA,GAAI,MAAA,EAAO;AAmBxC,IAAM,KAAA,GAGT,IAAA,CAAK,CAAA,EAAG,SAAS;AAErB,IAAM,YAAA,GAAe,CAAI,KAAA,EAAiB,YAAA,KACxC,OAAO,KAAK,CAAA,GAAI,MAAM,KAAA,GAAQ,YAAA;AAEzB,IAAM,QAAA,GAGT,IAAA,CAAK,CAAA,EAAG,YAAY;AAajB,IAAM,MAAA,GAAS,CAAI,KAAA,KAAuB;AAC/C,EAAA,IAAI,MAAA,CAAO,KAAK,CAAA,EAAG;AACjB,IAAA,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAUO,IAAM,OAAA,GAAU,CAAI,MAAA,KAAqC;AAC9D,EAAA,MAAM,MAAW,EAAC;AAClB,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,IAAI,MAAA,CAAO,CAAC,CAAA,EAAG;AACb,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,KAAK,CAAA;AAAA,IAClB;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAcO,IAAM,UAAA,GAAa,CAAO,KAAA,EAAqB,EAAA,KACpD,QAAQ,KAAA,CAAM,GAAA,CAAI,EAAE,CAAC;AAWhB,IAAM,QAAA,GAAW,CAAO,KAAA,EAA6B,EAAA,KAC1D,YAAY,YAAA,CAAa,KAAK,GAAG,EAAE;AAErC,IAAM,OAAA,GAAU,CAAI,KAAA,EAAiB,EAAA,KAAqC;AACxE,EAAA,IAAI,MAAA,CAAO,KAAK,CAAA,EAAG,EAAA,CAAG,MAAM,KAAK,CAAA;AACjC,EAAA,OAAO,KAAA;AACT,CAAA;AAYO,IAAM,GAAA,GAGT,IAAA,CAAK,CAAA,EAAG,OAAO;AAEnB,IAAM,WAAA,GAAc,CAAI,KAAA,EAAiB,EAAA,KAA6B;AACpE,EAAA,IAAI,MAAA,CAAO,KAAK,CAAA,EAAG,EAAA,EAAG;AACtB,EAAA,OAAO,KAAA;AACT,CAAA;AAYO,IAAM,OAAA,GAGT,IAAA,CAAK,CAAA,EAAG,WAAW;AAEvB,IAAM,YAAA,GAAe,CAAC,OAAA,KAA6B;AACjD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA,IAAK,OAAO,OAAO,CAAA;AAAA,EAClD,CAAA,CAAA,MAAQ;AAEN,IAAA,OAAO,OAAO,OAAO,CAAA;AAAA,EACvB;AACF,CAAA;AAaO,IAAM,IAAA,GAAO,CAAI,KAAA,KACtB,MAAA,CAAO,KAAK,CAAA,GAAI,CAAA,KAAA,EAAQ,YAAA,CAAa,KAAA,CAAM,KAAK,CAAC,CAAA,CAAA,CAAA,GAAM","file":"index.cjs","sourcesContent":["/**\n * Owning module for the dual-form call idiom used across `@onrails/maybe`:\n * a function accepts either data-first (`map(m, fn)`) or curried data-last\n * (`map(fn)(m)`) invocation, dispatching on argument count against a fixed\n * `arity`.\n *\n * This is a deliberate twin of `packages/result/src/internal/dual.ts` — keep\n * the two in sync. No cross-package imports are allowed in this repo, so the\n * helper is vendored independently in each package rather than shared.\n */\nexport function dual<F>(arity: number, body: (...args: never[]) => unknown): F {\n const dispatch = (...args: unknown[]): unknown => {\n if (args.length >= arity) return (body as (...a: unknown[]) => unknown)(...args);\n return (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n };\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import { dual } from \"./internal/dual.js\";\n\n/**\n * Tagged optional value — a discriminated union over `_tag` where absence\n * (`None`) is an expected outcome, not a failure. Construct with {@link some} /\n * {@link none}, narrow with {@link isSome} / {@link isNone}, and collapse with\n * {@link match}.\n *\n * @example\n * ```ts\n * function findUser(id: string): Maybe<User> {\n * return fromNullable(db.users.get(id));\n * }\n * ```\n */\nexport type Maybe<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/** The `Some` branch of a {@link Maybe} — a present value tagged `\"Some\"`. */\nexport type Some<T> = Extract<Maybe<T>, { _tag: \"Some\" }>;\n\n/** The `None` branch of a {@link Maybe} — expected absence, tagged `\"None\"`. */\nexport type None = Extract<Maybe<never>, { _tag: \"None\" }>;\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/**\n * Fantasy Land `pure` — alias of {@link some}. One lift name shared across\n * the trio (`of` / `Result.of` / `ResultAsync.of`) for generic and FL-style\n * code.\n *\n * @example\n * ```ts\n * const m = of(1); // Maybe<number> — identical to some(1)\n * ```\n */\nexport const of = some;\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => ({ _tag: \"None\" });\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(cache.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport const map: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\n <T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, mapImpl);\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const flatMap: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\n <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, flatMapImpl);\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport const match: {\n <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\n <T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\n} = dual(3, matchImpl);\n\nconst unwrapOrImpl = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T>(maybe: Maybe<T>, defaultValue: T): T;\n <T>(defaultValue: T): (maybe: Maybe<T>) => T;\n} = dual(2, unwrapOrImpl);\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (isNone(maybe)) {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport const tap: {\n <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\n <T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapImpl);\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport const tapNone: {\n <T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\n <T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapNoneImpl);\n\nconst printPayload = (payload: unknown): string => {\n try {\n return JSON.stringify(payload) ?? String(payload);\n } catch {\n // cyclic or non-JSON payload — a debug printer must not throw\n return String(payload);\n }\n};\n\n/**\n * Debug printer — renders a maybe as `Some(…)` / `None` for logs. Payloads\n * print as JSON (values are plain data by design); non-JSON payloads fall\n * back to `String(...)`. Mirror of the result package's `show`.\n *\n * @example\n * ```ts\n * show(some(1)); // 'Some(1)'\n * show(none()); // 'None'\n * ```\n */\nexport const show = <T>(maybe: Maybe<T>): string =>\n isSome(maybe) ? `Some(${printPayload(maybe.value)})` : \"None\";\n"]}
|