@nunofyobiz/effect-extras 3.1.0 → 3.2.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/README.md CHANGED
@@ -18,6 +18,10 @@ are pure, generic, and carry **no** domain or framework knowledge — that is th
18
18
  whole point, and the bar every addition has to clear (see
19
19
  [What belongs here](#what-belongs-here)).
20
20
 
21
+ > **Requires [Effect v4](https://github.com/Effect-TS/effect-smol)** (peer
22
+ > `effect@^4.0.0-beta.*`). The published `effect.website` docs still describe v3,
23
+ > which differs (e.g. `Result` replaced `Either`).
24
+
21
25
  ```ts
22
26
  import {
23
27
  ArrayX,
@@ -74,10 +78,12 @@ package is scope creep, so the bar for adding something is deliberately high.
74
78
  A utility belongs here only if **all** of these hold:
75
79
 
76
80
  1. **It is not already in Effect.** If `effect` (or an `@effect/*` package)
77
- already does it, use that directly. Check the [Effect docs](https://effect.website)
78
- first the built-in modules (`Array`, `Option`, `Record`, `Predicate`,
79
- `String`, `Number`, `Order`, `Result`, `Match`, `Struct`, …) are wide and
80
- well-tested, and most "manipulate this shape" needs already exist there.
81
+ already does it, use that directly. Check the built-in modules first (`Array`,
82
+ `Option`, `Record`, `Predicate`, `String`, `Number`, `Order`, `Result`,
83
+ `Match`, `Struct`, …) they're wide and well-tested, and most "manipulate this
84
+ shape" needs already exist there. This package targets **Effect v4** (beta);
85
+ the [Effect docs](https://effect.website) are **v3**, so verify any v4 signature
86
+ against the installed `node_modules/effect` types.
81
87
  2. **It is generic and pure.** It operates on type parameters (`<A>`), has no
82
88
  side effects, no mutations, and would make sense in a project that shares
83
89
  nothing with yours.
package/dist/SchemaX.d.ts CHANGED
@@ -59,6 +59,58 @@ export declare const TrimmedNonEmptyString: Schema.decodeTo<Schema.toType<Schema
59
59
  * @since 0.0.0
60
60
  */
61
61
  export declare const URLSafeFilePath: Schema.decodeTo<Schema.toType<Schema.decodeTo<Schema.toType<Schema.NonEmptyString>, Schema.NonEmptyString, never, never>>, Schema.decodeTo<Schema.toType<Schema.NonEmptyString>, Schema.NonEmptyString, never, never>, never, never>;
62
+ /**
63
+ * A `Schema` that decodes a numeric string into a JS `number`, **failing loudly**
64
+ * when the value isn't a safe integer instead of silently rounding it.
65
+ *
66
+ * `Schema.NumberFromString` has no range guard, so a string past
67
+ * `Number.MAX_SAFE_INTEGER` — e.g. a Postgres `int8` column, which node-postgres
68
+ * returns as a string — decodes to a silently-rounded number
69
+ * (`Number("9223372036854775807")` is `9223372036854776000`). This codec adds
70
+ * `Schema.isInt` (which is `Number.isSafeInteger`), so any value that can't be
71
+ * represented exactly fails decoding rather than corrupting data. On encode a
72
+ * `number` is written back as a decimal string.
73
+ *
74
+ * Being an integer guard, it also rejects fractional strings (`"3.14"`). Note
75
+ * the failure names the *already-parsed* number (the rounded value), since the
76
+ * check runs after `NumberFromString` has produced the JS `number` — by which
77
+ * point the original digits are gone. The loud failure still flags the offending
78
+ * input.
79
+ *
80
+ * The parsing step is `NumberFromString`'s, so its lenient `Number(...)` coercion
81
+ * carries through: blank input decodes to zero (`""` and `" "` → `0`), and
82
+ * exponential / hex notation is accepted (`"1e3"` → `1000`, `"0x10"` → `16`).
83
+ * The guard protects against unsafe-integer *overflow*, not against
84
+ * non-canonical numeric strings — a caller that needs strict input (reject blanks
85
+ * or only accept plain decimal digits) should compose an upstream check.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { Effect, Result, Schema } from "effect"
90
+ * import { SchemaX } from "@nunofyobiz/effect-extras"
91
+ *
92
+ * // A safe integer round-trips
93
+ * const decoded = Effect.runSync(
94
+ * Schema.decodeEffect(SchemaX.IntFromString)("42"),
95
+ * )
96
+ * assert.deepStrictEqual(decoded, 42)
97
+ *
98
+ * const encoded = Effect.runSync(
99
+ * Schema.encodeEffect(SchemaX.IntFromString)(42),
100
+ * )
101
+ * assert.deepStrictEqual(encoded, "42")
102
+ *
103
+ * // A value past Number.MAX_SAFE_INTEGER fails instead of silently rounding
104
+ * const result = Effect.runSync(
105
+ * Effect.result(Schema.decodeEffect(SchemaX.IntFromString)("9223372036854775807")),
106
+ * )
107
+ * assert.deepStrictEqual(Result.isFailure(result), true)
108
+ * ```
109
+ *
110
+ * @category constructors
111
+ * @since 0.0.0
112
+ */
113
+ export declare const IntFromString: Schema.NumberFromString;
62
114
  /**
63
115
  * Transforms a `bigint` `Schema` so its value is clamped to be non-negative,
64
116
  * mapping any value below `0n` up to `0n` on both decode and encode.
@@ -1 +1 @@
1
- {"version":3,"file":"SchemaX.d.ts","sourceRoot":"","sources":["../src/SchemaX.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAU,MAAM,EAAwB,MAAM,QAAQ,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,qBAAqB,4FAKjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,sOAK3B,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,iBAAiB,GAxC3B,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,uDAwCM,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,IAChE,CAAC,CAAC,eAAe,CAAC,CAAC;AAQrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAOxC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAOxC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAC5B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAE,CAOhE,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GACtB,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAE,CACpC,CAAC"}
1
+ {"version":3,"file":"SchemaX.d.ts","sourceRoot":"","sources":["../src/SchemaX.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAU,MAAM,EAAwB,MAAM,QAAQ,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,qBAAqB,4FAKjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,sOAK3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,aAAa,yBAIzB,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,iBAAiB,GAxC3B,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,uDAwCM,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,IAChE,CAAC,CAAC,eAAe,CAAC,CAAC;AAQrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAOxC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAOxC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAC5B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAE,CAOhE,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GACtB,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EACzC,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAErD,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,GAAG,MAAM,IAAI,KACZ,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAE,CACpC,CAAC"}
package/dist/SchemaX.js CHANGED
@@ -65,6 +65,60 @@ export const URLSafeFilePath = /*#__PURE__*/TrimmedNonEmptyString.pipe(/*#__PURE
65
65
  decode: /*#__PURE__*/SchemaGetter.transform(path => decodeURIComponent(path)),
66
66
  encode: /*#__PURE__*/SchemaGetter.transform(path => encodeURIComponent(path))
67
67
  }));
