@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 +10 -4
- package/dist/SchemaX.d.ts +52 -0
- package/dist/SchemaX.d.ts.map +1 -1
- package/dist/SchemaX.js +54 -0
- package/dist/SchemaX.js.map +1 -1
- package/package.json +1 -1
- package/src/SchemaX.ts +57 -0
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
|
|
78
|
-
|
|
79
|
-
`
|
|
80
|
-
|
|
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.
|
package/dist/SchemaX.d.ts.map
CHANGED
|
@@ -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)),
|
package/dist/SchemaX.js.map
CHANGED
|
@@ -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,
|
|
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":"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","ignoreList":[]}
|
package/package.json
CHANGED
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) =>
|