@onrails/maybe 0.2.2 → 0.2.5

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/dist/index.d.ts CHANGED
@@ -1,244 +1,2 @@
1
- /**
2
- * Tagged optional value — a discriminated union over `_tag` where absence
3
- * (`None`) is an expected outcome, not a failure. Construct with {@link some} /
4
- * {@link none}, narrow with {@link isSome} / {@link isNone}, and collapse with
5
- * {@link match}.
6
- *
7
- * @example
8
- * ```ts
9
- * function findUser(id: string): Maybe<User> {
10
- * return fromNullable(db.users.get(id));
11
- * }
12
- * ```
13
- */
14
- type Maybe<T> = {
15
- readonly _tag: "Some";
16
- readonly value: T;
17
- } | {
18
- readonly _tag: "None";
19
- };
20
- /** The `Some` branch of a {@link Maybe} — a present value tagged `"Some"`. */
21
- type Some<T> = Extract<Maybe<T>, {
22
- _tag: "Some";
23
- }>;
24
- /** The `None` branch of a {@link Maybe} — expected absence, tagged `"None"`. */
25
- type None = Extract<Maybe<never>, {
26
- _tag: "None";
27
- }>;
28
- /**
29
- * Lifts a value into the `Some` branch.
30
- *
31
- * @example
32
- * ```ts
33
- * const m = some(1); // Maybe<number>
34
- * ```
35
- */
36
- declare const some: <T>(value: T) => Maybe<T>;
37
- /**
38
- * Fantasy Land `pure` — alias of {@link some}. One lift name shared across
39
- * the trio (`of` / `Result.of` / `ResultAsync.of`) for generic and FL-style
40
- * code.
41
- *
42
- * @example
43
- * ```ts
44
- * const m = of(1); // Maybe<number> — identical to some(1)
45
- * ```
46
- */
47
- declare const of: <T>(value: T) => Maybe<T>;
48
- /**
49
- * The `None` value — represents expected absence. Not a failure.
50
- *
51
- * @example
52
- * ```ts
53
- * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();
54
- * ```
55
- */
56
- declare const none: <T = never>() => Maybe<T>;
57
- /**
58
- * Type-narrowing predicate: returns `true` when the value is `Some`.
59
- *
60
- * @example
61
- * ```ts
62
- * if (isSome(m)) {
63
- * console.log(m.value); // narrowed to Some branch
64
- * }
65
- * ```
66
- */
67
- declare const isSome: <T>(maybe: Maybe<T>) => maybe is Some<T>;
68
- /**
69
- * Type-narrowing predicate: returns `true` when the value is `None`.
70
- *
71
- * @example
72
- * ```ts
73
- * if (isNone(fromNullable(user))) {
74
- * return redirect("/login"); // narrowed to None branch
75
- * }
76
- * ```
77
- */
78
- declare const isNone: <T>(maybe: Maybe<T>) => maybe is None;
79
- /**
80
- * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become
81
- * `None`; anything else becomes `Some`.
82
- *
83
- * @example
84
- * ```ts
85
- * fromNullable(cache.get(key)); // Maybe<V>
86
- * fromNullable(null); // None
87
- * ```
88
- */
89
- declare const fromNullable: <T>(value: T | null | undefined) => Maybe<T>;
90
- /**
91
- * Transform the `Some` value, passing `None` through unchanged. Dual-form.
92
- *
93
- * @example
94
- * ```ts
95
- * map(some(2), (n) => n * 3); // Some 6 — data-first
96
- * pipe(fromNullable(input), map((s) => s.trim())); // curried
97
- * ```
98
- */
99
- declare const map: {
100
- <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;
101
- <T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;
102
- };
103
- /**
104
- * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;
105
- * short-circuits on `None`. Dual-form.
106
- *
107
- * @example
108
- * ```ts
109
- * flatMap(fromNullable(user), (u) =>
110
- * u.active ? some(u) : none(),
111
- * );
112
- * ```
113
- */
114
- declare const flatMap: {
115
- <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;
116
- <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;
117
- };
118
- /**
119
- * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer
120
- * {@link flatMap} as the canonical name; reach for `andThen` only when porting
121
- * neverthrow code or matching its vocabulary.
122
- *
123
- * @example
124
- * ```ts
125
- * andThen(fromNullable(user), (u) =>
126
- * u.active ? some(u) : none(),
127
- * );
128
- * ```
129
- */
130
- declare const andThen: {
131
- <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;
132
- <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;
133
- };
134
- /**
135
- * Terminal collapse — fold both branches into a single value. Positional,
136
- * dual-form. Same shape as `Result.match` for sibling consistency.
137
- *
138
- * For files that also import `match` from `ts-pattern`, use a namespace
139
- * import (`import * as Maybe from "@onrails/maybe"` → `Maybe.match`) to
140
- * dissolve the collision.
141
- *
142
- * @example
143
- * ```ts
144
- * const greeting = match(
145
- * fromNullable(user),
146
- * (u) => `hello ${u.name}`,
147
- * () => "hello guest",
148
- * );
149
- * ```
150
- */
151
- declare const match: {
152
- <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;
153
- <T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;
154
- };
155
- declare const unwrapOr: {
156
- <T>(maybe: Maybe<T>, defaultValue: T): T;
157
- <T>(defaultValue: T): (maybe: Maybe<T>) => T;
158
- };
159
- /**
160
- * Unwraps the `Some` value or throws if the value is `None`.
161
- *
162
- * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.
163
- * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.
164
- *
165
- * @example
166
- * ```ts
167
- * expect(unwrap(parseUser(raw))).toEqual(expected);
168
- * ```
169
- */
170
- declare const unwrap: <T>(maybe: Maybe<T>) => T;
171
- /**
172
- * Drop every `None` and collect the remaining `Some` values into an array.
173
- *
174
- * @example
175
- * ```ts
176
- * compact([some(1), none(), some(2)]); // [1, 2]
177
- * ```
178
- */
179
- declare const compact: <T>(maybes: readonly Maybe<T>[]) => T[];
180
- /**
181
- * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,
182
- * collect the rest.
183
- *
184
- * @example
185
- * ```ts
186
- * compactMap(rawRows, (row) =>
187
- * row.deletedAt ? none() : some(row.id),
188
- * );
189
- * // string[]
190
- * ```
191
- */
192
- declare const compactMap: <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>) => U[];
193
- /**
194
- * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through
195
- * a `Maybe`-returning step. Convenient for chaining lookups.
196
- *
197
- * @example
198
- * ```ts
199
- * optional(rawId, (id) => fromNullable(cache.get(id)));
200
- * ```
201
- */
202
- declare const optional: <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>) => Maybe<U>;
203
- /**
204
- * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.
205
- * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.
206
- *
207
- * @example
208
- * ```ts
209
- * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2
210
- * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some
211
- * ```
212
- */
213
- declare const tap: {
214
- <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;
215
- <T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;
216
- };
217
- /**
218
- * Run a side effect when the `Maybe` is `None`, pass it through unchanged.
219
- * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.
220
- *
221
- * @example
222
- * ```ts
223
- * tapNone(none(), () => metrics.miss()); // runs effect, returns None
224
- * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried
225
- * ```
226
- */
227
- declare const tapNone: {
228
- <T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;
229
- <T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;
230
- };
231
- /**
232
- * Debug printer — renders a maybe as `Some(…)` / `None` for logs. Payloads
233
- * print as JSON (values are plain data by design); non-JSON payloads fall
234
- * back to `String(...)`. Mirror of the result package's `show`.
235
- *
236
- * @example
237
- * ```ts
238
- * show(some(1)); // 'Some(1)'
239
- * show(none()); // 'None'
240
- * ```
241
- */
242
- declare const show: <T>(maybe: Maybe<T>) => string;
243
-
244
- export { type Maybe, type None, type Some, andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr };
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";
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  // src/internal/dual.ts
2
2
  function dual(arity, body) {
3
- const dispatch = (...args) => {
4
- if (args.length >= arity) return body(...args);
5
- return (self) => body(self, ...args);
6
- };
3
+ const dispatch = (...args) => args.length >= arity ? body(...args) : (self) => body(self, ...args);
7
4
  return dispatch;
8
5
  }
