@onrails/maybe 0.1.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 ADDED
@@ -0,0 +1,47 @@
1
+ # @onrails/maybe — design
2
+
3
+ Expected absence as a tagged union — not failure. Pairs with `@onrails/result` at boundaries.
4
+
5
+ ## Goals
6
+
7
+ - Same runtime model as `@onrails/result`: `{ _tag }` variants, dual-form module functions, `sideEffects: false`.
8
+ - **Some / None** — not Ok/Err. Do not use Maybe for parse/IO/auth failures.
9
+ - Optional `@onrails/maybe/interop` bridge (`toResult`, `fromResult`) keyed on `@onrails/result`.
10
+
11
+ ## Runtime model
12
+
13
+ ```ts
14
+ type Maybe<T> =
15
+ | { readonly _tag: "Some"; readonly value: T }
16
+ | { readonly _tag: "None" };
17
+ ```
18
+
19
+ ## API parity with `@onrails/result`
20
+
21
+ | Result | Maybe |
22
+ |--------|-------|
23
+ | `ok` / `err` | `some` / `none` |
24
+ | `of` | `of` (= `some`) |
25
+ | `map` (dual) | `map` (dual) |
26
+ | `flatMap` (dual) | `flatMap` (dual) |
27
+ | `match` (positional, dual) | `match` (positional, dual) |
28
+ | `matchResult` | `matchMaybe` (collision-free alias) |
29
+ | `unwrapOr` | `unwrapOr` (= `getOrElse`) |
30
+ | — | `compactMap` (`compact` ∘ `map`) |
31
+ | — | `optional` (`flatMap` ∘ `fromNullable`) |
32
+
33
+ ## Type tests
34
+
35
+ `test/types.spec.ts` uses `ts-expect` (`expectType` + `TypeEqual`) — compile-time assertions, no runtime cost.
36
+
37
+ ## Exports
38
+
39
+ | Subpath | Purpose |
40
+ |---------|---------|
41
+ | `@onrails/maybe` | Core |
42
+ | `@onrails/maybe/fluent` | Dot-style chains |
43
+ | `@onrails/maybe/interop` | `Result` bridge (peer: `@onrails/result`) |
44
+
45
+ ## Deferred
46
+
47
+ - Fantasy Land instances.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alan R. Soares
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @onrails/maybe
2
+
3
+ Tagged `Maybe` for **expected absence** — cache miss, optional field, not-ready-yet. Not for failures (use `@onrails/result`).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @onrails/maybe
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { compactMap, fromNullable, isSome, match, some } from "@onrails/maybe";
15
+
16
+ const row = fromNullable(db.get(id));
17
+
18
+ const name = match(
19
+ row,
20
+ (r) => r.name,
21
+ () => "guest",
22
+ );
23
+ ```
24
+
25
+ ## Result boundary
26
+
27
+ ```ts
28
+ import { toResult } from "@onrails/maybe/interop";
29
+
30
+ const user = toResult(fromNullable(row), () => ({ kind: "not_found" as const }));
31
+ ```
32
+
33
+ ## Subpaths
34
+
35
+ | Path | Contents |
36
+ |------|----------|
37
+ | `@onrails/maybe` | Core |
38
+ | `@onrails/maybe/fluent` | `fluent(maybe)` chains |
39
+ | `@onrails/maybe/interop` | `toResult`, `fromResult`, `NoneError` |
40
+
41
+ See [DESIGN.md](./DESIGN.md).
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ // src/maybe.ts
4
+ var some = (value) => ({ _tag: "Some", value });
5
+ var none = () => ({ _tag: "None" });
6
+ var isSome = (maybe) => maybe._tag === "Some";
7
+ var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
8
+ function map(...args) {
9
+ if (args.length === 2) return mapImpl(args[0], args[1]);
10
+ const fn = args[0];
11
+ return (maybe) => mapImpl(maybe, fn);
12
+ }
13
+ var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
14
+ function flatMap(...args) {
15
+ if (args.length === 2) return flatMapImpl(args[0], args[1]);
16
+ const fn = args[0];
17
+ return (maybe) => flatMapImpl(maybe, fn);
18
+ }
19
+ var andThen = flatMap;
20
+ var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
21
+ function match(...args) {
22
+ if (args.length === 3) return matchImpl(args[0], args[1], args[2]);
23
+ const [onSome, onNone] = args;
24
+ return (maybe) => matchImpl(maybe, onSome, onNone);
25
+ }
26
+ var getOrElse = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
27
+ var unwrapOr = getOrElse;
28
+
29
+ // src/fluent.ts
30
+ var fluent = (maybe) => Object.assign(maybe, {
31
+ map(fn) {
32
+ return fluent(map(maybe, fn));
33
+ },
34
+ flatMap(fn) {
35
+ return fluent(flatMap(maybe, fn));
36
+ },
37
+ andThen(fn) {
38
+ return fluent(andThen(maybe, fn));
39
+ },
40
+ match(onSome, onNone) {
41
+ return match(maybe, onSome, onNone);
42
+ },
43
+ getOrElse(defaultValue) {
44
+ return getOrElse(maybe, defaultValue);
45
+ },
46
+ unwrapOr(defaultValue) {
47
+ return unwrapOr(maybe, defaultValue);
48
+ }
49
+ });
50
+
51
+ exports.fluent = fluent;
52
+ //# sourceMappingURL=fluent.cjs.map
53
+ //# sourceMappingURL=fluent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":";;;AAYO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM,CAAA;AAa/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AA0B/E,IAAM,OAAA,GAAU,CAAO,KAAA,EAAiB,EAAA,KACtC,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA,CAAK,EAAA,CAAG,KAAA,CAAM,KAAK,CAAC,IAAI,IAAA,EAAQ;AAa3C,SAAS,OACX,IAAA,EACM;AACT,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AACtD,EAAA,MAAM,EAAA,GAAK,KAAK,CAAC,CAAA;AACjB,EAAA,OAAO,CAAC,KAAA,KAA0B,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACrD;AAEA,IAAM,WAAA,GAAc,CAAO,KAAA,EAAiB,EAAA,KAC1C,MAAA,CAAO,KAAK,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAI,IAAA,EAAQ;AAerC,SAAS,WACX,IAAA,EAGM;AACT,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,WAAA,CAAY,KAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAC1D,EAAA,MAAM,EAAA,GAAK,KAAK,CAAC,CAAA;AACjB,EAAA,OAAO,CAAC,KAAA,KAA0B,WAAA,CAAY,KAAA,EAAO,EAAE,CAAA;AACzD;AAGO,IAAM,OAAA,GAAU,OAAA;AAEvB,IAAM,SAAA,GAAY,CAAO,KAAA,EAAiB,MAAA,EAAyB,MAAA,KACjE,MAAA,CAAO,KAAK,CAAA,GAAI,MAAA,CAAO,KAAA,CAAM,KAAK,CAAA,GAAI,MAAA,EAAO;AAoBxC,SAAS,SACX,IAAA,EAGM;AACT,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA,CAAU,IAAA,CAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AACjE,EAAA,MAAM,CAAC,MAAA,EAAQ,MAAM,CAAA,GAAI,IAAA;AACzB,EAAA,OAAO,CAAC,KAAA,KAA0B,SAAA,CAAU,KAAA,EAAO,QAAQ,MAAM,CAAA;AACnE;AAaO,IAAM,SAAA,GAAY,CAAI,KAAA,EAAiB,YAAA,KAC5C,OAAO,KAAK,CAAA,GAAI,MAAM,KAAA,GAAQ,YAAA;AAGzB,IAAM,QAAA,GAAW,SAAA;;;ACrJjB,IAAM,MAAA,GAAS,CAAI,KAAA,KACxB,MAAA,CAAO,OAAO,KAAA,EAAO;AAAA,EACnB,IAAO,EAAA,EAAqB;AAC1B,IAAA,OAAO,MAAA,CAAO,GAAA,CAAI,KAAA,EAAO,EAAE,CAAC,CAAA;AAAA,EAC9B,CAAA;AAAA,EACA,QAAW,EAAA,EAA4B;AACrC,IAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA;AAAA,EAClC,CAAA;AAAA,EACA,QAAW,EAAA,EAA4B;AACrC,IAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA;AAAA,EAClC,CAAA;AAAA,EACA,KAAA,CAAS,QAAyB,MAAA,EAAiB;AACjD,IAAA,OAAO,KAAA,CAAM,KAAA,EAAO,MAAA,EAAQ,MAAM,CAAA;AAAA,EACpC,CAAA;AAAA,EACA,UAAU,YAAA,EAAiB;AACzB,IAAA,OAAO,SAAA,CAAU,OAAO,YAAY,CAAA;AAAA,EACtC,CAAA;AAAA,EACA,SAAS,YAAA,EAAiB;AACxB,IAAA,OAAO,QAAA,CAAS,OAAO,YAAY,CAAA;AAAA,EACrC;AACF,CAAC","file":"fluent.cjs","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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","import { andThen, flatMap, getOrElse, type Maybe, map, match, unwrapOr } from \"./maybe.js\";\n\nexport type FluentMaybe<T> = Maybe<T> & {\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n getOrElse(defaultValue: T): T;\n unwrapOr(defaultValue: T): T;\n};\n\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> =>\n Object.assign(maybe, {\n map<U>(fn: (value: T) => U) {\n return fluent(map(maybe, fn));\n },\n flatMap<U>(fn: (value: T) => Maybe<U>) {\n return fluent(flatMap(maybe, fn));\n },\n andThen<U>(fn: (value: T) => Maybe<U>) {\n return fluent(andThen(maybe, fn));\n },\n match<U>(onSome: (value: T) => U, onNone: () => U) {\n return match(maybe, onSome, onNone);\n },\n getOrElse(defaultValue: T) {\n return getOrElse(maybe, defaultValue);\n },\n unwrapOr(defaultValue: T) {\n return unwrapOr(maybe, defaultValue);\n },\n });\n"]}
@@ -0,0 +1,13 @@
1
+ import { M as Maybe } from './types-vkiMAo4w.cjs';
2
+
3
+ type FluentMaybe<T> = Maybe<T> & {
4
+ map<U>(fn: (value: T) => U): FluentMaybe<U>;
5
+ flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
6
+ andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
7
+ match<U>(onSome: (value: T) => U, onNone: () => U): U;
8
+ getOrElse(defaultValue: T): T;
9
+ unwrapOr(defaultValue: T): T;
10
+ };
11
+ declare const fluent: <T>(maybe: Maybe<T>) => FluentMaybe<T>;
12
+
13
+ export { type FluentMaybe, fluent };
@@ -0,0 +1,13 @@
1
+ import { M as Maybe } from './types-vkiMAo4w.js';
2
+
3
+ type FluentMaybe<T> = Maybe<T> & {
4
+ map<U>(fn: (value: T) => U): FluentMaybe<U>;
5
+ flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
6
+ andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;
7
+ match<U>(onSome: (value: T) => U, onNone: () => U): U;
8
+ getOrElse(defaultValue: T): T;
9
+ unwrapOr(defaultValue: T): T;
10
+ };
11
+ declare const fluent: <T>(maybe: Maybe<T>) => FluentMaybe<T>;
12
+
13
+ export { type FluentMaybe, fluent };
package/dist/fluent.js ADDED
@@ -0,0 +1,51 @@
1
+ // src/maybe.ts
2
+ var some = (value) => ({ _tag: "Some", value });
3
+ var none = () => ({ _tag: "None" });
4
+ var isSome = (maybe) => maybe._tag === "Some";
5
+ var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
6
+ function map(...args) {
7
+ if (args.length === 2) return mapImpl(args[0], args[1]);
8
+ const fn = args[0];
9
+ return (maybe) => mapImpl(maybe, fn);
10
+ }
11
+ var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
12
+ function flatMap(...args) {
13
+ if (args.length === 2) return flatMapImpl(args[0], args[1]);
14
+ const fn = args[0];
15
+ return (maybe) => flatMapImpl(maybe, fn);
16
+ }
17
+ var andThen = flatMap;
18
+ var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
19
+ function match(...args) {
20
+ if (args.length === 3) return matchImpl(args[0], args[1], args[2]);
21
+ const [onSome, onNone] = args;
22
+ return (maybe) => matchImpl(maybe, onSome, onNone);
23
+ }
24
+ var getOrElse = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
25
+ var unwrapOr = getOrElse;
26
+
27
+ // src/fluent.ts
28
+ var fluent = (maybe) => Object.assign(maybe, {
29
+ map(fn) {
30
+ return fluent(map(maybe, fn));
31
+ },
32
+ flatMap(fn) {
33
+ return fluent(flatMap(maybe, fn));
34
+ },
35
+ andThen(fn) {
36
+ return fluent(andThen(maybe, fn));
37
+ },
38
+ match(onSome, onNone) {
39
+ return match(maybe, onSome, onNone);
40
+ },
41
+ getOrElse(defaultValue) {
42
+ return getOrElse(maybe, defaultValue);
43
+ },
44
+ unwrapOr(defaultValue) {
45
+ return unwrapOr(maybe, defaultValue);
46
+ }
47
+ });
48
+
49
+ export { fluent };
50
+ //# sourceMappingURL=fluent.js.map
51
+ //# sourceMappingURL=fluent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts","../src/fluent.ts"],"names":[],"mappings":"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","file":"fluent.js","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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","import { andThen, flatMap, getOrElse, type Maybe, map, match, unwrapOr } from \"./maybe.js\";\n\nexport type FluentMaybe<T> = Maybe<T> & {\n map<U>(fn: (value: T) => U): FluentMaybe<U>;\n flatMap<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n andThen<U>(fn: (value: T) => Maybe<U>): FluentMaybe<U>;\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n getOrElse(defaultValue: T): T;\n unwrapOr(defaultValue: T): T;\n};\n\nexport const fluent = <T>(maybe: Maybe<T>): FluentMaybe<T> =>\n Object.assign(maybe, {\n map<U>(fn: (value: T) => U) {\n return fluent(map(maybe, fn));\n },\n flatMap<U>(fn: (value: T) => Maybe<U>) {\n return fluent(flatMap(maybe, fn));\n },\n andThen<U>(fn: (value: T) => Maybe<U>) {\n return fluent(andThen(maybe, fn));\n },\n match<U>(onSome: (value: T) => U, onNone: () => U) {\n return match(maybe, onSome, onNone);\n },\n getOrElse(defaultValue: T) {\n return getOrElse(maybe, defaultValue);\n },\n unwrapOr(defaultValue: T) {\n return unwrapOr(maybe, defaultValue);\n },\n });\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ // src/maybe.ts
4
+ var some = (value) => ({ _tag: "Some", value });
5
+ var of = some;
6
+ var none = () => ({ _tag: "None" });
7
+ var isSome = (maybe) => maybe._tag === "Some";
8
+ var isNone = (maybe) => maybe._tag === "None";
9
+ var fromNullable = (value) => value == null ? none() : some(value);
10
+ var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
11
+ function map(...args) {
12
+ if (args.length === 2) return mapImpl(args[0], args[1]);
13
+ const fn = args[0];
14
+ return (maybe) => mapImpl(maybe, fn);
15
+ }
16
+ var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
17
+ function flatMap(...args) {
18
+ if (args.length === 2) return flatMapImpl(args[0], args[1]);
19
+ const fn = args[0];
20
+ return (maybe) => flatMapImpl(maybe, fn);
21
+ }
22
+ var andThen = flatMap;
23
+ var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
24
+ function match(...args) {
25
+ if (args.length === 3) return matchImpl(args[0], args[1], args[2]);
26
+ const [onSome, onNone] = args;
27
+ return (maybe) => matchImpl(maybe, onSome, onNone);
28
+ }
29
+ var matchMaybe = match;
30
+ var getOrElse = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
31
+ var unwrapOr = getOrElse;
32
+ var unwrap = (maybe) => {
33
+ if (isNone(maybe)) {
34
+ throw new Error("Called unwrap on None");
35
+ }
36
+ return maybe.value;
37
+ };
38
+ var compact = (maybes) => {
39
+ const out = [];
40
+ for (const m of maybes) {
41
+ if (isSome(m)) {
42
+ out.push(m.value);
43
+ }
44
+ }
45
+ return out;
46
+ };
47
+ var compactMap = (items, fn) => compact(items.map(fn));
48
+ var optional = (value, fn) => flatMapImpl(fromNullable(value), fn);
49
+
50
+ exports.andThen = andThen;
51
+ exports.compact = compact;
52
+ exports.compactMap = compactMap;
53
+ exports.flatMap = flatMap;
54
+ exports.fromNullable = fromNullable;
55
+ exports.getOrElse = getOrElse;
56
+ exports.isNone = isNone;
57
+ exports.isSome = isSome;
58
+ exports.map = map;
59
+ exports.match = match;
60
+ exports.matchMaybe = matchMaybe;
61
+ exports.none = none;
62
+ exports.of = of;
63
+ exports.optional = optional;
64
+ exports.some = some;
65
+ exports.unwrap = unwrap;
66
+ exports.unwrapOr = unwrapOr;
67
+ //# sourceMappingURL=index.cjs.map
68
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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"]}
@@ -0,0 +1,150 @@
1
+ import { M as Maybe, N as None, S as Some } from './types-vkiMAo4w.cjs';
2
+
3
+ /**
4
+ * Lifts a value into the `Some` branch.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const m = some(1); // Maybe<number>
9
+ * ```
10
+ */
11
+ declare const some: <T>(value: T) => Maybe<T>;
12
+ /** Fantasy Land `of` — alias of {@link some}. */
13
+ declare const of: <T>(value: T) => Maybe<T>;
14
+ /**
15
+ * The `None` value — represents expected absence. Not a failure.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();
20
+ * ```
21
+ */
22
+ declare const none: <T = never>() => Maybe<T>;
23
+ /**
24
+ * Type-narrowing predicate: returns `true` when the value is `Some`.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * if (isSome(m)) {
29
+ * console.log(m.value); // narrowed to Some branch
30
+ * }
31
+ * ```
32
+ */
33
+ declare const isSome: <T>(maybe: Maybe<T>) => maybe is Some<T>;
34
+ /**
35
+ * Type-narrowing predicate: returns `true` when the value is `None`.
36
+ */
37
+ declare const isNone: <T>(maybe: Maybe<T>) => maybe is None;
38
+ /**
39
+ * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become
40
+ * `None`; anything else becomes `Some`.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * fromNullable(map.get(key)); // Maybe<V>
45
+ * fromNullable(null); // None
46
+ * ```
47
+ */
48
+ declare const fromNullable: <T>(value: T | null | undefined) => Maybe<T>;
49
+ /**
50
+ * Transform the `Some` value, passing `None` through unchanged. Dual-form.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * map(some(2), (n) => n * 3); // Some 6 — data-first
55
+ * pipe(fromNullable(input), map((s) => s.trim())); // curried
56
+ * ```
57
+ */
58
+ declare function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;
59
+ declare function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;
60
+ /**
61
+ * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;
62
+ * short-circuits on `None`. Dual-form.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * flatMap(fromNullable(user), (u) =>
67
+ * u.active ? some(u) : none(),
68
+ * );
69
+ * ```
70
+ */
71
+ declare function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;
72
+ declare function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;
73
+ /** Alias for {@link flatMap} — same dual-form shape. */
74
+ declare const andThen: typeof flatMap;
75
+ /**
76
+ * Terminal collapse — fold both branches into a single value. Positional,
77
+ * dual-form. Same shape as `Result.match` for sibling consistency.
78
+ *
79
+ * For files that also import `match` from `ts-pattern`, the alias
80
+ * {@link matchMaybe} is identical.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const greeting = match(
85
+ * fromNullable(user),
86
+ * (u) => `hello ${u.name}`,
87
+ * () => "hello guest",
88
+ * );
89
+ * ```
90
+ */
91
+ declare function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;
92
+ declare function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;
93
+ /** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */
94
+ declare const matchMaybe: typeof match;
95
+ /**
96
+ * Returns the `Some` value, or `defaultValue` when the value is `None`.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * getOrElse(fromNullable(user), { name: "guest" });
101
+ * ```
102
+ */
103
+ declare const getOrElse: <T>(maybe: Maybe<T>, defaultValue: T) => T;
104
+ /** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */
105
+ declare const unwrapOr: <T>(maybe: Maybe<T>, defaultValue: T) => T;
106
+ /**
107
+ * Unwraps the `Some` value or throws if the value is `None`. Use in tests
108
+ * or at the boundary of an assertion; prefer `match` / `getOrElse` in
109
+ * production code paths.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * expect(unwrap(parseUser(raw))).toEqual(expected);
114
+ * ```
115
+ */
116
+ declare const unwrap: <T>(maybe: Maybe<T>) => T;
117
+ /**
118
+ * Drop every `None` and collect the remaining `Some` values into an array.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * compact([some(1), none(), some(2)]); // [1, 2]
123
+ * ```
124
+ */
125
+ declare const compact: <T>(maybes: readonly Maybe<T>[]) => T[];
126
+ /**
127
+ * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,
128
+ * collect the rest.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * compactMap(rawRows, (row) =>
133
+ * row.deletedAt ? none() : some(row.id),
134
+ * );
135
+ * // string[]
136
+ * ```
137
+ */
138
+ declare const compactMap: <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>) => U[];
139
+ /**
140
+ * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through
141
+ * a `Maybe`-returning step. Convenient for chaining lookups.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * optional(rawId, (id) => fromNullable(cache.get(id)));
146
+ * ```
147
+ */
148
+ declare const optional: <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>) => Maybe<U>;
149
+
150
+ export { Maybe, None, Some, andThen, compact, compactMap, flatMap, fromNullable, getOrElse, isNone, isSome, map, match, matchMaybe, none, of, optional, some, unwrap, unwrapOr };
@@ -0,0 +1,150 @@
1
+ import { M as Maybe, N as None, S as Some } from './types-vkiMAo4w.js';
2
+
3
+ /**
4
+ * Lifts a value into the `Some` branch.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const m = some(1); // Maybe<number>
9
+ * ```
10
+ */
11
+ declare const some: <T>(value: T) => Maybe<T>;
12
+ /** Fantasy Land `of` — alias of {@link some}. */
13
+ declare const of: <T>(value: T) => Maybe<T>;
14
+ /**
15
+ * The `None` value — represents expected absence. Not a failure.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();
20
+ * ```
21
+ */
22
+ declare const none: <T = never>() => Maybe<T>;
23
+ /**
24
+ * Type-narrowing predicate: returns `true` when the value is `Some`.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * if (isSome(m)) {
29
+ * console.log(m.value); // narrowed to Some branch
30
+ * }
31
+ * ```
32
+ */
33
+ declare const isSome: <T>(maybe: Maybe<T>) => maybe is Some<T>;
34
+ /**
35
+ * Type-narrowing predicate: returns `true` when the value is `None`.
36
+ */
37
+ declare const isNone: <T>(maybe: Maybe<T>) => maybe is None;
38
+ /**
39
+ * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become
40
+ * `None`; anything else becomes `Some`.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * fromNullable(map.get(key)); // Maybe<V>
45
+ * fromNullable(null); // None
46
+ * ```
47
+ */
48
+ declare const fromNullable: <T>(value: T | null | undefined) => Maybe<T>;
49
+ /**
50
+ * Transform the `Some` value, passing `None` through unchanged. Dual-form.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * map(some(2), (n) => n * 3); // Some 6 — data-first
55
+ * pipe(fromNullable(input), map((s) => s.trim())); // curried
56
+ * ```
57
+ */
58
+ declare function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;
59
+ declare function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;
60
+ /**
61
+ * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;
62
+ * short-circuits on `None`. Dual-form.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * flatMap(fromNullable(user), (u) =>
67
+ * u.active ? some(u) : none(),
68
+ * );
69
+ * ```
70
+ */
71
+ declare function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;
72
+ declare function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;
73
+ /** Alias for {@link flatMap} — same dual-form shape. */
74
+ declare const andThen: typeof flatMap;
75
+ /**
76
+ * Terminal collapse — fold both branches into a single value. Positional,
77
+ * dual-form. Same shape as `Result.match` for sibling consistency.
78
+ *
79
+ * For files that also import `match` from `ts-pattern`, the alias
80
+ * {@link matchMaybe} is identical.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const greeting = match(
85
+ * fromNullable(user),
86
+ * (u) => `hello ${u.name}`,
87
+ * () => "hello guest",
88
+ * );
89
+ * ```
90
+ */
91
+ declare function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;
92
+ declare function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;
93
+ /** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */
94
+ declare const matchMaybe: typeof match;
95
+ /**
96
+ * Returns the `Some` value, or `defaultValue` when the value is `None`.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * getOrElse(fromNullable(user), { name: "guest" });
101
+ * ```
102
+ */
103
+ declare const getOrElse: <T>(maybe: Maybe<T>, defaultValue: T) => T;
104
+ /** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */
105
+ declare const unwrapOr: <T>(maybe: Maybe<T>, defaultValue: T) => T;
106
+ /**
107
+ * Unwraps the `Some` value or throws if the value is `None`. Use in tests
108
+ * or at the boundary of an assertion; prefer `match` / `getOrElse` in
109
+ * production code paths.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * expect(unwrap(parseUser(raw))).toEqual(expected);
114
+ * ```
115
+ */
116
+ declare const unwrap: <T>(maybe: Maybe<T>) => T;
117
+ /**
118
+ * Drop every `None` and collect the remaining `Some` values into an array.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * compact([some(1), none(), some(2)]); // [1, 2]
123
+ * ```
124
+ */
125
+ declare const compact: <T>(maybes: readonly Maybe<T>[]) => T[];
126
+ /**
127
+ * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,
128
+ * collect the rest.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * compactMap(rawRows, (row) =>
133
+ * row.deletedAt ? none() : some(row.id),
134
+ * );
135
+ * // string[]
136
+ * ```
137
+ */
138
+ declare const compactMap: <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>) => U[];
139
+ /**
140
+ * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through
141
+ * a `Maybe`-returning step. Convenient for chaining lookups.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * optional(rawId, (id) => fromNullable(cache.get(id)));
146
+ * ```
147
+ */
148
+ declare const optional: <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>) => Maybe<U>;
149
+
150
+ export { Maybe, None, Some, andThen, compact, compactMap, flatMap, fromNullable, getOrElse, isNone, isSome, map, match, matchMaybe, none, of, optional, some, unwrap, unwrapOr };
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ // src/maybe.ts
2
+ var some = (value) => ({ _tag: "Some", value });
3
+ var of = some;
4
+ var none = () => ({ _tag: "None" });
5
+ var isSome = (maybe) => maybe._tag === "Some";
6
+ var isNone = (maybe) => maybe._tag === "None";
7
+ var fromNullable = (value) => value == null ? none() : some(value);
8
+ var mapImpl = (maybe, fn) => isSome(maybe) ? some(fn(maybe.value)) : none();
9
+ function map(...args) {
10
+ if (args.length === 2) return mapImpl(args[0], args[1]);
11
+ const fn = args[0];
12
+ return (maybe) => mapImpl(maybe, fn);
13
+ }
14
+ var flatMapImpl = (maybe, fn) => isSome(maybe) ? fn(maybe.value) : none();
15
+ function flatMap(...args) {
16
+ if (args.length === 2) return flatMapImpl(args[0], args[1]);
17
+ const fn = args[0];
18
+ return (maybe) => flatMapImpl(maybe, fn);
19
+ }
20
+ var andThen = flatMap;
21
+ var matchImpl = (maybe, onSome, onNone) => isSome(maybe) ? onSome(maybe.value) : onNone();
22
+ function match(...args) {
23
+ if (args.length === 3) return matchImpl(args[0], args[1], args[2]);
24
+ const [onSome, onNone] = args;
25
+ return (maybe) => matchImpl(maybe, onSome, onNone);
26
+ }
27
+ var matchMaybe = match;
28
+ var getOrElse = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
29
+ var unwrapOr = getOrElse;
30
+ var unwrap = (maybe) => {
31
+ if (isNone(maybe)) {
32
+ throw new Error("Called unwrap on None");
33
+ }
34
+ return maybe.value;
35
+ };
36
+ var compact = (maybes) => {
37
+ const out = [];
38
+ for (const m of maybes) {
39
+ if (isSome(m)) {
40
+ out.push(m.value);
41
+ }
42
+ }
43
+ return out;
44
+ };
45
+ var compactMap = (items, fn) => compact(items.map(fn));
46
+ var optional = (value, fn) => flatMapImpl(fromNullable(value), fn);
47
+
48
+ export { andThen, compact, compactMap, flatMap, fromNullable, getOrElse, isNone, isSome, map, match, matchMaybe, none, of, optional, some, unwrap, unwrapOr };
49
+ //# sourceMappingURL=index.js.map
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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"]}
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ var result = require('@onrails/result');
4
+
5
+ // src/interop.ts
6
+
7
+ // src/maybe.ts
8
+ var some = (value) => ({ _tag: "Some", value });
9
+ var none = () => ({ _tag: "None" });
10
+ var isSome = (maybe) => maybe._tag === "Some";
11
+ var fromNullable = (value) => value == null ? none() : some(value);
12
+
13
+ // src/interop.ts
14
+ var noneError = () => ({ _tag: "None" });
15
+ var toResult = (maybe, onNone) => isSome(maybe) ? result.ok(maybe.value) : result.err(onNone());
16
+ var fromResult = (result$1) => result.isOk(result$1) ? some(result$1.value) : none();
17
+ var nullableToResult = (value, onNone) => toResult(fromNullable(value), onNone);
18
+ var isNoneError = (result$1) => !result.isOk(result$1) && isNoneErrorValue(result$1.error);
19
+ var isNoneErrorValue = (error) => typeof error === "object" && error !== null && "_tag" in error && error._tag === "None";
20
+
21
+ exports.fromResult = fromResult;
22
+ exports.isNoneError = isNoneError;
23
+ exports.noneError = noneError;
24
+ exports.nullableToResult = nullableToResult;
25
+ exports.toResult = toResult;
26
+ //# sourceMappingURL=interop.cjs.map
27
+ //# sourceMappingURL=interop.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts","../src/interop.ts"],"names":["ok","err","result","isOk"],"mappings":";;;;;;;AAYO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM,CAAA;AAa/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAiBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;ACjDjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAGnD,IAAM,QAAA,GAAW,CAAO,KAAA,EAAiB,MAAA,KAC9C,MAAA,CAAO,KAAK,CAAA,GAAIA,SAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAIC,UAAA,CAAI,QAAQ;AAGzC,IAAM,UAAA,GAAa,CAAOC,QAAA,KAC/BC,WAAA,CAAKD,QAAM,IAAI,IAAA,CAAKA,QAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AAG/B,IAAM,gBAAA,GAAmB,CAC9B,KAAA,EACA,MAAA,KACiB,SAAS,YAAA,CAAa,KAAK,GAAG,MAAM;AAGhD,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":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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","import { err, isOk, ok, type Result } from \"@onrails/result\";\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\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\n/** Lift absence into a domain error. */\nexport const toResult = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok(maybe.value) : err(onNone());\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none<T>();\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult = <T, E>(\n value: T | null | undefined,\n onNone: () => E,\n): Result<T, E> => toResult(fromNullable(value), onNone);\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"]}
@@ -0,0 +1,18 @@
1
+ import { Result } from '@onrails/result';
2
+ import { M as Maybe } from './types-vkiMAo4w.cjs';
3
+
4
+ /** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */
5
+ type NoneError = {
6
+ readonly _tag: "None";
7
+ };
8
+ declare const noneError: () => NoneError;
9
+ /** Lift absence into a domain error. */
10
+ declare const toResult: <T, E>(maybe: Maybe<T>, onNone: () => E) => Result<T, E>;
11
+ /** Ok values become Some; any Err becomes None (errors are not absence). */
12
+ declare const fromResult: <T, E>(result: Result<T, E>) => Maybe<T>;
13
+ /** Nullable edge → Maybe; explicit None error at the boundary. */
14
+ declare const nullableToResult: <T, E>(value: T | null | undefined, onNone: () => E) => Result<T, E>;
15
+ /** True when a Result failed specifically with {@link NoneError}. */
16
+ declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
17
+
18
+ export { type NoneError, fromResult, isNoneError, noneError, nullableToResult, toResult };
@@ -0,0 +1,18 @@
1
+ import { Result } from '@onrails/result';
2
+ import { M as Maybe } from './types-vkiMAo4w.js';
3
+
4
+ /** Sentinel error tag — `Maybe<T>` bridges to `Result<T, NoneError>`. */
5
+ type NoneError = {
6
+ readonly _tag: "None";
7
+ };
8
+ declare const noneError: () => NoneError;
9
+ /** Lift absence into a domain error. */
10
+ declare const toResult: <T, E>(maybe: Maybe<T>, onNone: () => E) => Result<T, E>;
11
+ /** Ok values become Some; any Err becomes None (errors are not absence). */
12
+ declare const fromResult: <T, E>(result: Result<T, E>) => Maybe<T>;
13
+ /** Nullable edge → Maybe; explicit None error at the boundary. */
14
+ declare const nullableToResult: <T, E>(value: T | null | undefined, onNone: () => E) => Result<T, E>;
15
+ /** True when a Result failed specifically with {@link NoneError}. */
16
+ declare const isNoneError: (result: Result<unknown, unknown>) => result is Result<never, NoneError>;
17
+
18
+ export { type NoneError, fromResult, isNoneError, noneError, nullableToResult, toResult };
@@ -0,0 +1,21 @@
1
+ import { ok, err, isOk } from '@onrails/result';
2
+
3
+ // src/interop.ts
4
+
5
+ // src/maybe.ts
6
+ var some = (value) => ({ _tag: "Some", value });
7
+ var none = () => ({ _tag: "None" });
8
+ var isSome = (maybe) => maybe._tag === "Some";
9
+ var fromNullable = (value) => value == null ? none() : some(value);
10
+
11
+ // src/interop.ts
12
+ var noneError = () => ({ _tag: "None" });
13
+ var toResult = (maybe, onNone) => isSome(maybe) ? ok(maybe.value) : err(onNone());
14
+ var fromResult = (result) => isOk(result) ? some(result.value) : none();
15
+ var nullableToResult = (value, onNone) => toResult(fromNullable(value), onNone);
16
+ var isNoneError = (result) => !isOk(result) && isNoneErrorValue(result.error);
17
+ var isNoneErrorValue = (error) => typeof error === "object" && error !== null && "_tag" in error && error._tag === "None";
18
+
19
+ export { fromResult, isNoneError, noneError, nullableToResult, toResult };
20
+ //# sourceMappingURL=interop.js.map
21
+ //# sourceMappingURL=interop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/maybe.ts","../src/interop.ts"],"names":[],"mappings":";;;;;AAYO,IAAM,OAAO,CAAI,KAAA,MAAwB,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAM,CAAA;AAa/D,IAAM,IAAA,GAAO,OAA4B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAYxD,IAAM,MAAA,GAAS,CAAI,KAAA,KAAsC,KAAA,CAAM,IAAA,KAAS,MAAA;AAiBxE,IAAM,YAAA,GAAe,CAAI,KAAA,KAC9B,KAAA,IAAS,OAAO,IAAA,EAAQ,GAAI,KAAK,KAAK,CAAA;;;ACjDjC,IAAM,SAAA,GAAY,OAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAGnD,IAAM,QAAA,GAAW,CAAO,KAAA,EAAiB,MAAA,KAC9C,MAAA,CAAO,KAAK,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,KAAK,CAAA,GAAI,GAAA,CAAI,QAAQ;AAGzC,IAAM,UAAA,GAAa,CAAO,MAAA,KAC/B,IAAA,CAAK,MAAM,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AAG/B,IAAM,gBAAA,GAAmB,CAC9B,KAAA,EACA,MAAA,KACiB,SAAS,YAAA,CAAa,KAAK,GAAG,MAAM;AAGhD,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":["import type { Maybe, None, Some } from \"./types.js\";\n\nexport type { Maybe, None, Some } from \"./types.js\";\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => ({ _tag: \"Some\", value });\n\n/** Fantasy Land `of` — alias of {@link some}. */\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 */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(map.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport function map<T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\nexport function map<T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\nexport function map(\n ...args: [Maybe<unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => mapImpl(maybe, fn);\n}\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport function flatMap<T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\nexport function flatMap<T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\nexport function flatMap(\n ...args:\n | [Maybe<unknown>, (value: unknown) => Maybe<unknown>]\n | [(value: unknown) => Maybe<unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (maybe: Maybe<unknown>) => flatMapImpl(maybe, fn);\n}\n\n/** Alias for {@link flatMap} — same dual-form shape. */\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`, the alias\n * {@link matchMaybe} is identical.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport function match<T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\nexport function match<T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\nexport function match(\n ...args:\n | [Maybe<unknown>, (value: unknown) => unknown, () => unknown]\n | [(value: unknown) => unknown, () => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onSome, onNone] = args;\n return (maybe: Maybe<unknown>) => matchImpl(maybe, onSome, onNone);\n}\n\n/** Collision-free alias — mirrors `matchResult` on `@onrails/result`. */\nexport const matchMaybe = match;\n\n/**\n * Returns the `Some` value, or `defaultValue` when the value is `None`.\n *\n * @example\n * ```ts\n * getOrElse(fromNullable(user), { name: \"guest\" });\n * ```\n */\nexport const getOrElse = <T>(maybe: Maybe<T>, defaultValue: T): T =>\n isSome(maybe) ? maybe.value : defaultValue;\n\n/** Alias — mirrors `@onrails/result` `unwrapOr`. Same as {@link getOrElse}. */\nexport const unwrapOr = getOrElse;\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`. Use in tests\n * or at the boundary of an assertion; prefer `match` / `getOrElse` in\n * production code paths.\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","import { err, isOk, ok, type Result } from \"@onrails/result\";\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\nexport const noneError = (): NoneError => ({ _tag: \"None\" });\n\n/** Lift absence into a domain error. */\nexport const toResult = <T, E>(maybe: Maybe<T>, onNone: () => E): Result<T, E> =>\n isSome(maybe) ? ok(maybe.value) : err(onNone());\n\n/** Ok values become Some; any Err becomes None (errors are not absence). */\nexport const fromResult = <T, E>(result: Result<T, E>): Maybe<T> =>\n isOk(result) ? some(result.value) : none<T>();\n\n/** Nullable edge → Maybe; explicit None error at the boundary. */\nexport const nullableToResult = <T, E>(\n value: T | null | undefined,\n onNone: () => E,\n): Result<T, E> => toResult(fromNullable(value), onNone);\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"]}
@@ -0,0 +1,15 @@
1
+ /** Tagged optional value — absence is not failure. */
2
+ type Maybe<T> = {
3
+ readonly _tag: "Some";
4
+ readonly value: T;
5
+ } | {
6
+ readonly _tag: "None";
7
+ };
8
+ type Some<T> = Extract<Maybe<T>, {
9
+ _tag: "Some";
10
+ }>;
11
+ type None = Extract<Maybe<never>, {
12
+ _tag: "None";
13
+ }>;
14
+
15
+ export type { Maybe as M, None as N, Some as S };
@@ -0,0 +1,15 @@
1
+ /** Tagged optional value — absence is not failure. */
2
+ type Maybe<T> = {
3
+ readonly _tag: "Some";
4
+ readonly value: T;
5
+ } | {
6
+ readonly _tag: "None";
7
+ };
8
+ type Some<T> = Extract<Maybe<T>, {
9
+ _tag: "Some";
10
+ }>;
11
+ type None = Extract<Maybe<never>, {
12
+ _tag: "None";
13
+ }>;
14
+
15
+ export type { Maybe as M, None as N, Some as S };
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@onrails/maybe",
3
+ "version": "0.1.0",
4
+ "description": "Tagged Maybe for expected absence — pure tagged unions, Result interop, tree-shakeable",
5
+ "license": "MIT",
6
+ "author": "Alan R. Soares",
7
+ "homepage": "https://alanrsoares.github.io/onrails/modules/_onrails_maybe.html",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/alanrsoares/onrails.git",
11
+ "directory": "packages/maybe"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/alanrsoares/onrails/issues"
15
+ },
16
+ "keywords": [
17
+ "maybe",
18
+ "optional",
19
+ "tagged-union",
20
+ "discriminated-union",
21
+ "typescript",
22
+ "bun",
23
+ "functional"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18",
27
+ "bun": ">=1.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "provenance": true
32
+ },
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "files": [
39
+ "dist",
40
+ "DESIGN.md",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "import": "./dist/index.js",
48
+ "require": "./dist/index.cjs"
49
+ },
50
+ "./fluent": {
51
+ "types": "./dist/fluent.d.ts",
52
+ "import": "./dist/fluent.js",
53
+ "require": "./dist/fluent.cjs"
54
+ },
55
+ "./interop": {
56
+ "types": "./dist/interop.d.ts",
57
+ "import": "./dist/interop.js",
58
+ "require": "./dist/interop.cjs"
59
+ }
60
+ },
61
+ "peerDependencies": {
62
+ "@onrails/result": "0.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@onrails/result": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "scripts": {
70
+ "typecheck": "tsc --noEmit",
71
+ "test": "bun test",
72
+ "check": "bun typecheck && bun test",
73
+ "build": "tsup",
74
+ "clean": "rm -rf dist",
75
+ "prepublishOnly": "bun run build"
76
+ },
77
+ "devDependencies": {
78
+ "@onrails/result": "0.0.0",
79
+ "ts-expect": "^1.3.0",
80
+ "tsup": "^8.5.1"
81
+ }
82
+ }