@ghostry/fabricator 0.0.2 → 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 -6
- package/dist/esm/Adapter/Core.js +3 -3
- package/dist/esm/Enumeration/Enumerate.js +16 -13
- package/dist/esm/Error/index.js +8 -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 +17 -24
- package/dist/esm/Primitive/recursive/Fabricator.js +1 -1
- package/dist/esm/Random/index.js +24 -79
- package/dist/esm/adapting.js +2 -0
- package/dist/esm/harnessing.js +1 -0
- package/dist/esm/index.js +1 -2
- package/dist/esm/internal.js +1 -2
- package/dist/types/Adapter/Core.d.ts +1 -1
- package/dist/types/Adapter/Types.d.ts +1 -1
- package/dist/types/Enumeration/Enumerate.d.ts +17 -15
- package/dist/types/Error/index.d.ts +15 -19
- package/dist/types/Fabricator/Constructor.d.ts +4 -4
- 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 +14 -31
- package/dist/types/Instance/Types.d.ts +35 -41
- package/dist/types/Primitive/opaque/Registry.d.ts +1 -1
- package/dist/types/Primitive/recursive/Fabricator.d.ts +8 -8
- package/dist/types/Random/Types.d.ts +97 -217
- package/dist/types/Random/index.d.ts +30 -48
- package/dist/types/Types.d.ts +3 -3
- package/dist/types/adapting.d.ts +32 -0
- package/dist/types/harnessing.d.ts +30 -0
- package/dist/types/index.d.ts +40 -75
- package/dist/types/internal.d.ts +5 -23
- package/package.json +23 -3
- package/dist/esm/Random/CallSite.js +0 -70
- package/dist/types/Random/CallSite.d.ts +0 -78
|
@@ -45,25 +45,6 @@ export declare namespace FabricatorError {
|
|
|
45
45
|
*/
|
|
46
46
|
limit: unknown);
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* `initialize({ attribution: { kind: "rooted", root } })` when `root` is not
|
|
50
|
-
* an absolute path or a `file://` URL. A relative root can never prefix a
|
|
51
|
-
* resolved caller file, so `relativize` would leave every file unchanged —
|
|
52
|
-
* the option would look configured while silently doing nothing. Thrown
|
|
53
|
-
* eagerly at `initialize()`, not deferred to wherever that would first become
|
|
54
|
-
* observable.
|
|
55
|
-
*/
|
|
56
|
-
class InvalidAttributionRootError extends FabricatorError {
|
|
57
|
-
/**
|
|
58
|
-
* The rejected root, as given.
|
|
59
|
-
*/
|
|
60
|
-
readonly root: string;
|
|
61
|
-
constructor(
|
|
62
|
-
/**
|
|
63
|
-
* The rejected root, as given.
|
|
64
|
-
*/
|
|
65
|
-
root: string);
|
|
66
|
-
}
|
|
67
48
|
/**
|
|
68
49
|
* A `self` placeholder resolved with no `T.recursive` expanding around it —
|
|
69
50
|
* only reachable by holding a `self` reference outside the `T.recursive(...)`
|
|
@@ -556,4 +537,19 @@ export declare namespace FabricatorError {
|
|
|
556
537
|
class SynchronousStackError extends FabricatorError {
|
|
557
538
|
constructor();
|
|
558
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* The `fabricator` provider of `integration(instance)`
|
|
542
|
+
* (`@ghostry/fabricator/harnessing`) was called outside that integration's
|
|
543
|
+
* own `around`.
|
|
544
|
+
*
|
|
545
|
+
* The provider hands back the per-test scope `around` just entered, so there
|
|
546
|
+
* is no scope for it to return anywhere else. `@ghostry/harness` never does
|
|
547
|
+
* this — it runs each provider inside its integration's `around` — so this
|
|
548
|
+
* means a composer that breaks the contract. Raised rather than returning the
|
|
549
|
+
* base instance, which would draw plausible data from the wrong configuration
|
|
550
|
+
* with no signal.
|
|
551
|
+
*/
|
|
552
|
+
class HarnessingProviderError extends FabricatorError {
|
|
553
|
+
constructor();
|
|
554
|
+
}
|
|
559
555
|
}
|
|
@@ -11,9 +11,9 @@ import { type AsFabricator } from "./Types";
|
|
|
11
11
|
* `function` that explicitly returns an object, so `new construct(schema)`
|
|
12
12
|
* behaves identically to `construct(schema)` — a `new` call's returned object
|
|
13
13
|
* always replaces the freshly-created `this`. The construct signature lets
|
|
14
|
-
* callers spell `new T.Fabricator(schema)` — or `new Fabricator(schema, {
|
|
15
|
-
* })` to pin this one build
|
|
16
|
-
*
|
|
14
|
+
* callers spell `new T.Fabricator(schema)` — or `new Fabricator(schema, { salt
|
|
15
|
+
* })` to pin this one build's salt slot, leaving every other slot to resolve as
|
|
16
|
+
* usual (see `construct()` for what `options.salt` does).
|
|
17
17
|
*/
|
|
18
18
|
export type Constructor = {
|
|
19
19
|
new <const $Schema extends Buildable>(schema: $Schema, options?: ConstructorOptions): AsFabricator<$Schema>;
|
|
@@ -23,7 +23,7 @@ export type Constructor = {
|
|
|
23
23
|
* internal `convert(schema: any)` — and the precisely-typed `construct()`
|
|
24
24
|
* boundary around it, both closed over a single instance's `source` so every
|
|
25
25
|
* fabricator this `construct()` produces draws from that instance's own
|
|
26
|
-
*
|
|
26
|
+
* salt/streams and never another instance's.
|
|
27
27
|
*
|
|
28
28
|
* `stack` is the instance's own lineage-wide ambient stack
|
|
29
29
|
* (`Instance/Core.ts`'s `toStack()`) — passed straight through to
|
|
@@ -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
|
+
};
|
|
@@ -11,36 +11,19 @@ export declare const DEFAULT_COMBINATORIAL_LIMIT = 1024;
|
|
|
11
11
|
/**
|
|
12
12
|
* The single place a `Config` inherits from a base — `initialize` lays its own
|
|
13
13
|
* config over an empty `{}` base (nothing to inherit, so every field falls
|
|
14
|
-
* through to a hardcoded default: an empty
|
|
15
|
-
* built-in algorithm,
|
|
16
|
-
*
|
|
17
|
-
* `fork` lays its overlay over the instance it was called on (a full,
|
|
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,
|
|
18
17
|
* already-resolved `Config`, so every field has a real value to fall back to).
|
|
19
18
|
* `base` is typed `Partial<Config<PlainObject>>` rather than `Config`
|
|
20
19
|
* specifically so both calls go through the same function.
|
|
21
20
|
*
|
|
22
|
-
* `
|
|
23
|
-
* `layer(...)` (`{@link isLayered}`) — a bare `
|
|
21
|
+
* `salt` composes onto the base rather than replacing it only when tagged with
|
|
22
|
+
* `layer(...)` (`{@link isLayered}`) — a bare `salt` (the ordinary meaning
|
|
24
23
|
* everywhere else in this library) replaces the base's outright, and an omitted
|
|
25
|
-
* `
|
|
26
|
-
* `
|
|
27
|
-
* `clock` is the default entropy; `
|
|
28
|
-
*
|
|
29
|
-
* `attribution` resolves through `resolveAttribution` at most once per call,
|
|
30
|
-
* and only when it's actually needed:
|
|
31
|
-
*
|
|
32
|
-
* - an explicit `over.attribution` always wins (resolved fresh, so `fork({
|
|
33
|
-
* attribution: { kind: "call site" } })` roots at _that_ call);
|
|
34
|
-
* - otherwise an already-resolved `base.attribution` is reused as-is — never
|
|
35
|
-
* re-resolved, which keeps a fork from silently re-rooting `"call site"` at
|
|
36
|
-
* wherever `fork()` itself happens to be called (`resolveCallerFile()` skips
|
|
37
|
-
* this library's own frames, so calling it from here still lands on the
|
|
38
|
-
* user's call site either way);
|
|
39
|
-
* - only when neither is available (the root case, `base.attribution` absent)
|
|
40
|
-
* does this fall back to resolving the `"call site"` default. That also keeps
|
|
41
|
-
* `initialize({ attribution: { kind: "none" } })` — or any other explicit
|
|
42
|
-
* override — from paying for a stack walk whose result would be immediately
|
|
43
|
-
* discarded.
|
|
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.
|
|
44
27
|
*
|
|
45
28
|
* `algorithm`/`types`: wholesale replacement when given, matching how
|
|
46
29
|
* `initialize({ types })` already behaves — no deep merge;
|
|
@@ -49,13 +32,13 @@ export declare const DEFAULT_COMBINATORIAL_LIMIT = 1024;
|
|
|
49
32
|
* inherited, or defaulted), so a bad limit fails at `fork()`/`initialize()`
|
|
50
33
|
* time rather than at first use. `clock` follows the given → inherited →
|
|
51
34
|
* default shape `algorithm` does, but stays _unresolved_ only for the explicit
|
|
52
|
-
* `"
|
|
35
|
+
* `"derived"` sentinel: an explicit `Date` and the unconfigured wall-clock
|
|
53
36
|
* default are stored as epoch milliseconds (a stated instant, inherited as-is
|
|
54
|
-
* from then on), while `"
|
|
55
|
-
* to a number, so `resolveClock` can re-derive it from whichever
|
|
56
|
-
* actually in effect at read time. An omitted `clock` on a
|
|
57
|
-
*
|
|
58
|
-
* itself `"
|
|
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"`.
|
|
59
42
|
*/
|
|
60
43
|
export declare function overlay<$Registry extends PlainObject>(base: Partial<Config<PlainObject>>, over: Overlay<$Registry>): Config<$Registry>;
|
|
61
44
|
/**
|
|
@@ -1,56 +1,51 @@
|
|
|
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
6
|
* A fully resolved instance configuration — every field present, nothing left
|
|
7
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
|
-
* as-is like an explicit `Date`. `resolveClock` (`Instance/Core.ts`) is the one
|
|
21
|
-
* place that resolves `"seeded"` to a number, called fresh wherever the clock
|
|
22
|
-
* actually matters (`instantiate`, the `context.clock` getter) rather than once
|
|
23
|
-
* 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.
|
|
24
20
|
*/
|
|
25
21
|
export type Config<$Registry extends PlainObject> = {
|
|
26
|
-
readonly
|
|
22
|
+
readonly salt: Salt;
|
|
27
23
|
readonly algorithm: Algorithm;
|
|
28
|
-
readonly attribution: Attribution;
|
|
29
24
|
readonly types: $Registry;
|
|
30
25
|
readonly limits: Limits;
|
|
31
|
-
readonly clock: number | "
|
|
26
|
+
readonly clock: number | "derived";
|
|
32
27
|
};
|
|
33
28
|
/**
|
|
34
29
|
* What `fork` accepts — a `Config` to lay over a base, every field optional.
|
|
35
|
-
* Identical to `Partial<Config>` but for `
|
|
36
|
-
* {@link Layered}: a bare `
|
|
37
|
-
* means everywhere else in this library), `layer(
|
|
38
|
-
* (`[...base.
|
|
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
|
|
39
34
|
* what `ConstructorOptions`' layered form is for a single construction.
|
|
40
|
-
* Omitting `
|
|
41
|
-
* caller-facing `Date | "
|
|
42
|
-
* "
|
|
43
|
-
* epoch milliseconds themselves. `"
|
|
44
|
-
* "now" from the instance
|
|
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
|
|
45
40
|
* wall-clock time instead.
|
|
46
41
|
*
|
|
47
|
-
* `initialize`'s own parameter keeps a plain `
|
|
42
|
+
* `initialize`'s own parameter keeps a plain `salt?: Salt`, so passing
|
|
48
43
|
* `layer(...)` there is a compile error: there is no base to layer onto at the
|
|
49
44
|
* root.
|
|
50
45
|
*/
|
|
51
|
-
export type Overlay<$Registry extends PlainObject> = Partial<Omit<Config<$Registry>, "
|
|
52
|
-
readonly
|
|
53
|
-
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;
|
|
54
49
|
};
|
|
55
50
|
/**
|
|
56
51
|
* One active `wrap` — its resolved config plus the single `RandomSource` every
|
|
@@ -100,24 +95,23 @@ export type Stack = {
|
|
|
100
95
|
* in effect."
|
|
101
96
|
*/
|
|
102
97
|
export type Context = {
|
|
103
|
-
readonly
|
|
98
|
+
readonly salt: ReadonlyArray<string>;
|
|
104
99
|
readonly algorithm: Algorithm;
|
|
105
|
-
readonly attribution: Attribution;
|
|
106
100
|
readonly clock: number;
|
|
107
101
|
};
|
|
108
102
|
/**
|
|
109
103
|
* A single initialized library instance: the registry it was given, and a
|
|
110
|
-
* `construct()` bound to its own isolated randomness — its own
|
|
111
|
-
* and
|
|
104
|
+
* `construct()` bound to its own isolated randomness — its own salt, builder,
|
|
105
|
+
* and construction counter/streams, held internally and never shared with any
|
|
112
106
|
* other `initialize()` call. Independently initialized instances (e.g. parallel
|
|
113
107
|
* tests) can never perturb each other.
|
|
114
108
|
*/
|
|
115
|
-
export interface Instance<$Registry extends PlainObject> extends Pick<RandomSource, "
|
|
109
|
+
export interface Instance<$Registry extends PlainObject> extends Pick<RandomSource, "salt"> {
|
|
116
110
|
/** The registry of type definers this instance was initialized with. */
|
|
117
111
|
readonly T: $Registry;
|
|
118
112
|
/**
|
|
119
113
|
* Turn a Schema built from `T` into a live Fabricator, deriving fresh
|
|
120
|
-
* randomness from this instance's own
|
|
114
|
+
* randomness from this instance's own salt for whichever leaves actually need
|
|
121
115
|
* it.
|
|
122
116
|
*/
|
|
123
117
|
Fabricator: Constructor;
|
|
@@ -138,8 +132,8 @@ export interface Instance<$Registry extends PlainObject> extends Pick<RandomSour
|
|
|
138
132
|
coverage: Enumerable;
|
|
139
133
|
/**
|
|
140
134
|
* Derive a new instance laid over this one: anything the overlay names
|
|
141
|
-
* overrides, anything it omits inherits. A bare `
|
|
142
|
-
* instance's
|
|
135
|
+
* overrides, anything it omits inherits. A bare `salt` replaces this
|
|
136
|
+
* instance's salt; `salt: layer(...)` appends onto it. A peer of an
|
|
143
137
|
* `initialize()` return value in every respect, including its own `fork`.
|
|
144
138
|
*/
|
|
145
139
|
fork<const $ForkRegistry extends PlainObject = $Registry>(overlay?: Overlay<$ForkRegistry>): Instance<$ForkRegistry>;
|
|
@@ -154,8 +148,8 @@ export interface Instance<$Registry extends PlainObject> extends Pick<RandomSour
|
|
|
154
148
|
* configuration again, not the wrap's.
|
|
155
149
|
*
|
|
156
150
|
* A nested `wrap` lays over whichever frame is currently active, not over the
|
|
157
|
-
* instance it was called on — so `overlay.
|
|
158
|
-
* nesting depth while a bare `
|
|
151
|
+
* instance it was called on — so `overlay.salt: layer(...)` accumulates with
|
|
152
|
+
* nesting depth while a bare `salt` still replaces outright, discarding every
|
|
159
153
|
* enclosing layer.
|
|
160
154
|
*/
|
|
161
155
|
wrap<$Return, const $WrapRegistry extends PlainObject = $Registry>(overlay: Overlay<$WrapRegistry>, block: (scope: Instance<$WrapRegistry>) => $Return): $Return;
|
|
@@ -6,7 +6,7 @@ import { Schema } from "./Schema";
|
|
|
6
6
|
* `T.opaque((random) => new Map<string, number>())` needs no annotation.
|
|
7
7
|
*
|
|
8
8
|
* `produce` receives this schema's own seeded stream, so an opaque value still
|
|
9
|
-
* replays from a
|
|
9
|
+
* replays from a salt; see `Types.ts`'s `Produce`. No bare form and no `.as()`
|
|
10
10
|
* — `produce` is the whole schema.
|
|
11
11
|
*/
|
|
12
12
|
export default function <$T>(produce: Produce<$T>): Schema<$T>;
|
|
@@ -29,13 +29,13 @@ export type Fabricator<$Schema extends {
|
|
|
29
29
|
* sibling expansions at the same depth (an `array` of three `self` children
|
|
30
30
|
* calls `fabricateAt` three times on one shared element Fabricator; the schema
|
|
31
31
|
* does not tell them apart). Each expansion gets its own _root_: `forkSource`
|
|
32
|
-
* mints an isolated `RandomSource`
|
|
33
|
-
* `fabricateAt`
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* mints an isolated `RandomSource` salted from this node's draw, and each
|
|
33
|
+
* `fabricateAt` resolves an ordinary construction root on it
|
|
34
|
+
* (`RandomSource.toRoot`), recorded on each expansion's `trace`. The private
|
|
35
|
+
* source's construction counter orders expansions; nothing to increment here.
|
|
36
|
+
* Isolation also keeps this node's data-dependent draws from perturbing (or
|
|
37
|
+
* being perturbed by) an unrelated Fabricator from the same `initialize()`
|
|
38
|
+
* instance.
|
|
39
39
|
*
|
|
40
40
|
* Each `self` gets its own independently-dispatched expansion — calling
|
|
41
41
|
* `context.self` twice (two array slots) is two `fabricateAt` calls, each with
|
|
@@ -43,4 +43,4 @@ export type Fabricator<$Schema extends {
|
|
|
43
43
|
* shared-element one. Sibling tree branches therefore draw independently, not a
|
|
44
44
|
* correlated shared sequence.
|
|
45
45
|
*/
|
|
46
|
-
export declare function Fabricator<$Body>(context: FabricatorContext<Schema<$Body>>, forkSource: (
|
|
46
|
+
export declare function Fabricator<$Body>(context: FabricatorContext<Schema<$Body>>, forkSource: (salt: string) => RandomSource, make: (schema: unknown, path: ReadonlyArray<string>, context: ConstructionContext) => NaiveFabricator<unknown>): Fabricator<Schema<$Body>>;
|