9
6
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts"],"names":[],"mappings":"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","file":"index.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"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts"],"names":[],"mappings":"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","file":"index.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"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Owning module for the dual-form call idiom used across `@onrails/maybe`:
3
+ * a function accepts either data-first (`map(m, fn)`) or curried data-last
4
+ * (`map(fn)(m)`) invocation, dispatching on argument count against a fixed
5
+ * `arity`.
6
+ *
7
+ * This is a deliberate twin of `packages/result/src/internal/dual.ts` — keep
8
+ * the two in sync. No cross-package imports are allowed in this repo, so the
9
+ * helper is vendored independently in each package rather than shared.
10
+ */
11
+ export declare function dual<F>(arity: number, body: (...args: never[]) => unknown): F;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Owning module for the dual-form call idiom used across `@onrails/maybe`:
3
+ * a function accepts either data-first (`map(m, fn)`) or curried data-last
4
+ * (`map(fn)(m)`) invocation, dispatching on argument count against a fixed
5
+ * `arity`.
6
+ *
7
+ * This is a deliberate twin of `packages/result/src/internal/dual.ts` — keep
8
+ * the two in sync. No cross-package imports are allowed in this repo, so the
9
+ * helper is vendored independently in each package rather than shared.
10
+ */
11
+ export declare function dual<F>(arity: number, body: (...args: never[]) => unknown): F;
package/dist/interop.cjs CHANGED
@@ -6,10 +6,7 @@ var result = require('@onrails/result');
6
6
 
