@nunofyobiz/effect-extras 0.0.1

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/dist/index.d.ts +3703 -0
  4. package/dist/index.js +1006 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +103 -0
  7. package/src/ArrayX/ArrayX.ts +818 -0
  8. package/src/ArrayX/index.ts +1 -0
  9. package/src/BigIntX/BigIntX.ts +35 -0
  10. package/src/BigIntX/index.ts +1 -0
  11. package/src/BooleanX/BooleanX.ts +24 -0
  12. package/src/BooleanX/index.ts +1 -0
  13. package/src/DurationX/DurationX.ts +178 -0
  14. package/src/DurationX/index.ts +1 -0
  15. package/src/EffectX/EffectX.ts +183 -0
  16. package/src/EffectX/index.ts +1 -0
  17. package/src/FormDataX/FormDataX.ts +57 -0
  18. package/src/FormDataX/index.ts +1 -0
  19. package/src/MapX/MapX.ts +54 -0
  20. package/src/MapX/index.ts +1 -0
  21. package/src/NonNullableX/NonNullableX.ts +290 -0
  22. package/src/NonNullableX/index.ts +2 -0
  23. package/src/NumberX/NumberX.ts +282 -0
  24. package/src/NumberX/index.ts +1 -0
  25. package/src/OptionX/OptionX.ts +234 -0
  26. package/src/OptionX/index.ts +1 -0
  27. package/src/OrderX/OrderX.ts +35 -0
  28. package/src/OrderX/index.ts +1 -0
  29. package/src/PredicateX/PredicateX.ts +98 -0
  30. package/src/PredicateX/index.ts +1 -0
  31. package/src/PromiseX/PromiseX.ts +32 -0
  32. package/src/PromiseX/index.ts +1 -0
  33. package/src/RecordX/RecordX.ts +478 -0
  34. package/src/RecordX/index.ts +1 -0
  35. package/src/ResultX/ResultX.ts +53 -0
  36. package/src/ResultX/index.ts +1 -0
  37. package/src/SchemaX/SchemaX.ts +324 -0
  38. package/src/SchemaX/index.ts +1 -0
  39. package/src/SetX/SetX.ts +160 -0
  40. package/src/SetX/index.ts +1 -0
  41. package/src/StringX/StringX.ts +97 -0
  42. package/src/StringX/index.ts +1 -0
  43. package/src/StructX/StructX.ts +310 -0
  44. package/src/StructX/index.ts +1 -0
  45. package/src/These/These.ts +1173 -0
  46. package/src/These/index.ts +1 -0
  47. package/src/index.ts +20 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Helpers for working with non-nullable values.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import {
7
+ Number as EffectNumber,
8
+ Match,
9
+ Order,
10
+ Ordering,
11
+ Predicate,
12
+ } from "effect";
13
+ import { dual } from "effect/Function";
14
+
15
+ /**
16
+ * Returns `value` narrowed to `NonNullable<A>`, throwing an `Error` if it is
17
+ * `null` or `undefined`.
18
+ *
19
+ * Use it at trusted boundaries where a value is known to be present but typed as
20
+ * nullable, turning a silent `undefined` into a loud failure. An optional
21
+ * `variableName` is woven into the thrown message to aid debugging. This is the
22
+ * function re-exported as `nn` from the module barrel, a terse shorthand handy
23
+ * inside string interpolations.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { NonNullableX } from "@nunofyobiz/effect-extras"
28
+ *
29
+ * assert.deepStrictEqual(NonNullableX.fromNullableOrThrow("value"), "value")
30
+ *
31
+ * assert.throws(
32
+ * () => NonNullableX.fromNullableOrThrow(null, "varName"),
33
+ * /Value is nullable: null \(variable name: varName\)/
34
+ * )
35
+ * ```
36
+ *
37
+ * @category unsafe
38
+ * @since 0.0.0
39
+ */
40
+ export const fromNullableOrThrow = <A>(
41
+ value: A,
42
+ variableName?: string,
43
+ ): NonNullable<A> => {
44
+ if (Predicate.isNotNullish(value)) {
45
+ return value;
46
+ }
47
+ throw new Error(
48
+ `Value is nullable: ${String(value)}${Predicate.isNotNullish(variableName) ? ` (variable name: ${variableName})` : ""}`,
49
+ );
50
+ };
51
+
52
+ /**
53
+ * Branches on whether `value` is nullish, passing the value narrowed to
54
+ * `NonNullable<A>` to the `whenNotNullable` handler.
55
+ *
56
+ * A nullable-aware sibling of `Match.value`: it folds a present-or-absent value
57
+ * into a single `B` without an `if`/`else` or a manual `!= null` check. Note that
58
+ * falsy-but-present values (`""`, `0`, `false`) take the `whenNotNullable`
59
+ * branch — only `null` and `undefined` are treated as absent. Supports both
60
+ * data-first and data-last (pipeable) call styles.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { NonNullableX } from "@nunofyobiz/effect-extras"
65
+ * import { pipe } from "effect"
66
+ *
67
+ * // Data-first — present value flows through narrowed
68
+ * assert.deepStrictEqual(
69
+ * NonNullableX.match("value", {
70
+ * whenNullable: () => "nullable",
71
+ * whenNotNullable: (value) => value
72
+ * }),
73
+ * "value"
74
+ * )
75
+ *
76
+ * // Data-last — null takes the whenNullable branch
77
+ * assert.deepStrictEqual(
78
+ * pipe(
79
+ * null,
80
+ * NonNullableX.match({
81
+ * whenNullable: () => "nullable",
82
+ * whenNotNullable: (value) => value
83
+ * })
84
+ * ),
85
+ * "nullable"
86
+ * )
87
+ * ```
88
+ *
89
+ * @category pattern matching
90
+ * @since 0.0.0
91
+ */
92
+ export const match = dual<
93
+ <A, B>(handlers: {
94
+ whenNullable: () => B;
95
+ whenNotNullable: (value: NonNullable<A>) => B;
96
+ }) => (value: A) => B,
97
+ <A, B>(
98
+ value: A,
99
+ handlers: {
100
+ whenNullable: () => B;
101
+ whenNotNullable: (value: NonNullable<A>) => B;
102
+ },
103
+ ) => B
104
+ >(
105
+ 2,
106
+ <A, B>(
107
+ value: A,
108
+ {
109
+ whenNullable,
110
+ whenNotNullable,
111
+ }: { whenNullable: () => B; whenNotNullable: (value: NonNullable<A>) => B },
112
+ ): B =>
113
+ Predicate.isNotNullish(value) ? whenNotNullable(value) : whenNullable(),
114
+ );
115
+
116
+ /**
117
+ * Applies `map` to `a` only when it is non-nullish, passing nullish inputs
118
+ * through unchanged.
119
+ *
120
+ * This is the nullable-preserving map: a present value is transformed to `B`,
121
+ * while `null` stays `null` and `undefined` stays `undefined`, so nullability is
122
+ * carried through the transformation. Supports both data-first and data-last
123
+ * (pipeable) call styles.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * import { NonNullableX } from "@nunofyobiz/effect-extras"
128
+ * import { pipe } from "effect"
129
+ *
130
+ * // Data-first — present value is transformed
131
+ * assert.deepStrictEqual(NonNullableX.map(1, (v: number) => v + 1), 2)
132
+ *
133
+ * // Data-last — nullish passes through unchanged
134
+ * const value: number | null = null
135
+ * assert.deepStrictEqual(
136
+ * pipe(
137
+ * value,
138
+ * NonNullableX.map<number | null, number>((v) => v + 1)
139
+ * ),
140
+ * null
141
+ * )
142
+ * ```
143
+ *
144
+ * @category mapping
145
+ * @since 0.0.0
146
+ */
147
+ export const map = dual<
148
+ <A, B>(
149
+ map: (a: NonNullable<A>) => B,
150
+ ) => (a: A) => B | (null & A) | (undefined & A),
151
+ <A, B>(
152
+ a: A,
153
+ map: (a: NonNullable<A>) => B,
154
+ ) => B | (null & A) | (undefined & A)
155
+ >(
156
+ 2,
157
+ <A, B>(
158
+ a: A,
159
+ map: (a: NonNullable<A>) => B,
160
+ ): B | (null & A) | (undefined & A) => {
161
+ if (Predicate.isNotNullish(a)) {
162
+ return map(a);
163
+ }
164
+
165
+ if (Predicate.isNullish(a)) {
166
+ return a;
167
+ }
168
+
169
+ throw new Error(`Value is neither nullable nor non-nullable: ${String(a)}`);
170
+ },
171
+ );
172
+
173
+ /**
174
+ * Lifts a total function `(a: A) => B` into one that tolerates nullish input,
175
+ * applying it to present values and passing `null`/`undefined` through unchanged.
176
+ *
177
+ * Where `map` operates on a value, `lift` transforms the function itself,
178
+ * yielding a reusable `(a: A | null | undefined) => B | null | undefined` you can
179
+ * drop into a `pipe`. Use it to adapt a plain transform to a nullable pipeline
180
+ * without wrapping each call site.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * import { NonNullableX } from "@nunofyobiz/effect-extras"
185
+ * import { Number, pipe } from "effect"
186
+ *
187
+ * const addOne = NonNullableX.lift(Number.sum(1))
188
+ *
189
+ * assert.deepStrictEqual(pipe(1, addOne), 2)
190
+ * assert.deepStrictEqual(pipe(null, addOne), null)
191
+ * assert.deepStrictEqual(pipe(undefined, addOne), undefined)
192
+ * ```
193
+ *
194
+ * @category mapping
195
+ * @since 0.0.0
196
+ */
197
+ export const lift =
198
+ <A, B>(map: (a: A) => B) =>
199
+ (a: A | null | undefined): B | null | undefined => {
200
+ if (Predicate.isNullish(a)) {
201
+ return a;
202
+ }
203
+
204
+ return map(a);
205
+ };
206
+
207
+ /**
208
+ * Extends an `Order.Order<A>` to an `Order.Order<A | null>`, deciding where
209
+ * `null` sorts relative to present values via the `behavior` argument.
210
+ *
211
+ * Pass `"value-null"` to push `null`s to the end and `"null-value"` to pull them
212
+ * to the front; two present values fall back to the wrapped order. Use it to sort
213
+ * collections that mix real values with gaps without a bespoke comparator.
214
+ * Supports both data-first and data-last (pipeable) call styles.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * import { NonNullableX } from "@nunofyobiz/effect-extras"
219
+ * import { Array, Order, pipe } from "effect"
220
+ *
221
+ * // "value-null" — nulls sorted last
222
+ * assert.deepStrictEqual(
223
+ * pipe(
224
+ * [null, 1, 3, null, 2],
225
+ * Array.sort(NonNullableX.nullableOrder(Order.Number, "value-null"))
226
+ * ),
227
+ * [1, 2, 3, null, null]
228
+ * )
229
+ *
230
+ * // "null-value" — nulls sorted first (data-last)
231
+ * assert.deepStrictEqual(
232
+ * pipe(
233
+ * [null, 1, 3, null, 2],
234
+ * Array.sort(pipe(Order.Number, NonNullableX.nullableOrder("null-value")))
235
+ * ),
236
+ * [null, null, 1, 2, 3]
237
+ * )
238
+ * ```
239
+ *
240
+ * @category ordering
241
+ * @since 0.0.0
242
+ */
243
+ export const nullableOrder = dual<
244
+ (
245
+ behavior: "value-null" | "null-value",
246
+ ) => <A>(order: Order.Order<A>) => Order.Order<A | null>,
247
+ <A>(
248
+ order: Order.Order<A>,
249
+ behavior: "value-null" | "null-value",
250
+ ) => Order.Order<A | null>
251
+ >(
252
+ 2,
253
+ <A>(
254
+ order: Order.Order<A>,
255
+ behavior: "value-null" | "null-value",
256
+ ): Order.Order<A | null> => {
257
+ // Prepare to sort them based on their nullability
258
+ const { nullableSortCategory, valueSortCategory } = Match.value(
259
+ behavior,
260
+ ).pipe(
261
+ Match.when("value-null", () => ({
262
+ nullableSortCategory: 1,
263
+ valueSortCategory: 0,
264
+ })),
265
+ Match.when("null-value", () => ({
266
+ nullableSortCategory: 0,
267
+ valueSortCategory: 1,
268
+ })),
269
+ Match.exhaustive,
270
+ );
271
+
272
+ return (a: A | null, b: A | null): Ordering.Ordering => {
273
+ // Right off the bat, if they are both defined just return the regular ordering
274
+ if (Predicate.isNotNullish(a) && Predicate.isNotNullish(b)) {
275
+ return order(a, b);
276
+ }
277
+
278
+ // Otherwise figure out which category each value is
279
+ const aCategory = Predicate.isNotNullish(a)
280
+ ? valueSortCategory
281
+ : nullableSortCategory;
282
+
283
+ const bCategory = Predicate.isNotNullish(b)
284
+ ? valueSortCategory
285
+ : nullableSortCategory;
286
+
287
+ return EffectNumber.sign(aCategory - bCategory);
288
+ };
289
+ },
290
+ );
@@ -0,0 +1,2 @@
1
+ export * as NonNullableX from "./NonNullableX";
2
+ export { fromNullableOrThrow as nn } from "./NonNullableX";
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Generic, framework-agnostic extensions to Effect's `Number` module.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import { Number as EffectNumber, Option, pipe } from "effect";
7
+ import { dual } from "effect/Function";
8
+
9
+ // Internal — used by unsafeLogBase.
10
+ const logBase = dual<
11
+ // Data-last typing
12
+ (base: number) => (number: number) => Option.Option<number>,
13
+ // Data-first typing
14
+ (number: number, base: number) => Option.Option<number>
15
+ >(2, (number: number, base: number): Option.Option<number> => {
16
+ if (number <= 0) {
17
+ return Option.none();
18
+ }
19
+
20
+ if (base <= 0 || base === 1) {
21
+ return Option.none();
22
+ }
23
+
24
+ if (base < 1 && number >= 1) {
25
+ return Option.none();
26
+ }
27
+
28
+ if (base >= 1 && number < 1) {
29
+ return Option.none();
30
+ }
31
+
32
+ return Option.some(Math.log(number) / Math.log(base));
33
+ });
34
+
35
+ /**
36
+ * Computes the logarithm of `number` in the given `base`, throwing when the
37
+ * inputs fall outside the domain of `log`.
38
+ *
39
+ * Throws when `number <= 0`, when `base` is `<= 0` or `1`, or when `number` and
40
+ * `base` sit on opposite sides of `1` (a fractional base with `number >= 1`, or
41
+ * a base `>= 1` with `number < 1`) — cases where the real logarithm is
42
+ * undefined or non-finite. Reach for it only when the inputs are already known
43
+ * to be valid.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { pipe } from "effect"
48
+ * import { NumberX } from "@nunofyobiz/effect-extras"
49
+ *
50
+ * // data-first
51
+ * assert.deepStrictEqual(NumberX.unsafeLogBase(8, 2), 3)
52
+ * assert.deepStrictEqual(NumberX.unsafeLogBase(100, 10), 2)
53
+ *
54
+ * // data-last (piped)
55
+ * assert.deepStrictEqual(pipe(8, NumberX.unsafeLogBase(2)), 3)
56
+ *
57
+ * // throws outside the domain of log
58
+ * assert.throws(() => NumberX.unsafeLogBase(0, 2))
59
+ * ```
60
+ *
61
+ * @category unsafe
62
+ * @since 0.0.0
63
+ */
64
+ export const unsafeLogBase = dual<
65
+ (base: number) => (number: number) => number,
66
+ (number: number, base: number) => number
67
+ >(2, (number: number, base: number): number =>
68
+ Option.getOrThrowWith(
69
+ logBase(number, base),
70
+ () => new Error(`Error calculating log base ${base} of ${number}`),
71
+ ),
72
+ );
73
+
74
+ /**
75
+ * Converts a number to a percentage of some total.
76
+ *
77
+ * Internal — used by unsafeToPercentOf.
78
+ */
79
+ const toPercentOf = dual<
80
+ // Data-last typing
81
+ (total: number) => (numerator: number) => Option.Option<number>,
82
+ // Data-first typing
83
+ (numerator: number, total: number) => Option.Option<number>
84
+ >(
85
+ 2,
86
+ (numerator: number, total: number): Option.Option<number> =>
87
+ pipe(
88
+ EffectNumber.divide(numerator, total),
89
+ Option.map((ratio) => ratio * 100),
90
+ ),
91
+ );
92
+
93
+ /**
94
+ * Expresses `numerator` as a percentage of `total`, throwing on division by
95
+ * zero.
96
+ *
97
+ * Returns `(numerator / total) * 100`. When `total` is `0` the percentage is
98
+ * undefined, so this throws rather than returning `Infinity` or `NaN` — use it
99
+ * only when `total` is known to be non-zero.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { pipe } from "effect"
104
+ * import { NumberX } from "@nunofyobiz/effect-extras"
105
+ *
106
+ * // data-first
107
+ * assert.deepStrictEqual(NumberX.unsafeToPercentOf(1, 2), 50)
108
+ * assert.deepStrictEqual(NumberX.unsafeToPercentOf(2, 1), 200)
109
+ *
110
+ * // data-last (piped)
111
+ * assert.deepStrictEqual(pipe(1, NumberX.unsafeToPercentOf(2)), 50)
112
+ *
113
+ * // throws on division by zero
114
+ * assert.throws(() => NumberX.unsafeToPercentOf(1, 0))
115
+ * ```
116
+ *
117
+ * @category unsafe
118
+ * @since 0.0.0
119
+ */
120
+ export const unsafeToPercentOf = dual<
121
+ // Data-last typing
122
+ (total: number) => (numerator: number) => number,
123
+ // Data-first typing
124
+ (numerator: number, total: number) => number
125
+ >(2, (numerator: number, total: number): number =>
126
+ Option.getOrThrowWith(
127
+ toPercentOf(numerator, total),
128
+ () => new Error(`Division by zero when dividing ${numerator} by ${total}`),
129
+ ),
130
+ );
131
+
132
+ /**
133
+ * Formats `number` with a fixed number of decimal places, returning a `string`.
134
+ *
135
+ * A pipeable wrapper around `Number.prototype.toFixed` — the result is rounded
136
+ * (not truncated) to `numberDigits` decimals and always carries exactly that
137
+ * many digits after the point.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * import { pipe } from "effect"
142
+ * import { NumberX } from "@nunofyobiz/effect-extras"
143
+ *
144
+ * // data-first
145
+ * assert.deepStrictEqual(NumberX.toFixed(3.236242, 2), "3.24")
146
+ *
147
+ * // data-last (piped)
148
+ * assert.deepStrictEqual(pipe(3.236242, NumberX.toFixed(0)), "3")
149
+ * ```
150
+ *
151
+ * @category conversions
152
+ * @since 0.0.0
153
+ */
154
+ export const toFixed = dual<
155
+ (numberDigits: number) => (number: number) => string,
156
+ (number: number, numberDigits: number) => string
157
+ >(2, (number: number, numberDigits: number): string =>
158
+ number.toFixed(numberDigits),
159
+ );
160
+
161
+ /**
162
+ * Rounds `number` to a fixed number of decimal places, returning a `number`.
163
+ *
164
+ * Unlike {@link toFixed}, the result stays a `number` (no trailing zeroes) — it
165
+ * formats via `toFixed` then parses back, which sidesteps the usual
166
+ * floating-point rounding artifacts. See
167
+ * https://stackoverflow.com/a/29494612/22875620.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * import { pipe } from "effect"
172
+ * import { NumberX } from "@nunofyobiz/effect-extras"
173
+ *
174
+ * // data-first
175
+ * assert.deepStrictEqual(NumberX.roundToDigits(3.236242, 2), 3.24)
176
+ *
177
+ * // data-last (piped)
178
+ * assert.deepStrictEqual(pipe(3.736242, NumberX.roundToDigits(0)), 4)
179
+ * ```
180
+ *
181
+ * @category mapping
182
+ * @since 0.0.0
183
+ */
184
+ export const roundToDigits = dual<
185
+ // Data-last typing
186
+ (numberDigits: number) => (number: number) => number,
187
+ // Data-first typing
188
+ (number: number, numberDigits: number) => number
189
+ >(2, (number: number, numberDigits: number): number =>
190
+ Number(number.toFixed(numberDigits)),
191
+ );
192
+
193
+ /**
194
+ * Renders `number` as a `string`, left-padded with zeroes to at least
195
+ * `numberDigits` characters.
196
+ *
197
+ * If the number's string representation is already as long as (or longer than)
198
+ * `numberDigits`, it is returned unchanged.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * import { pipe } from "effect"
203
+ * import { NumberX } from "@nunofyobiz/effect-extras"
204
+ *
205
+ * // data-first
206
+ * assert.deepStrictEqual(NumberX.padLeftZeroes(1, 3), "001")
207
+ *
208
+ * // longer than the target width is returned unchanged
209
+ * assert.deepStrictEqual(NumberX.padLeftZeroes(1000, 3), "1000")
210
+ *
211
+ * // data-last (piped)
212
+ * assert.deepStrictEqual(pipe(10, NumberX.padLeftZeroes(3)), "010")
213
+ * ```
214
+ *
215
+ * @category conversions
216
+ * @since 0.0.0
217
+ */
218
+ export const padLeftZeroes = dual<
219
+ (numberDigits: number) => (number: number) => string,
220
+ (number: number, numberDigits: number) => string
221
+ >(2, (number: number, numberDigits: number): string =>
222
+ number.toString().padStart(numberDigits, "0"),
223
+ );
224
+
225
+ /**
226
+ * Converts a `0`-indexed value to its `1`-indexed rank by adding `1`.
227
+ *
228
+ * Handy for presentation where humans count from one (e.g. the element at
229
+ * index `0` is shown as "item 1").
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * import { NumberX } from "@nunofyobiz/effect-extras"
234
+ *
235
+ * assert.deepStrictEqual(NumberX.indexToRank(0), 1)
236
+ * assert.deepStrictEqual(NumberX.indexToRank(4), 5)
237
+ * ```
238
+ *
239
+ * @category mapping
240
+ * @since 0.0.0
241
+ */
242
+ export const indexToRank = (index: number): number => index + 1;
243
+
244
+ const EXCEL_COLUMNS_BASE_CHARS = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"];
245
+
246
+ /**
247
+ * Converts a `0`-indexed column number into its bijective base-26 spreadsheet
248
+ * label (`A`, `B`, …, `Z`, `AA`, `AB`, …).
249
+ *
250
+ * Returns `None` for a negative `index`; every non-negative index maps to a
251
+ * `Some` label. Indexing is `0`-based here, so `0` is `"A"` and `25` is `"Z"`,
252
+ * unlike the `1`-based numbering shown in most spreadsheet references.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * import { Option } from "effect"
257
+ * import { NumberX } from "@nunofyobiz/effect-extras"
258
+ *
259
+ * assert.deepStrictEqual(NumberX.indexToExcel(0), Option.some("A"))
260
+ * assert.deepStrictEqual(NumberX.indexToExcel(26), Option.some("AA"))
261
+ * assert.deepStrictEqual(NumberX.indexToExcel(-1), Option.none())
262
+ * ```
263
+ *
264
+ * @category conversions
265
+ * @since 0.0.0
266
+ */
267
+ export const indexToExcel = (index: number): Option.Option<string> => {
268
+ if (index < 0) {
269
+ return Option.none();
270
+ }
271
+
272
+ const baseChars = EXCEL_COLUMNS_BASE_CHARS;
273
+
274
+ let excel = "";
275
+ const base = baseChars.length;
276
+ do {
277
+ excel = baseChars[index % base] + excel;
278
+ index = Math.floor(index / base) - 1;
279
+ } while (index >= 0);
280
+
281
+ return Option.some(excel);
282
+ };
@@ -0,0 +1 @@
1
+ export * as NumberX from "./NumberX";