@onrails/maybe 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +4 -0
- package/README.md +4 -0
- package/dist/fluent.cjs +73 -6
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +4 -7
- package/dist/fluent.d.ts +4 -7
- package/dist/fluent.js +73 -6
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +82 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -244
- package/dist/index.d.ts +2 -244
- package/dist/index.js +82 -8
- package/dist/index.js.map +1 -1
- package/dist/internal/dual.d.cts +11 -0
- package/dist/internal/dual.d.ts +11 -0
- package/dist/internal/proto.d.cts +3 -0
- package/dist/internal/proto.d.ts +3 -0
- package/dist/interop.cjs +73 -6
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.d.cts +8 -11
- package/dist/interop.d.ts +8 -11
- package/dist/interop.js +73 -6
- package/dist/interop.js.map +1 -1
- package/dist/maybe.d.cts +283 -0
- package/dist/maybe.d.ts +283 -0
- package/package.json +29 -14
package/dist/index.d.cts
CHANGED
|
@@ -1,244 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 { MaybeData, MaybeMethods, None, Some } from "./maybe.js";
|
|
2
|
+
export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, Maybe, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,244 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 { MaybeData, MaybeMethods, None, Some } from "./maybe.js";
|
|
2
|
+
export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, Maybe, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr, } from "./maybe.js";
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,90 @@
|
|
|
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
|
|
|
7
|
+
// src/internal/proto.ts
|
|
8
|
+
var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
|
|
9
|
+
var SomeImpl = class _SomeImpl {
|
|
10
|
+
_tag;
|
|
11
|
+
value;
|
|
12
|
+
// explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape
|
|
13
|
+
constructor(value) {
|
|
14
|
+
this._tag = "Some";
|
|
15
|
+
this.value = value;
|
|
16
|
+
}
|
|
17
|
+
[INSPECT](_depth, options, inspect) {
|
|
18
|
+
return `Some(${inspect(this.value, options)})`;
|
|
19
|
+
}
|
|
20
|
+
map(fn) {
|
|
21
|
+
return new _SomeImpl(fn(this.value));
|
|
22
|
+
}
|
|
23
|
+
flatMap(fn) {
|
|
24
|
+
return fn(this.value);
|
|
25
|
+
}
|
|
26
|
+
andThen(fn) {
|
|
27
|
+
return fn(this.value);
|
|
28
|
+
}
|
|
29
|
+
match(onSome, _onNone) {
|
|
30
|
+
return onSome(this.value);
|
|
31
|
+
}
|
|
32
|
+
unwrapOr(_defaultValue) {
|
|
33
|
+
return this.value;
|
|
34
|
+
}
|
|
35
|
+
tap(fn) {
|
|
36
|
+
fn(this.value);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
tapNone(_fn) {
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var NoneImpl = class _NoneImpl {
|
|
44
|
+
_tag;
|
|
45
|
+
constructor() {
|
|
46
|
+
this._tag = "None";
|
|
47
|
+
}
|
|
48
|
+
[INSPECT]() {
|
|
49
|
+
return "None";
|
|
50
|
+
}
|
|
51
|
+
map(_fn) {
|
|
52
|
+
return new _NoneImpl();
|
|
53
|
+
}
|
|
54
|
+
flatMap(_fn) {
|
|
55
|
+
return new _NoneImpl();
|
|
56
|
+
}
|
|
57
|
+
andThen(_fn) {
|
|
58
|
+
return new _NoneImpl();
|
|
59
|
+
}
|
|
60
|
+
match(_onSome, onNone) {
|
|
61
|
+
return onNone();
|
|
62
|
+
}
|
|
63
|
+
unwrapOr(defaultValue) {
|
|
64
|
+
return defaultValue;
|
|
65
|
+
}
|
|
66
|
+
tap(_fn) {
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
tapNone(fn) {
|
|
70
|
+
fn();
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var newSome = (value) => new SomeImpl(value);
|
|
75
|
+
var newNone = () => new NoneImpl();
|
|
76
|
+
|
|
10
77
|
// src/maybe.ts
|
|
11
|
-
var some = (value) => (
|
|
78
|
+
var some = (value) => newSome(value);
|
|
12
79
|
var of = some;
|
|
13
|
-
var none = () => (
|
|
80
|
+
var none = () => newNone();
|
|
81
|
+
var Maybe = {
|
|
82
|
+
from: (data) => {
|
|
83
|
+
if (data._tag === "Some") return newSome(data.value);
|
|
84
|
+
if (data._tag === "None") return newNone();
|
|
85
|
+
throw new TypeError(`Maybe.from: unknown _tag ${String(data._tag)}`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
14
88
|
var isSome = (maybe) => maybe._tag === "Some";
|
|
15
89
|
var isNone = (maybe) => maybe._tag === "None";
|
|
16
90
|
var fromNullable = (value) => value == null ? none() : some(value);
|
|
@@ -24,7 +98,7 @@ var match = dual(3, matchImpl);
|
|
|
24
98
|
var unwrapOrImpl = (maybe, defaultValue) => isSome(maybe) ? maybe.value : defaultValue;
|
|
25
99
|
var unwrapOr = dual(2, unwrapOrImpl);
|
|
26
100
|
var unwrap = (maybe) => {
|
|
27
|
-
if (
|
|
101
|
+
if (maybe._tag === "None") {
|
|
28
102
|
throw new Error("Called unwrap on None");
|
|
29
103
|
}
|
|
30
104
|
return maybe.value;
|
|
@@ -59,6 +133,6 @@ var printPayload = (payload) => {
|
|
|
59
133
|
};
|
|
60
134
|
var show = (maybe) => isSome(maybe) ? `Some(${printPayload(maybe.value)})` : "None";
|
|
61
135
|
|
|
62
|
-
export { andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr };
|
|
136
|
+
export { Maybe, andThen, compact, compactMap, flatMap, fromNullable, isNone, isSome, map, match, none, of, optional, show, some, tap, tapNone, unwrap, unwrapOr };
|
|
63
137
|
//# sourceMappingURL=index.js.map
|
|
64
138
|
//# sourceMappingURL=index.js.map
|
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/internal/proto.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 type { Maybe, MaybeMethods } from \"../maybe.js\";\n\n/**\n * `util.inspect` / `Bun.inspect` / `console.log` hook: render `Ok(1)` instead of dumping the class\n * name and every prototype method. Symbol-keyed, so it adds no enumerable field and JSON is unchanged.\n */\nconst INSPECT = Symbol.for(\"nodejs.util.inspect.custom\");\ntype Inspect = (value: unknown, options?: unknown) => string;\n\n/**\n * Live carrier classes. `_tag` and `value` are own enumerable fields, so JSON\n * and `structuredClone` see plain data; every method lives on the prototype\n * and is therefore non-enumerable. Internal only: build carriers with\n * `some` / `none`.\n */\nclass SomeImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"Some\";\n readonly value: T;\n // explicit assignment order keeps `_tag` first, so JSON key order matches the plain-data shape\n constructor(value: T) {\n this._tag = \"Some\";\n this.value = value;\n }\n [INSPECT](_depth: number, options: unknown, inspect: Inspect): string {\n return `Some(${inspect(this.value, options)})`;\n }\n map<U>(fn: (value: T) => U): Maybe<U> {\n return new SomeImpl(fn(this.value));\n }\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U> {\n return fn(this.value);\n }\n match<U>(onSome: (value: T) => U, _onNone: () => U): U {\n return onSome(this.value);\n }\n unwrapOr<U>(_defaultValue: U): T | U {\n return this.value;\n }\n tap(fn: (value: T) => void): Maybe<T> {\n fn(this.value);\n return this;\n }\n tapNone(_fn: () => void): Maybe<T> {\n return this;\n }\n}\n\nclass NoneImpl<T> implements MaybeMethods<T> {\n readonly _tag: \"None\";\n constructor() {\n this._tag = \"None\";\n }\n [INSPECT](): string {\n return \"None\";\n }\n map<U>(_fn: (value: T) => U): Maybe<U> {\n return new NoneImpl<U>();\n }\n flatMap<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n andThen<U>(_fn: (value: T) => Maybe<U>): Maybe<U> {\n return new NoneImpl<U>();\n }\n match<U>(_onSome: (value: T) => U, onNone: () => U): U {\n return onNone();\n }\n unwrapOr<U>(defaultValue: U): T | U {\n return defaultValue;\n }\n tap(_fn: (value: T) => void): Maybe<T> {\n return this;\n }\n tapNone(fn: () => void): Maybe<T> {\n fn();\n return this;\n }\n}\n\nexport const newSome = <T>(value: T): Maybe<T> => new SomeImpl(value);\nexport const newNone = <T>(): Maybe<T> => new NoneImpl<T>();\n","import { dual } from \"./internal/dual.js\";\nimport { newNone, newSome } from \"./internal/proto.js\";\n\n/**\n * Tagged optional value — a discriminated union over `_tag` where absence\n * (`None`) is an expected outcome, not a failure. Construct with {@link some} /\n * {@link none}, narrow with {@link isSome} / {@link isNone}, and collapse with\n * {@link match}.\n *\n * @example\n * ```ts\n * function findUser(id: string): Maybe<User> {\n * return fromNullable(db.users.get(id));\n * }\n * ```\n */\nexport type Maybe<T> = MaybeData<T> & MaybeMethods<T>;\n\n/**\n * Plain-data shape of a {@link Maybe}: what `JSON.parse`, `structuredClone`\n * and `postMessage` hand back. It has no methods; lift it with `Maybe.from`.\n */\nexport type MaybeData<T> = { readonly _tag: \"Some\"; readonly value: T } | { readonly _tag: \"None\" };\n\n/**\n * Methods every live {@link Maybe} carries on its (non-enumerable) prototype.\n * Core semantics, mirroring `ResultMethods`: `tap` does not swallow throws.\n */\nexport interface MaybeMethods<T> {\n /** Transform the `Some` value; `None` passes through. */\n map<U>(fn: (value: T) => U): Maybe<U>;\n /** Canonical bind; short-circuits on `None`. */\n flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Alias of {@link MaybeMethods.flatMap}. */\n andThen<U>(fn: (value: T) => Maybe<U>): Maybe<U>;\n /** Fold both branches into one value. */\n match<U>(onSome: (value: T) => U, onNone: () => U): U;\n /** The `Some` value, or `defaultValue` on `None`. */\n unwrapOr<U>(defaultValue: U): T | U;\n /** Observe the `Some` value, pass the maybe through. */\n tap(fn: (value: T) => void): Maybe<T>;\n /** Observe `None`, pass the maybe through. */\n tapNone(fn: () => void): Maybe<T>;\n}\n\n/** The `Some` branch of a {@link Maybe} — a present value tagged `\"Some\"`. */\nexport type Some<T> = Extract<Maybe<T>, { _tag: \"Some\" }>;\n\n/** The `None` branch of a {@link Maybe} — expected absence, tagged `\"None\"`. */\nexport type None = Extract<Maybe<never>, { _tag: \"None\" }>;\n\n/**\n * Lifts a value into the `Some` branch.\n *\n * @example\n * ```ts\n * const m = some(1); // Maybe<number>\n * ```\n */\nexport const some = <T>(value: T): Maybe<T> => newSome(value);\n\n/**\n * Fantasy Land `pure` — alias of {@link some}. One lift name shared across\n * the trio (`of` / `Result.of` / `ResultAsync.of`) for generic and FL-style\n * code.\n *\n * @example\n * ```ts\n * const m = of(1); // Maybe<number> — identical to some(1)\n * ```\n */\nexport const of = some;\n\n/**\n * The `None` value — represents expected absence. Not a failure.\n *\n * @example\n * ```ts\n * const cached: Maybe<User> = cache.has(id) ? some(cache.get(id)) : none();\n * ```\n */\nexport const none = <T = never>(): Maybe<T> => newNone<T>();\n\n/**\n * Value-side companion of the {@link Maybe} type. `from` lifts plain data back\n * into a live `Maybe` at every deserialization sink (`JSON.parse`,\n * `structuredClone`, `postMessage`, Redux rehydration). It trusts the shape,\n * but an unknown `_tag` is a defect and throws a `TypeError` rather than\n * silently becoming `None`.\n *\n * @example\n * ```ts\n * const live = Maybe.from<number>(JSON.parse(JSON.stringify(some(1))));\n * live.map((n) => n + 1);\n * ```\n */\nexport const Maybe = {\n from: <T>(data: MaybeData<T>): Maybe<T> => {\n if (data._tag === \"Some\") return newSome(data.value);\n if (data._tag === \"None\") return newNone<T>();\n throw new TypeError(`Maybe.from: unknown _tag ${String((data as { _tag?: unknown })._tag)}`);\n },\n} as const;\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `Some`.\n *\n * @example\n * ```ts\n * if (isSome(m)) {\n * console.log(m.value); // narrowed to Some branch\n * }\n * ```\n */\nexport const isSome = <T>(maybe: Maybe<T>): maybe is Some<T> => maybe._tag === \"Some\";\n\n/**\n * Type-narrowing predicate: returns `true` when the value is `None`.\n *\n * @example\n * ```ts\n * if (isNone(fromNullable(user))) {\n * return redirect(\"/login\"); // narrowed to None branch\n * }\n * ```\n */\nexport const isNone = <T>(maybe: Maybe<T>): maybe is None => maybe._tag === \"None\";\n\n/**\n * Lift a nullable value into `Maybe<T>` — `null` and `undefined` become\n * `None`; anything else becomes `Some`.\n *\n * @example\n * ```ts\n * fromNullable(cache.get(key)); // Maybe<V>\n * fromNullable(null); // None\n * ```\n */\nexport const fromNullable = <T>(value: T | null | undefined): Maybe<T> =>\n value == null ? none<T>() : some(value);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each accepts either shape:\n// data-first: `map(maybe, fn)`\n// curried: `map(fn)(maybe)`\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U> =>\n isSome(maybe) ? some(fn(maybe.value)) : none<U>();\n\n/**\n * Transform the `Some` value, passing `None` through unchanged. Dual-form.\n *\n * @example\n * ```ts\n * map(some(2), (n) => n * 3); // Some 6 — data-first\n * pipe(fromNullable(input), map((s) => s.trim())); // curried\n * ```\n */\nexport const map: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => U): Maybe<U>;\n <T, U>(fn: (value: T) => U): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, mapImpl);\n\nconst flatMapImpl = <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U> =>\n isSome(maybe) ? fn(maybe.value) : none<U>();\n\n/**\n * Canonical bind for `Maybe`. Chains a `Maybe`-returning step;\n * short-circuits on `None`. Dual-form.\n *\n * @example\n * ```ts\n * flatMap(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const flatMap: {\n <T, U>(maybe: Maybe<T>, fn: (value: T) => Maybe<U>): Maybe<U>;\n <T, U>(fn: (value: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;\n} = dual(2, flatMapImpl);\n\n/**\n * Neverthrow-compat alias of {@link flatMap} — same dual-form shape. Prefer\n * {@link flatMap} as the canonical name; reach for `andThen` only when porting\n * neverthrow code or matching its vocabulary.\n *\n * @example\n * ```ts\n * andThen(fromNullable(user), (u) =>\n * u.active ? some(u) : none(),\n * );\n * ```\n */\nexport const andThen = flatMap;\n\nconst matchImpl = <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U =>\n isSome(maybe) ? onSome(maybe.value) : onNone();\n\n/**\n * Terminal collapse — fold both branches into a single value. Positional,\n * dual-form. Same shape as `Result.match` for sibling consistency.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as Maybe from \"@onrails/maybe\"` → `Maybe.match`) to\n * dissolve the collision.\n *\n * @example\n * ```ts\n * const greeting = match(\n * fromNullable(user),\n * (u) => `hello ${u.name}`,\n * () => \"hello guest\",\n * );\n * ```\n */\nexport const match: {\n <T, U>(maybe: Maybe<T>, onSome: (value: T) => U, onNone: () => U): U;\n <T, U>(onSome: (value: T) => U, onNone: () => U): (maybe: Maybe<T>) => U;\n} = dual(3, matchImpl);\n\nconst unwrapOrImpl = <T, U>(maybe: Maybe<T>, defaultValue: U): T | U =>\n isSome(maybe) ? maybe.value : defaultValue;\n\nexport const unwrapOr: {\n <T, U>(maybe: Maybe<T>, defaultValue: U): T | U;\n <U>(defaultValue: U): <T>(maybe: Maybe<T>) => T | U;\n} = dual(2, unwrapOrImpl);\n\n/**\n * Unwraps the `Some` value or throws if the value is `None`.\n *\n * @throws when the value is None — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\n *\n * @example\n * ```ts\n * expect(unwrap(parseUser(raw))).toEqual(expected);\n * ```\n */\nexport const unwrap = <T>(maybe: Maybe<T>): T => {\n if (maybe._tag === \"None\") {\n throw new Error(\"Called unwrap on None\");\n }\n return maybe.value;\n};\n\n/**\n * Drop every `None` and collect the remaining `Some` values into an array.\n *\n * @example\n * ```ts\n * compact([some(1), none(), some(2)]); // [1, 2]\n * ```\n */\nexport const compact = <T>(maybes: readonly Maybe<T>[]): T[] => {\n const out: T[] = [];\n for (const m of maybes) {\n if (isSome(m)) {\n out.push(m.value);\n }\n }\n return out;\n};\n\n/**\n * `compact(items.map(fn))` — map each item to `Maybe`, drop `None`,\n * collect the rest.\n *\n * @example\n * ```ts\n * compactMap(rawRows, (row) =>\n * row.deletedAt ? none() : some(row.id),\n * );\n * // string[]\n * ```\n */\nexport const compactMap = <T, U>(items: readonly T[], fn: (item: T) => Maybe<U>): U[] =>\n compact(items.map(fn));\n\n/**\n * `flatMap(fromNullable(value), fn)` — lift a nullable then bind through\n * a `Maybe`-returning step. Convenient for chaining lookups.\n *\n * @example\n * ```ts\n * optional(rawId, (id) => fromNullable(cache.get(id)));\n * ```\n */\nexport const optional = <T, U>(value: T | null | undefined, fn: (value: T) => Maybe<U>): Maybe<U> =>\n flatMapImpl(fromNullable(value), fn);\n\nconst tapImpl = <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T> => {\n if (isSome(maybe)) fn(maybe.value);\n return maybe;\n};\n\n/**\n * Run a side effect on the `Some` value, pass the `Maybe` through unchanged.\n * `None` is a no-op. Mirrors `tap` from `@onrails/result`. Dual-form.\n *\n * @example\n * ```ts\n * tap(some(2), (n) => console.log(n)); // logs 2, returns Some 2\n * pipe(fromNullable(row), tap((r) => sink.push(r))); // curried — collect on Some\n * ```\n */\nexport const tap: {\n <T>(maybe: Maybe<T>, fn: (value: T) => void): Maybe<T>;\n <T>(fn: (value: T) => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapImpl);\n\nconst tapNoneImpl = <T>(maybe: Maybe<T>, fn: () => void): Maybe<T> => {\n if (isNone(maybe)) fn();\n return maybe;\n};\n\n/**\n * Run a side effect when the `Maybe` is `None`, pass it through unchanged.\n * `Some` is a no-op. The `None`-side mirror of {@link tap}. Dual-form.\n *\n * @example\n * ```ts\n * tapNone(none(), () => metrics.miss()); // runs effect, returns None\n * pipe(fromNullable(hit), tapNone(() => metrics.miss())); // curried\n * ```\n */\nexport const tapNone: {\n <T>(maybe: Maybe<T>, fn: () => void): Maybe<T>;\n <T>(fn: () => void): (maybe: Maybe<T>) => Maybe<T>;\n} = dual(2, tapNoneImpl);\n\nconst printPayload = (payload: unknown): string => {\n try {\n return JSON.stringify(payload) ?? String(payload);\n } catch {\n // cyclic or non-JSON payload — a debug printer must not throw\n return String(payload);\n }\n};\n\n/**\n * Debug printer — renders a maybe as `Some(…)` / `None` for logs. Payloads\n * print as JSON (values are plain data by design); non-JSON payloads fall\n * back to `String(...)`. Mirror of the result package's `show`.\n *\n * @example\n * ```ts\n * show(some(1)); // 'Some(1)'\n * show(none()); // 'None'\n * ```\n */\nexport const show = <T>(maybe: Maybe<T>): string =>\n isSome(maybe) ? `Some(${printPayload(maybe.value)})` : \"None\";\n"]}
|