@ghostry/fabricator 0.0.3 → 0.0.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/dist/esm/Enumeration/Enumerate.js +7 -6
- package/dist/esm/Error/index.js +1 -1
- package/dist/esm/Fabricator/Constructor.js +11 -10
- package/dist/esm/Harnessing/Core.js +7 -16
- package/dist/esm/Instance/Core.js +51 -17
- package/dist/esm/Instance/Stack/Async.js +10 -2
- package/dist/esm/Instance/Stack/Sync.js +2 -1
- package/dist/esm/Instance/Stack/Visible.js +13 -0
- package/dist/esm/Primitive/recursive/Fabricator.js +1 -1
- package/dist/esm/Random/index.js +2 -2
- package/dist/types/Enumeration/Enumerate.d.ts +15 -15
- package/dist/types/Enumeration/Types.d.ts +2 -2
- package/dist/types/Error/index.d.ts +8 -7
- package/dist/types/Fabricator/Constructor.d.ts +10 -7
- package/dist/types/Fabricator/Types.d.ts +15 -14
- package/dist/types/Harnessing/Core.d.ts +25 -25
- package/dist/types/Harnessing/Types.d.ts +56 -13
- package/dist/types/Instance/Core.d.ts +17 -6
- package/dist/types/Instance/Stack/Async.d.ts +7 -0
- package/dist/types/Instance/Stack/Sync.d.ts +5 -3
- package/dist/types/Instance/Stack/Visible.d.ts +22 -0
- package/dist/types/Instance/Types.d.ts +169 -19
- package/dist/types/Primitive/choice/Fabricator.d.ts +1 -1
- package/dist/types/Primitive/nullable/Fabricator.d.ts +1 -1
- package/dist/types/Primitive/nullish/Fabricator.d.ts +2 -2
- package/dist/types/Primitive/object/optional/Fabricator.d.ts +2 -2
- package/dist/types/Primitive/recursive/Fabricator.d.ts +7 -8
- package/dist/types/Primitive/recursive/Types.d.ts +1 -1
- package/dist/types/Primitive/recursive/self/Types.d.ts +1 -1
- package/dist/types/Primitive/undefinable/Fabricator.d.ts +1 -1
- package/dist/types/Random/Types.d.ts +23 -21
- package/dist/types/Random/index.d.ts +3 -3
- package/dist/types/harnessing.d.ts +18 -6
- package/dist/types/index.d.ts +11 -2
- package/dist/types/internal.d.ts +1 -1
- package/package.json +4 -1
|
@@ -38,34 +38,77 @@ export type Identity = {
|
|
|
38
38
|
/**
|
|
39
39
|
* One context key's value, as a function of the test's `Identity` rather than a
|
|
40
40
|
* fixed value.
|
|
41
|
+
*
|
|
42
|
+
* `established` is whatever this integration's own wrapper handed forward. For
|
|
43
|
+
* this integration that is the scoped `Instance` `wrap` opened, which is the
|
|
44
|
+
* whole of what `provides.fabricator` returns — so the value travels from the
|
|
45
|
+
* wrapper to the provider directly, with no mutable slot written on the way in
|
|
46
|
+
* and read on the way out.
|
|
47
|
+
*/
|
|
48
|
+
export type Provider<$Value, $Established = void> = (args: ProviderArgs<$Established>) => $Value;
|
|
49
|
+
/**
|
|
50
|
+
* What `@ghostry/harness` hands {@link Integration.frame}: one object, never
|
|
51
|
+
* positional arguments, so a field added to the contract later is a key an
|
|
52
|
+
* existing hook ignores rather than a parameter it has to thread past.
|
|
41
53
|
*/
|
|
42
|
-
export type
|
|
54
|
+
export type FrameArgs = {
|
|
55
|
+
readonly identity: Identity;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* What it hands each provider: everything {@link FrameArgs} carries, plus what
|
|
59
|
+
* this integration's own wrapper established — for this integration, the scoped
|
|
60
|
+
* `Instance` that `wrap` opened.
|
|
61
|
+
*/
|
|
62
|
+
export type ProviderArgs<$Established = void> = FrameArgs & {
|
|
63
|
+
readonly established: $Established;
|
|
64
|
+
};
|
|
43
65
|
/**
|
|
44
66
|
* The keys an integration contributes, and how each is produced. Homomorphic
|
|
45
67
|
* over `$Context`, so the context an integration contributes is read back out
|
|
46
68
|
* of this object's shape with no separate key declaration to keep in sync.
|
|
47
69
|
*/
|
|
48
|
-
export type Provides<$Context extends object> = {
|
|
49
|
-
readonly [$Key in keyof $Context]: Provider<$Context[$Key]>;
|
|
70
|
+
export type Provides<$Context extends object, $Established = void> = {
|
|
71
|
+
readonly [$Key in keyof $Context]: Provider<$Context[$Key], $Established>;
|
|
50
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* How an integration runs the body when the body must run _inside_ something.
|
|
75
|
+
* Here that is fabricator's own `wrap`, whose block parameter is already this
|
|
76
|
+
* shape — the scoped `Instance` it opens is what reaches `body`, and therefore
|
|
77
|
+
* what reaches the providers.
|
|
78
|
+
*
|
|
79
|
+
* Generic in its return and must hand the body's value back unchanged: that is
|
|
80
|
+
* what keeps a synchronous test synchronous and what lets frames nest.
|
|
81
|
+
*/
|
|
82
|
+
export type Wrapper<$Established = void> = <$Return>(body: (established: $Established) => $Return) => $Return;
|
|
83
|
+
/**
|
|
84
|
+
* What {@link Integration.frame} returns: a generator with **one** suspension
|
|
85
|
+
* point. Everything before the `yield` is setup, the body runs at the `yield`,
|
|
86
|
+
* and everything after it is teardown, resumed when the body _settles_.
|
|
87
|
+
*
|
|
88
|
+
* `@ghostry/harness` accepts an `AsyncGenerator` here too. This declares only
|
|
89
|
+
* the synchronous half, because that is the half this integration uses and the
|
|
90
|
+
* narrower type still satisfies the wider one — the same reason `frame` is
|
|
91
|
+
* required below though it is optional there. Opening a fabricator scope is
|
|
92
|
+
* synchronous, and declaring the async arm would invite an integration that
|
|
93
|
+
* promotes every test in the suite to a promise for no reason.
|
|
94
|
+
*/
|
|
95
|
+
export type Frame<$Established = void> = Generator<Wrapper<$Established> | void, void, unknown>;
|
|
51
96
|
/**
|
|
52
97
|
* What `integration(instance)` is, as `@ghostry/harness`'s `initialize` sees
|
|
53
98
|
* it. The subset of that package's contract this integration actually uses, not
|
|
54
|
-
* a copy of all of it:
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* though optional there, because this integration always declares it.
|
|
99
|
+
* a copy of all of it: an object lacking an optional member still satisfies the
|
|
100
|
+
* contract structurally, so `frame` is required here though optional there,
|
|
101
|
+
* because this integration always declares it.
|
|
58
102
|
*
|
|
59
103
|
* `provides` is the _only_ source of context keys; `initialize` rejects a
|
|
60
104
|
* collision across integrations by reading `Object.keys(provides)`. Each
|
|
61
|
-
* provider runs
|
|
62
|
-
*
|
|
63
|
-
* work and what lets frames nest.
|
|
105
|
+
* provider runs _inside_ that frame and receives the same `$Established` the
|
|
106
|
+
* wrapper handed to the body.
|
|
64
107
|
*/
|
|
65
|
-
export type Integration<$Context extends object> = {
|
|
108
|
+
export type Integration<$Context extends object, $Established = void> = {
|
|
66
109
|
readonly name: string;
|
|
67
|
-
readonly provides: Provides<$Context>;
|
|
68
|
-
|
|
110
|
+
readonly provides: Provides<$Context, $Established>;
|
|
111
|
+
frame(args: FrameArgs): Frame<$Established>;
|
|
69
112
|
};
|
|
70
113
|
/**
|
|
71
114
|
* The slice of the test context `integration(instance)` contributes — one key,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RandomSource } from "../Random/Types";
|
|
2
2
|
import type { PlainObject } from "../Utility/Types";
|
|
3
|
-
import type { Config, Instance, Overlay, Stack } from "./Types";
|
|
3
|
+
import type { Ancestry, Config, Instance, Overlay, Stack } from "./Types";
|
|
4
4
|
/**
|
|
5
5
|
* `combinatorial`'s default limit — `2**10`, so it admits ten independent
|
|
6
6
|
* binary axes before requiring the caller to raise it explicitly. Each
|
|
@@ -51,12 +51,23 @@ export declare function overlay<$Registry extends PlainObject>(base: Partial<Con
|
|
|
51
51
|
* than each independently re-deriving one from the same config (and so silently
|
|
52
52
|
* diverging/duplicating construction ordinals).
|
|
53
53
|
*
|
|
54
|
-
* `stack`
|
|
55
|
-
* function never reads
|
|
56
|
-
* `Fabricator`/`combinatorial`/`coverage`
|
|
57
|
-
*
|
|
54
|
+
* `stack` and `ancestry` are threaded straight through to
|
|
55
|
+
* `Constructor`/`enumerables` — this function never reads the stack itself,
|
|
56
|
+
* only passes both along so every built `Fabricator`/`combinatorial`/`coverage`
|
|
57
|
+
* can consult whichever frame is visible to _this_ instance _at the moment each
|
|
58
|
+
* is called_, not at this moment.
|
|
59
|
+
*
|
|
60
|
+
* `parent` describes the instance this one is derived from, and its absence is
|
|
61
|
+
* the single marker of a root: `initialize` passes none, every `fork`/`wrap`
|
|
62
|
+
* passes the receiver's.
|
|
63
|
+
*
|
|
64
|
+
* A fresh token is minted either way, so no two instances share an identity,
|
|
65
|
+
* and the `ancestry` built here is _this_ instance's: the parent's plus one.
|
|
58
66
|
*/
|
|
59
|
-
export declare function instantiate<$Registry extends PlainObject>(config: Config<$Registry>, stack: Stack
|
|
67
|
+
export declare function instantiate<$Registry extends PlainObject>(config: Config<$Registry>, stack: Stack, parent?: {
|
|
68
|
+
ancestry: Ancestry;
|
|
69
|
+
root: Instance<PlainObject>;
|
|
70
|
+
}): {
|
|
60
71
|
instance: Instance<$Registry>;
|
|
61
72
|
source: RandomSource;
|
|
62
73
|
};
|
|
@@ -7,6 +7,13 @@ import type { Stack } from "../Types";
|
|
|
7
7
|
* package importable on a runtime with no `node:async_hooks` while every
|
|
8
8
|
* runtime that has one gets async-safe `wrap` with nothing to configure.
|
|
9
9
|
*
|
|
10
|
+
* The store holds the whole open chain, not one `Frame`: `run` _replaces_ the
|
|
11
|
+
* store for the duration of `block`, so `enter` rebuilds the chain with the new
|
|
12
|
+
* frame appended. A fresh array per `enter` is also what isolates concurrent
|
|
13
|
+
* `wrap`s — each async context keeps the chain it entered with, and an inner
|
|
14
|
+
* `enter` cannot mutate an outer one's view. Chains are at nesting depth, so
|
|
15
|
+
* copying one costs nothing worth avoiding.
|
|
16
|
+
*
|
|
10
17
|
* `AsyncLocalStorage.run` returns whatever `block` returns, so this satisfies
|
|
11
18
|
* `enter`'s sync-preserving `<$Return>` signature exactly as the sync carrier
|
|
12
19
|
* does — a synchronous `wrap` is unaffected by which carrier is in play.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Stack } from "../Types";
|
|
2
2
|
/**
|
|
3
3
|
* The synchronous carrier: a private `Frame[]`, pushed on `enter` and popped in
|
|
4
|
-
* a `finally` — correct even around a `throw` from `block`.
|
|
4
|
+
* a `finally` — correct even around a `throw` from `block`. Already in
|
|
5
|
+
* outermost-first order, which is the order `visible` reports, so it hands the
|
|
6
|
+
* array straight to `toVisible` and does no filtering of its own.
|
|
5
7
|
*
|
|
6
8
|
* Selected by the `#stack` `default` condition (`package.json`), i.e. on any
|
|
7
9
|
* runtime without `node:async_hooks` — in practice a browser bundle. Its frame
|
|
8
10
|
* 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
|
|
10
|
-
* shared
|
|
11
|
+
* async block's frame unwinds at the block's first suspension point, and one
|
|
12
|
+
* shared array could not represent two overlapping scopes even if it did await.
|
|
11
13
|
* Both are why `asynchronous` is `false` and `wrap` refuses an async block here
|
|
12
14
|
* rather than resolving it against the base instance with no signal.
|
|
13
15
|
*/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Ancestry, Frame, Stack } from "../Types";
|
|
2
|
+
/**
|
|
3
|
+
* The frames in `frames` that `ancestry` may resolve against, outermost first —
|
|
4
|
+
* the single definition of {@link Stack.visible}'s rule, which both carriers
|
|
5
|
+
* delegate to so neither can drift from the other. A carrier's own job is
|
|
6
|
+
* reduced to holding the chain in whatever way its runtime allows.
|
|
7
|
+
*
|
|
8
|
+
* Order is preserved rather than reduced to the innermost match, because the
|
|
9
|
+
* count is `context.depth` and the innermost is just the last element.
|
|
10
|
+
* Skipping, rather than stopping at, the first invisible frame is the outward
|
|
11
|
+
* walk: with a parent's `wrap` open and a child's nested inside it, that
|
|
12
|
+
* child's sibling must pass over the inner frame and still resolve against the
|
|
13
|
+
* outer one.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toVisible(frames: ReadonlyArray<Frame>, ancestry: Ancestry): ReadonlyArray<Frame>;
|
|
16
|
+
/**
|
|
17
|
+
* The one frame a read resolves against: the innermost frame visible to
|
|
18
|
+
* `ancestry`, or `undefined` outside any. Every consumer — `resolveScope`,
|
|
19
|
+
* `effectiveSource`, the `context` getters — goes through here rather than
|
|
20
|
+
* indexing `visible()` itself, so "innermost visible" has one definition.
|
|
21
|
+
*/
|
|
22
|
+
export declare function toInnermostFrame(stack: Stack, ancestry: Ancestry): Frame | undefined;
|
|
@@ -47,27 +47,77 @@ export type Overlay<$Registry extends PlainObject> = Partial<Omit<Config<$Regist
|
|
|
47
47
|
readonly salt?: Salt | Layered;
|
|
48
48
|
readonly clock?: Date | "derived" | undefined;
|
|
49
49
|
};
|
|
50
|
+
declare const Brand: unique symbol;
|
|
51
|
+
/**
|
|
52
|
+
* One instance's identity — minted fresh by every `instantiate`
|
|
53
|
+
* (`Instance/Core.ts`) and never equal to any other. Branded, and the brand key
|
|
54
|
+
* is module-private, so a token cannot be forged from outside this package: the
|
|
55
|
+
* only way to hold one is to have been handed an `Instance`.
|
|
56
|
+
*/
|
|
57
|
+
export type Token = symbol & {
|
|
58
|
+
readonly [Brand]: "Instance";
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Where an instance sits in its lineage: every token from the root down to and
|
|
62
|
+
* including its own, so `ancestry[0]` is the lineage identity (`a.ancestry[0]
|
|
63
|
+
* === b.ancestry[0]` answers "same root?") and the last element is the instance
|
|
64
|
+
* itself. Non-empty by construction — a root `initialize()` mints one token
|
|
65
|
+
* before there is anything to inherit.
|
|
66
|
+
*
|
|
67
|
+
* Derived from the _receiver_: `fork` and `wrap` alike append to the ancestry
|
|
68
|
+
* of the instance they were called on, exactly as they both lay their overlay
|
|
69
|
+
* over that instance's `config`. One rule, so an instance's position and its
|
|
70
|
+
* configuration always agree about who its parent is.
|
|
71
|
+
*
|
|
72
|
+
* Two chains describe instances on the same ancestral line when either is a
|
|
73
|
+
* prefix of the other, which is what {@link Stack.visible} tests. That relation
|
|
74
|
+
* decides whose calls resolve against whose frames: a parent's calls resolve
|
|
75
|
+
* against a child's frame and a child's against a parent's, while two siblings
|
|
76
|
+
* resolve against neither's. Note this never crosses lineages, and not for want
|
|
77
|
+
* of identity — two roots hold two separate carriers, so a `wrap` on one pushes
|
|
78
|
+
* where the other's reads never look.
|
|
79
|
+
*/
|
|
80
|
+
export type Ancestry = readonly [Token, ...ReadonlyArray<Token>];
|
|
50
81
|
/**
|
|
51
82
|
* One active `wrap` — its resolved config plus the single `RandomSource` every
|
|
52
83
|
* build reached inside that `wrap` shares, whether reached implicitly (any
|
|
53
|
-
* instance
|
|
84
|
+
* instance on the origin's ancestral line consulting the frame) or explicitly
|
|
54
85
|
* (`scope.Fabricator`, the `Instance` passed to the block). Storing the scope's
|
|
55
86
|
* own already-built `source` here, rather than each consumer re-deriving one
|
|
56
87
|
* from `config`, keeps the two routes resolving against the _same_ source —
|
|
57
88
|
* sharing one set of construction-ordinal counters — instead of each silently
|
|
58
89
|
* starting its own.
|
|
90
|
+
*
|
|
91
|
+
* `instance` is that same scope, kept so `context.scope()` can hand back the
|
|
92
|
+
* configuration in effect as a usable `Instance` rather than only as its
|
|
93
|
+
* separate `salt`/`algorithm`/`clock` fields.
|
|
94
|
+
*
|
|
95
|
+
* `ancestry` is the **origin's** — the instance `wrap` was called on — not the
|
|
96
|
+
* scope's. The scope is a fresh child of the origin, so keying on it would make
|
|
97
|
+
* every `fork` taken off that origin a _sibling_ of the scope, and calls on
|
|
98
|
+
* those forks would stop resolving against the frame. Keying on the origin
|
|
99
|
+
* keeps everything on the origin's own line resolving against it, which is the
|
|
100
|
+
* whole point of entering one.
|
|
59
101
|
*/
|
|
60
102
|
export type Frame = {
|
|
61
103
|
readonly config: Config<PlainObject>;
|
|
62
104
|
readonly source: RandomSource;
|
|
105
|
+
readonly instance: Instance<PlainObject>;
|
|
106
|
+
readonly ancestry: Ancestry;
|
|
63
107
|
};
|
|
64
108
|
/**
|
|
65
|
-
* The
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
109
|
+
* The ambient frame carrier. Created once at a root `initialize()` and threaded
|
|
110
|
+
* — never re-created — through every `fork`/`wrap` descended from it (see
|
|
111
|
+
* `instantiate`, `Instance/Core.ts`).
|
|
112
|
+
*
|
|
113
|
+
* A carrier holds a chain of open frames and nothing else; which of them any
|
|
114
|
+
* given reader may see is {@link Ancestry}'s business, resolved by
|
|
115
|
+
* {@link visible}. Two unrelated `initialize()` calls hold separate carriers
|
|
116
|
+
* and so stay fully isolated — and because a reader is now gated on ancestry
|
|
117
|
+
* rather than on carrier identity, handing the _same_ carrier to two
|
|
118
|
+
* `initialize()` calls does not join them either: their roots mint unrelated
|
|
119
|
+
* tokens, so neither one's reads ever resolve the other's frames. `initialize({
|
|
120
|
+
* stack })` is therefore purely a choice of carrier.
|
|
71
121
|
*/
|
|
72
122
|
export type Stack = {
|
|
73
123
|
/**
|
|
@@ -80,24 +130,71 @@ export type Stack = {
|
|
|
80
130
|
* instance. See `Instance/Stack/Sync.ts` and `Instance/Stack/Async.ts`.
|
|
81
131
|
*/
|
|
82
132
|
readonly asynchronous: boolean;
|
|
83
|
-
current(): Frame | undefined;
|
|
84
133
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
134
|
+
* Every open frame `ancestry` may resolve against, outermost first: those
|
|
135
|
+
* whose own `ancestry` is a prefix of this one or has this one as a prefix.
|
|
136
|
+
* The innermost visible frame — what a build or a `context` read actually
|
|
137
|
+
* resolves against — is the last element, and the count is `context.depth`.
|
|
138
|
+
*
|
|
139
|
+
* Filtering, rather than simply reporting the innermost frame, is what makes
|
|
140
|
+
* the outward walk possible: with a parent's `wrap` open and a child's nested
|
|
141
|
+
* inside it, the child's _sibling_ must skip the inner frame and still find
|
|
142
|
+
* the outer one. Callers never filter themselves — `toVisible`
|
|
143
|
+
* (`Instance/Stack/Visible.ts`) is the single definition both carriers
|
|
144
|
+
* delegate to, so the rule cannot drift between them.
|
|
145
|
+
*/
|
|
146
|
+
visible(ancestry: Ancestry): ReadonlyArray<Frame>;
|
|
147
|
+
/**
|
|
148
|
+
* Append `frame`, run `block`, remove it in a `finally`, so a frame unwinds
|
|
149
|
+
* correctly even if `block` throws. Appends rather than replaces: the chain
|
|
150
|
+
* has to stay intact for {@link visible} to walk outward past a frame this
|
|
151
|
+
* reader cannot see.
|
|
87
152
|
*/
|
|
88
153
|
enter<$Return>(frame: Frame, block: () => $Return): $Return;
|
|
89
154
|
};
|
|
90
155
|
/**
|
|
91
|
-
*
|
|
92
|
-
* or
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
156
|
+
* What is in effect _right now_ — the innermost `wrap` frame this instance's
|
|
157
|
+
* calls can resolve against, or the instance itself when there is none.
|
|
158
|
+
*
|
|
159
|
+
* The four value properties are getters, so they are a live view only while
|
|
160
|
+
* this stays an object: destructuring one, or spreading the object, calls that
|
|
161
|
+
* getter once and freezes the result.
|
|
162
|
+
*
|
|
163
|
+
* {@link Context.scope} is deliberately a **function** rather than another
|
|
164
|
+
* getter: it is the one member a caller _acts through_ rather than reads, so
|
|
165
|
+
* freezing it would yield correct-looking code deriving from the wrong base. As
|
|
166
|
+
* a function it survives destructuring — `const { scope } = instance.context`,
|
|
167
|
+
* then `scope()`, still resolves live. Distinct from `ConstructionContext`
|
|
168
|
+
* (`Fabricator/Types.ts`), which is one construction's internal dispatch
|
|
169
|
+
* plumbing; this is the caller-facing "what is in effect."
|
|
96
170
|
*/
|
|
97
171
|
export type Context = {
|
|
98
172
|
readonly salt: ReadonlyArray<string>;
|
|
99
173
|
readonly algorithm: Algorithm;
|
|
100
174
|
readonly clock: number;
|
|
175
|
+
/**
|
|
176
|
+
* The configuration in effect as an `Instance` — the visible frame's own
|
|
177
|
+
* scope, or this instance outside any. This is how to compose deliberately
|
|
178
|
+
* against whatever is active: `context.scope().wrap({ salt: layer("x") },
|
|
179
|
+
* ...)` lays over the frame in effect, where a plain `wrap` lays over the
|
|
180
|
+
* instance it was called on. Unlike rebuilding an overlay out of `salt` by
|
|
181
|
+
* hand, it carries `types`, `limits`, `algorithm` and `clock` across too.
|
|
182
|
+
*
|
|
183
|
+
* A function, not a getter, so capturing it captures the _lookup_ rather than
|
|
184
|
+
* one answer (see this type's own note above). What it returns is an ordinary
|
|
185
|
+
* `Instance`, fixed like any other — so holding the **result** across a frame
|
|
186
|
+
* change is a caller stating they wanted that one, while holding `scope`
|
|
187
|
+
* itself stays live.
|
|
188
|
+
*/
|
|
189
|
+
scope(): Instance<PlainObject>;
|
|
190
|
+
/**
|
|
191
|
+
* How many frames are currently visible to this instance — 0 outside any.
|
|
192
|
+
* Genuine dynamic nesting depth, counted off the carrier rather than inferred
|
|
193
|
+
* from {@link Ancestry}: a frame a sibling cannot see is not counted for that
|
|
194
|
+
* sibling, and entering two `wrap`s on one instance reads as 2 even though
|
|
195
|
+
* neither deepened anyone's ancestry.
|
|
196
|
+
*/
|
|
197
|
+
readonly depth: number;
|
|
101
198
|
};
|
|
102
199
|
/**
|
|
103
200
|
* A single initialized library instance: the registry it was given, and a
|
|
@@ -109,6 +206,49 @@ export type Context = {
|
|
|
109
206
|
export interface Instance<$Registry extends PlainObject> extends Pick<RandomSource, "salt"> {
|
|
110
207
|
/** The registry of type definers this instance was initialized with. */
|
|
111
208
|
readonly T: $Registry;
|
|
209
|
+
/**
|
|
210
|
+
* This instance's position in its lineage — see {@link Ancestry}. Exposed so a
|
|
211
|
+
* caller can reason about which frames a given instance's calls resolve
|
|
212
|
+
* against; the tokens themselves are opaque and comparable only by identity.
|
|
213
|
+
* For the ordinary "same lineage?" question, compare {@link root} instead.
|
|
214
|
+
*/
|
|
215
|
+
readonly ancestry: Ancestry;
|
|
216
|
+
/**
|
|
217
|
+
* The instance at the head of this lineage — the one `initialize()` returned.
|
|
218
|
+
* A root's own `root` is itself, so this is never `undefined` and no caller
|
|
219
|
+
* handles absence.
|
|
220
|
+
*
|
|
221
|
+
* Its job is identity: `a.root === b.root` answers "same lineage?", which is
|
|
222
|
+
* what `fork`/`wrap` descent preserves and what two separate `initialize()`
|
|
223
|
+
* calls never share — even when handed the same `stack`.
|
|
224
|
+
*
|
|
225
|
+
* It is **not** a way to reach "the ambient instance": every instance in a
|
|
226
|
+
* lineage resolves against the frames on its own line, so there is nothing to
|
|
227
|
+
* reach for. Nor is it a configuration to build against in preference to this
|
|
228
|
+
* one — `root`'s config is the lineage's starting point, not whatever is
|
|
229
|
+
* currently in effect. For that, see {@link Context.scope}.
|
|
230
|
+
*
|
|
231
|
+
* Typed at `PlainObject`, which is a deliberate shortcut rather than a
|
|
232
|
+
* necessity — unlike {@link Context.scope}, whose registry depends on which
|
|
233
|
+
* instance entered the innermost visible frame and so cannot be known
|
|
234
|
+
* statically at all.
|
|
235
|
+
*
|
|
236
|
+
* A root's registry is fixed at `initialize` and nothing later disturbs it: a
|
|
237
|
+
* `fork({ types })` mints a _new_ instance with a different registry and
|
|
238
|
+
* leaves the root exactly as it was. What is lost is the descendant's ability
|
|
239
|
+
* to name it — after such a fork this instance's `$Registry` is the fork's,
|
|
240
|
+
* so the root's is no longer recoverable from it. Typing this
|
|
241
|
+
* `Instance<$Registry>` would therefore be wrong if someone overrode
|
|
242
|
+
* `types`.
|
|
243
|
+
*
|
|
244
|
+
* Recovering it would mean threading a second parameter (`Instance<$Registry,
|
|
245
|
+
* $Root>`) through `fork` and `wrap`, which is a poor trade for an accessor
|
|
246
|
+
* whose job is identity: if you mean to _build_, you want the registry of the
|
|
247
|
+
* instance you are holding, not the one the lineage started from. So `root.T`
|
|
248
|
+
* is untyped, while `root.Fabricator` is unaffected since it carries no
|
|
249
|
+
* registry parameter.
|
|
250
|
+
*/
|
|
251
|
+
readonly root: Instance<PlainObject>;
|
|
112
252
|
/**
|
|
113
253
|
* Turn a Schema built from `T` into a live Fabricator, deriving fresh
|
|
114
254
|
* randomness from this instance's own salt for whichever leaves actually need
|
|
@@ -147,10 +287,19 @@ export interface Instance<$Registry extends PlainObject> extends Pick<RandomSour
|
|
|
147
287
|
* — a build reached after an `await` inside `block` sees this instance's own
|
|
148
288
|
* configuration again, not the wrap's.
|
|
149
289
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
290
|
+
* The overlay lays over _this instance's_ config, exactly as `fork`'s does,
|
|
291
|
+
* whether or not a frame is already open. So a nested `wrap` accumulates when
|
|
292
|
+
* it is called on the enclosing scope — `wrap(a, (scope) => scope.wrap(b,
|
|
293
|
+
* ...))` — and restates from this instance when it is called on a receiver
|
|
294
|
+
* bound outside, as a destructured `wrap` is. To compose onto whatever is
|
|
295
|
+
* active regardless of receiver, go through `context.scope().wrap(...)`.
|
|
296
|
+
*
|
|
297
|
+
* While the block runs, calls made on this instance's ancestral line resolve
|
|
298
|
+
* against the scope — its forks, their forks, and its own ancestors up to the
|
|
299
|
+
* root. Calls on a _sibling_ do not: a frame entered on one `fork` is not one
|
|
300
|
+
* that another `fork` of the same parent can resolve against, which keeps two
|
|
301
|
+
* unrelated derivations from drawing each other's data. See
|
|
302
|
+
* {@link Ancestry}.
|
|
154
303
|
*/
|
|
155
304
|
wrap<$Return, const $WrapRegistry extends PlainObject = $Registry>(overlay: Overlay<$WrapRegistry>, block: (scope: Instance<$WrapRegistry>) => $Return): $Return;
|
|
156
305
|
/**
|
|
@@ -161,3 +310,4 @@ export interface Instance<$Registry extends PlainObject> extends Pick<RandomSour
|
|
|
161
310
|
*/
|
|
162
311
|
readonly context: Context;
|
|
163
312
|
}
|
|
313
|
+
export {};
|
|
@@ -26,6 +26,6 @@ export type Fabricator<$Schema extends {
|
|
|
26
26
|
* shared across every element. The weighted pick happens on this field's
|
|
27
27
|
* private stream; only the chosen option's `fabricate()` is called, so an
|
|
28
28
|
* unpicked option never advances its stream (safe for the same reason skipping
|
|
29
|
-
* `object.omittable`'s inner draw is — see
|
|
29
|
+
* `object.omittable`'s inner draw is — see AGENTS.md's "Randomness").
|
|
30
30
|
*/
|
|
31
31
|
export declare function Fabricator<$Items extends Items>(context: FabricatorContext<Schema<$Items>>, weightings: ReadonlyArray<readonly [number, NaiveFabricator<any>]>): Fabricator<Schema<$Items>>;
|
|
@@ -24,7 +24,7 @@ export type Fabricator<$Schema extends {
|
|
|
24
24
|
* `source` is already dispatched into its own independent stream by
|
|
25
25
|
* `Constructor.ts` regardless of this roll, skipping its draw here can never
|
|
26
26
|
* perturb any other field's reproducibility (see
|
|
27
|
-
* `object/omittable/Fabricator.ts`, and
|
|
27
|
+
* `object/omittable/Fabricator.ts`, and AGENTS.md's "Randomness"). 50/50 by
|
|
28
28
|
* default; `.weighted(...)` (`Schema.ts`) reweights either outcome relative to
|
|
29
29
|
* that same default of `1`.
|
|
30
30
|
*/
|
|
@@ -24,12 +24,12 @@ export type Fabricator<$Schema extends {
|
|
|
24
24
|
* by default; not achievable by composing `nullable(T.undefinable(inner))`'s
|
|
25
25
|
* two independent 50/50 rolls (that is 50/25/25, not 33/33/33), hence the
|
|
26
26
|
* dedicated three-way `weighted()` here — see `object/optional/Fabricator.ts`
|
|
27
|
-
* and
|
|
27
|
+
* and AGENTS.md's "Why `T.optional` isn't `omittable(undefinable(inner))`".
|
|
28
28
|
* `.weighted(...)` (`Schema.ts`) reweights individual outcomes relative to that
|
|
29
29
|
* same default of `1`; an unspecified outcome keeps it. Skipping `source`'s
|
|
30
30
|
* draw on the two non-"value" outcomes is safe for the same reason as
|
|
31
31
|
* `object/omittable/Fabricator.ts`: `source` already has its own independent
|
|
32
|
-
* stream, minted at build time regardless of this roll (see
|
|
32
|
+
* stream, minted at build time regardless of this roll (see AGENTS.md's
|
|
33
33
|
* "Randomness").
|
|
34
34
|
*/
|
|
35
35
|
export declare function Fabricator<$Definition extends Definition>(context: FabricatorContext<Schema<$Definition>>, source: BaseFabricator<any>): Fabricator<Schema<$Definition>>;
|
|
@@ -32,12 +32,12 @@ export declare function isObjectOptionalFabricator(candidate: BaseFabricator<unk
|
|
|
32
32
|
* Uniform (1/3 each) by default; not achievable by composing
|
|
33
33
|
* `object.omittable(T.undefinable(inner))`'s two independent 50/50 rolls (that
|
|
34
34
|
* is 50/25/25, not 33/33/33), hence the dedicated three-way `weighted()` here —
|
|
35
|
-
* see
|
|
35
|
+
* see AGENTS.md's "Why `T.optional` isn't `omittable(undefinable(inner))`"
|
|
36
36
|
* under "Compound / field-only kinds". `.weighted(...)` (`Schema.ts`) reweights
|
|
37
37
|
* individual outcomes relative to that same default of `1`; an unspecified
|
|
38
38
|
* outcome keeps it. Skipping `source`'s draw on the two non-"value" outcomes is
|
|
39
39
|
* safe for the same reason as `object/omittable/Fabricator.ts`: `source`
|
|
40
40
|
* already has its own independent stream, minted at build time regardless of
|
|
41
|
-
* this roll (see
|
|
41
|
+
* this roll (see AGENTS.md's "Randomness").
|
|
42
42
|
*/
|
|
43
43
|
export declare function Fabricator<$Definition extends Definition>(context: FabricatorContext<Schema<$Definition>>, source: BaseFabricator<any>): Fabricator<Schema<$Definition>>;
|
|
@@ -28,14 +28,13 @@ export type Fabricator<$Schema extends {
|
|
|
28
28
|
* how deep this `fabricate()` goes — so no structural path distinguishes
|
|
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
|
-
* does not tell them apart).
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* instance.
|
|
31
|
+
* does not tell them apart). `forkSource` mints an isolated `RandomSource`
|
|
32
|
+
* salted from this node's draw, and each `fabricateAt` resolves a
|
|
33
|
+
* `ConstructionTrace` on it (`RandomSource.toConstructionTrace`), recorded on
|
|
34
|
+
* each expansion's `trace`. The private source's construction counter orders
|
|
35
|
+
* expansions; nothing to increment here. Isolation also keeps this node's
|
|
36
|
+
* data-dependent draws from perturbing (or being perturbed by) an unrelated
|
|
37
|
+
* Fabricator from the same `initialize()` instance.
|
|
39
38
|
*
|
|
40
39
|
* Each `self` gets its own independently-dispatched expansion — calling
|
|
41
40
|
* `context.self` twice (two array slots) is two `fabricateAt` calls, each with
|
|
@@ -6,7 +6,7 @@ import { Produces, type Adaptation, type Kind, type Meta } from "../../Types";
|
|
|
6
6
|
* `TS2456` ("`RecursiveValue` circularly references itself") — legal _only_
|
|
7
7
|
* because `ValueOf`'s second argument is read through an interface member
|
|
8
8
|
* (`this["bindings"]` on every composite `Core`), which TypeScript defers. Do
|
|
9
|
-
* not rewrite as a conditional; that reintroduces the error. See `
|
|
9
|
+
* not rewrite as a conditional; that reintroduces the error. See `AGENTS.md`'s
|
|
10
10
|
* "`ValueOf`'s `$Bindings`".
|
|
11
11
|
*
|
|
12
12
|
* Wherever `self` sits in `$Body` (nested through `array`/`object`/`tuple`/
|
|
@@ -15,7 +15,7 @@ export type Fabricated<$Bindings extends unknown[] = []> = $Bindings[0];
|
|
|
15
15
|
export type Meta = Record<string, never>;
|
|
16
16
|
/**
|
|
17
17
|
* An `interface`, not a `type` alias, so `this["bindings"]` resolves — the
|
|
18
|
-
* entire mechanism this kind exists for. See `
|
|
18
|
+
* entire mechanism this kind exists for. See `AGENTS.md`'s "`ValueOf`'s
|
|
19
19
|
* `$Bindings`" section for why threading requires this.
|
|
20
20
|
*/
|
|
21
21
|
export interface Core<$Adaptations extends Adaptations = {}> {
|
|
@@ -24,7 +24,7 @@ export type Fabricator<$Schema extends {
|
|
|
24
24
|
* since `source` is already dispatched into its own independent stream by
|
|
25
25
|
* `Constructor.ts` regardless of this roll, skipping its draw here can never
|
|
26
26
|
* perturb any other field's reproducibility (see
|
|
27
|
-
* `object/omittable/Fabricator.ts`, and
|
|
27
|
+
* `object/omittable/Fabricator.ts`, and AGENTS.md's "Randomness"). 50/50 by
|
|
28
28
|
* default; `.weighted(...)` (`Schema.ts`) reweights either outcome relative to
|
|
29
29
|
* that same default of `1`.
|
|
30
30
|
*/
|
|
@@ -68,8 +68,8 @@ export type Trace = {
|
|
|
68
68
|
};
|
|
69
69
|
/**
|
|
70
70
|
* Caller-supplied overrides for the construction-owned {@link Trace} slots
|
|
71
|
-
* {@link RandomSource.
|
|
72
|
-
* they are per-node and applied in `construct()`, not here.
|
|
71
|
+
* {@link RandomSource.toConstructionTrace} resolves. `path`/`kind` are the only
|
|
72
|
+
* slots absent: they are per-node and applied in `construct()`, not here.
|
|
73
73
|
*
|
|
74
74
|
* `salt` is a pin like the rest — it substitutes into that trace slot and does
|
|
75
75
|
* nothing else. It does not fork, so it neither resets nor sidesteps this
|
|
@@ -82,17 +82,17 @@ export type Trace = {
|
|
|
82
82
|
* ordinal" — is taken verbatim and does not advance the counter. That is all a
|
|
83
83
|
* replay needs, since every real {@link Trace} carries a defined ordinal.
|
|
84
84
|
*/
|
|
85
|
-
export type
|
|
85
|
+
export type ConstructionPins = {
|
|
86
86
|
salt?: ReadonlyArray<string> | undefined;
|
|
87
87
|
clock?: number | undefined;
|
|
88
88
|
ordinal?: number | null | undefined;
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* Every {@link Trace} slot fixed once per construction, before a leaf supplies
|
|
92
|
+
* its own `path`/`kind`. `RandomSource.toConstructionTrace` resolves this once;
|
|
93
|
+
* callers spread it into a full {@link Trace} per leaf and hand that to
|
|
94
|
+
* `toStreamFromTrace`. One construction-ordinal bump is reused across every
|
|
95
|
+
* leaf that construction dispatches.
|
|
96
96
|
*/
|
|
97
97
|
export type ConstructionTrace = Omit<Trace, "path" | "kind">;
|
|
98
98
|
/**
|
|
@@ -173,9 +173,9 @@ export type Options = {
|
|
|
173
173
|
* constructions like any other. Two same-salt builds therefore diverge.
|
|
174
174
|
*
|
|
175
175
|
* `clock` / `ordinal` pin the construction-owned {@link Trace} slots
|
|
176
|
-
* {@link RandomSource.
|
|
177
|
-
* given `ordinal` — a number, or `null` for "no ordinal" — is taken
|
|
178
|
-
* with no counter bump, which is all a replay needs; without it, the
|
|
176
|
+
* {@link RandomSource.toConstructionTrace} would otherwise resolve. Definedness,
|
|
177
|
+
* not `in`: a given `ordinal` — a number, or `null` for "no ordinal" — is taken
|
|
178
|
+
* verbatim with no counter bump, which is all a replay needs; without it, the
|
|
179
179
|
* construction takes the source counter's next value. A salted construction is
|
|
180
180
|
* not, by default, asking for a different "now"; a replayed trace whose `clock`
|
|
181
181
|
* is present explicitly is.
|
|
@@ -205,20 +205,22 @@ export type ConstructorOptions = {
|
|
|
205
205
|
*/
|
|
206
206
|
export type RandomSource = {
|
|
207
207
|
/**
|
|
208
|
-
* Resolve
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* leaf: the returned {@link ConstructionTrace} is what every leaf
|
|
213
|
-
* completes into a full {@link Trace} and hands to
|
|
214
|
-
* construction-ordinal bump serves the whole
|
|
208
|
+
* Resolve this source's `salt`/`clock` and the next construction ordinal,
|
|
209
|
+
* each overridable by {@link ConstructionPins}. Called once per `new
|
|
210
|
+
* Fabricator(...)` (or per lazy expansion of a `T.recursive` schema, each of
|
|
211
|
+
* which resolves its own construction trace on a private forked source),
|
|
212
|
+
* never per leaf: the returned {@link ConstructionTrace} is what every leaf
|
|
213
|
+
* beneath it completes into a full {@link Trace} and hands to
|
|
214
|
+
* `toStreamFromTrace`. One construction-ordinal bump serves the whole
|
|
215
|
+
* construction.
|
|
215
216
|
*/
|
|
216
|
-
|
|
217
|
+
toConstructionTrace(pins?: ConstructionPins): ConstructionTrace;
|
|
217
218
|
/**
|
|
218
219
|
* The algorithm this source (and every fork of it) hashes with. Stream
|
|
219
220
|
* derivation is _not_ a member: it depends on no per-source state, so it is
|
|
220
|
-
* the free function `toStreamFromTrace(algorithm, trace)`.
|
|
221
|
-
* only stateful member (the construction
|
|
221
|
+
* the free function `toStreamFromTrace(algorithm, trace)`.
|
|
222
|
+
* `toConstructionTrace` is the only stateful member (the construction
|
|
223
|
+
* counter).
|
|
222
224
|
*/
|
|
223
225
|
readonly algorithm: Algorithm;
|
|
224
226
|
/**
|