@solana/options 6.3.1 → 6.3.2-canary-20260313112147

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/options",
3
- "version": "6.3.1",
3
+ "version": "6.3.2-canary-20260313112147",
4
4
  "description": "Managing and serializing Rust-like Option types in JavaScript",
5
5
  "homepage": "https://www.solanakit.com/api#solanaoptions",
6
6
  "exports": {
@@ -33,7 +33,8 @@
33
33
  "types": "./dist/types/index.d.ts",
34
34
  "type": "commonjs",
35
35
  "files": [
36
- "./dist/"
36
+ "./dist/",
37
+ "./src/"
37
38
  ],
38
39
  "sideEffects": false,
39
40
  "keywords": [
@@ -55,11 +56,11 @@
55
56
  "maintained node versions"
56
57
  ],
57
58
  "dependencies": {
58
- "@solana/codecs-core": "6.3.1",
59
- "@solana/codecs-data-structures": "6.3.1",
60
- "@solana/codecs-numbers": "6.3.1",
61
- "@solana/codecs-strings": "6.3.1",
62
- "@solana/errors": "6.3.1"
59
+ "@solana/codecs-core": "6.3.2-canary-20260313112147",
60
+ "@solana/codecs-data-structures": "6.3.2-canary-20260313112147",
61
+ "@solana/codecs-numbers": "6.3.2-canary-20260313112147",
62
+ "@solana/codecs-strings": "6.3.2-canary-20260313112147",
63
+ "@solana/errors": "6.3.2-canary-20260313112147"
63
64
  },
64
65
  "peerDependencies": {
65
66
  "typescript": "^5.0.0"
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * This package allows us to manage and serialize Rust-like Option types in JavaScript.
3
+ * It can be used standalone, but it is also exported as part of Kit
4
+ * [`@solana/kit`](https://github.com/anza-xyz/kit/tree/main/packages/kit).
5
+ *
6
+ * This package is also part of the [`@solana/codecs` package](https://github.com/anza-xyz/kit/tree/main/packages/codecs)
7
+ * which acts as an entry point for all codec packages as well as for their documentation.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export * from './option';
12
+ export * from './option-codec';
13
+ export * from './unwrap-option';
14
+ export * from './unwrap-option-recursively';
@@ -0,0 +1,398 @@
1
+ import {
2
+ assertIsFixedSize,
3
+ Codec,
4
+ combineCodec,
5
+ containsBytes,
6
+ Decoder,
7
+ Encoder,
8
+ fixDecoderSize,
9
+ FixedSizeCodec,
10
+ FixedSizeDecoder,
11
+ FixedSizeEncoder,
12
+ fixEncoderSize,
13
+ ReadonlyUint8Array,
14
+ transformDecoder,
15
+ transformEncoder,
16
+ VariableSizeCodec,
17
+ VariableSizeDecoder,
18
+ VariableSizeEncoder,
19
+ } from '@solana/codecs-core';
20
+ import {
21
+ getBooleanDecoder,
22
+ getBooleanEncoder,
23
+ getConstantDecoder,
24
+ getConstantEncoder,
25
+ getTupleDecoder,
26
+ getTupleEncoder,
27
+ getUnionDecoder,
28
+ getUnionEncoder,
29
+ getUnitDecoder,
30
+ getUnitEncoder,
31
+ } from '@solana/codecs-data-structures';
32
+ import {
33
+ FixedSizeNumberCodec,
34
+ FixedSizeNumberDecoder,
35
+ FixedSizeNumberEncoder,
36
+ getU8Decoder,
37
+ getU8Encoder,
38
+ NumberCodec,
39
+ NumberDecoder,
40
+ NumberEncoder,
41
+ } from '@solana/codecs-numbers';
42
+
43
+ import { isOption, isSome, None, none, Option, OptionOrNullable, Some, some } from './option';
44
+ import { wrapNullable } from './unwrap-option';
45
+
46
+ /**
47
+ * Defines the configuration options for {@link Option} codecs.
48
+ *
49
+ * The `getOptionCodec` function behaves similarly to {@link getNullableCodec}
50
+ * but encodes `Option<T>` types instead of `T | null` types.
51
+ *
52
+ * This configuration controls how {@link None} values are encoded and how presence
53
+ * is determined when decoding.
54
+ *
55
+ * @typeParam TPrefix - A number codec, encoder, or decoder used as the presence prefix.
56
+ *
57
+ * @see {@link getOptionEncoder}
58
+ * @see {@link getOptionDecoder}
59
+ * @see {@link getOptionCodec}
60
+ */
61
+ export type OptionCodecConfig<TPrefix extends NumberCodec | NumberDecoder | NumberEncoder> = {
62
+ /**
63
+ * Specifies how {@link None} values are represented in the encoded data.
64
+ *
65
+ * - By default, {@link None} values are omitted from encoding.
66
+ * - `'zeroes'`: The bytes allocated for the value are filled with zeroes. This requires a fixed-size codec for the item.
67
+ * - Custom byte array: {@link None} values are replaced with a predefined byte sequence. This results in a variable-size codec.
68
+ *
69
+ * @defaultValue No explicit `noneValue` is used; {@link None} values are omitted.
70
+ */
71
+ noneValue?: ReadonlyUint8Array | 'zeroes';
72
+
73
+ /**
74
+ * The presence prefix used to distinguish between {@link None} and present values.
75
+ *
76
+ * - By default, a `u8` prefix is used (`0 = None`, `1 = Some`).
77
+ * - Custom number codec: Allows defining a different number size for the prefix.
78
+ * - `null`: No prefix is used; `noneValue` (if provided) determines {@link None}.
79
+ * If no `noneValue` is set, {@link None} is identified by the absence of bytes.
80
+ *
81
+ * @defaultValue `u8` prefix.
82
+ */
83
+ prefix?: TPrefix | null;
84
+ };
85
+
86
+ /**
87
+ * Returns an encoder for optional values using the {@link Option} type.
88
+ *
89
+ * This encoder serializes an {@link OptionOrNullable} value using a configurable approach:
90
+ * - By default, a `u8` prefix is used (`0 = None`, `1 = Some`). This can be customized or disabled.
91
+ * - If `noneValue: 'zeroes'` is set, {@link None} values are encoded as zeroes.
92
+ * - If `noneValue` is a byte array, {@link None} values are replaced with the provided constant.
93
+ *
94
+ * Unlike {@link getNullableEncoder}, this encoder accepts both {@link Option} and {@link Nullable} values.
95
+ *
96
+ * For more details, see {@link getOptionCodec}.
97
+ *
98
+ * @typeParam TFrom - The type of the main value being encoded.
99
+ *
100
+ * @param item - The encoder for the value that may be present.
101
+ * @param config - Configuration options for encoding optional values.
102
+ * @returns A `FixedSizeEncoder` or `VariableSizeEncoder` for encoding option values.
103
+ *
104
+ * @example
105
+ * Encoding an optional string.
106
+ * ```ts
107
+ * const stringCodec = addCodecSizePrefix(getUtf8Codec(), getU32Codec());
108
+ * const encoder = getOptionEncoder(stringCodec);
109
+ *
110
+ * encoder.encode(some('Hi'));
111
+ * encoder.encode('Hi');
112
+ * // 0x01020000004869
113
+ * // | | └-- utf8 string content ("Hi").
114
+ * // | └-- u32 string prefix (2 characters).
115
+ * // └-- 1-byte prefix (Some).
116
+ *
117
+ * encoder.encode(none());
118
+ * encoder.encode(null);
119
+ * // 0x00
120
+ * // └-- 1-byte prefix (None).
121
+ * ```
122
+ *
123
+ * @see {@link getOptionCodec}
124
+ */
125
+ export function getOptionEncoder<TFrom, TSize extends number>(
126
+ item: FixedSizeEncoder<TFrom, TSize>,
127
+ config: OptionCodecConfig<NumberEncoder> & { noneValue: 'zeroes'; prefix: null },
128
+ ): FixedSizeEncoder<OptionOrNullable<TFrom>, TSize>;
129
+ export function getOptionEncoder<TFrom>(
130
+ item: FixedSizeEncoder<TFrom>,
131
+ config: OptionCodecConfig<FixedSizeNumberEncoder> & { noneValue: 'zeroes' },
132
+ ): FixedSizeEncoder<OptionOrNullable<TFrom>>;
133
+ export function getOptionEncoder<TFrom>(
134
+ item: FixedSizeEncoder<TFrom>,
135
+ config: OptionCodecConfig<NumberEncoder> & { noneValue: 'zeroes' },
136
+ ): VariableSizeEncoder<OptionOrNullable<TFrom>>;
137
+ export function getOptionEncoder<TFrom>(
138
+ item: Encoder<TFrom>,
139
+ config?: OptionCodecConfig<NumberEncoder> & { noneValue?: ReadonlyUint8Array },
140
+ ): VariableSizeEncoder<OptionOrNullable<TFrom>>;
141
+ export function getOptionEncoder<TFrom>(
142
+ item: Encoder<TFrom>,
143
+ config: OptionCodecConfig<NumberEncoder> = {},
144
+ ): Encoder<OptionOrNullable<TFrom>> {
145
+ const prefix = (() => {
146
+ if (config.prefix === null) {
147
+ return transformEncoder(getUnitEncoder(), (_boolean: boolean) => undefined);
148
+ }
149
+ return getBooleanEncoder({ size: config.prefix ?? getU8Encoder() });
150
+ })();
151
+ const noneValue = (() => {
152
+ if (config.noneValue === 'zeroes') {
153
+ assertIsFixedSize(item);
154
+ return fixEncoderSize(getUnitEncoder(), item.fixedSize);
155
+ }
156
+ if (!config.noneValue) {
157
+ return getUnitEncoder();
158
+ }
159
+ return getConstantEncoder(config.noneValue);
160
+ })();
161
+
162
+ return getUnionEncoder(
163
+ [
164
+ transformEncoder(getTupleEncoder([prefix, noneValue]), (_value: None | null): [boolean, void] => [
165
+ false,
166
+ undefined,
167
+ ]),
168
+ transformEncoder(getTupleEncoder([prefix, item]), (value: Some<TFrom> | TFrom): [boolean, TFrom] => [
169
+ true,
170
+ isOption(value) && isSome(value) ? value.value : value,
171
+ ]),
172
+ ],
173
+ variant => {
174
+ const option = isOption<TFrom>(variant) ? variant : wrapNullable(variant);
175
+ return Number(isSome(option));
176
+ },
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Returns a decoder for optional values using the {@link Option} type.
182
+ *
183
+ * This decoder deserializes an `Option<T>` value using a configurable approach:
184
+ * - By default, a `u8` prefix is used (`0 = None`, `1 = Some`). This can be customized or disabled.
185
+ * - If `noneValue: 'zeroes'` is set, `None` values are identified by zeroes.
186
+ * - If `noneValue` is a byte array, `None` values match the provided constant.
187
+ *
188
+ * Unlike {@link getNullableDecoder}, this decoder always outputs an {@link Option} type.
189
+ *
190
+ * For more details, see {@link getOptionCodec}.
191
+ *
192
+ * @typeParam TTo - The type of the main value being decoded.
193
+ *
194
+ * @param item - The decoder for the value that may be present.
195
+ * @param config - Configuration options for decoding optional values.
196
+ * @returns A `FixedSizeDecoder` or `VariableSizeDecoder` for decoding option values.
197
+ *
198
+ * @example
199
+ * Decoding an optional string with a size prefix.
200
+ * ```ts
201
+ * const stringCodec = addCodecSizePrefix(getUtf8Codec(), getU32Codec());
202
+ * const decoder = getOptionDecoder(stringCodec);
203
+ *
204
+ * decoder.decode(new Uint8Array([0x01, 0x02, 0x00, 0x00, 0x00, 0x48, 0x69]));
205
+ * // some('Hi')
206
+ *
207
+ * decoder.decode(new Uint8Array([0x00]));
208
+ * // none()
209
+ * ```
210
+ *
211
+ * @see {@link getOptionCodec}
212
+ */
213
+ export function getOptionDecoder<TTo, TSize extends number>(
214
+ item: FixedSizeDecoder<TTo, TSize>,
215
+ config: OptionCodecConfig<NumberDecoder> & { noneValue: 'zeroes'; prefix: null },
216
+ ): FixedSizeDecoder<Option<TTo>, TSize>;
217
+ export function getOptionDecoder<TTo>(
218
+ item: FixedSizeDecoder<TTo>,
219
+ config: OptionCodecConfig<FixedSizeNumberDecoder> & { noneValue: 'zeroes' },
220
+ ): FixedSizeDecoder<Option<TTo>>;
221
+ export function getOptionDecoder<TTo>(
222
+ item: FixedSizeDecoder<TTo>,
223
+ config: OptionCodecConfig<NumberDecoder> & { noneValue: 'zeroes' },
224
+ ): VariableSizeDecoder<Option<TTo>>;
225
+ export function getOptionDecoder<TTo>(
226
+ item: Decoder<TTo>,
227
+ config?: OptionCodecConfig<NumberDecoder> & { noneValue?: ReadonlyUint8Array },
228
+ ): VariableSizeDecoder<Option<TTo>>;
229
+ export function getOptionDecoder<TTo>(
230
+ item: Decoder<TTo>,
231
+ config: OptionCodecConfig<NumberDecoder> = {},
232
+ ): Decoder<Option<TTo>> {
233
+ const prefix = (() => {
234
+ if (config.prefix === null) {
235
+ return transformDecoder(getUnitDecoder(), () => false);
236
+ }
237
+ return getBooleanDecoder({ size: config.prefix ?? getU8Decoder() });
238
+ })();
239
+ const noneValue = (() => {
240
+ if (config.noneValue === 'zeroes') {
241
+ assertIsFixedSize(item);
242
+ return fixDecoderSize(getUnitDecoder(), item.fixedSize);
243
+ }
244
+ if (!config.noneValue) {
245
+ return getUnitDecoder();
246
+ }
247
+ return getConstantDecoder(config.noneValue);
248
+ })();
249
+
250
+ return getUnionDecoder(
251
+ [
252
+ transformDecoder(getTupleDecoder([prefix, noneValue]), () => none<TTo>()),
253
+ transformDecoder(getTupleDecoder([prefix, item]), ([, value]) => some(value)),
254
+ ],
255
+ (bytes, offset) => {
256
+ if (config.prefix === null && !config.noneValue) {
257
+ return Number(offset < bytes.length);
258
+ }
259
+ if (config.prefix === null && config.noneValue != null) {
260
+ const zeroValue =
261
+ config.noneValue === 'zeroes' ? new Uint8Array(noneValue.fixedSize).fill(0) : config.noneValue;
262
+ return containsBytes(bytes, zeroValue, offset) ? 0 : 1;
263
+ }
264
+ return Number(prefix.read(bytes, offset)[0]);
265
+ },
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Returns a codec for encoding and decoding optional values using the {@link Option} type.
271
+ *
272
+ * This codec serializes and deserializes `Option<T>` values using a configurable approach:
273
+ * - By default, a `u8` prefix is used (`0 = None`, `1 = Some`).
274
+ * - If `noneValue: 'zeroes'` is set, `None` values are encoded/decoded as zeroes.
275
+ * - If `noneValue` is a byte array, `None` values are represented by the provided constant.
276
+ * - If `prefix: null` is set, the codec determines `None` values solely from `noneValue` or the presence of bytes.
277
+ *
278
+ * For more details on the configuration options, see {@link OptionCodecConfig}.
279
+ *
280
+ * Note that this behaves similarly to {@link getNullableCodec}, except it
281
+ * encodes {@link OptionOrNullable} values and decodes {@link Option} values.
282
+ *
283
+ * @typeParam TFrom - The type of the main value being encoded.
284
+ * @typeParam TTo - The type of the main value being decoded.
285
+ *
286
+ * @param item - The codec for the value that may be present.
287
+ * @param config - Configuration options for encoding and decoding option values.
288
+ * @returns A `FixedSizeCodec` or `VariableSizeCodec` for encoding and decoding option values.
289
+ *
290
+ * @example
291
+ * Encoding and decoding an optional string with a size prefix.
292
+ * ```ts
293
+ * const stringCodec = addCodecSizePrefix(getUtf8Codec(), getU32Codec());
294
+ * const codec = getOptionCodec(stringCodec);
295
+ *
296
+ * const someBytes = codec.encode(some('Hi'));
297
+ * // 0x01020000004869
298
+ * // | | └-- utf8 string content ("Hi").
299
+ * // | └-- u32 string prefix (2 characters).
300
+ * // └-- 1-byte prefix (Some).
301
+ *
302
+ * const noneBytes = codec.encode(none());
303
+ * // 0x00
304
+ * // └-- 1-byte prefix (None).
305
+ *
306
+ * codec.decode(someBytes); // some('Hi')
307
+ * codec.decode(noneBytes); // none()
308
+ * ```
309
+ *
310
+ * @example
311
+ * Encoding nullable values.
312
+ * ```ts
313
+ * const stringCodec = addCodecSizePrefix(getUtf8Codec(), getU32Codec());
314
+ * const codec = getOptionCodec(stringCodec);
315
+ *
316
+ * const someBytes = codec.encode('Hi'); // 0x01020000004869
317
+ * const noneBytes = codec.encode(null); // 0x00
318
+ *
319
+ * codec.decode(someBytes); // some('Hi')
320
+ * codec.decode(noneBytes); // none()
321
+ * ```
322
+ *
323
+ * @example
324
+ * Encoding and decoding an optional number with a fixed size.
325
+ * ```ts
326
+ * const codec = getOptionCodec(getU16Codec(), { noneValue: 'zeroes' });
327
+ *
328
+ * const someBytes = codec.encode(some(42)); // 0x012a00
329
+ * const noneBytes = codec.encode(none()); // 0x000000
330
+ *
331
+ * codec.decode(someBytes); // some(42)
332
+ * codec.decode(noneBytes); // none()
333
+ * ```
334
+ *
335
+ * @example
336
+ * Encoding and decoding {@link None} values with a custom byte sequence and no prefix.
337
+ * ```ts
338
+ * const codec = getOptionCodec(getU16Codec(), {
339
+ * noneValue: new Uint8Array([0xff, 0xff]),
340
+ * prefix: null,
341
+ * });
342
+ *
343
+ * const someBytes = codec.encode(some(42)); // 0x2a00
344
+ * const noneBytes = codec.encode(none()); // 0xffff
345
+ *
346
+ * codec.decode(someBytes); // some(42)
347
+ * codec.decode(noneBytes); // none()
348
+ * ```
349
+ *
350
+ * @example
351
+ * Identifying {@link None} values by the absence of bytes.
352
+ * ```ts
353
+ * const codec = getOptionCodec(getU16Codec(), { prefix: null });
354
+ *
355
+ * const someBytes = codec.encode(some(42)); // 0x2a00
356
+ * const noneBytes = codec.encode(none()); // new Uint8Array(0)
357
+ *
358
+ * codec.decode(someBytes); // some(42)
359
+ * codec.decode(noneBytes); // none()
360
+ * ```
361
+ *
362
+ * @remarks
363
+ * Separate {@link getOptionEncoder} and {@link getOptionDecoder} functions are available.
364
+ *
365
+ * ```ts
366
+ * const bytes = getOptionEncoder(getU32Encoder()).encode(some(42));
367
+ * const value = getOptionDecoder(getU32Decoder()).decode(bytes);
368
+ * ```
369
+ *
370
+ * @see {@link getOptionEncoder}
371
+ * @see {@link getOptionDecoder}
372
+ */
373
+ export function getOptionCodec<TFrom, TTo extends TFrom, TSize extends number>(
374
+ item: FixedSizeCodec<TFrom, TTo, TSize>,
375
+ config: OptionCodecConfig<NumberCodec> & { noneValue: 'zeroes'; prefix: null },
376
+ ): FixedSizeCodec<OptionOrNullable<TFrom>, Option<TTo>, TSize>;
377
+ export function getOptionCodec<TFrom, TTo extends TFrom = TFrom>(
378
+ item: FixedSizeCodec<TFrom, TTo>,
379
+ config: OptionCodecConfig<FixedSizeNumberCodec> & { noneValue: 'zeroes' },
380
+ ): FixedSizeCodec<OptionOrNullable<TFrom>, Option<TTo>>;
381
+ export function getOptionCodec<TFrom, TTo extends TFrom = TFrom>(
382
+ item: FixedSizeCodec<TFrom, TTo>,
383
+ config: OptionCodecConfig<NumberCodec> & { noneValue: 'zeroes' },
384
+ ): VariableSizeCodec<OptionOrNullable<TFrom>, Option<TTo>>;
385
+ export function getOptionCodec<TFrom, TTo extends TFrom = TFrom>(
386
+ item: Codec<TFrom, TTo>,
387
+ config?: OptionCodecConfig<NumberCodec> & { noneValue?: ReadonlyUint8Array },
388
+ ): VariableSizeCodec<OptionOrNullable<TFrom>, Option<TTo>>;
389
+ export function getOptionCodec<TFrom, TTo extends TFrom = TFrom>(
390
+ item: Codec<TFrom, TTo>,
391
+ config: OptionCodecConfig<NumberCodec> = {},
392
+ ): Codec<OptionOrNullable<TFrom>, Option<TTo>> {
393
+ type ConfigCast = OptionCodecConfig<NumberCodec> & { noneValue?: ReadonlyUint8Array };
394
+ return combineCodec(
395
+ getOptionEncoder<TFrom>(item, config as ConfigCast),
396
+ getOptionDecoder<TTo>(item, config as ConfigCast),
397
+ );
398
+ }
package/src/option.ts ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * An implementation of the Rust `Option<T>` type in JavaScript.
3
+ *
4
+ * In Rust, optional values are represented using `Option<T>`, which can be either:
5
+ * - `Some(T)`, indicating a present value.
6
+ * - `None`, indicating the absence of a value.
7
+ *
8
+ * In JavaScript, this is typically represented as `T | null`. However, this approach fails with nested options.
9
+ * For example, `Option<Option<T>>` in Rust would translate to `T | null | null` in JavaScript, which is equivalent to `T | null`.
10
+ * This means there is no way to differentiate between `Some(None)` and `None`, making nested options impossible.
11
+ *
12
+ * This `Option` type helps solve this by mirroring Rust’s `Option<T>` type.
13
+ *
14
+ * ```ts
15
+ * type Option<T> = Some<T> | None;
16
+ * type Some<T> = { __option: 'Some'; value: T };
17
+ * type None = { __option: 'None' };
18
+ * ```
19
+ *
20
+ * @typeParam T - The type of the contained value.
21
+ *
22
+ * @example
23
+ * Here's how you can create `Option` values.
24
+ *
25
+ * To improve developer experience, helper functions are available.
26
+ * TypeScript can infer the type of `T` or it can be explicitly provided.
27
+ *
28
+ * ```ts
29
+ * // Create an option with a value.
30
+ * some('Hello World');
31
+ * some<number | string>(123);
32
+ *
33
+ * // Create an empty option.
34
+ * none();
35
+ * none<number | string>();
36
+ * ```
37
+ *
38
+ * @see {@link Some}
39
+ * @see {@link None}
40
+ * @see {@link some}
41
+ * @see {@link none}
42
+ */
43
+ export type Option<T> = None | Some<T>;
44
+
45
+ /**
46
+ * A flexible type that allows working with {@link Option} values or nullable values.
47
+ *
48
+ * It defines a looser type that can be used when encoding {@link Option | Options}.
49
+ * This allows us to pass `null` or the nested value directly whilst still
50
+ * supporting the Option type for use-cases that need more type safety.
51
+ *
52
+ * @typeParam T - The type of the contained value.
53
+ *
54
+ * @example
55
+ * Accepting both `Option<T>` and `T | null` as input.
56
+ * ```ts
57
+ * function double(value: OptionOrNullable<number>) {
58
+ * const option = isOption(value) ? value : wrapNullable(value);
59
+ * return isSome(option) ? option.value * 2 : 'No value';
60
+ * }
61
+ *
62
+ * double(42); // 84
63
+ * double(some(21)); // 42
64
+ * double(none()); // "No value"
65
+ * double(null); // "No value"
66
+ * ```
67
+ *
68
+ * @see {@link Option}
69
+ * @see {@link isOption}
70
+ * @see {@link wrapNullable}
71
+ */
72
+ export type OptionOrNullable<T> = Option<T> | T | null;
73
+
74
+ /**
75
+ * Represents an {@link Option} that contains a value.
76
+ *
77
+ * This type mirrors Rust’s `Some(T)`, indicating that a value is present.
78
+ *
79
+ * For more details, see {@link Option}.
80
+ *
81
+ * @typeParam T - The type of the contained value.
82
+ *
83
+ * @example
84
+ * Creating a `Some` value.
85
+ * ```ts
86
+ * const value = some(42);
87
+ * isSome(value); // true
88
+ * isNone(value); // false
89
+ * ```
90
+ *
91
+ * @see {@link Option}
92
+ * @see {@link some}
93
+ * @see {@link isSome}
94
+ */
95
+ export type Some<T> = Readonly<{ __option: 'Some'; value: T }>;
96
+
97
+ /**
98
+ * Represents an {@link Option} that contains no value.
99
+ *
100
+ * This type mirrors Rust’s `None`, indicating the absence of a value.
101
+ *
102
+ * For more details, see {@link Option}.
103
+ *
104
+ * @example
105
+ * Creating a `None` value.
106
+ * ```ts
107
+ * const empty = none();
108
+ * isNone(empty); // true
109
+ * isSome(empty); // false
110
+ * ```
111
+ *
112
+ * @see {@link Option}
113
+ * @see {@link none}
114
+ * @see {@link isNone}
115
+ */
116
+ export type None = Readonly<{ __option: 'None' }>;
117
+
118
+ /**
119
+ * Creates a new {@link Option} that contains a value.
120
+ *
121
+ * This function explicitly wraps a value in an {@link Option} type.
122
+ *
123
+ * @typeParam T - The type of the contained value.
124
+ *
125
+ * @param value - The value to wrap in an {@link Option}.
126
+ * @returns An {@link Option} containing the provided value.
127
+ *
128
+ * @example
129
+ * Wrapping a value in an `Option`.
130
+ * ```ts
131
+ * const option = some('Hello');
132
+ * option.value; // "Hello"
133
+ * isOption(option); // true
134
+ * isSome(option); // true
135
+ * isNone(option); // false
136
+ * ```
137
+ *
138
+ * @see {@link Option}
139
+ * @see {@link Some}
140
+ */
141
+ export const some = <T>(value: T): Option<T> => ({ __option: 'Some', value });
142
+
143
+ /**
144
+ * Creates a new {@link Option} that contains no value.
145
+ *
146
+ * This function explicitly represents an absent value.
147
+ *
148
+ * @typeParam T - The type of the expected absent value.
149
+ *
150
+ * @returns An {@link Option} containing no value.
151
+ *
152
+ * @example
153
+ * Creating an empty `Option`.
154
+ * ```ts
155
+ * const empty = none<number>();
156
+ * isOption(empty); // true
157
+ * isSome(empty); // false
158
+ * isNone(empty); // true
159
+ * ```
160
+ *
161
+ * @see {@link Option}
162
+ * @see {@link None}
163
+ */
164
+ export const none = <T>(): Option<T> => ({ __option: 'None' });
165
+
166
+ /**
167
+ * Checks whether the given value is an {@link Option}.
168
+ *
169
+ * This function determines whether an input follows the `Option<T>` structure.
170
+ *
171
+ * @typeParam T - The type of the contained value.
172
+ *
173
+ * @param input - The value to check.
174
+ * @returns `true` if the value is an {@link Option}, `false` otherwise.
175
+ *
176
+ * @example
177
+ * Checking for `Option` values.
178
+ * ```ts
179
+ * isOption(some(42)); // true
180
+ * isOption(none()); // true
181
+ * isOption(42); // false
182
+ * isOption(null); // false
183
+ * isOption("anything else"); // false
184
+ * ```
185
+ *
186
+ * @see {@link Option}
187
+ */
188
+ export const isOption = <T = unknown>(input: unknown): input is Option<T> =>
189
+ !!(
190
+ input &&
191
+ typeof input === 'object' &&
192
+ '__option' in input &&
193
+ ((input.__option === 'Some' && 'value' in input) || input.__option === 'None')
194
+ );
195
+
196
+ /**
197
+ * Checks whether the given {@link Option} contains a value.
198
+ *
199
+ * This function acts as a type guard, ensuring the value is a {@link Some}.
200
+ *
201
+ * @typeParam T - The type of the contained value.
202
+ *
203
+ * @param option - The {@link Option} to check.
204
+ * @returns `true` if the option is a {@link Some}, `false` otherwise.
205
+ *
206
+ * @example
207
+ * Checking for `Some` values.
208
+ * ```ts
209
+ * isSome(some(42)); // true
210
+ * isSome(none()); // false
211
+ * ```
212
+ *
213
+ * @see {@link Option}
214
+ * @see {@link Some}
215
+ */
216
+ export const isSome = <T>(option: Option<T>): option is Some<T> => option.__option === 'Some';
217
+
218
+ /**
219
+ * Checks whether the given {@link Option} contains no value.
220
+ *
221
+ * This function acts as a type guard, ensuring the value is a {@link None}.
222
+ *
223
+ * @typeParam T - The type of the expected value.
224
+ *
225
+ * @param option - The {@link Option} to check.
226
+ * @returns `true` if the option is a {@link None}, `false` otherwise.
227
+ *
228
+ * @example
229
+ * Checking for `None` values.
230
+ * ```ts
231
+ * isNone(some(42)); // false
232
+ * isNone(none()); // true
233
+ * ```
234
+ *
235
+ * @see {@link Option}
236
+ * @see {@link None}
237
+ */
238
+ export const isNone = <T>(option: Option<T>): option is None => option.__option === 'None';
@@ -0,0 +1,150 @@
1
+ import { isOption, isSome, None, Some } from './option';
2
+
3
+ /**
4
+ * Defines types that should not be recursively unwrapped.
5
+ *
6
+ * These types are preserved as-is when using {@link unwrapOptionRecursively}.
7
+ *
8
+ * @see {@link unwrapOptionRecursively}
9
+ */
10
+ type UnUnwrappables =
11
+ | Date
12
+ | Int8Array
13
+ | Int16Array
14
+ | Int32Array
15
+ | Uint8Array
16
+ | Uint16Array
17
+ | Uint32Array
18
+ | bigint
19
+ | boolean
20
+ | number
21
+ | string
22
+ | symbol
23
+ | null
24
+ | undefined;
25
+
26
+ /**
27
+ * A type that recursively unwraps nested {@link Option} types.
28
+ *
29
+ * This type resolves all nested {@link Option} values, ensuring
30
+ * that deeply wrapped values are properly extracted.
31
+ *
32
+ * - If `T` is an {@link Option}, it resolves to the contained value.
33
+ * - If `T` is a known primitive or immutable type, it remains unchanged.
34
+ * - If `T` is an object or array, it recursively unwraps any options found.
35
+ *
36
+ * The fallback type `U` (default: `null`) is used in place of `None` values.
37
+ *
38
+ * @typeParam T - The type to be unwrapped.
39
+ * @typeParam U - The fallback type for `None` values (defaults to `null`).
40
+ *
41
+ * @example
42
+ * Resolving nested `Option` types.
43
+ * ```ts
44
+ * UnwrappedOption<Some<Some<string>>>; // string
45
+ * UnwrappedOption<None>; // null
46
+ * ```
47
+ *
48
+ * @example
49
+ * Resolving options inside objects and arrays.
50
+ * ```ts
51
+ * UnwrappedOption<{ a: Some<number>; b: None }>; // { a: number; b: null }
52
+ * UnwrappedOption<[Some<number>, None]>; // [number, null]
53
+ * ```
54
+ *
55
+ * @see {@link unwrapOptionRecursively}
56
+ */
57
+ export type UnwrappedOption<T, U = null> =
58
+ T extends Some<infer TValue>
59
+ ? UnwrappedOption<TValue, U>
60
+ : T extends None
61
+ ? U
62
+ : T extends UnUnwrappables
63
+ ? T
64
+ : T extends object
65
+ ? { [key in keyof T]: UnwrappedOption<T[key], U> }
66
+ : T extends Array<infer TItem>
67
+ ? Array<UnwrappedOption<TItem, U>>
68
+ : T;
69
+
70
+ /**
71
+ * Recursively unwraps all nested {@link Option} types within a value.
72
+ *
73
+ * This function traverses a given value and removes all instances
74
+ * of {@link Option}, replacing them with their contained values.
75
+ *
76
+ * - If an {@link Option} is encountered, its value is extracted.
77
+ * - If an array or object is encountered, its elements are traversed recursively.
78
+ * - If `None` is encountered, it is replaced with the fallback value (default: `null`).
79
+ *
80
+ * @typeParam T - The type of the input value.
81
+ * @typeParam U - The fallback type for `None` values (defaults to `null`).
82
+ *
83
+ * @param input - The value to unwrap.
84
+ * @param fallback - A function that provides a fallback value for `None` options.
85
+ * @returns The recursively unwrapped value.
86
+ *
87
+ * @example
88
+ * Recursively unwrapping nested options.
89
+ * ```ts
90
+ * unwrapOptionRecursively(some(some('Hello World'))); // "Hello World"
91
+ * unwrapOptionRecursively(some(none<string>())); // null
92
+ * ```
93
+ *
94
+ * @example
95
+ * Recursively unwrapping options inside objects and arrays.
96
+ * ```ts
97
+ * unwrapOptionRecursively({
98
+ * a: 'hello',
99
+ * b: none(),
100
+ * c: [{ c1: some(42) }, { c2: none() }],
101
+ * });
102
+ * // { a: "hello", b: null, c: [{ c1: 42 }, { c2: null }] }
103
+ * ```
104
+ *
105
+ * @example
106
+ * Using a fallback value for `None` options.
107
+ * ```ts
108
+ * unwrapOptionRecursively(
109
+ * {
110
+ * a: 'hello',
111
+ * b: none(),
112
+ * c: [{ c1: some(42) }, { c2: none() }],
113
+ * },
114
+ * () => 'Default',
115
+ * );
116
+ * // { a: "hello", b: "Default", c: [{ c1: 42 }, { c2: "Default" }] }
117
+ * ```
118
+ *
119
+ * @remarks
120
+ * This function does not mutate objects or arrays.
121
+ *
122
+ * @see {@link Option}
123
+ * @see {@link UnwrappedOption}
124
+ */
125
+ export function unwrapOptionRecursively<T>(input: T): UnwrappedOption<T>;
126
+ export function unwrapOptionRecursively<T, U>(input: T, fallback: () => U): UnwrappedOption<T, U>;
127
+ export function unwrapOptionRecursively<T, U = null>(input: T, fallback?: () => U): UnwrappedOption<T, U> {
128
+ // Types to bypass.
129
+ if (!input || ArrayBuffer.isView(input)) {
130
+ return input as UnwrappedOption<T, U>;
131
+ }
132
+
133
+ const next = <X>(x: X) =>
134
+ (fallback ? unwrapOptionRecursively(x, fallback) : unwrapOptionRecursively(x)) as UnwrappedOption<X, U>;
135
+
136
+ // Handle Option.
137
+ if (isOption(input)) {
138
+ if (isSome(input)) return next(input.value) as UnwrappedOption<T, U>;
139
+ return (fallback ? fallback() : null) as UnwrappedOption<T, U>;
140
+ }
141
+
142
+ // Walk.
143
+ if (Array.isArray(input)) {
144
+ return input.map(next) as UnwrappedOption<T, U>;
145
+ }
146
+ if (typeof input === 'object') {
147
+ return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, next(v)])) as UnwrappedOption<T, U>;
148
+ }
149
+ return input as UnwrappedOption<T, U>;
150
+ }
@@ -0,0 +1,64 @@
1
+ import { isSome, none, Option, some } from './option';
2
+
3
+ /**
4
+ * Unwraps the value of an {@link Option}, returning its contained value or a fallback.
5
+ *
6
+ * This function extracts the value `T` from an `Option<T>` type.
7
+ * - If the option is {@link Some}, it returns the contained value `T`.
8
+ * - If the option is {@link None}, it returns the fallback value `U`, which defaults to `null`.
9
+ *
10
+ * @typeParam T - The type of the contained value.
11
+ * @typeParam U - The type of the fallback value (defaults to `null`).
12
+ *
13
+ * @param option - The {@link Option} to unwrap.
14
+ * @param fallback - A function that provides a fallback value if the option is {@link None}.
15
+ * @returns The contained value if {@link Some}, otherwise the fallback value.
16
+ *
17
+ * @example
18
+ * Unwrapping an `Option` with no fallback.
19
+ * ```ts
20
+ * unwrapOption(some('Hello World')); // "Hello World"
21
+ * unwrapOption(none()); // null
22
+ * ```
23
+ *
24
+ * @example
25
+ * Providing a custom fallback value.
26
+ * ```ts
27
+ * unwrapOption(some('Hello World'), () => 'Default'); // "Hello World"
28
+ * unwrapOption(none(), () => 'Default'); // "Default"
29
+ * ```
30
+ *
31
+ * @see {@link Option}
32
+ * @see {@link Some}
33
+ * @see {@link None}
34
+ */
35
+ export function unwrapOption<T>(option: Option<T>): T | null;
36
+ export function unwrapOption<T, U>(option: Option<T>, fallback: () => U): T | U;
37
+ export function unwrapOption<T, U = null>(option: Option<T>, fallback?: () => U): T | U {
38
+ if (isSome(option)) return option.value;
39
+ return fallback ? fallback() : (null as U);
40
+ }
41
+
42
+ /**
43
+ * Wraps a nullable value into an {@link Option}.
44
+ *
45
+ * - If the input value is `null`, this function returns {@link None}.
46
+ * - Otherwise, it wraps the value in {@link Some}.
47
+ *
48
+ * @typeParam T - The type of the contained value.
49
+ *
50
+ * @param nullable - The nullable value to wrap.
51
+ * @returns An {@link Option} wrapping the value.
52
+ *
53
+ * @example
54
+ * Wrapping nullable values.
55
+ * ```ts
56
+ * wrapNullable('Hello World'); // Option<string> (Some)
57
+ * wrapNullable<string>(null); // Option<string> (None)
58
+ * ```
59
+ *
60
+ * @see {@link Option}
61
+ * @see {@link Some}
62
+ * @see {@link None}
63
+ */
64
+ export const wrapNullable = <T>(nullable: T | null): Option<T> => (nullable !== null ? some(nullable) : none<T>());