@ghostry/fabricator 0.0.1 → 0.0.3
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 +18 -12
- package/dist/esm/Adapter/Core.js +3 -3
- package/dist/esm/Enumeration/Enumerate.js +16 -13
- package/dist/esm/Error/index.js +16 -8
- package/dist/esm/Fabricator/Constructor.js +12 -14
- package/dist/esm/Harnessing/Core.js +30 -0
- package/dist/esm/Harnessing/Salt.js +12 -0
- package/dist/esm/Harnessing/Types.js +1 -0
- package/dist/esm/Instance/Core.js +25 -41
- package/dist/esm/Instance/Stack/Async.js +10 -0
- package/dist/esm/Instance/Stack/Sync.js +16 -0
- package/dist/esm/Primitive/bigint/Registry.js +12 -12
- package/dist/esm/Primitive/boolean/Registry.js +2 -1
- package/dist/esm/Primitive/date/Registry.js +15 -14
- package/dist/esm/Primitive/null/Registry.js +2 -1
- package/dist/esm/Primitive/number/Registry.js +18 -17
- package/dist/esm/Primitive/recursive/Fabricator.js +1 -1
- package/dist/esm/Primitive/symbol/Registry.js +2 -1
- package/dist/esm/Primitive/undefined/Registry.js +2 -1
- package/dist/esm/Random/index.js +24 -79
- package/dist/esm/Utility/Core.js +6 -1
- package/dist/esm/adapting.js +2 -0
- package/dist/esm/harnessing.js +1 -0
- package/dist/esm/index.js +5 -4
- package/dist/esm/internal.js +2 -2
- package/dist/types/Adapter/Core.d.ts +30 -33
- package/dist/types/Adapter/Types.d.ts +78 -88
- package/dist/types/Bound.d.ts +15 -15
- package/dist/types/Distribution/index.d.ts +54 -61
- package/dist/types/Enumeration/Enumerate.d.ts +24 -24
- package/dist/types/Enumeration/Plan.d.ts +22 -26
- package/dist/types/Enumeration/Types.d.ts +38 -43
- package/dist/types/Error/index.d.ts +103 -89
- package/dist/types/Fabricator/Constructor.d.ts +24 -26
- package/dist/types/Fabricator/Types.d.ts +73 -81
- package/dist/types/Harnessing/Core.d.ts +43 -0
- package/dist/types/Harnessing/Salt.d.ts +31 -0
- package/dist/types/Harnessing/Types.d.ts +79 -0
- package/dist/types/Instance/Core.d.ts +38 -74
- package/dist/types/Instance/Stack/Async.d.ts +14 -0
- package/dist/types/Instance/Stack/Sync.d.ts +14 -0
- package/dist/types/Instance/Types.d.ts +98 -102
- package/dist/types/Primitive/always/Schema.d.ts +8 -8
- package/dist/types/Primitive/always/Types.d.ts +7 -7
- package/dist/types/Primitive/array/Registry.d.ts +10 -8
- package/dist/types/Primitive/array/Schema.d.ts +4 -5
- package/dist/types/Primitive/array/Types.d.ts +6 -6
- package/dist/types/Primitive/bigint/Registry.d.ts +6 -6
- package/dist/types/Primitive/bigint/Schema.d.ts +8 -8
- package/dist/types/Primitive/bigint/Types.d.ts +5 -5
- package/dist/types/Primitive/boolean/Outcomes.d.ts +8 -8
- package/dist/types/Primitive/boolean/Registry.d.ts +2 -12
- package/dist/types/Primitive/boolean/Schema.d.ts +8 -8
- package/dist/types/Primitive/boolean/Types.d.ts +3 -3
- package/dist/types/Primitive/choice/Fabricator.d.ts +6 -7
- package/dist/types/Primitive/choice/Registry.d.ts +13 -13
- package/dist/types/Primitive/choice/Schema.d.ts +6 -6
- package/dist/types/Primitive/choice/Types.d.ts +11 -11
- package/dist/types/Primitive/date/Registry.d.ts +25 -48
- package/dist/types/Primitive/date/Schema.d.ts +9 -10
- package/dist/types/Primitive/date/Types.d.ts +4 -4
- package/dist/types/Primitive/enum/Registry.d.ts +13 -13
- package/dist/types/Primitive/enum/Schema.d.ts +5 -5
- package/dist/types/Primitive/enum/Types.d.ts +17 -19
- package/dist/types/Primitive/namespace.d.ts +10 -10
- package/dist/types/Primitive/null/Registry.d.ts +2 -2
- package/dist/types/Primitive/null/Schema.d.ts +2 -2
- package/dist/types/Primitive/null/Types.d.ts +3 -3
- package/dist/types/Primitive/nullable/Fabricator.d.ts +7 -7
- package/dist/types/Primitive/nullable/Schema.d.ts +13 -13
- package/dist/types/Primitive/nullable/Types.d.ts +9 -10
- package/dist/types/Primitive/nullish/Fabricator.d.ts +12 -13
- package/dist/types/Primitive/nullish/Schema.d.ts +8 -8
- package/dist/types/Primitive/nullish/Types.d.ts +12 -13
- package/dist/types/Primitive/number/Registry.d.ts +20 -38
- package/dist/types/Primitive/number/Schema.d.ts +11 -12
- package/dist/types/Primitive/number/Types.d.ts +13 -13
- package/dist/types/Primitive/number/defaults.d.ts +3 -3
- package/dist/types/Primitive/object/Fabricator.d.ts +15 -15
- package/dist/types/Primitive/object/Registry.d.ts +8 -8
- package/dist/types/Primitive/object/Schema.d.ts +10 -10
- package/dist/types/Primitive/object/Types.d.ts +20 -22
- package/dist/types/Primitive/object/compute/Fabricator.d.ts +4 -5
- package/dist/types/Primitive/object/compute/Schema.d.ts +4 -4
- package/dist/types/Primitive/object/compute/Types.d.ts +19 -20
- package/dist/types/Primitive/object/omittable/Fabricator.d.ts +10 -10
- package/dist/types/Primitive/object/omittable/Outcomes.d.ts +2 -2
- package/dist/types/Primitive/object/omittable/Schema.d.ts +11 -11
- package/dist/types/Primitive/object/omittable/Types.d.ts +14 -15
- package/dist/types/Primitive/object/optional/Fabricator.d.ts +16 -18
- package/dist/types/Primitive/object/optional/Outcomes.d.ts +2 -2
- package/dist/types/Primitive/object/optional/Schema.d.ts +4 -4
- package/dist/types/Primitive/object/optional/Types.d.ts +17 -17
- package/dist/types/Primitive/opaque/Registry.d.ts +3 -3
- package/dist/types/Primitive/opaque/Schema.d.ts +8 -9
- package/dist/types/Primitive/record/Registry.d.ts +8 -6
- package/dist/types/Primitive/record/Schema.d.ts +9 -11
- package/dist/types/Primitive/record/Types.d.ts +25 -25
- package/dist/types/Primitive/recursive/Fabricator.d.ts +20 -21
- package/dist/types/Primitive/recursive/Registry.d.ts +6 -7
- package/dist/types/Primitive/recursive/Schema.d.ts +8 -8
- package/dist/types/Primitive/recursive/Terminate.d.ts +13 -14
- package/dist/types/Primitive/recursive/Types.d.ts +31 -31
- package/dist/types/Primitive/recursive/self/Fabricator.d.ts +11 -12
- package/dist/types/Primitive/recursive/self/Schema.d.ts +10 -10
- package/dist/types/Primitive/recursive/self/Types.d.ts +7 -8
- package/dist/types/Primitive/string/Constants.d.ts +12 -13
- package/dist/types/Primitive/string/Fabricator.d.ts +2 -2
- package/dist/types/Primitive/string/Registry.d.ts +10 -16
- package/dist/types/Primitive/string/Schema.d.ts +5 -5
- package/dist/types/Primitive/string/Types.d.ts +17 -17
- package/dist/types/Primitive/symbol/Fabricator.d.ts +2 -2
- package/dist/types/Primitive/symbol/Registry.d.ts +4 -11
- package/dist/types/Primitive/symbol/Schema.d.ts +6 -6
- package/dist/types/Primitive/symbol/Types.d.ts +2 -2
- package/dist/types/Primitive/tuple/Fabricator.d.ts +7 -8
- package/dist/types/Primitive/tuple/Schema.d.ts +5 -5
- package/dist/types/Primitive/tuple/Types.d.ts +24 -25
- package/dist/types/Primitive/undefinable/Fabricator.d.ts +7 -7
- package/dist/types/Primitive/undefinable/Schema.d.ts +13 -13
- package/dist/types/Primitive/undefinable/Types.d.ts +10 -10
- package/dist/types/Primitive/undefined/Registry.d.ts +2 -2
- package/dist/types/Primitive/undefined/Schema.d.ts +4 -4
- package/dist/types/Primitive/undefined/Types.d.ts +3 -3
- package/dist/types/Random/Generator/sfc32.d.ts +4 -4
- package/dist/types/Random/Types.d.ts +151 -293
- package/dist/types/Random/index.d.ts +56 -81
- package/dist/types/Schema/Core.d.ts +19 -21
- package/dist/types/Schema/Registry.d.ts +3 -3
- package/dist/types/Schema/Types.d.ts +41 -48
- package/dist/types/Types.d.ts +39 -45
- package/dist/types/Utility/Core.d.ts +18 -9
- package/dist/types/adapting.d.ts +32 -0
- package/dist/types/harnessing.d.ts +30 -0
- package/dist/types/index.d.ts +95 -128
- package/dist/types/internal.d.ts +53 -40
- package/package.json +39 -7
- package/dist/esm/Random/CallSite.js +0 -56
- package/dist/types/Random/CallSite.d.ts +0 -59
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Instance } from "../Instance/Types";
|
|
2
|
+
import type { PlainObject } from "../Utility/Types";
|
|
3
|
+
import type { FabricatorTestContext, Integration } from "./Types";
|
|
4
|
+
/**
|
|
5
|
+
* Decorate an existing `Instance` as a `@ghostry/harness` integration.
|
|
6
|
+
* `around(identity, body)` is `instance.wrap({ salt: layer(saltFor(identity))
|
|
7
|
+
* })` — one line of real work. Construction ordinals therefore restart per test
|
|
8
|
+
* (each `wrap` re-instantiates), which is what makes `.only`, filters, shards,
|
|
9
|
+
* and `.concurrent` unable to shift a neighbor's data. That per-test
|
|
10
|
+
* partitioning is also why the salt needs no file in it; see `saltFor`
|
|
11
|
+
* (`Harnessing/Salt.ts`).
|
|
12
|
+
*
|
|
13
|
+
* `around`, not `setup`: the ambient frame has to enclose the body, and only
|
|
14
|
+
* `around` does. Its `finally` running at the call boundary rather than at test
|
|
15
|
+
* settlement costs nothing here — there is no teardown, and the
|
|
16
|
+
* `AsyncLocalStorage` carrier keeps the frame alive across the body's `await`s
|
|
17
|
+
* regardless of when `wrap` returns. On the synchronous carrier an async body
|
|
18
|
+
* still raises `SynchronousStackError` from `wrap`. Declaring no `setup` also
|
|
19
|
+
* keeps `@ghostry/harness` on its uninstrumented path, where a synchronous
|
|
20
|
+
* assertion failure is reported at the user's own line.
|
|
21
|
+
*
|
|
22
|
+
* `provides.fabricator` hands back the scope `wrap` gave its block, not the
|
|
23
|
+
* base instance, so `context.fabricator.salt` is the per-test salt and
|
|
24
|
+
* `.fork()` forks from the test's configuration. The two calls meet through
|
|
25
|
+
* `scope`: set just before `body()`, read by the provider, restored in a
|
|
26
|
+
* `finally`. That is safe under `.concurrent` because `@ghostry/harness` runs a
|
|
27
|
+
* provider synchronously inside its own integration's `around` — no other test
|
|
28
|
+
* can enter between the write and the read, and the context object already
|
|
29
|
+
* holds the scope by the body's first `await`. Restoring rather than clearing
|
|
30
|
+
* keeps a nested `around` from dropping its parent's scope. A provider reached
|
|
31
|
+
* with no scope is a composer breaking that contract, and raises
|
|
32
|
+
* `HarnessingProviderError` rather than quietly providing the base instance.
|
|
33
|
+
*
|
|
34
|
+
* Nothing here reads `instance.context`. The integration is a pure function of
|
|
35
|
+
* the `Identity` it is handed and the instance it decorates, and never sets
|
|
36
|
+
* `clock`. A pinned `Date` (the recommended setup) and the wall-clock default
|
|
37
|
+
* are already concrete numbers by the time `overlay()` sees them, so they
|
|
38
|
+
* inherit through every `wrap` unchanged: salt varies per test, "now" does not.
|
|
39
|
+
* `clock: "derived"` is left alone on purpose — that policy's documented
|
|
40
|
+
* meaning is that the salt _is_ the reproducibility unit, so per-test clocks
|
|
41
|
+
* are the request honored, not a bug to override.
|
|
42
|
+
*/
|
|
43
|
+
export declare function integration<$Registry extends PlainObject>(instance: Instance<$Registry>): Integration<FabricatorTestContext<$Registry>>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Identity } from "./Types";
|
|
2
|
+
/**
|
|
3
|
+
* The salt `integration` layers onto the instance for one `Identity`.
|
|
4
|
+
*
|
|
5
|
+
* [ <kind>, ...<describe names>, <name>, <row?> ]
|
|
6
|
+
*
|
|
7
|
+
* **No file, deliberately.** Every test body runs inside its own
|
|
8
|
+
* `instance.wrap`, which builds a fresh `RandomSource` with a fresh
|
|
9
|
+
* construction counter, so constructions are already partitioned per test.
|
|
10
|
+
* Identity comes from a path, never from an execution counter or a location
|
|
11
|
+
* that moves when a file does.
|
|
12
|
+
*
|
|
13
|
+
* The accepted cost: two tests with the same `kind`, describe path, and name in
|
|
14
|
+
* _different files_ share a salt and therefore draw the same data. Each is
|
|
15
|
+
* still deterministic. That is the same trade already made in dropping
|
|
16
|
+
* duplicate-path detection, and the fix is the ordinary one — give them
|
|
17
|
+
* distinguishable names.
|
|
18
|
+
*
|
|
19
|
+
* `kind` leads. Without it, an empty-named test collides with its enclosing
|
|
20
|
+
* suite scope: `path` never carries a leaf's own name, only its describes, and
|
|
21
|
+
* `name` is `""` for both a `"suite"` identity (by design — see `Identity`) and
|
|
22
|
+
* any test a user happens to name `""`, so at the same `path` the two arrays
|
|
23
|
+
* would otherwise be identical. `kind` does not disambiguate two suite
|
|
24
|
+
* identities that share a `path` (a `beforeAll` and an `afterAll` in one
|
|
25
|
+
* `describe`, say) — that collision is intentional, a suite's setup and
|
|
26
|
+
* teardown sharing one deterministic scope, and a caller who wants otherwise
|
|
27
|
+
* already has `instance.wrap(...)` inside the hook body.
|
|
28
|
+
*
|
|
29
|
+
* The row is appended only for `.each`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function saltFor(identity: Identity): ReadonlyArray<string>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Instance } from "../Instance/Types";
|
|
2
|
+
import type { PlainObject } from "../Utility/Types";
|
|
3
|
+
/**
|
|
4
|
+
* What identifies one registered test or suite — the material a salt is derived
|
|
5
|
+
* from. Named to match how this codebase already talks about the concept:
|
|
6
|
+
* fabricator's own reproducibility guide reaches for `layer(...)` for "a tenant
|
|
7
|
+
* id, a test's own name" — this is that identity, structured.
|
|
8
|
+
*
|
|
9
|
+
* Declared here rather than imported from `@ghostry/harness`, so neither
|
|
10
|
+
* package depends on the other — the same arrangement `Adapter`/`walk` already
|
|
11
|
+
* uses for schema adapters. Satisfied structurally.
|
|
12
|
+
*
|
|
13
|
+
* **Carries no file, deliberately.** Nothing here identifies where the test was
|
|
14
|
+
* registered, which is what lets `@ghostry/harness` skip stack walking entirely
|
|
15
|
+
* — see `saltFor` (`Harnessing/Salt.ts`) for why the file turned out to be
|
|
16
|
+
* redundant against per-test `wrap` partitioning. The consequence for the
|
|
17
|
+
* contract: an integration that genuinely needs the registering file cannot get
|
|
18
|
+
* it from here, and adding it back means solving the frame-skip problem for
|
|
19
|
+
* every wrapper between the user's `it(...)` and the integration. Worth
|
|
20
|
+
* revisiting only against a real requirement, not speculatively.
|
|
21
|
+
*/
|
|
22
|
+
export type Identity = {
|
|
23
|
+
/**
|
|
24
|
+
* Disambiguates an empty-named test from its enclosing suite scope — `name`
|
|
25
|
+
* is `""` for both, and `path` never carries a leaf's own name. Two suite
|
|
26
|
+
* identities that share a `path` (`beforeAll` and `afterAll` in one
|
|
27
|
+
* `describe`) are not disambiguated by this: that collision is intentional
|
|
28
|
+
* (see `saltFor`, `Harnessing/Salt.ts`).
|
|
29
|
+
*/
|
|
30
|
+
readonly kind: "test" | "suite";
|
|
31
|
+
/** Enclosing describe names, outer → inner. */
|
|
32
|
+
readonly path: ReadonlyArray<string>;
|
|
33
|
+
/** The test name; empty for a suite-scoped callback. */
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** `.each` row index (0-based), `undefined` when the test is not from `.each`. */
|
|
36
|
+
readonly row: number | undefined;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* One context key's value, as a function of the test's `Identity` rather than a
|
|
40
|
+
* fixed value.
|
|
41
|
+
*/
|
|
42
|
+
export type Provider<$Value> = (identity: Identity) => $Value;
|
|
43
|
+
/**
|
|
44
|
+
* The keys an integration contributes, and how each is produced. Homomorphic
|
|
45
|
+
* over `$Context`, so the context an integration contributes is read back out
|
|
46
|
+
* of this object's shape with no separate key declaration to keep in sync.
|
|
47
|
+
*/
|
|
48
|
+
export type Provides<$Context extends object> = {
|
|
49
|
+
readonly [$Key in keyof $Context]: Provider<$Context[$Key]>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* What `integration(instance)` is, as `@ghostry/harness`'s `initialize` sees
|
|
53
|
+
* it. The subset of that package's contract this integration actually uses, not
|
|
54
|
+
* a copy of all of it: `@ghostry/harness` also accepts an optional `setup`,
|
|
55
|
+
* which fabricator has no teardown to put in, and an object lacking an optional
|
|
56
|
+
* member still satisfies the contract structurally. `around` is required here,
|
|
57
|
+
* though optional there, because this integration always declares it.
|
|
58
|
+
*
|
|
59
|
+
* `provides` is the _only_ source of context keys; `initialize` rejects a
|
|
60
|
+
* collision across integrations by reading `Object.keys(provides)`. Each
|
|
61
|
+
* provider runs inside its integration's `around`. `around` is generic in its
|
|
62
|
+
* return and must return the body's value unchanged: that is what makes async
|
|
63
|
+
* work and what lets frames nest.
|
|
64
|
+
*/
|
|
65
|
+
export type Integration<$Context extends object> = {
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly provides: Provides<$Context>;
|
|
68
|
+
around<$Return>(identity: Identity, body: () => $Return): $Return;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The slice of the test context `integration(instance)` contributes — one key,
|
|
72
|
+
* `fabricator`, holding the per-test scoped `Instance`. The same instance is
|
|
73
|
+
* also ambient for the body's duration (`instance.wrap`), so a body that
|
|
74
|
+
* ignores this and uses the caller's own `instance` still sees the per-test
|
|
75
|
+
* salt.
|
|
76
|
+
*/
|
|
77
|
+
export type FabricatorTestContext<$Registry extends PlainObject = PlainObject> = {
|
|
78
|
+
readonly fabricator: Instance<$Registry>;
|
|
79
|
+
};
|
|
@@ -2,95 +2,59 @@ import type { RandomSource } from "../Random/Types";
|
|
|
2
2
|
import type { PlainObject } from "../Utility/Types";
|
|
3
3
|
import type { Config, Instance, Overlay, Stack } from "./Types";
|
|
4
4
|
/**
|
|
5
|
-
* `combinatorial`'s default limit — `2**10`, so it admits ten
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* one.
|
|
5
|
+
* `combinatorial`'s default limit — `2**10`, so it admits ten independent
|
|
6
|
+
* binary axes before requiring the caller to raise it explicitly. Each
|
|
7
|
+
* enumerated instance costs a full build and fabricate, so this is as much a
|
|
8
|
+
* wall-clock guard as a combinatorial one.
|
|
10
9
|
*/
|
|
11
10
|
export declare const DEFAULT_COMBINATORIAL_LIMIT = 1024;
|
|
12
11
|
/**
|
|
13
|
-
* The single place a `Config` inherits from a base — `initialize` lays
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `Partial<Config<PlainObject>>` rather than `Config` specifically so
|
|
22
|
-
* both calls go through the same function.
|
|
12
|
+
* The single place a `Config` inherits from a base — `initialize` lays its own
|
|
13
|
+
* config over an empty `{}` base (nothing to inherit, so every field falls
|
|
14
|
+
* through to a hardcoded default: an empty salt, wall-clock `clock`, the
|
|
15
|
+
* built-in algorithm, the default registry, the default combinatorial limit),
|
|
16
|
+
* and `fork` lays its overlay over the instance it was called on (a full,
|
|
17
|
+
* already-resolved `Config`, so every field has a real value to fall back to).
|
|
18
|
+
* `base` is typed `Partial<Config<PlainObject>>` rather than `Config`
|
|
19
|
+
* specifically so both calls go through the same function.
|
|
23
20
|
*
|
|
24
|
-
* `
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* base's
|
|
28
|
-
* (
|
|
29
|
-
*
|
|
30
|
-
* entropy; `seed` is an optional mixer.
|
|
31
|
-
*
|
|
32
|
-
* `attribution` resolves through `resolveAttribution` at most once per
|
|
33
|
-
* call, and only when it's actually needed:
|
|
34
|
-
* - an explicit `over.attribution` always wins (resolved fresh, so
|
|
35
|
-
* `fork({ attribution: { kind: "call site" } })` roots at *that*
|
|
36
|
-
* call);
|
|
37
|
-
* - otherwise an already-resolved `base.attribution` is reused as-is
|
|
38
|
-
* — never re-resolved, which keeps a fork from silently re-rooting
|
|
39
|
-
* `"call site"` at wherever `fork()` itself happens to be called
|
|
40
|
-
* (`resolveCallerFile()` skips this library's own frames, so calling
|
|
41
|
-
* it from here still lands on the user's call site either way);
|
|
42
|
-
* - only when neither is available (the root case, `base.attribution`
|
|
43
|
-
* absent) does this fall back to resolving the `"call site"`
|
|
44
|
-
* default.
|
|
45
|
-
* That also keeps `initialize({ attribution: { kind: "none" } })` — or
|
|
46
|
-
* any other explicit override — from paying for a stack walk whose
|
|
47
|
-
* result would be immediately discarded.
|
|
21
|
+
* `salt` composes onto the base rather than replacing it only when tagged with
|
|
22
|
+
* `layer(...)` (`{@link isLayered}`) — a bare `salt` (the ordinary meaning
|
|
23
|
+
* everywhere else in this library) replaces the base's outright, and an omitted
|
|
24
|
+
* `salt` inherits the base's unchanged (or, at the root, an empty mixer via
|
|
25
|
+
* `normalizeSalt(undefined)`, unless an env var supplies one). Wall-clock
|
|
26
|
+
* `clock` is the default entropy; `salt` is an optional mixer.
|
|
48
27
|
*
|
|
49
28
|
* `algorithm`/`types`: wholesale replacement when given, matching how
|
|
50
29
|
* `initialize({ types })` already behaves — no deep merge;
|
|
51
30
|
* `registry.extend(...)` is the existing tool for that. `limits` is
|
|
52
31
|
* re-validated through `resolveCombinatorialLimit` whenever given (or
|
|
53
|
-
* inherited, or defaulted), so a bad limit fails at
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
62
|
-
*
|
|
63
|
-
* instant unless that parent was itself `"seeded"`.
|
|
32
|
+
* inherited, or defaulted), so a bad limit fails at `fork()`/`initialize()`
|
|
33
|
+
* time rather than at first use. `clock` follows the given → inherited →
|
|
34
|
+
* default shape `algorithm` does, but stays _unresolved_ only for the explicit
|
|
35
|
+
* `"derived"` sentinel: an explicit `Date` and the unconfigured wall-clock
|
|
36
|
+
* default are stored as epoch milliseconds (a stated instant, inherited as-is
|
|
37
|
+
* from then on), while `"derived"` is left as the sentinel rather than
|
|
38
|
+
* collapsed to a number, so `resolveClock` can re-derive it from whichever
|
|
39
|
+
* `salt` is actually in effect at read time. An omitted `clock` on a
|
|
40
|
+
* `fork`/`wrap` whose salt changed therefore keeps the parent's instant unless
|
|
41
|
+
* that parent was itself `"derived"`.
|
|
64
42
|
*/
|
|
65
43
|
export declare function overlay<$Registry extends PlainObject>(base: Partial<Config<PlainObject>>, over: Overlay<$Registry>): Config<$Registry>;
|
|
66
|
-
/**
|
|
67
|
-
* Builds a `Stack`: closes over a private `Frame[]`, pushing on `enter`
|
|
68
|
-
* and popping in a `finally` — correct even around a `throw` from
|
|
69
|
-
* `block`. One per root `initialize()`, threaded — never re-created —
|
|
70
|
-
* through every `fork`/`wrap` descended from it, so it stays
|
|
71
|
-
* per-lineage rather than module-level: two unrelated `initialize()`
|
|
72
|
-
* calls each get their own stack and can never perturb each other,
|
|
73
|
-
* while every instance sharing one stack (a fork included, no matter
|
|
74
|
-
* where in the lineage it was created) resolves against the same
|
|
75
|
-
* active frame.
|
|
76
|
-
*/
|
|
77
|
-
export declare function toStack(): Stack;
|
|
78
44
|
/**
|
|
79
45
|
* The shared body `initialize` and `fork` both reduce to: build a
|
|
80
46
|
* `RandomSource` from an already-resolved `Config`, then everything an
|
|
81
|
-
* `Instance` exposes off of it. Returns the `RandomSource` alongside
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* construction ordinals).
|
|
47
|
+
* `Instance` exposes off of it. Returns the `RandomSource` alongside the
|
|
48
|
+
* `Instance` — `initialize`/`fork` discard it, `wrap` keeps it to stash on the
|
|
49
|
+
* `Frame` it pushes, so implicit (ambient) and explicit (`scope.Fabricator`)
|
|
50
|
+
* construction inside one `wrap` resolve against the very same source rather
|
|
51
|
+
* than each independently re-deriving one from the same config (and so silently
|
|
52
|
+
* diverging/duplicating construction ordinals).
|
|
88
53
|
*
|
|
89
|
-
* `stack` is threaded straight through to `Constructor`/`enumerables`
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* not at this moment.
|
|
54
|
+
* `stack` is threaded straight through to `Constructor`/`enumerables` — this
|
|
55
|
+
* function never reads or writes it itself, only passes it along so every built
|
|
56
|
+
* `Fabricator`/`combinatorial`/`coverage` can consult whichever frame is active
|
|
57
|
+
* _at the moment each is called_, not at this moment.
|
|
94
58
|
*/
|
|
95
59
|
export declare function instantiate<$Registry extends PlainObject>(config: Config<$Registry>, stack: Stack): {
|
|
96
60
|
instance: Instance<$Registry>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Stack } from "../Types";
|
|
2
|
+
/**
|
|
3
|
+
* The asynchronous carrier, and the **only** module in this package importing
|
|
4
|
+
* anything from `node:`. Nothing imports it directly: `Instance/Core.ts`
|
|
5
|
+
* imports `#stack`, whose `node`/`bun`/`deno` conditions (`package.json`)
|
|
6
|
+
* resolve here while `default` resolves to `./sync.ts`. That is what keeps the
|
|
7
|
+
* package importable on a runtime with no `node:async_hooks` while every
|
|
8
|
+
* runtime that has one gets async-safe `wrap` with nothing to configure.
|
|
9
|
+
*
|
|
10
|
+
* `AsyncLocalStorage.run` returns whatever `block` returns, so this satisfies
|
|
11
|
+
* `enter`'s sync-preserving `<$Return>` signature exactly as the sync carrier
|
|
12
|
+
* does — a synchronous `wrap` is unaffected by which carrier is in play.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toStack(): Stack;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Stack } from "../Types";
|
|
2
|
+
/**
|
|
3
|
+
* The synchronous carrier: a private `Frame[]`, pushed on `enter` and popped in
|
|
4
|
+
* a `finally` — correct even around a `throw` from `block`.
|
|
5
|
+
*
|
|
6
|
+
* Selected by the `#stack` `default` condition (`package.json`), i.e. on any
|
|
7
|
+
* runtime without `node:async_hooks` — in practice a browser bundle. Its frame
|
|
8
|
+
* cannot survive an `await`: `enter` returns `block()` without awaiting, so an
|
|
9
|
+
* async block's frame unwinds at the block's first suspension point, and a
|
|
10
|
+
* shared LIFO could not represent two overlapping scopes even if it did await.
|
|
11
|
+
* Both are why `asynchronous` is `false` and `wrap` refuses an async block here
|
|
12
|
+
* rather than resolving it against the base instance with no signal.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toStack(): Stack;
|
|
@@ -1,85 +1,85 @@
|
|
|
1
1
|
import type { Enumerable, Limits } from "../Enumeration/Types";
|
|
2
2
|
import type { Constructor } from "../Fabricator/Constructor";
|
|
3
|
-
import type { Algorithm,
|
|
3
|
+
import type { Algorithm, Layered, RandomSource, Salt } from "../Random/Types";
|
|
4
4
|
import type { PlainObject } from "../Utility/Types";
|
|
5
5
|
/**
|
|
6
|
-
* A fully resolved instance configuration — every field present, nothing
|
|
7
|
-
*
|
|
6
|
+
* A fully resolved instance configuration — every field present, nothing left
|
|
7
|
+
* to default. `Overlay` is what `fork` accepts; `overlay()`
|
|
8
8
|
* (`Instance/Core.ts`) is the only thing producing a complete `Config`.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `fork({ seed: layer(...) })`). The unconfigured default is a wall-
|
|
21
|
-
* clock number, inherited as-is like an explicit `Date`. `resolveClock`
|
|
22
|
-
* (`Instance/Core.ts`) is the one place that resolves `"seeded"` to a
|
|
23
|
-
* number, called fresh wherever the clock actually matters
|
|
24
|
-
* (`instantiate`, the `context.clock` getter) rather than once here.
|
|
10
|
+
* `salt` is declared at its caller-facing `Salt` type but always holds the
|
|
11
|
+
* normalized array once `overlay()` has run (`Instance.salt` is the
|
|
12
|
+
* authoritative read). `clock` holds _either_ a resolved instant (epoch
|
|
13
|
+
* milliseconds) or the unresolved `"derived"` policy — never collapsed to a
|
|
14
|
+
* number by `overlay()` when `"derived"`, because that policy must re-derive
|
|
15
|
+
* whenever the salt it composes changes (a `fork({ salt: layer(...) })`). The
|
|
16
|
+
* unconfigured default is a wall- clock number, inherited as-is like an
|
|
17
|
+
* explicit `Date`. `resolveClock` (`Instance/Core.ts`) is the one place that
|
|
18
|
+
* resolves `"derived"` to a number, called fresh wherever the clock actually
|
|
19
|
+
* matters (`instantiate`, the `context.clock` getter) rather than once here.
|
|
25
20
|
*/
|
|
26
21
|
export type Config<$Registry extends PlainObject> = {
|
|
27
|
-
readonly
|
|
22
|
+
readonly salt: Salt;
|
|
28
23
|
readonly algorithm: Algorithm;
|
|
29
|
-
readonly attribution: Attribution;
|
|
30
24
|
readonly types: $Registry;
|
|
31
25
|
readonly limits: Limits;
|
|
32
|
-
readonly clock: number | "
|
|
26
|
+
readonly clock: number | "derived";
|
|
33
27
|
};
|
|
34
28
|
/**
|
|
35
|
-
* What `fork` accepts — a `Config` to lay over a base, every field
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* `
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* instance seed; omitting `clock` at the root captures wall-clock time
|
|
48
|
-
* instead.
|
|
29
|
+
* What `fork` accepts — a `Config` to lay over a base, every field optional.
|
|
30
|
+
* Identical to `Partial<Config>` but for `salt`, which additionally accepts a
|
|
31
|
+
* {@link Layered}: a bare `Salt` replaces the base's salt outright (what `salt`
|
|
32
|
+
* means everywhere else in this library), `layer(salt)` appends onto it
|
|
33
|
+
* (`[...base.salt, ...salt]`) — the shape a wrapping integration wants, and
|
|
34
|
+
* what `ConstructorOptions`' layered form is for a single construction.
|
|
35
|
+
* Omitting `salt` inherits the base's unchanged. `clock` similarly accepts the
|
|
36
|
+
* caller-facing `Date | "derived"` rather than `Config`'s own resolved `number
|
|
37
|
+
* | "derived"`, so a caller can hand in a literal instant without converting it
|
|
38
|
+
* to epoch milliseconds themselves. `"derived"` is the explicit opt-in that
|
|
39
|
+
* derives "now" from the instance salt; omitting `clock` at the root captures
|
|
40
|
+
* wall-clock time instead.
|
|
49
41
|
*
|
|
50
|
-
* `initialize`'s own parameter keeps a plain `
|
|
51
|
-
* `layer(...)` there is a compile error: there is no base to layer onto
|
|
52
|
-
*
|
|
42
|
+
* `initialize`'s own parameter keeps a plain `salt?: Salt`, so passing
|
|
43
|
+
* `layer(...)` there is a compile error: there is no base to layer onto at the
|
|
44
|
+
* root.
|
|
53
45
|
*/
|
|
54
|
-
export type Overlay<$Registry extends PlainObject> = Partial<Omit<Config<$Registry>, "
|
|
55
|
-
readonly
|
|
56
|
-
readonly clock?: Date | "
|
|
46
|
+
export type Overlay<$Registry extends PlainObject> = Partial<Omit<Config<$Registry>, "salt" | "clock">> & {
|
|
47
|
+
readonly salt?: Salt | Layered;
|
|
48
|
+
readonly clock?: Date | "derived" | undefined;
|
|
57
49
|
};
|
|
58
50
|
/**
|
|
59
|
-
* One active `wrap` — its resolved config plus the single `RandomSource`
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* its own.
|
|
51
|
+
* One active `wrap` — its resolved config plus the single `RandomSource` every
|
|
52
|
+
* build reached inside that `wrap` shares, whether reached implicitly (any
|
|
53
|
+
* instance in the lineage consulting the active frame) or explicitly
|
|
54
|
+
* (`scope.Fabricator`, the `Instance` passed to the block). Storing the scope's
|
|
55
|
+
* own already-built `source` here, rather than each consumer re-deriving one
|
|
56
|
+
* from `config`, keeps the two routes resolving against the _same_ source —
|
|
57
|
+
* sharing one set of construction-ordinal counters — instead of each silently
|
|
58
|
+
* starting its own.
|
|
68
59
|
*/
|
|
69
60
|
export type Frame = {
|
|
70
61
|
readonly config: Config<PlainObject>;
|
|
71
62
|
readonly source: RandomSource;
|
|
72
63
|
};
|
|
73
64
|
/**
|
|
74
|
-
* The per-lineage ambient stack. Created once at a root `initialize()`
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* lineage that instance was itself created.
|
|
65
|
+
* The per-lineage ambient stack. Created once at a root `initialize()` and
|
|
66
|
+
* threaded — never re-created — through every `fork`/`wrap` descended from it
|
|
67
|
+
* (see `instantiate`, `Instance/Core.ts`). Two unrelated `initialize()` calls
|
|
68
|
+
* stay fully isolated; one lineage's `wrap` reaches every instance in that
|
|
69
|
+
* lineage — its `Fabricator`, `combinatorial`, and `coverage` alike —
|
|
70
|
+
* regardless of where in the lineage that instance was itself created.
|
|
81
71
|
*/
|
|
82
72
|
export type Stack = {
|
|
73
|
+
/**
|
|
74
|
+
* Whether a frame survives an `await` inside the block it was entered for.
|
|
75
|
+
*
|
|
76
|
+
* `false` means the carrier is a plain LIFO whose frame unwinds at the
|
|
77
|
+
* block's first suspension point — correct for synchronous use, and the
|
|
78
|
+
* reason `wrap` (`Instance/Core.ts`) rejects an async block outright rather
|
|
79
|
+
* than letting a build after the `await` silently resolve against the base
|
|
80
|
+
* instance. See `Instance/Stack/Sync.ts` and `Instance/Stack/Async.ts`.
|
|
81
|
+
*/
|
|
82
|
+
readonly asynchronous: boolean;
|
|
83
83
|
current(): Frame | undefined;
|
|
84
84
|
/**
|
|
85
85
|
* Push `frame`, run `block`, pop — in a `finally`, so a frame unwinds
|
|
@@ -88,80 +88,76 @@ export type Stack = {
|
|
|
88
88
|
enter<$Return>(frame: Frame, block: () => $Return): $Return;
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
|
-
* The configuration in effect
|
|
92
|
-
*
|
|
93
|
-
* `ConstructionContext` (`Fabricator/Types.ts`), which is one
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* The configuration in effect _right now_ — the innermost active `wrap` frame,
|
|
92
|
+
* or this instance's own when there is none. Distinct from
|
|
93
|
+
* `ConstructionContext` (`Fabricator/Types.ts`), which is one construction's
|
|
94
|
+
* internal dispatch plumbing; this is the caller-facing "what configuration is
|
|
95
|
+
* in effect."
|
|
96
96
|
*/
|
|
97
97
|
export type Context = {
|
|
98
|
-
readonly
|
|
98
|
+
readonly salt: ReadonlyArray<string>;
|
|
99
99
|
readonly algorithm: Algorithm;
|
|
100
|
-
readonly attribution: Attribution;
|
|
101
100
|
readonly clock: number;
|
|
102
101
|
};
|
|
103
102
|
/**
|
|
104
|
-
* A single initialized library instance: the registry it was given, and
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
103
|
+
* A single initialized library instance: the registry it was given, and a
|
|
104
|
+
* `construct()` bound to its own isolated randomness — its own salt, builder,
|
|
105
|
+
* and construction counter/streams, held internally and never shared with any
|
|
106
|
+
* other `initialize()` call. Independently initialized instances (e.g. parallel
|
|
107
|
+
* tests) can never perturb each other.
|
|
109
108
|
*/
|
|
110
|
-
export interface Instance<$Registry extends PlainObject> extends Pick<RandomSource, "
|
|
109
|
+
export interface Instance<$Registry extends PlainObject> extends Pick<RandomSource, "salt"> {
|
|
111
110
|
/** The registry of type definers this instance was initialized with. */
|
|
112
111
|
readonly T: $Registry;
|
|
113
112
|
/**
|
|
114
113
|
* Turn a Schema built from `T` into a live Fabricator, deriving fresh
|
|
115
|
-
* randomness from this instance's own
|
|
116
|
-
*
|
|
114
|
+
* randomness from this instance's own salt for whichever leaves actually need
|
|
115
|
+
* it.
|
|
117
116
|
*/
|
|
118
117
|
Fabricator: Constructor;
|
|
119
118
|
/**
|
|
120
|
-
* Every combination of every enumerable node in `schema` — every enum
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* Every combination of every enumerable node in `schema` — every enum member,
|
|
120
|
+
* both sides of an optional field, and so on — as a cartesian product,
|
|
121
|
+
* lazily. Throws eagerly, before producing anything, if the count exceeds
|
|
122
|
+
* `limits.combinatorial` (see `initialize`'s config).
|
|
124
123
|
*/
|
|
125
124
|
combinatorial: Enumerable;
|
|
126
125
|
/**
|
|
127
|
-
* The minimum set of instances such that every option of every
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
126
|
+
* The minimum set of instances such that every option of every enumerable
|
|
127
|
+
* node in `schema` appears at least once — count equal to the _widest_ single
|
|
128
|
+
* axis, not the product, with narrower axes cycling to fill it. Unbounded:
|
|
129
|
+
* its count can never exceed the schema as written, so unlike `combinatorial`
|
|
130
|
+
* it carries no limit.
|
|
132
131
|
*/
|
|
133
132
|
coverage: Enumerable;
|
|
134
133
|
/**
|
|
135
134
|
* Derive a new instance laid over this one: anything the overlay names
|
|
136
|
-
* overrides, anything it omits inherits. A bare `
|
|
137
|
-
* instance's
|
|
138
|
-
* `initialize()` return value in every respect, including its own
|
|
139
|
-
* `fork`.
|
|
135
|
+
* overrides, anything it omits inherits. A bare `salt` replaces this
|
|
136
|
+
* instance's salt; `salt: layer(...)` appends onto it. A peer of an
|
|
137
|
+
* `initialize()` return value in every respect, including its own `fork`.
|
|
140
138
|
*/
|
|
141
139
|
fork<const $ForkRegistry extends PlainObject = $Registry>(overlay?: Overlay<$ForkRegistry>): Instance<$ForkRegistry>;
|
|
142
140
|
/**
|
|
143
|
-
* `fork(overlay)`, made ambient for the synchronous extent of `block`:
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* after an `await` inside `block` sees this instance's own
|
|
141
|
+
* `fork(overlay)`, made ambient for the synchronous extent of `block`: every
|
|
142
|
+
* `new Fabricator(...)`, `combinatorial(...)`, and `coverage(...)` reached
|
|
143
|
+
* inside — on this instance or any other in the same lineage — resolves
|
|
144
|
+
* against the fork instead, with nothing threaded through. The fork is also
|
|
145
|
+
* passed to `block`: use it explicitly where that reads better, and
|
|
146
|
+
* _necessarily_ for any async work, which the ambient frame does not survive
|
|
147
|
+
* — a build reached after an `await` inside `block` sees this instance's own
|
|
151
148
|
* configuration again, not the wrap's.
|
|
152
149
|
*
|
|
153
|
-
* A nested `wrap` lays over whichever frame is currently active, not
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
150
|
+
* A nested `wrap` lays over whichever frame is currently active, not over the
|
|
151
|
+
* instance it was called on — so `overlay.salt: layer(...)` accumulates with
|
|
152
|
+
* nesting depth while a bare `salt` still replaces outright, discarding every
|
|
153
|
+
* enclosing layer.
|
|
157
154
|
*/
|
|
158
155
|
wrap<$Return, const $WrapRegistry extends PlainObject = $Registry>(overlay: Overlay<$WrapRegistry>, block: (scope: Instance<$WrapRegistry>) => $Return): $Return;
|
|
159
156
|
/**
|
|
160
|
-
* The configuration in effect right now: the innermost active `wrap`
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* `wrap`s.
|
|
157
|
+
* The configuration in effect right now: the innermost active `wrap` frame's,
|
|
158
|
+
* or this instance's own outside any `wrap`. A live view, not a snapshot —
|
|
159
|
+
* reflects whichever frame is active at the moment each property is read,
|
|
160
|
+
* since one `Instance` outlives any number of `wrap`s.
|
|
165
161
|
*/
|
|
166
162
|
readonly context: Context;
|
|
167
163
|
}
|
|
@@ -2,17 +2,17 @@ import { type AdaptationEntry } from "../../Adapter/Core";
|
|
|
2
2
|
import type { Adaptations, Adapter, Adapting, WithAdaptations } from "../../Adapter/Types";
|
|
3
3
|
import type { Core, Value } from "./Types";
|
|
4
4
|
/**
|
|
5
|
-
* A fixed value, so unlike every other kind there is no `.as()` — nothing
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* A fixed value, so unlike every other kind there is no `.as()` — nothing left
|
|
6
|
+
* to override about producing it. `adapt` still applies: what a value _means_
|
|
7
|
+
* to an external schema library is a separate question from what it fabricates
|
|
8
|
+
* to, and for a value TypeBox cannot pin exactly (a `symbol`, or any value's
|
|
9
|
+
* _static_ type — see `Adapter/TypeBox`'s `toConst`) it is the only way to
|
|
10
|
+
* close the gap.
|
|
11
11
|
*/
|
|
12
12
|
export interface Schema<$Value extends Value = Value, $Adaptations extends Adaptations = {}> extends Core<$Value, $Adaptations> {
|
|
13
13
|
/**
|
|
14
|
-
* Override what this schema maps to in one or more external schema
|
|
15
|
-
*
|
|
14
|
+
* Override what this schema maps to in one or more external schema libraries
|
|
15
|
+
* — see `string/Schema.ts`'s `adapt` for the full contract.
|
|
16
16
|
*/
|
|
17
17
|
adapt: <const $Adapter extends Adapter, $Returnable extends ReturnType<$Adapter["convert"]>>(adapter: $Adapter, produce: (adapting: Adapting<Schema<$Value, $Adaptations>>) => $Returnable) => Schema<$Value, WithAdaptations<$Adaptations, AdaptationEntry<$Adapter, $Returnable>>>;
|
|
18
18
|
}
|