@systemfsoftware/effect-schema-law 0.7.0 → 0.9.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/CHANGELOG.md +26 -0
- package/README.md +13 -10
- package/dist/effect-schema-law-untrimmed.d.ts +144 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @systemfsoftware/effect-schema-law
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Declarations that were never meant to be hidden are public again.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- The peer requirements for `effect` and for the Effect test-runner integration now accept any compatible `4.0.0-rc` release, instead of demanding one exact release candidate.
|
|
12
|
+
|
|
13
|
+
Installing alongside a newer release candidate no longer reports an unmet peer dependency or resolves a second copy of `effect` into the dependency tree.
|
|
14
|
+
|
|
15
|
+
## 0.8.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- cut over to effect v4 (4.0.0-rc.108): public surface derives from effect types; peers flip effect ^3→^4
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Array types are spelled one way. `Array<T>` and `ReadonlyArray<T>` in emitted
|
|
24
|
+
declarations become `T[]` and `readonly T[]`, which the type checker cannot tell
|
|
25
|
+
apart: no exported type changes, only how it is written.
|
|
26
|
+
|
|
27
|
+
- New version is published through npm trusted publishing, so it carries a provenance attestation you can verify.
|
|
28
|
+
|
|
3
29
|
## 0.7.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -27,22 +27,25 @@ ruleOfSchemas('Email', Email)
|
|
|
27
27
|
import { boundedUnion, ruleOfSchemas } from '@systemfsoftware/effect-schema-law'
|
|
28
28
|
import { Schema as S } from 'effect'
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
// The tag is declared once, by the schema. A hand-written `readonly _tag: 'Lit'`
|
|
31
|
+
// member is refused by `no-manual-tag-member`, and deriving it keeps the tag and
|
|
32
|
+
// the schema from drifting apart.
|
|
33
|
+
const LitBase = S.TaggedStruct('Lit', { value: S.JsonNumber })
|
|
34
|
+
const AddBase = S.TaggedStruct('Add', {})
|
|
35
|
+
|
|
36
|
+
type Lit = S.Schema.Type<typeof LitBase>
|
|
37
|
+
type Add = S.Schema.Type<typeof AddBase> & {
|
|
36
38
|
readonly left: Expr
|
|
37
39
|
readonly right: Expr
|
|
38
40
|
}
|
|
39
41
|
type Expr = Lit | Add
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Only the self-referential fields stay hand-written. The tag never participates in
|
|
44
|
+
// the recursion, so the base carries it and `suspend` carries only the cycle.
|
|
45
|
+
const Add: S.Codec<Add> = S.suspend((): S.Codec<Add> => S.Struct({ ...AddBase.fields, left: Expr, right: Expr }))
|
|
43
46
|
|
|
44
|
-
const Expr: S.
|
|
45
|
-
base: [
|
|
47
|
+
const Expr: S.Codec<Expr> = boundedUnion('Expr', {
|
|
48
|
+
base: [LitBase],
|
|
46
49
|
recur: [Add],
|
|
47
50
|
})
|
|
48
51
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as AST from 'effect/SchemaAST';
|
|
2
|
+
import { FastCheck } from 'effect/testing';
|
|
3
|
+
import { Schema } from 'effect';
|
|
4
|
+
|
|
5
|
+
/** Verdict for one schema's obligation set, carrying the detail R7 requires in a failure. */
|
|
6
|
+
export declare interface AdequacyReport {
|
|
7
|
+
readonly adequate: boolean;
|
|
8
|
+
readonly undischarged: readonly Obligation[];
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Which obligations no declared generator discharges. A bare "adequacy failed" leaves
|
|
14
|
+
* the author nothing to act on, so the message names each node's tag, every path
|
|
15
|
+
* reaching it, and the witness that proves the weakening is permissive.
|
|
16
|
+
*/
|
|
17
|
+
export declare const adequacyReport: (schema: Schema.ConstraintDecoder<unknown>, generators: RefusalGenerators) => AdequacyReport;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One weakening of an Effect schema, produced by `armsOf`. Each arm identifies
|
|
21
|
+
* the AST node it removes; the rebuilt tree is the surrounding schema with
|
|
22
|
+
* that node replaced by its child or, for a dropped refinement, by the same
|
|
23
|
+
* node without that check.
|
|
24
|
+
*
|
|
25
|
+
* Two schemas reaching the same `node` share its identity and therefore its
|
|
26
|
+
* obligation key — the deduplication is the entire point of the shape.
|
|
27
|
+
*/
|
|
28
|
+
export declare interface Arm {
|
|
29
|
+
readonly kind: 'drop-refinement' | 'drop-to-arm' | 'drop-from-arm';
|
|
30
|
+
readonly path: string;
|
|
31
|
+
/** The node this arm removes — the obligation key. Two arms removing it are one obligation. */
|
|
32
|
+
readonly node: AST.AST;
|
|
33
|
+
readonly weakened: AST.AST;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Walk an Effect schema's AST and return every weakenable arm, recursively
|
|
38
|
+
* through `Objects`, `Union`, `Arrays`, `Declaration`, and `Suspend`, plus
|
|
39
|
+
* the v4 per-node `Checks` (refinements) and encoding `Link` chains
|
|
40
|
+
* (transformations). The walk terminates on `Suspend` cycles at `depthCap`
|
|
41
|
+
* levels; every other AST tag is a leaf whose children cannot hold a
|
|
42
|
+
* refinement, and `Union` over structurally identical members is fine because
|
|
43
|
+
* the walk keys arms by node identity, not shape.
|
|
44
|
+
*
|
|
45
|
+
* The arm's `node` is the AST node its weakening removes; reference identity
|
|
46
|
+
* is the obligation key (R3). `weakened` is the enclosing tree with `node`
|
|
47
|
+
* replaced by its child, ready to be passed to `Schema.make`.
|
|
48
|
+
*/
|
|
49
|
+
export declare const armsOf: (schema: Schema.ConstraintDecoder<unknown>) => readonly Arm[];
|
|
50
|
+
|
|
51
|
+
/** An arm no source could draw for: the search could not look, which is not the same as finding nothing. */
|
|
52
|
+
export declare interface BlindArm {
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly kind: string;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export declare const boundedUnion: <Base extends readonly [Schema.ConstraintCodec<unknown, unknown>, ...readonly Schema.ConstraintCodec<unknown, unknown>[]], Recur extends readonly [Schema.ConstraintCodec<unknown, unknown>, ...readonly Schema.ConstraintCodec<unknown, unknown>[]]>(identifier: string, options: {
|
|
59
|
+
readonly base: Base;
|
|
60
|
+
readonly recur: Recur;
|
|
61
|
+
readonly maxDepth?: number;
|
|
62
|
+
}) => Schema.Codec<Base[number]['Type'] | Recur[number]['Type'], Base[number]['Encoded'] | Recur[number]['Encoded']>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* For each obligation, return the names of the generators whose draws
|
|
66
|
+
* the weakened schema accepts AND the original rejects. Generators
|
|
67
|
+
* whose draws the original also accepts do not discharge — they are
|
|
68
|
+
* the language the original already covers. Generators whose draws
|
|
69
|
+
* the weakened schema rejects do not discharge either.
|
|
70
|
+
*
|
|
71
|
+
* The original schema is part of the signature because discharge is
|
|
72
|
+
* defined relative to it.
|
|
73
|
+
*/
|
|
74
|
+
export declare const dischargedBy: (schema: Schema.ConstraintDecoder<unknown>, obligations: ReadonlyMap<AST.AST, Obligation>, generators: Readonly<Record<string, NamedArbitrary>>) => ReadonlyMap<AST.AST, readonly string[]>;
|
|
75
|
+
|
|
76
|
+
/** True when `value` is a witness for some obligation: accepted by that weakening, rejected by the schema. */
|
|
77
|
+
export declare const discriminates: (schema: Schema.ConstraintDecoder<unknown>, obligations: ReadonlyMap<AST.AST, Obligation>, value: unknown) => boolean;
|
|
78
|
+
|
|
79
|
+
declare type NamedArbitrary = FastCheck.Arbitrary<unknown>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* One node-keyed refutation obligation: a witness exists that the
|
|
83
|
+
* weakened schema accepts and the original rejects. Obligations are
|
|
84
|
+
* keyed by the AST node the arm removes (R3); several paths reaching
|
|
85
|
+
* one node are one obligation carrying several paths.
|
|
86
|
+
*/
|
|
87
|
+
export declare interface Obligation {
|
|
88
|
+
readonly node: AST.AST;
|
|
89
|
+
readonly tag: string;
|
|
90
|
+
readonly paths: readonly string[];
|
|
91
|
+
readonly weakened: AST.AST;
|
|
92
|
+
readonly witness: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Every arm of a schema, split into the ones a witness proved and the ones nothing could see. */
|
|
96
|
+
export declare interface ObligationScan {
|
|
97
|
+
readonly obligations: ReadonlyMap<AST.AST, Obligation>;
|
|
98
|
+
readonly blind: readonly BlindArm[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export declare const obligationsOf: (schema: Schema.ConstraintDecoder<unknown>) => ReadonlyMap<AST.AST, Obligation>;
|
|
102
|
+
|
|
103
|
+
/** Named refusal generators: each draws rejection-class inputs the schema must reject. */
|
|
104
|
+
export declare type RefusalGenerators = Record<string, FastCheck.Arbitrary<unknown>>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Property-test one schema's rejection contract. Registers per generator a refusal
|
|
108
|
+
* property (the schema rejects every draw) and a discrimination property (each draw is
|
|
109
|
+
* explained by some weakening), plus one adequacy property for the schema.
|
|
110
|
+
*
|
|
111
|
+
* Names follow the house convention: `∀b_<generator>_⊥`, `∀g_<generator>_discriminates`,
|
|
112
|
+
* `∀s_<schema>_adequate` — disjoint from `ruleOfSchemas`' `∀x_<name>_=x` pair.
|
|
113
|
+
*/
|
|
114
|
+
export declare const refutes: (schema: Schema.ConstraintDecoder<unknown>, generators: RefusalGenerators) => void;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Property-test the round-trip and encode-stability laws of any Effect Schema.
|
|
118
|
+
*
|
|
119
|
+
* Registers two fast-check properties with `@effect/vitest`:
|
|
120
|
+
* 1. `∀x. enc(dec(enc(x))) === enc(x)` — encode stability across decode.
|
|
121
|
+
* 2. `∀x. dec(enc(x)) === x` — round-trip identity.
|
|
122
|
+
*
|
|
123
|
+
* Use inside a `describe` block to scope the generated tests.
|
|
124
|
+
*/
|
|
125
|
+
export declare const ruleOfSchemas: <A, I>(name: string, schema: Schema.Codec<A, I>) => void;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Walk `armsOf` and classify every arm. An arm is an obligation iff a witness
|
|
129
|
+
* exists — an input the weakened schema accepts and the original rejects —
|
|
130
|
+
* drawn from a fallback chain (encoded arbitrary, type arbitrary, generic
|
|
131
|
+
* pool). An arm whose every source failed to construct is `blind`: "no
|
|
132
|
+
* obligation" and "could not look" are different answers, and collapsing
|
|
133
|
+
* them is the silent miss this scan exists to prevent.
|
|
134
|
+
*/
|
|
135
|
+
export declare const scanObligations: (schema: Schema.ConstraintDecoder<unknown>) => ObligationScan;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The sampling budget is a property of the contract, not a knob. The
|
|
139
|
+
* plan pins the expectation against a literal table; the kernel draws
|
|
140
|
+
* up to this many candidates per source before exhausting the chain.
|
|
141
|
+
*/
|
|
142
|
+
export declare const WITNESS_BUDGET = 256;
|
|
143
|
+
|
|
144
|
+
export { }
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/effect-schema-law",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
9
|
-
"directory": "packages/effect
|
|
9
|
+
"directory": "packages/core/effect/schema/law"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/effect
|
|
11
|
+
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/core/effect/schema/law#readme",
|
|
12
12
|
"bugs": "https://github.com/systemfsoftware/systemfsoftware/issues",
|
|
13
13
|
"description": "Property-test the codec laws of any Effect Schema in one call — decode/encode round-trip identity and encode stability, generated with @effect/vitest and fast-check.",
|
|
14
14
|
"keywords": [
|
|
@@ -36,24 +36,24 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@effect/vitest": "4.0.0-rc.
|
|
39
|
+
"@effect/vitest": "4.0.0-rc.111",
|
|
40
40
|
"@microsoft/api-extractor": "^7.58.7",
|
|
41
41
|
"@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
|
|
42
42
|
"@types/node": "^24",
|
|
43
|
-
"effect": "4.0.0-rc.
|
|
43
|
+
"effect": "4.0.0-rc.111",
|
|
44
44
|
"fast-check": "^4",
|
|
45
45
|
"oxlint": "^1.77.0",
|
|
46
46
|
"rimraf": "^6.1.3",
|
|
47
47
|
"tsdown": "^0.22.14",
|
|
48
48
|
"vitest": "^4.1.10",
|
|
49
|
+
"@systemfsoftware/effect-gherkin-spec": "^2.0.1",
|
|
50
|
+
"@systemfsoftware/tsconfig": "^1.3.3",
|
|
49
51
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
50
|
-
"@systemfsoftware/tsconfig": "^1.3.2",
|
|
51
|
-
"@systemfsoftware/effect-gherkin-spec": "^1.0.0",
|
|
52
52
|
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@effect/vitest": "4.0.0-rc.
|
|
56
|
-
"effect": "4.0.0-rc.
|
|
55
|
+
"@effect/vitest": "^4.0.0-rc.111",
|
|
56
|
+
"effect": "^4.0.0-rc.111",
|
|
57
57
|
"vitest": "*"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|