7
7
  // src/internal/dual.ts
8
8
  function dual(arity, body) {
9
- const dispatch = (...args) => {
10
- if (args.length >= arity) return body(...args);
11
- return (self) => body(self, ...args);
12
- };
9
+ const dispatch = (...args) => args.length >= arity ? body(...args) : (self) => body(self, ...args);
13
10
  return dispatch;
14
11
  }
15
12
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/interop.ts"],"names":["ok","err","result","isOk"],"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,CAAA;AAsB/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAwBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;AClFjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAE1D,IAAM,YAAA,GAAe,CAAO,KAAA,EAAiB,MAAA,KAC3C,MAAA,CAAO,KAAK,CAAA,GAAIA,SAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAIC,UAAA,CAAI,QAAQ,CAAA;AAGzC,IAAM,QAAA,GAGT,IAAA,CAAK,CAAA,EAAG,YAAY;AAExB,IAAM,cAAA,GAAiB,CAAOC,QAAA,KAC5BC,WAAA,CAAKD,QAAM,IAAI,IAAA,CAAKA,QAAA,CAAO,KAAK,CAAA,GAAI,IAAA,EAAK;AAGpC,IAAM,UAAA,GAGT,IAAA,CAAK,CAAA,EAAG,cAAc;AAE1B,IAAM,oBAAA,GAAuB,CAAO,KAAA,EAA6B,MAAA,KAC/D,aAAa,YAAA,CAAa,KAAK,GAAG,MAAM,CAAA;AAGnC,IAAM,gBAAA,GAGT,IAAA,CAAK,CAAA,EAAG,oBAAoB;AAGzB,IAAM,WAAA,GAAc,CAACA,QAAA,KAC1B,CAACC,YAAKD,QAAM,CAAA,IAAK,gBAAA,CAAiBA,QAAA,CAAO,KAAK;AAEhD,IAAM,gBAAA,GAAmB,CAAC,KAAA,KACxB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,MAAA,IAAU,KAAA,IACT,KAAA,CAA4B,IAAA,KAAS,MAAA","file":"interop.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 { err, isOk, ok, type Result } from \"@onrails/result\";\nimport { dual } from \"./internal/dual.js\";\nimport { fromNullable, isSome, type Maybe, none, some } from \"./maybe.js\";\n\n/** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */\nexport type NoneError = { readonly _tag: \"None\" };\n\n/** Construct the {@link NoneError} sentinel. */\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\nconst toResultImpl = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok(maybe.value) : err(onNone());\n\n/** Lift absence into a domain error. */\nexport const toResult: {\n <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;\n} = dual(2, toResultImpl);\n\nconst fromResultImpl = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none();\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult: {\n <T, E>(result: Result<T, E>): Maybe<T>;\n <T, E>(): (result: Result<T, E>) => Maybe<T>;\n} = dual(1, fromResultImpl);\n\nconst nullableToResultImpl = <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E> =>\n toResultImpl(fromNullable(value), onNone);\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult: {\n <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;\n} = dual(2, nullableToResultImpl);\n\n/** True when a Result failed specifically with {@link NoneError}. */\nexport const isNoneError = (result: Result<unknown, unknown>): result is Result<never, NoneError> =>\n !isOk(result) && isNoneErrorValue(result.error);\n\nconst isNoneErrorValue = (error: unknown): error is NoneError =>\n typeof error === \"object\" &&\n error !== null &&\n \"_tag\" in error &&\n (error as { _tag: unknown })._tag === \"None\";\n"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/interop.ts"],"names":["ok","err","result","isOk"],"mappings":";;;;;;;AAUO,SAAS,IAAA,CAAQ,OAAe,IAAA,EAAwC;AAC7E,EAAA,MAAM,QAAA,GAAW,CAAA,GAAI,IAAA,KACnB,IAAA,CAAK,UAAU,KAAA,GACV,IAAA,CAAsC,GAAG,IAAI,IAC9C,CAAC,IAAA,KAAmB,IAAA,CAAsC,IAAA,EAAM,GAAG,IAAI,CAAA;AAE7E,EAAA,OAAO,QAAA;AACT;;;ACcO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM,CAAA;AAsB/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAwBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;AClFjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAE1D,IAAM,YAAA,GAAe,CAAO,KAAA,EAAiB,MAAA,KAC3C,MAAA,CAAO,KAAK,CAAA,GAAIA,SAAA,CAAS,KAAA,CAAM,KAAK,CAAA,GAAIC,UAAA,CAAU,QAAQ,CAAA;AAGrD,IAAM,QAAA,GAGT,IAAA,CAAK,CAAA,EAAG,YAAY;AAExB,IAAM,cAAA,GAAiB,CAAOC,QAAA,KAC5BC,WAAA,CAAKD,QAAM,IAAI,IAAA,CAAKA,QAAA,CAAO,KAAK,CAAA,GAAI,IAAA,EAAK;AAGpC,IAAM,UAAA,GAGT,IAAA,CAAK,CAAA,EAAG,cAAc;AAE1B,IAAM,oBAAA,GAAuB,CAAO,KAAA,EAA6B,MAAA,KAC/D,aAAa,YAAA,CAAa,KAAK,GAAG,MAAM,CAAA;AAGnC,IAAM,gBAAA,GAGT,IAAA,CAAK,CAAA,EAAG,oBAAoB;AAGzB,IAAM,WAAA,GAAc,CAACA,QAAA,KAC1B,CAACC,YAAKD,QAAM,CAAA,IAAK,gBAAA,CAAiBA,QAAA,CAAO,KAAK;AAEhD,IAAM,gBAAA,GAAmB,CAAC,KAAA,KACxB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,MAAA,IAAU,KAAA,IACT,KAAA,CAA4B,IAAA,KAAS,MAAA","file":"interop.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 { err, isOk, ok, type Result } from \"@onrails/result\";\nimport { dual } from \"./internal/dual.js\";\nimport { fromNullable, isSome, type Maybe, none, some } from \"./maybe.js\";\n\n/** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */\nexport type NoneError = { readonly _tag: \"None\" };\n\n/** Construct the {@link NoneError} sentinel. */\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\nconst toResultImpl = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok<T, E>(maybe.value) : err<T, E>(onNone());\n\n/** Lift absence into a domain error. */\nexport const toResult: {\n <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;\n} = dual(2, toResultImpl);\n\nconst fromResultImpl = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none();\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult: {\n <T, E>(result: Result<T, E>): Maybe<T>;\n <T, E>(): (result: Result<T, E>) => Maybe<T>;\n} = dual(1, fromResultImpl);\n\nconst nullableToResultImpl = <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E> =>\n toResultImpl(fromNullable(value), onNone);\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult: {\n <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;\n} = dual(2, nullableToResultImpl);\n\n/** True when a Result failed specifically with {@link NoneError}. */\nexport const isNoneError = (result: Result<unknown, unknown>): result is Result<never, NoneError> =>\n !isOk(result) && isNoneErrorValue(result.error);\n\nconst isNoneErrorValue = (error: unknown): error is NoneError =>\n typeof error === \"object\" &&\n error !== null &&\n \"_tag\" in error &&\n (error as { _tag: unknown })._tag === \"None\";\n"]}
@@ -1,28 +1,25 @@
1
- import { Result } from '@onrails/result';
2
- import { Maybe } from './index.cjs';
3
-
1
+ import { type Result } from "@onrails/result";
2
+ import { type Maybe } from "./maybe.js";
4
3
  /** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */
