@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,310 @@
1
+ /**
2
+ * Generic, framework-agnostic extensions to Effect's `Struct` module.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import { Option, Predicate, Record, pipe } from "effect";
7
+ import { dual } from "effect/Function";
8
+
9
+ /**
10
+ * Describes the shape of a per-field transformation over an object `O`: a record
11
+ * `T` whose values are functions taking the corresponding field of `O`.
12
+ *
13
+ * Used to type the "evolve" pattern, where each provided key maps to a function
14
+ * that receives that field's current value. Copied from Effect's internal
15
+ * `Struct.evolve()` type, which is not exported — it would be better to use
16
+ * theirs directly if it ever becomes public.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { StructX } from "@nunofyobiz/effect-extras"
21
+ *
22
+ * type Transform = StructX.PartialTransform<
23
+ * { a: number; b: string },
24
+ * { a: (n: number) => boolean }
25
+ * >
26
+ *
27
+ * const evolve: Transform = { a: (n) => n > 0 }
28
+ *
29
+ * assert.deepStrictEqual(evolve.a(1), true)
30
+ * ```
31
+ *
32
+ * @category models
33
+ * @since 0.0.0
34
+ */
35
+ export type PartialTransform<O, T> = {
36
+ [K in keyof T]: T[K] extends (a: O[K & keyof O]) => unknown
37
+ ? T[K]
38
+ : (a: O[K & keyof O]) => unknown;
39
+ };
40
+
41
+ /**
42
+ * Builds a singleton record `{ [name]: value }` when `value` is defined, or an
43
+ * empty object `{}` when it is `undefined` — meant to be spread into an object
44
+ * literal.
45
+ *
46
+ * This is the canonical fix for `exactOptionalPropertyTypes: true` (which this
47
+ * repo enables for Effect Schema). Under that flag, spreading
48
+ * `{ key: maybeUndefined }` into an object whose key is `key?: T` is a type
49
+ * error: the property must be *absent*, not present-but-`undefined`. Spreading
50
+ * `...defined("key", maybeUndefined)` instead conditionally omits the property
51
+ * altogether. Note that `null` and other falsy-but-defined values (`0`, `""`,
52
+ * `false`) are kept — only `undefined` is dropped.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { StructX } from "@nunofyobiz/effect-extras"
57
+ *
58
+ * // Defined value → singleton record
59
+ * assert.deepStrictEqual(StructX.defined("name", "Ada"), { name: "Ada" })
60
+ *
61
+ * // undefined → property omitted entirely
62
+ * assert.deepStrictEqual(StructX.defined("name", undefined), {})
63
+ *
64
+ * // Falsy-but-defined values are kept
65
+ * assert.deepStrictEqual(StructX.defined("name", 0), { name: 0 })
66
+ *
67
+ * // Typical use: spread to conditionally include an optional field
68
+ * const maybeAge: number | undefined = undefined
69
+ * assert.deepStrictEqual({ id: 1, ...StructX.defined("age", maybeAge) }, {
70
+ * id: 1,
71
+ * })
72
+ * ```
73
+ *
74
+ * @category constructors
75
+ * @since 0.0.0
76
+ */
77
+ export const defined = <const K extends string, V>(
78
+ name: K,
79
+ value: V | undefined,
80
+ ): Partial<Record<K, Exclude<V, undefined>>> =>
81
+ Predicate.isUndefined(value)
82
+ ? {}
83
+ : Record.singleton(name, value as Exclude<V, undefined>);
84
+
85
+ /**
86
+ * Removes every property whose value is `undefined` from `record`, narrowing
87
+ * each remaining property type to exclude `undefined`.
88
+ *
89
+ * The bulk counterpart of {@link defined}: instead of building one optional
90
+ * field, it filters a whole object. Very useful for update/patch actions where
91
+ * `undefined` means "leave this field unchanged" while every other value —
92
+ * including `null`, `0`, `""`, `false`, and `Option.none()` — means "set the
93
+ * field to exactly this".
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * import { StructX } from "@nunofyobiz/effect-extras"
98
+ *
99
+ * // Drops `b` (undefined) but keeps every other value, including null/0/false
100
+ * assert.deepStrictEqual(
101
+ * StructX.filterDefined({ a: "x", b: undefined, c: 0, d: null, e: false }),
102
+ * { a: "x", c: 0, d: null, e: false },
103
+ * )
104
+ * ```
105
+ *
106
+ * @category filtering
107
+ * @since 0.0.0
108
+ */
109
+ export const filterDefined = <R extends Record<string, unknown>>(
110
+ record: R,
111
+ ): Partial<{ [P in keyof R]: Exclude<R[P], undefined> }> =>
112
+ Object.entries(record).reduce(
113
+ (accumulator, [key, value]) => ({ ...accumulator, ...defined(key, value) }),
114
+ {} as Partial<{ [P in keyof R]: Exclude<R[P], undefined> }>,
115
+ );
116
+
117
+ /**
118
+ * Builds a singleton record `{ [name]: value }` when `value` is `Some`, or an
119
+ * empty object `{}` when it is `None` — meant to be spread into an object
120
+ * literal.
121
+ *
122
+ * The `Option`-valued sibling of {@link defined}: where `defined` keys off
123
+ * `undefined`, this keys off `Option` presence. Spread `...some("key", opt)` to
124
+ * conditionally include a field only when the `Option` carries a value, which
125
+ * stays correct under `exactOptionalPropertyTypes: true`.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * import { Option } from "effect"
130
+ * import { StructX } from "@nunofyobiz/effect-extras"
131
+ *
132
+ * // Some → singleton record
133
+ * assert.deepStrictEqual(StructX.some("name", Option.some("Ada")), {
134
+ * name: "Ada",
135
+ * })
136
+ *
137
+ * // None → property omitted entirely
138
+ * assert.deepStrictEqual(StructX.some("name", Option.none()), {})
139
+ * ```
140
+ *
141
+ * @category constructors
142
+ * @since 0.0.0
143
+ */
144
+ export const some = <const K extends string, V>(
145
+ name: K,
146
+ value: Option.Option<V>,
147
+ ): Partial<Record<K, V>> =>
148
+ Option.match(value, {
149
+ onSome: (someValue) => Record.singleton(name, someValue),
150
+ onNone: () => ({}),
151
+ });
152
+
153
+ /**
154
+ * Like {@link some}, but with swapped argument order for piping. Returns a
155
+ * singleton record `{ [name]: value }` when the Option is `Some`, or an
156
+ * empty object `{}` when `None`.
157
+ *
158
+ * Internal-only — exists to support {@link pickSome} in data-last form.
159
+ * Not exported because the codebase has no direct callers.
160
+ */
161
+ const someSingleton: {
162
+ <const K extends string>(
163
+ name: K,
164
+ ): <V>(value: Option.Option<V>) => Partial<Record<K, V>>;
165
+ <V, const K extends string>(
166
+ value: Option.Option<V>,
167
+ name: K,
168
+ ): Partial<Record<K, V>>;
169
+ } = dual(
170
+ 2,
171
+ <V, const K extends string>(
172
+ value: Option.Option<V>,
173
+ name: K,
174
+ ): Partial<Record<K, V>> => some(name, value),
175
+ );
176
+
177
+ /**
178
+ * Looks up `key` in a record of `Option` values and returns a singleton record
179
+ * when the value is `Some`, or `{}` when the key is absent or its value is
180
+ * `None`.
181
+ *
182
+ * Combines `Record.get` + `Option.flatten` + a singleton builder in one call —
183
+ * ideal for spreading an optional field out of a lookup table into an object
184
+ * literal under `exactOptionalPropertyTypes: true`. By default the output key
185
+ * matches the lookup key; pass `renameKeyTo` to emit the value under a different
186
+ * name.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * import { Option } from "effect"
191
+ * import { StructX } from "@nunofyobiz/effect-extras"
192
+ *
193
+ * const lookup = {
194
+ * a: Option.some(100),
195
+ * b: Option.none<number>(),
196
+ * }
197
+ *
198
+ * // Some → singleton record under the same key
199
+ * assert.deepStrictEqual(StructX.pickSome(lookup, "a"), { a: 100 })
200
+ *
201
+ * // None → property omitted entirely
202
+ * assert.deepStrictEqual(StructX.pickSome(lookup, "b"), {})
203
+ *
204
+ * // Rename the output key
205
+ * assert.deepStrictEqual(StructX.pickSome(lookup, "a", "count"), { count: 100 })
206
+ * ```
207
+ *
208
+ * @category constructors
209
+ * @since 0.0.0
210
+ */
211
+ export function pickSome<const K extends string, V>(
212
+ record: Record<K, Option.Option<V>>,
213
+ key: K,
214
+ ): Partial<Record<K, V>>;
215
+ export function pickSome<const K1 extends string, V, const K2 extends string>(
216
+ record: Record<K1, Option.Option<V>>,
217
+ key: K1,
218
+ renameKeyTo: K2,
219
+ ): Partial<Record<K2, V>>;
220
+ export function pickSome<const K1 extends string, V, const K2 extends string>(
221
+ record: Record<K1, Option.Option<V>>,
222
+ key: K1,
223
+ renameKeyTo?: K2,
224
+ ): Partial<Record<K1 | K2, V>> {
225
+ return pipe(
226
+ Record.get(record, key),
227
+ Option.flatten,
228
+ someSingleton(renameKeyTo ?? key),
229
+ );
230
+ }
231
+
232
+ /**
233
+ * Builds a singleton record `{ [name]: value }` when `value` is truthy, or an
234
+ * empty object `{}` otherwise — meant to be spread into an object literal.
235
+ *
236
+ * Stricter than {@link defined}: it drops not just `undefined` but every falsy
237
+ * value (`null`, `false`, `0`, `""`, `NaN`), and narrows the value type
238
+ * accordingly. Use it to spread a field only when it carries a meaningful,
239
+ * non-falsy value.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { StructX } from "@nunofyobiz/effect-extras"
244
+ *
245
+ * // Truthy value → singleton record
246
+ * assert.deepStrictEqual(StructX.truthy("name", "Ada"), { name: "Ada" })
247
+ *
248
+ * // Falsy values → property omitted entirely
249
+ * assert.deepStrictEqual(StructX.truthy("name", ""), {})
250
+ * assert.deepStrictEqual(StructX.truthy("name", 0), {})
251
+ * assert.deepStrictEqual(StructX.truthy("name", false), {})
252
+ * ```
253
+ *
254
+ * @category constructors
255
+ * @since 0.0.0
256
+ */
257
+ export const truthy = <const K extends string, V>(
258
+ name: K,
259
+ value: V,
260
+ ): Partial<Record<K, Exclude<NonNullable<V>, false | 0 | "">>> =>
261
+ Predicate.isTruthy(value)
262
+ ? Record.singleton(name, value as Exclude<NonNullable<V>, false | 0 | "">)
263
+ : {};
264
+
265
+ /**
266
+ * Returns `true` when `object` has `key` set to a non-nullish value, narrowing
267
+ * that property to `NonNullable` on the type level.
268
+ *
269
+ * A type guard combining `hasProperty` with a nullish check: after a successful
270
+ * test, TypeScript treats `object[key]` as present and free of `null` /
271
+ * `undefined`. Handy as a predicate passed to `Array.filter` to keep only the
272
+ * elements whose `key` is populated.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * import { pipe } from "effect"
277
+ * import { StructX } from "@nunofyobiz/effect-extras"
278
+ *
279
+ * // data-first
280
+ * assert.deepStrictEqual(
281
+ * StructX.hasNotNullableProperty({ id: "1" }, "id"),
282
+ * true,
283
+ * )
284
+ *
285
+ * // data-last (pipeable); a null value fails the guard
286
+ * assert.deepStrictEqual(
287
+ * pipe({ id: null }, StructX.hasNotNullableProperty("id")),
288
+ * false,
289
+ * )
290
+ * ```
291
+ *
292
+ * @category refinements
293
+ * @since 0.0.0
294
+ */
295
+ export const hasNotNullableProperty = dual<
296
+ <T, K extends keyof T>(
297
+ key: K,
298
+ ) => (object: T) => object is T & Record<K, NonNullable<T[K]>>,
299
+ <T, K extends keyof T>(
300
+ object: T,
301
+ key: K,
302
+ ) => object is T & Record<K, NonNullable<T[K]>>
303
+ >(
304
+ 2,
305
+ <T, K extends keyof T>(
306
+ object: T,
307
+ key: K,
308
+ ): object is T & Record<K, NonNullable<T[K]>> =>
309
+ Predicate.hasProperty(object, key) && Predicate.isNotNullish(object[key]),
310
+ );
@@ -0,0 +1 @@
1
+ export * as StructX from "./StructX";