68
+ /**
69
+ * A `Schema` that decodes a numeric string into a JS `number`, **failing loudly**
70
+ * when the value isn't a safe integer instead of silently rounding it.
71
+ *
72
+ * `Schema.NumberFromString` has no range guard, so a string past
73
+ * `Number.MAX_SAFE_INTEGER` — e.g. a Postgres `int8` column, which node-postgres
74
+ * returns as a string — decodes to a silently-rounded number
75
+ * (`Number("9223372036854775807")` is `9223372036854776000`). This codec adds
76
+ * `Schema.isInt` (which is `Number.isSafeInteger`), so any value that can't be
77
+ * represented exactly fails decoding rather than corrupting data. On encode a
78
+ * `number` is written back as a decimal string.
79
+ *
80
+ * Being an integer guard, it also rejects fractional strings (`"3.14"`). Note
81
+ * the failure names the *already-parsed* number (the rounded value), since the
82
+ * check runs after `NumberFromString` has produced the JS `number` — by which
83
+ * point the original digits are gone. The loud failure still flags the offending
84
+ * input.
85
+ *
86
+ * The parsing step is `NumberFromString`'s, so its lenient `Number(...)` coercion
87
+ * carries through: blank input decodes to zero (`""` and `" "` → `0`), and
88
+ * exponential / hex notation is accepted (`"1e3"` → `1000`, `"0x10"` → `16`).
89
+ * The guard protects against unsafe-integer *overflow*, not against
90
+ * non-canonical numeric strings — a caller that needs strict input (reject blanks
91
+ * or only accept plain decimal digits) should compose an upstream check.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { Effect, Result, Schema } from "effect"
96
+ * import { SchemaX } from "@nunofyobiz/effect-extras"
97
+ *
98
+ * // A safe integer round-trips
99
+ * const decoded = Effect.runSync(
100
+ * Schema.decodeEffect(SchemaX.IntFromString)("42"),
101
+ * )
102
+ * assert.deepStrictEqual(decoded, 42)
103
+ *
104
+ * const encoded = Effect.runSync(
105
+ * Schema.encodeEffect(SchemaX.IntFromString)(42),
106
+ * )
107
+ * assert.deepStrictEqual(encoded, "42")
108
+ *
109
+ * // A value past Number.MAX_SAFE_INTEGER fails instead of silently rounding
110
+ * const result = Effect.runSync(
111
+ * Effect.result(Schema.decodeEffect(SchemaX.IntFromString)("9223372036854775807")),
112
+ * )
113
+ * assert.deepStrictEqual(Result.isFailure(result), true)
114
+ * ```
115
+ *
116
+ * @category constructors
117
+ * @since 0.0.0
118
+ */
119
+ export const IntFromString = /*#__PURE__*/Schema.NumberFromString.check(/*#__PURE__*/Schema.isInt({
120
+ expected: "a safe integer (no overflow past Number.MAX_SAFE_INTEGER)"
121
+ }));
68
122
  // Internal — only used to construct nonNegativeBigInt below.