5
- type NoneError = {
4
+ export type NoneError = {
6
5
  readonly _tag: "None";
7
6
  };
8
7
  /** Construct the {@link NoneError} sentinel. */
9
- declare const noneError: () => NoneError;
8
+ export declare const noneError: () => NoneError;
10
9
  /** Lift absence into a domain error. */
11
- declare const toResult: {
10
+ export declare const toResult: {
12
11
  <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;
13
12
  <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;
14
13
  };
15
14
  /** Ok values become Some; any Err becomes None (errors are not absence). */
16
- declare const fromResult: {
15
+ export declare const fromResult: {
17
16
  <T, E>(result: Result<T, E>): Maybe<T>;
18
17
  <T, E>(): (result: Result<T, E>) => Maybe<T>;
19
18
  };
20
19
  /** Nullable edge → Maybe; explicit None error at the boundary. */
21
- declare const nullableToResult: {
20
+ export declare const nullableToResult: {
22
21
  <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;
23
22
  <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;
24
23
  };
25
24
  /** True when a Result failed specifically with {@link NoneError}. */
26
- declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
27
-
28
- export { type NoneError, fromResult, isNoneError, noneError, nullableToResult, toResult };
25
+ export declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
package/dist/interop.d.ts CHANGED
@@ -1,28 +1,25 @@
1
- import { Result } from '@onrails/result';
2
- import { Maybe } from './index.js';
3
-
1
+ import { type Result } from "@onrails/result";
2
+ import { type Maybe } from "./maybe.js";
4
3
  /** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */
5
- type NoneError = {
4
+ export type NoneError = {
6
5
  readonly _tag: "None";
7
6
  };
8
7
  /** Construct the {@link NoneError} sentinel. */
9
- declare const noneError: () => NoneError;
8
+ export declare const noneError: () => NoneError;
10
9
  /** Lift absence into a domain error. */
11
- declare const toResult: {
10
+ export declare const toResult: {
12
11
  <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;
13
12
  <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;
14
13
  };
15
14
  /** Ok values become Some; any Err becomes None (errors are not absence). */
16
- declare const fromResult: {
15
+ export declare const fromResult: {
17
16
  <T, E>(result: Result<T, E>): Maybe<T>;
18
17
  <T, E>(): (result: Result<T, E>) => Maybe<T>;
19
18
  };
20
19
  /** Nullable edge → Maybe; explicit None error at the boundary. */
21
- declare const nullableToResult: {
20
+ export declare const nullableToResult: {
22
21
  <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;
23
22
  <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;
24
23
  };
25
24
  /** True when a Result failed specifically with {@link NoneError}. */
26
- declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
27
-
28
- export { type NoneError, fromResult, isNoneError, noneError, nullableToResult, toResult };
25
+ export declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
package/dist/interop.js CHANGED
@@ -4,10 +4,7 @@ import { isOk, ok, err } from '@onrails/result';
4
4
 
5
5
  // src/internal/dual.ts
6
6
  function dual(arity, body) {
7
- const dispatch = (...args) => {
8
- if (args.length >= arity) return body(...args);
9
- return (self) => body(self, ...args);
10
- };
7
+ const dispatch = (...args) => args.length >= arity ? body(...args) : (self) => body(self, ...args);
11
8
  return dispatch;
12
9
  }
13
10
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/interop.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,CAAA;AAsB/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAwBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;AClFjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAE1D,IAAM,YAAA,GAAe,CAAO,KAAA,EAAiB,MAAA,KAC3C,MAAA,CAAO,KAAK,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA;AAGzC,IAAM,QAAA,GAGT,IAAA,CAAK,CAAA,EAAG,YAAY;AAExB,IAAM,cAAA,GAAiB,CAAO,MAAA,KAC5B,IAAA,CAAK,MAAM,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA,EAAK;AAGpC,IAAM,UAAA,GAGT,IAAA,CAAK,CAAA,EAAG,cAAc;AAE1B,IAAM,oBAAA,GAAuB,CAAO,KAAA,EAA6B,MAAA,KAC/D,aAAa,YAAA,CAAa,KAAK,GAAG,MAAM,CAAA;AAGnC,IAAM,gBAAA,GAGT,IAAA,CAAK,CAAA,EAAG,oBAAoB;AAGzB,IAAM,WAAA,GAAc,CAAC,MAAA,KAC1B,CAAC,KAAK,MAAM,CAAA,IAAK,gBAAA,CAAiB,MAAA,CAAO,KAAK;AAEhD,IAAM,gBAAA,GAAmB,CAAC,KAAA,KACxB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,MAAA,IAAU,KAAA,IACT,KAAA,CAA4B,IAAA,KAAS,MAAA","file":"interop.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 { err, isOk, ok, type Result } from \"@onrails/result\";\nimport { dual } from \"./internal/dual.js\";\nimport { fromNullable, isSome, type Maybe, none, some } from \"./maybe.js\";\n\n/** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */\nexport type NoneError = { readonly _tag: \"None\" };\n\n/** Construct the {@link NoneError} sentinel. */\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\nconst toResultImpl = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok(maybe.value) : err(onNone());\n\n/** Lift absence into a domain error. */\nexport const toResult: {\n <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;\n} = dual(2, toResultImpl);\n\nconst fromResultImpl = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none();\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult: {\n <T, E>(result: Result<T, E>): Maybe<T>;\n <T, E>(): (result: Result<T, E>) => Maybe<T>;\n} = dual(1, fromResultImpl);\n\nconst nullableToResultImpl = <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E> =>\n toResultImpl(fromNullable(value), onNone);\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult: {\n <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;\n} = dual(2, nullableToResultImpl);\n\n/** True when a Result failed specifically with {@link NoneError}. */\nexport const isNoneError = (result: Result<unknown, unknown>): result is Result<never, NoneError> =>\n !isOk(result) && isNoneErrorValue(result.error);\n\nconst isNoneErrorValue = (error: unknown): error is NoneError =>\n typeof error === \"object\" &&\n error !== null &&\n \"_tag\" in error &&\n (error as { _tag: unknown })._tag === \"None\";\n"]}
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/maybe.ts","../src/interop.ts"],"names":[],"mappings":";;;;;AAUO,SAAS,IAAA,CAAQ,OAAe,IAAA,EAAwC;AAC7E,EAAA,MAAM,QAAA,GAAW,CAAA,GAAI,IAAA,KACnB,IAAA,CAAK,UAAU,KAAA,GACV,IAAA,CAAsC,GAAG,IAAI,IAC9C,CAAC,IAAA,KAAmB,IAAA,CAAsC,IAAA,EAAM,GAAG,IAAI,CAAA;AAE7E,EAAA,OAAO,QAAA;AACT;;;ACcO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM,CAAA;AAsB/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAwBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;AClFjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAE1D,IAAM,YAAA,GAAe,CAAO,KAAA,EAAiB,MAAA,KAC3C,MAAA,CAAO,KAAK,CAAA,GAAI,EAAA,CAAS,KAAA,CAAM,KAAK,CAAA,GAAI,GAAA,CAAU,QAAQ,CAAA;AAGrD,IAAM,QAAA,GAGT,IAAA,CAAK,CAAA,EAAG,YAAY;AAExB,IAAM,cAAA,GAAiB,CAAO,MAAA,KAC5B,IAAA,CAAK,MAAM,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA,EAAK;AAGpC,IAAM,UAAA,GAGT,IAAA,CAAK,CAAA,EAAG,cAAc;AAE1B,IAAM,oBAAA,GAAuB,CAAO,KAAA,EAA6B,MAAA,KAC/D,aAAa,YAAA,CAAa,KAAK,GAAG,MAAM,CAAA;AAGnC,IAAM,gBAAA,GAGT,IAAA,CAAK,CAAA,EAAG,oBAAoB;AAGzB,IAAM,WAAA,GAAc,CAAC,MAAA,KAC1B,CAAC,KAAK,MAAM,CAAA,IAAK,gBAAA,CAAiB,MAAA,CAAO,KAAK;AAEhD,IAAM,gBAAA,GAAmB,CAAC,KAAA,KACxB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,MAAA,IAAU,KAAA,IACT,KAAA,CAA4B,IAAA,KAAS,MAAA","file":"interop.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 { err, isOk, ok, type Result } from \"@onrails/result\";\nimport { dual } from \"./internal/dual.js\";\nimport { fromNullable, isSome, type Maybe, none, some } from \"./maybe.js\";\n\n/** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */\nexport type NoneError = { readonly _tag: \"None\" };\n\n/** Construct the {@link NoneError} sentinel. */\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\nconst toResultImpl = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok<T, E>(maybe.value) : err<T, E>(onNone());\n\n/** Lift absence into a domain error. */\nexport const toResult: {\n <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(maybe: Maybe<T>) => Result<T, E>;\n} = dual(2, toResultImpl);\n\nconst fromResultImpl = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none();\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult: {\n <T, E>(result: Result<T, E>): Maybe<T>;\n <T, E>(): (result: Result<T, E>) => Maybe<T>;\n} = dual(1, fromResultImpl);\n\nconst nullableToResultImpl = <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E> =>\n toResultImpl(fromNullable(value), onNone);\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult: {\n <T, E>(value: T | null | undefined, onNone: () => E): Result<T, E>;\n <E>(onNone: () => E): <T>(value: T | null | undefined) => Result<T, E>;\n} = dual(2, nullableToResultImpl);\n\n/** True when a Result failed specifically with {@link NoneError}. */\nexport const isNoneError = (result: Result<unknown, unknown>): result is Result<never, NoneError> =>\n !isOk(result) && isNoneErrorValue(result.error);\n\nconst isNoneErrorValue = (error: unknown): error is NoneError =>\n typeof error === \"object\" &&\n error !== null &&\n \"_tag\" in error &&\n (error as { _tag: unknown })._tag === \"None\";\n"]}