@typed/guard 1.0.0-beta.4 → 1.0.0-beta.5

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
@@ -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` (unwraps `AsGuard`).
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 the guard’s output. Dual.
73
- - **`bindTo(guard, key)`** — Wraps the guard’s output in an object under the given key: `{ [key]: O }`. Dual.
74
- - **`bind(guard, key, f)`** — Runs a second guard on the first’s output and merges the result under `key` into the output object. Dual.
75
- - **`let(guard, key, value)`** — Adds a fixed property to the guard’s output. Dual.
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.Number.pipe(Schema.positive());
85
- const guardDecode = Guard.fromSchemaDecode(Positive);
86
-
87
- // Inside Effect.gen(function* () { ... })
88
- const result =
89
- yield *
90
- guardDecode(42).pipe(
91
- Effect.map(
92
- Option.match({
93
- onNone: () => "invalid",
94
- onSome: (n) => `ok: ${n}`,
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(guardDecode, even);
121
+ const positiveEven = Guard.pipe(positive, even);
102
122
  ```