@onrails/maybe 0.2.5 → 0.3.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 CHANGED
@@ -46,3 +46,7 @@ type Maybe<T> =
46
46
  ## Deferred
47
47
 
48
48
  - Fantasy Land instances.
49
+
50
+ ## Prototype carriers (breaking)
51
+
52
+ `some` / `none` return live carriers with `map`, `flatMap`/`andThen`, `match`, `unwrapOr`, `tap` and `tapNone` on a shared non-enumerable prototype (`src/internal/proto.ts`); `_tag` and `value` stay own enumerable properties, so JSON output is unchanged. After `JSON.parse`, `structuredClone`, spread or `postMessage`, lift plain data with `Maybe.from` (`MaybeData<T>` is its input). Hand-written `{ _tag: "Some", value }` literals are no longer a `Maybe`; `onrails-codemod --literals` rewrites them. The same Redux/immer caveats as `@onrails/result` apply (see its DESIGN).
package/README.md CHANGED
@@ -44,6 +44,10 @@ import { toResult } from "@onrails/maybe/interop";
44
44
  const user = toResult(fromNullable(row), () => ({ kind: "not_found" as const }));
45
45
  ```
46
46
 
47
+ ## Prototype carriers
48
+
49
+ `some()` / `none()` return carriers with `map`, `flatMap` / `andThen`, `match`, `unwrapOr`, `tap` and `tapNone` on a non-enumerable prototype; `JSON.stringify` output is unchanged. Data that went through `JSON.parse`, `structuredClone`, spread or `postMessage` is plain: lift it with `Maybe.from` (`MaybeData<T>` is its input). Hand-written `{ _tag: "Some", value }` literals are no longer `Maybe`; run `onrails-codemod <dir> --literals`. See the Redux / immer notes in [@onrails/result](../result/README.md#breaking-changes).
50
+
47
51
  ## Subpaths
48
52
 
49
53
  | Path | Contents |
package/dist/fluent.cjs CHANGED
@@ -6,9 +6,79 @@ function dual(arity, body) {
6
6
  return dispatch;
7
7
  }
8
8
 
9
+ // src/internal/proto.ts
10
+ var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
11
+ var SomeImpl = class _SomeImpl {
12
+ _tag;
13
+ value;
14
+ // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape
15
+ constructor(value) {
16
+ this._tag = "Some";
17
+ this.value = value;
18
+ }
19
+ [INSPECT](_depth, options, inspect) {
20
+ return `Some(${inspect(this.value, options)})`;
21
+ }
22
+ map(fn) {
23
+ return new _SomeImpl(fn(this.value));
24
+ }
25
+ flatMap(fn) {
26
+ return fn(this.value);
27
+ }
28
+ andThen(fn) {
29
+ return fn(this.value);
30
+ }
31
+ match(onSome, _onNone) {
32
+ return onSome(this.value);
33
+ }
34
+ unwrapOr(_defaultValue) {
35
+ return this.value;
36
+ }
37
+ tap(fn) {
38
+ fn(this.value);
39
+ return this;
40
+ }
41
+ tapNone(_fn) {
42
+ return this;
43
+ }
44
+ };
45
+ var NoneImpl = class _NoneImpl {
46
+ _tag;
47
+ constructor() {
48
+ this._tag = "None";
49
+ }
50
+ [INSPECT]() {
51
+ return "None";
52
+ }
53
+ map(_fn) {
54
+ return new _NoneImpl();
55
+ }
56
+ flatMap(_fn) {
57
+ return new _NoneImpl();
58
+ }
59
+ andThen(_fn) {
60
+ return new _NoneImpl();
61
+ }
62
+ match(_onSome, onNone) {
63
+ return onNone();
64
+ }
65
+ unwrapOr(defaultValue) {
66
+ return defaultValue;
67
+ }
68
+ tap(_fn) {
69
+ return this;
70
+ }
71
+ tapNone(fn) {
72
+ fn();
73
+ return this;
74
+ }
75
+ };
76
+ var newSome = (value) => new SomeImpl(value);
77
+ var newNone = () => new NoneImpl();
78
+
9
79
  // src/maybe.ts
10
- var some = (value) => ({ _tag: "Some", value });
11
- var none = () => ({ _tag: "None" });
80
+ var some = (value) => newSome(value);
81
+ var none = () => newNone();
12
82
  var isSome = (maybe) => maybe._tag === "Some";
13
83
  var isNone = (maybe) => maybe._tag === "None";
14
84
  var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
@@ -1 +1 @@
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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\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"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/internal/proto.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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import type { Maybe, MaybeMethods } from \"../maybe.js\";\n\n/**\n * `util.inspect` / `Bun.inspect` / `console.log` hook: render `Ok(1)` instead of dumping the class\n * name and every prototype method. Symbol-keyed, so it adds no enumerable field and JSON is unchanged.\n */\nconst INSPECT = Symbol.for(\"nodejs.util.inspect.custom\");\ntype Inspect = (value: unknown, options?: unknown) => string;\n\n/**\n * Live carrier classes. `_tag` and `value` are own enumerable fields, so JSON\n * and `structuredClone` see plain data; every method lives on the prototype\n * and is therefore non-enumerable. Internal only: build carriers with\n * `some` / `none`.\n */\nclass SomeImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"Some\";\n readonly value: T;\n // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape\n constructor(value: T) {\n this._tag = \"Some\";\n this.value = value;\n }\n [INSPECT](_depth: number, options: unknown, inspect: Inspect): string {\n return `Some(${inspect(this.value, options)})`;\n }\n map<U>(fn: (value: T) => U): Maybe<U> {\n return new SomeImpl(fn(this.value));\n }\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n match<U>(onSome: (value: T) => U, _onNone: () => U): U {\n return onSome(this.value);\n }\n unwrapOr<U>(_defaultValue: U): T | U {\n return this.value;\n }\n tap(fn: (value: T) => void): Maybe<T> {\n fn(this.value);\n return this;\n }\n tapNone(_fn: () => void): Maybe<T> {\n return this;\n }\n}\n\nclass NoneImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"None\";\n constructor() {\n this._tag = \"None\";\n }\n [INSPECT](): string {\n return \"None\";\n }\n map<U>(_fn: (value: T) => U): Maybe<U> {\n return new NoneImpl<U>();\n }\n flatMap<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n andThen<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n match<U>(_onSome: (value: T) => U, onNone: () => U): U {\n return onNone();\n }\n unwrapOr<U>(defaultValue: U): T | U {\n return defaultValue;\n }\n tap(_fn: (value: T) => void): Maybe<T> {\n return this;\n }\n tapNone(fn: () => void): Maybe<T> {\n fn();\n return this;\n }\n}\n\nexport const newSome = <T>(value: T): Maybe<T> => new SomeImpl(value);\nexport const newNone = <T>(): Maybe<T> => new NoneImpl<T>();\n","import { dual } from \"./internal/dual.js\";\nimport { newNone, newSome } from \"./internal/proto.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> = MaybeData<T> & MaybeMethods<T>;\n\n/**\n * Plain-data shape of a {@link Maybe}: what `JSON.parse`, `structuredClone`\n * and `postMessage` hand back. It has no methods; lift it with `Maybe.from`.\n */\nexport type MaybeData<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/**\n * Methods every live {@link Maybe} carries on its (non-enumerable) prototype.\n * Core semantics, mirroring `ResultMethods`: `tap` does not swallow throws.\n */\nexport interface MaybeMethods<T> {\n /** Transform the `Some` value; `None` passes through. */\n map<U>(fn: (value: T) => U): Maybe<U>;\n /** Canonical bind; short-circuits on `None`. */\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Alias of {@link MaybeMethods.flatMap}. */\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Fold both branches into one value. */\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n /** The `Some` value, or `defaultValue` on `None`. */\n unwrapOr<U>(defaultValue: U): T | U;\n /** Observe the `Some` value, pass the maybe through. */\n tap(fn: (value: T) => void): Maybe<T>;\n /** Observe `None`, pass the maybe through. */\n tapNone(fn: () => void): Maybe<T>;\n}\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> => newSome(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> => newNone<T>();\n\n/**\n * Value-side companion of the {@link Maybe} type. `from` lifts plain data back\n * into a live `Maybe` at every deserialization sink (`JSON.parse`,\n * `structuredClone`, `postMessage`, Redux rehydration). It trusts the shape,\n * but an unknown `_tag` is a defect and throws a `TypeError` rather than\n * silently becoming `None`.\n *\n * @example\n * ```ts\n * const live = Maybe.from<number>(JSON.parse(JSON.stringify(some(1))));\n * live.map((n) => n + 1);\n * ```\n */\nexport const Maybe = {\n from: <T>(data: MaybeData<T>): Maybe<T> => {\n if (data._tag === \"Some\") return newSome(data.value);\n if (data._tag === \"None\") return newNone<T>();\n throw new TypeError(`Maybe.from: unknown _tag ${String((data as { _tag?: unknown })._tag)}`);\n },\n} as const;\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, U>(maybe: Maybe<T>, defaultValue: U): T | U =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T, U>(maybe: Maybe<T>, defaultValue: U): T | U;\n <U>(defaultValue: U): <T>(maybe: Maybe<T>) => T | U;\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 (maybe._tag === \"None\") {\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<U>(defaultValue: U): T | U;\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
@@ -8,7 +8,7 @@ export type FluentMaybe<T> = {
8
8
  tap(fn: (value: T) => void): FluentMaybe<T>;
9
9
  tapNone(fn: () => void): FluentMaybe<T>;
10
10
  match<U>(onSome: (value: T) => U, onNone: () => U): U;
11
- unwrapOr(defaultValue: T): T;
11
+ unwrapOr<U>(defaultValue: U): T | U;
12
12
  /** Exit the wrapper — hand back the canonical plain-data maybe. */
13
13
  toMaybe(): Maybe<T>;
14
14
  /** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */
package/dist/fluent.d.ts CHANGED
@@ -8,7 +8,7 @@ export type FluentMaybe<T> = {
8
8
  tap(fn: (value: T) => void): FluentMaybe<T>;
9
9
  tapNone(fn: () => void): FluentMaybe<T>;
10
10
  match<U>(onSome: (value: T) => U, onNone: () => U): U;
11
- unwrapOr(defaultValue: T): T;
11
+ unwrapOr<U>(defaultValue: U): T | U;
12
12
  /** Exit the wrapper — hand back the canonical plain-data maybe. */
13
13
  toMaybe(): Maybe<T>;
14
14
  /** Debug terminal — `Some(…)` / `None`, same output as {@link show}. */
package/dist/fluent.js CHANGED
@@ -4,9 +4,79 @@ function dual(arity, body) {
4
4
  return dispatch;
5
5
  }
6
6
 
7
+ // src/internal/proto.ts
8
+ var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
9
+ var SomeImpl = class _SomeImpl {
10
+ _tag;
11
+ value;
12
+ // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape
13
+ constructor(value) {
14
+ this._tag = "Some";
15
+ this.value = value;
16
+ }
17
+ [INSPECT](_depth, options, inspect) {
18
+ return `Some(${inspect(this.value, options)})`;
19
+ }
20
+ map(fn) {
21
+ return new _SomeImpl(fn(this.value));
22
+ }
23
+ flatMap(fn) {
24
+ return fn(this.value);
25
+ }
26
+ andThen(fn) {
27
+ return fn(this.value);
28
+ }
29
+ match(onSome, _onNone) {
30
+ return onSome(this.value);
31
+ }
32
+ unwrapOr(_defaultValue) {
33
+ return this.value;
34
+ }
35
+ tap(fn) {
36
+ fn(this.value);
37
+ return this;
38
+ }
39
+ tapNone(_fn) {
40
+ return this;
41
+ }
42
+ };
43
+ var NoneImpl = class _NoneImpl {
44
+ _tag;
45
+ constructor() {
46
+ this._tag = "None";
47
+ }
48
+ [INSPECT]() {
49
+ return "None";
50
+ }
51
+ map(_fn) {
52
+ return new _NoneImpl();
53
+ }
54
+ flatMap(_fn) {
55
+ return new _NoneImpl();
56
+ }
57
+ andThen(_fn) {
58
+ return new _NoneImpl();
59
+ }
60
+ match(_onSome, onNone) {
61
+ return onNone();
62
+ }
63
+ unwrapOr(defaultValue) {
64
+ return defaultValue;
65
+ }
66
+ tap(_fn) {
67
+ return this;
68
+ }
69
+ tapNone(fn) {
70
+ fn();
71
+ return this;
72
+ }
73
+ };
74
+ var newSome = (value) => new SomeImpl(value);
75
+ var newNone = () => new NoneImpl();
76
+
7
77
  // src/maybe.ts
8
- var some = (value) => ({ _tag: "Some", value });
9
- var none = () => ({ _tag: "None" });
78
+ var some = (value) => newSome(value);
79
+ var none = () => newNone();
10
80
  var isSome = (maybe) => maybe._tag === "Some";
11
81
  var isNone = (maybe) => maybe._tag === "None";
12
82
  var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
@@ -1 +1 @@
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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\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"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/internal/proto.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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import type { Maybe, MaybeMethods } from \"../maybe.js\";\n\n/**\n * `util.inspect` / `Bun.inspect` / `console.log` hook: render `Ok(1)` instead of dumping the class\n * name and every prototype method. Symbol-keyed, so it adds no enumerable field and JSON is unchanged.\n */\nconst INSPECT = Symbol.for(\"nodejs.util.inspect.custom\");\ntype Inspect = (value: unknown, options?: unknown) => string;\n\n/**\n * Live carrier classes. `_tag` and `value` are own enumerable fields, so JSON\n * and `structuredClone` see plain data; every method lives on the prototype\n * and is therefore non-enumerable. Internal only: build carriers with\n * `some` / `none`.\n */\nclass SomeImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"Some\";\n readonly value: T;\n // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape\n constructor(value: T) {\n this._tag = \"Some\";\n this.value = value;\n }\n [INSPECT](_depth: number, options: unknown, inspect: Inspect): string {\n return `Some(${inspect(this.value, options)})`;\n }\n map<U>(fn: (value: T) => U): Maybe<U> {\n return new SomeImpl(fn(this.value));\n }\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n match<U>(onSome: (value: T) => U, _onNone: () => U): U {\n return onSome(this.value);\n }\n unwrapOr<U>(_defaultValue: U): T | U {\n return this.value;\n }\n tap(fn: (value: T) => void): Maybe<T> {\n fn(this.value);\n return this;\n }\n tapNone(_fn: () => void): Maybe<T> {\n return this;\n }\n}\n\nclass NoneImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"None\";\n constructor() {\n this._tag = \"None\";\n }\n [INSPECT](): string {\n return \"None\";\n }\n map<U>(_fn: (value: T) => U): Maybe<U> {\n return new NoneImpl<U>();\n }\n flatMap<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n andThen<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n match<U>(_onSome: (value: T) => U, onNone: () => U): U {\n return onNone();\n }\n unwrapOr<U>(defaultValue: U): T | U {\n return defaultValue;\n }\n tap(_fn: (value: T) => void): Maybe<T> {\n return this;\n }\n tapNone(fn: () => void): Maybe<T> {\n fn();\n return this;\n }\n}\n\nexport const newSome = <T>(value: T): Maybe<T> => new SomeImpl(value);\nexport const newNone = <T>(): Maybe<T> => new NoneImpl<T>();\n","import { dual } from \"./internal/dual.js\";\nimport { newNone, newSome } from \"./internal/proto.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> = MaybeData<T> & MaybeMethods<T>;\n\n/**\n * Plain-data shape of a {@link Maybe}: what `JSON.parse`, `structuredClone`\n * and `postMessage` hand back. It has no methods; lift it with `Maybe.from`.\n */\nexport type MaybeData<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/**\n * Methods every live {@link Maybe} carries on its (non-enumerable) prototype.\n * Core semantics, mirroring `ResultMethods`: `tap` does not swallow throws.\n */\nexport interface MaybeMethods<T> {\n /** Transform the `Some` value; `None` passes through. */\n map<U>(fn: (value: T) => U): Maybe<U>;\n /** Canonical bind; short-circuits on `None`. */\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Alias of {@link MaybeMethods.flatMap}. */\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Fold both branches into one value. */\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n /** The `Some` value, or `defaultValue` on `None`. */\n unwrapOr<U>(defaultValue: U): T | U;\n /** Observe the `Some` value, pass the maybe through. */\n tap(fn: (value: T) => void): Maybe<T>;\n /** Observe `None`, pass the maybe through. */\n tapNone(fn: () => void): Maybe<T>;\n}\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> => newSome(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> => newNone<T>();\n\n/**\n * Value-side companion of the {@link Maybe} type. `from` lifts plain data back\n * into a live `Maybe` at every deserialization sink (`JSON.parse`,\n * `structuredClone`, `postMessage`, Redux rehydration). It trusts the shape,\n * but an unknown `_tag` is a defect and throws a `TypeError` rather than\n * silently becoming `None`.\n *\n * @example\n * ```ts\n * const live = Maybe.from<number>(JSON.parse(JSON.stringify(some(1))));\n * live.map((n) => n + 1);\n * ```\n */\nexport const Maybe = {\n from: <T>(data: MaybeData<T>): Maybe<T> => {\n if (data._tag === \"Some\") return newSome(data.value);\n if (data._tag === \"None\") return newNone<T>();\n throw new TypeError(`Maybe.from: unknown _tag ${String((data as { _tag?: unknown })._tag)}`);\n },\n} as const;\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, U>(maybe: Maybe<T>, defaultValue: U): T | U =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T, U>(maybe: Maybe<T>, defaultValue: U): T | U;\n <U>(defaultValue: U): <T>(maybe: Maybe<T>) => T | U;\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 (maybe._tag === \"None\") {\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<U>(defaultValue: U): T | U;\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
@@ -6,10 +6,87 @@ function dual(arity, body) {
6
6
  return dispatch;
7
7
  }
8
8
 
9
+ // src/internal/proto.ts
10
+ var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
11
+ var SomeImpl = class _SomeImpl {
12
+ _tag;
13
+ value;
14
+ // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape
15
+ constructor(value) {
16
+ this._tag = "Some";
17
+ this.value = value;
18
+ }
19
+ [INSPECT](_depth, options, inspect) {
20
+ return `Some(${inspect(this.value, options)})`;
21
+ }
22
+ map(fn) {
23
+ return new _SomeImpl(fn(this.value));
24
+ }
25
+ flatMap(fn) {
26
+ return fn(this.value);
27
+ }
28
+ andThen(fn) {
29
+ return fn(this.value);
30
+ }
31
+ match(onSome, _onNone) {
32
+ return onSome(this.value);
33
+ }
34
+ unwrapOr(_defaultValue) {
35
+ return this.value;
36
+ }
37
+ tap(fn) {
38
+ fn(this.value);
39
+ return this;
40
+ }
41
+ tapNone(_fn) {
42
+ return this;
43
+ }
44
+ };
45
+ var NoneImpl = class _NoneImpl {
46
+ _tag;
47
+ constructor() {
48
+ this._tag = "None";
49
+ }
50
+ [INSPECT]() {
51
+ return "None";
52
+ }
53
+ map(_fn) {
54
+ return new _NoneImpl();
55
+ }
56
+ flatMap(_fn) {
57
+ return new _NoneImpl();
58
+ }
59
+ andThen(_fn) {
60
+ return new _NoneImpl();
61
+ }
62
+ match(_onSome, onNone) {
63
+ return onNone();
64
+ }
65
+ unwrapOr(defaultValue) {
66
+ return defaultValue;
67
+ }
68
+ tap(_fn) {
69
+ return this;
70
+ }
71
+ tapNone(fn) {
72
+ fn();
73
+ return this;
74
+ }
75
+ };
76
+ var newSome = (value) => new SomeImpl(value);
77
+ var newNone = () => new NoneImpl();
78
+
9
79
  // src/maybe.ts
10
- var some = (value) => ({ _tag: "Some", value });
80
+ var some = (value) => newSome(value);
11
81
  var of = some;
12
- var none = () => ({ _tag: "None" });
82
+ var none = () => newNone();
83
+ var Maybe = {
84
+ from: (data) => {
85
+ if (data._tag === "Some") return newSome(data.value);
86
+ if (data._tag === "None") return newNone();
87
+ throw new TypeError(`Maybe.from: unknown _tag ${String(data._tag)}`);
88
+ }
89
+ };
13
90
  var isSome = (maybe) => maybe._tag === "Some";
14
91
  var isNone = (maybe) => maybe._tag === "None";
15
92
  var fromNullable = (value) => value == null ? none() : some(value);
@@ -23,7 +100,7 @@ var match = dual(3, matchImpl);
23
100
  var unwrapOrImpl = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
24
101
  var unwrapOr = dual(2, unwrapOrImpl);
25
102
  var unwrap = (maybe) => {
26
- if (isNone(maybe)) {
103
+ if (maybe._tag === "None") {
27
104
  throw new Error("Called unwrap on None");
28
105
  }
29
106
  return maybe.value;
@@ -58,6 +135,7 @@ var printPayload = (payload) => {
58
135
  };
59
136
  var show = (maybe) => isSome(maybe) ? `Some(${printPayload(maybe.value)})` : "None";
60
137
 
138
+ exports.Maybe = Maybe;
61
139
  exports.andThen = andThen;
62
140
  exports.compact = compact;
63
141
  exports.compactMap = compactMap;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts"],"names":[],"mappings":"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","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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\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"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/internal/proto.ts","../src/maybe.ts"],"names":[],"mappings":"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","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 args.length >= arity\n ? (body as (...a: unknown[]) => unknown)(...args)\n : (self: unknown) => (body as (...a: unknown[]) => unknown)(self, ...args);\n // cast: the caller's overloaded type annotation supplies the real signature\n return dispatch as F;\n}\n","import type { Maybe, MaybeMethods } from \"../maybe.js\";\n\n/**\n * `util.inspect` / `Bun.inspect` / `console.log` hook: render `Ok(1)` instead of dumping the class\n * name and every prototype method. Symbol-keyed, so it adds no enumerable field and JSON is unchanged.\n */\nconst INSPECT = Symbol.for(\"nodejs.util.inspect.custom\");\ntype Inspect = (value: unknown, options?: unknown) => string;\n\n/**\n * Live carrier classes. `_tag` and `value` are own enumerable fields, so JSON\n * and `structuredClone` see plain data; every method lives on the prototype\n * and is therefore non-enumerable. Internal only: build carriers with\n * `some` / `none`.\n */\nclass SomeImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"Some\";\n readonly value: T;\n // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape\n constructor(value: T) {\n this._tag = \"Some\";\n this.value = value;\n }\n [INSPECT](_depth: number, options: unknown, inspect: Inspect): string {\n return `Some(${inspect(this.value, options)})`;\n }\n map<U>(fn: (value: T) => U): Maybe<U> {\n return new SomeImpl(fn(this.value));\n }\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n match<U>(onSome: (value: T) => U, _onNone: () => U): U {\n return onSome(this.value);\n }\n unwrapOr<U>(_defaultValue: U): T | U {\n return this.value;\n }\n tap(fn: (value: T) => void): Maybe<T> {\n fn(this.value);\n return this;\n }\n tapNone(_fn: () => void): Maybe<T> {\n return this;\n }\n}\n\nclass NoneImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"None\";\n constructor() {\n this._tag = \"None\";\n }\n [INSPECT](): string {\n return \"None\";\n }\n map<U>(_fn: (value: T) => U): Maybe<U> {\n return new NoneImpl<U>();\n }\n flatMap<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n andThen<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n match<U>(_onSome: (value: T) => U, onNone: () => U): U {\n return onNone();\n }\n unwrapOr<U>(defaultValue: U): T | U {\n return defaultValue;\n }\n tap(_fn: (value: T) => void): Maybe<T> {\n return this;\n }\n tapNone(fn: () => void): Maybe<T> {\n fn();\n return this;\n }\n}\n\nexport const newSome = <T>(value: T): Maybe<T> => new SomeImpl(value);\nexport const newNone = <T>(): Maybe<T> => new NoneImpl<T>();\n","import { dual } from \"./internal/dual.js\";\nimport { newNone, newSome } from \"./internal/proto.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> = MaybeData<T> & MaybeMethods<T>;\n\n/**\n * Plain-data shape of a {@link Maybe}: what `JSON.parse`, `structuredClone`\n * and `postMessage` hand back. It has no methods; lift it with `Maybe.from`.\n */\nexport type MaybeData<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/**\n * Methods every live {@link Maybe} carries on its (non-enumerable) prototype.\n * Core semantics, mirroring `ResultMethods`: `tap` does not swallow throws.\n */\nexport interface MaybeMethods<T> {\n /** Transform the `Some` value; `None` passes through. */\n map<U>(fn: (value: T) => U): Maybe<U>;\n /** Canonical bind; short-circuits on `None`. */\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Alias of {@link MaybeMethods.flatMap}. */\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Fold both branches into one value. */\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n /** The `Some` value, or `defaultValue` on `None`. */\n unwrapOr<U>(defaultValue: U): T | U;\n /** Observe the `Some` value, pass the maybe through. */\n tap(fn: (value: T) => void): Maybe<T>;\n /** Observe `None`, pass the maybe through. */\n tapNone(fn: () => void): Maybe<T>;\n}\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> => newSome(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> => newNone<T>();\n\n/**\n * Value-side companion of the {@link Maybe} type. `from` lifts plain data back\n * into a live `Maybe` at every deserialization sink (`JSON.parse`,\n * `structuredClone`, `postMessage`, Redux rehydration). It trusts the shape,\n * but an unknown `_tag` is a defect and throws a `TypeError` rather than\n * silently becoming `None`.\n *\n * @example\n * ```ts\n * const live = Maybe.from<number>(JSON.parse(JSON.stringify(some(1))));\n * live.map((n) => n + 1);\n * ```\n */\nexport const Maybe = {\n from: <T>(data: MaybeData<T>): Maybe<T> => {\n if (data._tag === \"Some\") return newSome(data.value);\n if (data._tag === \"None\") return newNone<T>();\n throw new TypeError(`Maybe.from: unknown _tag ${String((data as { _tag?: unknown })._tag)}`);\n },\n} as const;\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, U>(maybe: Maybe<T>, defaultValue: U): T | U =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T, U>(maybe: Maybe<T>, defaultValue: U): T | U;\n <U>(defaultValue: U): <T>(maybe: Maybe<T>) => T | U;\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 (maybe._tag === \"None\") {\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"]}
package/dist/index.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- export type { Maybe, None, Some } from "./maybe.js";
2
- export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";
1
+ export type { MaybeData, MaybeMethods, None, Some } from "./maybe.js";
2
+ export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, Maybe, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { Maybe, None, Some } from "./maybe.js";
2
- export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";
1
+ export type { MaybeData, MaybeMethods, None, Some } from "./maybe.js";
2
+ export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, Maybe, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";