@typed/guard 1.0.0-beta.4 → 1.0.0-beta.6
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 +42 -22
- package/dist/index.d.ts +519 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +408 -19
- package/examples/basic.ts +23 -0
- package/package.json +27 -14
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -314
- package/src/index.test.ts +0 -366
- package/src/index.ts +0 -656
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @typed/guard
|
|
2
2
|
|
|
3
|
-
> **Beta:** This package is in beta; APIs may change.
|
|
3
|
+
> **Beta:** This package is in beta; APIs may change between beta releases. Review the
|
|
4
|
+
> [repository releases](https://github.com/TylorS/typed-smol/releases) before upgrading.
|
|
4
5
|
|
|
5
6
|
`@typed/guard` provides **Effect-based guards**: functions that take an input and produce `Effect<Option<O>, E, R>`. Guards can be composed with `pipe`, `map`, `filter`, `bind`, and integrated with Effect Schema (`fromSchemaDecode` / `fromSchemaEncode`, `decode` / `encode`). Use them for validation, parsing, and route matching when you want a composable “maybe this input becomes this output” with effects.
|
|
6
7
|
|
|
@@ -8,6 +9,16 @@
|
|
|
8
9
|
|
|
9
10
|
- `effect`
|
|
10
11
|
|
|
12
|
+
## Outcome model
|
|
13
|
+
|
|
14
|
+
A guard returns `Effect.Effect<Option.Option<O>, E, R>`. Its three outcomes are distinct:
|
|
15
|
+
|
|
16
|
+
- `Some(output)` means the input matched and produced `output`.
|
|
17
|
+
- `None` means the input did not match. It is a successful Effect, so recovery combinators do not run.
|
|
18
|
+
- An Effect failure means evaluation failed in the typed `E` channel. Defects and interruption remain in the Effect cause.
|
|
19
|
+
|
|
20
|
+
Guard combinators preserve this distinction. Sequential composition stops on `None`, propagates failures, and runs the next guard only for `Some`.
|
|
21
|
+
|
|
11
22
|
## API overview
|
|
12
23
|
|
|
13
24
|
- **Type:** `Guard<I, O, E, R>` — `(input: I) => Effect.Effect<Option.Option<O>, E, R>`; `GuardInput` = Guard | AsGuard.
|
|
@@ -29,7 +40,9 @@
|
|
|
29
40
|
|
|
30
41
|
### Core
|
|
31
42
|
|
|
32
|
-
- **`getGuard(guard)`** — Normalizes a `GuardInput` to a `Guard` (
|
|
43
|
+
- **`getGuard(guard)`** — Normalizes a `GuardInput` to a `Guard`. Functions are always used directly. An `AsGuard` object must have an own callable `asGuard` property, and `asGuard()` must return a function; invalid adapters throw `TypeError` during normalization.
|
|
44
|
+
|
|
45
|
+
Class adapters must define `asGuard` as an own arrow field, such as `readonly asGuard = () => guard`. A prototype method is not a runtime adapter; use an own arrow field or a plain `{ asGuard: () => guard }` wrapper.
|
|
33
46
|
|
|
34
47
|
### Composition
|
|
35
48
|
|
|
@@ -44,6 +57,8 @@
|
|
|
44
57
|
|
|
45
58
|
- **`liftPredicate(predicate)`** — Builds a guard from a predicate. With a refinement `(a: A) => a is B`, output is narrowed to `B`; otherwise `Guard<A, A>`.
|
|
46
59
|
|
|
60
|
+
The predicate is deferred until the returned Effect runs. If it throws, the exception is an Effect defect rather than a typed error. Use an effectful `Guard` when failure belongs in the `E` channel.
|
|
61
|
+
|
|
47
62
|
```ts
|
|
48
63
|
liftPredicate<A, B extends A>(predicate: (a: A) => a is B): Guard<A, B>;
|
|
49
64
|
liftPredicate<A>(predicate: (a: A) => boolean): Guard<A, A>;
|
|
@@ -51,6 +66,8 @@ liftPredicate<A>(predicate: (a: A) => boolean): Guard<A, A>;
|
|
|
51
66
|
|
|
52
67
|
- **`any(guards)`** — Takes an object of named guards and returns a guard whose input is the intersection of all guard inputs and whose output is the tagged union `{ _tag: key; value: output }`. Tries each guard in order and returns the first match.
|
|
53
68
|
|
|
69
|
+
`any` snapshots own enumerable string and symbol keys when it is called. Inherited and non-enumerable properties are ignored. ECMAScript own-key order applies: integer-index strings in ascending order, other strings in insertion order, then symbols in insertion order. Candidate Effects run sequentially and evaluation stops after the first `Some`.
|
|
70
|
+
|
|
54
71
|
### Schema
|
|
55
72
|
|
|
56
73
|
- **`fromSchemaDecode(schema)`** — Builds a guard from an Effect Schema: input is the schema’s encoded type, output is the schema’s type. Uses `Schema.decodeEffect`.
|
|
@@ -69,34 +86,37 @@ liftPredicate<A>(predicate: (a: A) => boolean): Guard<A, A>;
|
|
|
69
86
|
|
|
70
87
|
### Struct helpers
|
|
71
88
|
|
|
72
|
-
- **`addTag(guard, value)`** — Adds a readonly `_tag` property to
|
|
73
|
-
- **`bindTo(guard, key)`** — Wraps
|
|
74
|
-
- **`bind(guard, key, f)`** — Runs a second guard on the first’s output and
|
|
75
|
-
- **`let(guard, key, value)`** — Adds a fixed property to
|
|
89
|
+
- **`addTag(guard, value)`** — Adds a readonly `_tag` property to an object output that does not already have one. Dual.
|
|
90
|
+
- **`bindTo(guard, key)`** — Wraps any guard output in an object under the given key: `{ [key]: O }`. Dual. Use this to enter the record-building workflow from a primitive, array, or class instance.
|
|
91
|
+
- **`bind(guard, key, f)`** — Runs a second guard on the first’s object output and adds the result under a new `key`. Dual.
|
|
92
|
+
- **`let(guard, key, value)`** — Adds a fixed property under a new key to an object output. Dual.
|
|
93
|
+
|
|
94
|
+
`let`, `addTag`, and `bind` require object outputs and reject statically known key collisions. They use object spread and produce a new plain object. They copy own enumerable string and symbol properties; they do not preserve prototypes, inherited properties, or non-enumerable properties. Enumerable getters and proxy traps may run during the copy. Use `bindTo` to enter this record-building workflow from a primitive output.
|
|
76
95
|
|
|
77
96
|
## Example
|
|
78
97
|
|
|
98
|
+
The [basic example](./examples/basic.ts) is runnable and checked by `test:types`.
|
|
99
|
+
|
|
79
100
|
```ts
|
|
80
101
|
import { Effect, Option } from "effect";
|
|
81
102
|
import * as Guard from "@typed/guard";
|
|
82
103
|
import * as Schema from "effect/Schema";
|
|
83
104
|
|
|
84
|
-
const Positive = Schema.
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// result === "ok: 42"
|
|
105
|
+
const Positive = Schema.Finite.check(Schema.isGreaterThan(0));
|
|
106
|
+
const positive = Guard.fromSchemaDecode(Positive);
|
|
107
|
+
|
|
108
|
+
const program = positive(42).pipe(
|
|
109
|
+
Effect.map(
|
|
110
|
+
Option.match({
|
|
111
|
+
onNone: () => "not a positive number",
|
|
112
|
+
onSome: (n) => `ok: ${n}`,
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const result = await Effect.runPromise(program);
|
|
118
|
+
console.log(result); // "ok: 42"
|
|
99
119
|
|
|
100
120
|
const even = Guard.liftPredicate((n: number) => n % 2 === 0);
|
|
101
|
-
const positiveEven = Guard.pipe(
|
|
121
|
+
const positiveEven = Guard.pipe(positive, even);
|
|
102
122
|
```
|