69
123
  const clampMinBigInt = min => schema => schema.pipe(Schema.decode({
70
124
  decode: SchemaGetter.transform(value => BigInt.max(value, min)),
@@ -1 +1 @@
1
- {"version":3,"file":"SchemaX.js","names":["BigInt","Schema","SchemaGetter","Struct","TrimmedNonEmptyString","NonEmptyString","pipe","decode","transform","s","trim","encode","URLSafeFilePath","path","decodeURIComponent","encodeURIComponent","clampMinBigInt","min","schema","value","max","nonNegativeBigInt","pick","keys","mapFields","fields","omit","partial","result","key","Object","optional","pickPartial"],"sources":["../src/SchemaX.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;AAKA,SAASA,MAAM,EAAEC,MAAM,EAAEC,YAAY,EAAEC,MAAM,QAAQ,QAAQ;AAE7D;;;;;;;;;;;;;;;;;;;;;;AAsBA,OAAO,MAAMC,qBAAqB,gBAAGH,MAAM,CAACI,cAAc,CAACC,IAAI,cAC7DL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,eAAEL,YAAY,CAACM,SAAS,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,EAAE,CAAC;EAC/CC,MAAM,eAAET,YAAY,CAACM,SAAS,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,EAAE;CAC/C,CAAC,CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,OAAO,MAAME,eAAe,gBAAGR,qBAAqB,CAACE,IAAI,cACvDL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,eAAEL,YAAY,CAACM,SAAS,CAAEK,IAAI,IAAKC,kBAAkB,CAACD,IAAI,CAAC,CAAC;EAClEF,MAAM,eAAET,YAAY,CAACM,SAAS,CAAEK,IAAI,IAAKE,kBAAkB,CAACF,IAAI,CAAC;CAClE,CAAC,CACH;AAED;AACA,MAAMG,cAAc,GACjBC,GAAW,IACsBC,MAAS,IACzCA,MAAM,CAACZ,IAAI,CACTL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,EAAEL,YAAY,CAACM,SAAS,CAAEW,KAAK,IAAKnB,MAAM,CAACoB,GAAG,CAACD,KAAK,EAAEF,GAAG,CAAC,CAAC;EACjEN,MAAM,EAAET,YAAY,CAACM,SAAS,CAAEW,KAAK,IAAKnB,MAAM,CAACoB,GAAG,CAACD,KAAK,EAAEF,GAAG,CAAC;CACjE,CAAC,CACH;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,OAAO,MAAMI,iBAAiB,gBAAGL,cAAc,CAAC,EAAE,CAAC;AA8BnD;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,OAAO,MAAMM,IAAI,GAAGA,CAIlBJ,MAA6B,EAC7B,GAAGK,IAAU,KAEbL,MAAM,CAACM,SAAS,CACbC,MAAM,IACLtB,MAAM,CAACmB,IAAI,CAACG,MAAM,EAAEF,IAAiC,CAGpD,CACJ;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,OAAO,MAAMG,IAAI,GAAGA,CAIlBR,MAA6B,EAC7B,GAAGK,IAAU,KAEbL,MAAM,CAACM,SAAS,CACbC,MAAM,IACLtB,MAAM,CAACuB,IAAI,CAACD,MAAM,EAAEF,IAAiC,CAGpD,CACJ;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,OAAO,MAAMI,OAAO,GAClBT,MAA6B,IAE7BA,MAAM,CAACM,SAAS,CAAEC,MAAM,IAAI;EAC1B,MAAMG,MAAM,GAAyD,EAAE;EACvE,KAAK,MAAMC,GAAG,IAAIC,MAAM,CAACP,IAAI,CAACE,MAAM,CAAqB,EAAE;IACzDG,MAAM,CAACC,GAAG,CAAC,GAAG5B,MAAM,CAAC8B,QAAQ,CAACN,MAAM,CAACI,GAAG,CAAC,CAAC;EAC5C;EACA,OAAOD,MAA6D;AACtE,CAAC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,OAAO,MAAMI,WAAW,GAAGA,CAIzBd,MAA6B,EAC7B,GAAGK,IAAU,KAEbI,OAAO,CAACL,IAAI,CAACJ,MAAM,EAAE,GAAGK,IAAI,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"SchemaX.js","names":["BigInt","Schema","SchemaGetter","Struct","TrimmedNonEmptyString","NonEmptyString","pipe","decode","transform","s","trim","encode","URLSafeFilePath","path","decodeURIComponent","encodeURIComponent","IntFromString","NumberFromString","check","isInt","expected","clampMinBigInt","min","schema","value","max","nonNegativeBigInt","pick","keys","mapFields","fields","omit","partial","result","key","Object","optional","pickPartial"],"sources":["../src/SchemaX.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;AAKA,SAASA,MAAM,EAAEC,MAAM,EAAEC,YAAY,EAAEC,MAAM,QAAQ,QAAQ;AAE7D;;;;;;;;;;;;;;;;;;;;;;AAsBA,OAAO,MAAMC,qBAAqB,gBAAGH,MAAM,CAACI,cAAc,CAACC,IAAI,cAC7DL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,eAAEL,YAAY,CAACM,SAAS,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,EAAE,CAAC;EAC/CC,MAAM,eAAET,YAAY,CAACM,SAAS,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,EAAE;CAC/C,CAAC,CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,OAAO,MAAME,eAAe,gBAAGR,qBAAqB,CAACE,IAAI,cACvDL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,eAAEL,YAAY,CAACM,SAAS,CAAEK,IAAI,IAAKC,kBAAkB,CAACD,IAAI,CAAC,CAAC;EAClEF,MAAM,eAAET,YAAY,CAACM,SAAS,CAAEK,IAAI,IAAKE,kBAAkB,CAACF,IAAI,CAAC;CAClE,CAAC,CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,OAAO,MAAMG,aAAa,gBAAGf,MAAM,CAACgB,gBAAgB,CAACC,KAAK,cACxDjB,MAAM,CAACkB,KAAK,CAAC;EACXC,QAAQ,EAAE;CACX,CAAC,CACH;AAED;AACA,MAAMC,cAAc,GACjBC,GAAW,IACsBC,MAAS,IACzCA,MAAM,CAACjB,IAAI,CACTL,MAAM,CAACM,MAAM,CAAC;EACZA,MAAM,EAAEL,YAAY,CAACM,SAAS,CAAEgB,KAAK,IAAKxB,MAAM,CAACyB,GAAG,CAACD,KAAK,EAAEF,GAAG,CAAC,CAAC;EACjEX,MAAM,EAAET,YAAY,CAACM,SAAS,CAAEgB,KAAK,IAAKxB,MAAM,CAACyB,GAAG,CAACD,KAAK,EAAEF,GAAG,CAAC;CACjE,CAAC,CACH;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,OAAO,MAAMI,iBAAiB,gBAAGL,cAAc,CAAC,EAAE,CAAC;AA8BnD;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,OAAO,MAAMM,IAAI,GAAGA,CAIlBJ,MAA6B,EAC7B,GAAGK,IAAU,KAEbL,MAAM,CAACM,SAAS,CACbC,MAAM,IACL3B,MAAM,CAACwB,IAAI,CAACG,MAAM,EAAEF,IAAiC,CAGpD,CACJ;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,OAAO,MAAMG,IAAI,GAAGA,CAIlBR,MAA6B,EAC7B,GAAGK,IAAU,KAEbL,MAAM,CAACM,SAAS,CACbC,MAAM,IACL3B,MAAM,CAAC4B,IAAI,CAACD,MAAM,EAAEF,IAAiC,CAGpD,CACJ;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,OAAO,MAAMI,OAAO,GAClBT,MAA6B,IAE7BA,MAAM,CAACM,SAAS,CAAEC,MAAM,IAAI;EAC1B,MAAMG,MAAM,GAAyD,EAAE;EACvE,KAAK,MAAMC,GAAG,IAAIC,MAAM,CAACP,IAAI,CAACE,MAAM,CAAqB,EAAE;IACzDG,MAAM,CAACC,GAAG,CAAC,GAAGjC,MAAM,CAACmC,QAAQ,CAACN,MAAM,CAACI,GAAG,CAAC,CAAC;EAC5C;EACA,OAAOD,MAA6D;AACtE,CAAC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,OAAO,MAAMI,WAAW,GAAGA,CAIzBd,MAA6B,EAC7B,GAAGK,IAAU,KAEbI,OAAO,CAACL,IAAI,CAACJ,MAAM,EAAE,GAAGK,IAAI,CAAC,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nunofyobiz/effect-extras",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Generic, framework-agnostic extensions of the Effect standard library (ArrayX, OptionX, RecordX, StructX, …).",
5
5
  "keywords": [
6
6
  "effect",
package/src/SchemaX.ts CHANGED
@@ -72,6 +72,63 @@ export const URLSafeFilePath = TrimmedNonEmptyString.pipe(
72
72
  }),
73
73
  );
74
74
 
75
+ /**
76
+ * A `Schema` that decodes a numeric string into a JS `number`, **failing loudly**
77
+ * when the value isn't a safe integer instead of silently rounding it.
78
+ *
79
+ * `Schema.NumberFromString` has no range guard, so a string past
80
+ * `Number.MAX_SAFE_INTEGER` — e.g. a Postgres `int8` column, which node-postgres
81
+ * returns as a string — decodes to a silently-rounded number
82
+ * (`Number("9223372036854775807")` is `9223372036854776000`). This codec adds
83
+ * `Schema.isInt` (which is `Number.isSafeInteger`), so any value that can't be
84
+ * represented exactly fails decoding rather than corrupting data. On encode a
85
+ * `number` is written back as a decimal string.
86
+ *
87
+ * Being an integer guard, it also rejects fractional strings (`"3.14"`). Note
88
+ * the failure names the *already-parsed* number (the rounded value), since the
89
+ * check runs after `NumberFromString` has produced the JS `number` — by which
90
+ * point the original digits are gone. The loud failure still flags the offending
91
+ * input.
92
+ *
93
+ * The parsing step is `NumberFromString`'s, so its lenient `Number(...)` coercion
94
+ * carries through: blank input decodes to zero (`""` and `" "` → `0`), and
95
+ * exponential / hex notation is accepted (`"1e3"` → `1000`, `"0x10"` → `16`).
96
+ * The guard protects against unsafe-integer *overflow*, not against
97
+ * non-canonical numeric strings — a caller that needs strict input (reject blanks
98
+ * or only accept plain decimal digits) should compose an upstream check.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { Effect, Result, Schema } from "effect"
103
+ * import { SchemaX } from "@nunofyobiz/effect-extras"
104
+ *
105
+ * // A safe integer round-trips
106
+ * const decoded = Effect.runSync(
107
+ * Schema.decodeEffect(SchemaX.IntFromString)("42"),
108
+ * )
109
+ * assert.deepStrictEqual(decoded, 42)
110
+ *
111
+ * const encoded = Effect.runSync(
112
+ * Schema.encodeEffect(SchemaX.IntFromString)(42),
113
+ * )
114
+ * assert.deepStrictEqual(encoded, "42")
115
+ *
116
+ * // A value past Number.MAX_SAFE_INTEGER fails instead of silently rounding
117
+ * const result = Effect.runSync(
118
+ * Effect.result(Schema.decodeEffect(SchemaX.IntFromString)("9223372036854775807")),
119
+ * )
120
+ * assert.deepStrictEqual(Result.isFailure(result), true)
121
+ * ```
122
+ *
123
+ * @category constructors
124
+ * @since 0.0.0
125
+ */
126
+ export const IntFromString = Schema.NumberFromString.check(
127
+ Schema.isInt({
128
+ expected: "a safe integer (no overflow past Number.MAX_SAFE_INTEGER)",
129
+ }),
130
+ );
131
+
75
132
  // Internal — only used to construct nonNegativeBigInt below.
76
133
  const clampMinBigInt =
77
134
  (min: bigint) =>