@opetope/react 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/README.md +55 -31
- package/README.ru.md +55 -30
- package/dist/command-hook-controller.d.ts +0 -1
- package/dist/command-hook.d.ts +7 -7
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scenario-slot.d.ts +5 -1
- package/dist/testing.d.ts +7 -2
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# @opetope/react
|
|
2
2
|
|
|
3
|
+
## 0.9.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [9a5a689]
|
|
8
|
+
- @opetope/runtime@0.9.2
|
|
9
|
+
- @opetope/core@0.9.2
|
|
10
|
+
|
|
11
|
+
## 0.9.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- d7f346a: A command hook is `{ run, inFlight, outcome }`, and an owned attachment step is `step(key, run, cleanup)`.
|
|
16
|
+
|
|
17
|
+
`CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `Call` fields of a
|
|
18
|
+
`useModel` selection. It bought one arrow in JSX and nothing the types did not already hold: `onClick={logout.run}`
|
|
19
|
+
never compiled, because a `MouseEvent` is not a `void` input. A command without input now binds as
|
|
20
|
+
`onClick={() => void logout.run()}` (D292).
|
|
21
|
+
|
|
22
|
+
`lastResult` and `lastError` are folded into one field, `outcome`: the last settled outcome of this consumer, typed
|
|
23
|
+
as the `CommandOutcome` that `run()` resolves to, without its `cancelled` branch. A `cancelled` outcome does not
|
|
24
|
+
move it and starting a run does not clear it, so a failure stays readable while the retry is in flight; it is
|
|
25
|
+
`undefined` until the first settle. An `ok` takes the place of a previous `failed` and a `failed` the place of a previous `ok`: the pair
|
|
26
|
+
that kept a stale success next to the latest failure is gone, and a product that needs both keeps them in the model.
|
|
27
|
+
Read the field through its discriminant: `outcome?.status === 'failed' ? outcome.error : null`.
|
|
28
|
+
|
|
29
|
+
`AttachmentOwner.step` and `ModuleAttachmentOwner.step` take their three arguments positionally, like every other
|
|
30
|
+
node body since D279: `step('open', ({ context }) => …, ({ context }) => …)`. The key is still a non-empty literal,
|
|
31
|
+
`cleanup` is still required and still returns `void`, and the contexts, the result, the rollback order and the
|
|
32
|
+
diagnostics are unchanged (D293).
|
|
33
|
+
|
|
34
|
+
Migration: `x.handler` → `() => void x.run()`; `x.lastError` → `x.outcome?.status === 'failed' ? x.outcome.error :
|
|
35
|
+
null`; `x.lastResult` → `x.outcome?.status === 'ok' ? x.outcome.value : undefined`; `step({ key, run, cleanup })` →
|
|
36
|
+
`step(key, run, cleanup)`. No compatibility aliases: packages, examples, guides and the ESLint rules move together.
|
|
37
|
+
|
|
38
|
+
- d7f346a: Documentation only: every public word now has a compiled or executable consumer, and the four that do not earn one
|
|
39
|
+
are recorded as candidates.
|
|
40
|
+
|
|
41
|
+
The contributor rule is that a public export requires an executable example or a packaged consumer. Measured across
|
|
42
|
+
the compiled specification §2 fixtures, the example application, the packaged consumers and one downstream
|
|
43
|
+
application, eight words had no compiled consumer, and two more — `useFeature` and `defineSwitchSlot` — had no
|
|
44
|
+
consumer inside the repository at all.
|
|
45
|
+
|
|
46
|
+
`useResource`, `collection` and `selectByKey` are vocabulary and get a compile-checked example, specification §2.13:
|
|
47
|
+
the index stands beside the list in the model and a row subscribes to its own key, while the table holds the
|
|
48
|
+
request, because `useResource` is the only reader that also retains — under the default `observer` retention every
|
|
49
|
+
other reader subscribes to a request nobody opened. `useSelector`, `useCommands`, `onDemand` and `useFeature` are
|
|
50
|
+
recorded as candidates to leave their entries, each with the same condition: a second application measured with
|
|
51
|
+
zero uptake. Until then each is proven by the packaged consumer, which also now exercises `defineSwitchSlot`.
|
|
52
|
+
Nothing was removed or moved.
|
|
53
|
+
|
|
54
|
+
One specification statement was wrong and is corrected in both languages: `onDemand(hostContract)` does not defer
|
|
55
|
+
binding to the first use. A non-optional application input still requires `bind(contract, value)` at
|
|
56
|
+
`openApplication`, and the word's only measured effect is the `on-demand-import` edge and the `deferred` state of
|
|
57
|
+
the host-contract node in the inspection plan. The Russian implementation guide said the same thing and now matches
|
|
58
|
+
the English.
|
|
59
|
+
|
|
60
|
+
No API, behaviour or budget changed: the public surface stays at 39 values and 38 types, and the size budgets are
|
|
61
|
+
untouched (D301).
|
|
62
|
+
|
|
63
|
+
- d7f346a: Six small words on the testing entries, so a test stops writing scheduling by hand.
|
|
64
|
+
|
|
65
|
+
`yieldTurn()` from `@opetope/core/testing`, re-exported by the runtime and React entries, hands the queue exactly
|
|
66
|
+
one turn, which is what an assertion that something did _not_ happen needs and what `settled` deliberately does not
|
|
67
|
+
give. A turn is defined by the framework's own scheduling, not by a tick count: the microtask queue drains to
|
|
68
|
+
exhaustion and one timer round runs, so the data flush, the microtask of grace before an observer retention
|
|
69
|
+
releases, the serialized effect loop and a `timers.delay(0)` already scheduled all go through — while a turn this
|
|
70
|
+
turn started, a longer timer and anything the host resumes on I/O do not (D300).
|
|
71
|
+
|
|
72
|
+
That turn is a real timer round, so under installed fake timers it — and `waitFor` and `eventually`, which take one
|
|
73
|
+
between attempts — resolves only when the test advances them. Advance asynchronously while awaiting the wait, or do
|
|
74
|
+
not fake timers in a test that uses these words; a faked wait hangs at the runner's own deadline rather than raising
|
|
75
|
+
`WaitTimeoutError`.
|
|
76
|
+
|
|
77
|
+
`waitFor` now answers with what its predicate answered. `false`, `null` and `undefined` are the three spellings of
|
|
78
|
+
«not yet»; every other value ends the wait and comes back. A second function may decide over the value the first one
|
|
79
|
+
read — typed `boolean`, read for truth, so it is the form to write when a falsy value is the answer — and a type
|
|
80
|
+
guard there narrows what the wait resolves with, which TypeScript infers from a plain boolean arrow. Both halves
|
|
81
|
+
answer now: an async reader is a compile error and a thenable is refused at runtime, because a promise is never «not
|
|
82
|
+
yet» and the wait would end on its first attempt. The existing untargeted and targeted forms keep working, and the
|
|
83
|
+
timeout still names the target and the fact that was busy.
|
|
84
|
+
|
|
85
|
+
`eventually(assertion, { timeoutMs })` re-runs a block of assertions until it stops throwing, separating attempts by
|
|
86
|
+
one turn — the unit everything here advances in, which is why there is no interval to configure. On expiry it
|
|
87
|
+
rethrows the last failure with the matcher's own words, prefixed by `eventually: no convergence in <timeoutMs> ms
|
|
88
|
+
after <attempts> attempts — ` and carrying a non-enumerable `eventually` record of `attempts`, `elapsedMs` and
|
|
89
|
+
`timeoutMs`, so an expiry does not read like a single failed assertion. A throw that is not an `Error` is rethrown
|
|
90
|
+
untouched.
|
|
91
|
+
|
|
92
|
+
`runCall(target, input?, { signal, timeoutMs })` takes a deadline on that one run. `run` is the top-level execution
|
|
93
|
+
the consumer owns (D292), so the deadline cancels the way that consumer cancels — by aborting the signal this run
|
|
94
|
+
supplied — and rejects with a `CallTimeoutError` naming the call and the deadline. A `signal` stands beside it and
|
|
95
|
+
the first of the two to abort supplies the reason, so a cancelled outcome is never reported as a timeout.
|
|
96
|
+
|
|
97
|
+
`command(run)` is now published by `@opetope/core/testing` as a `Call` fixture with no binding: the body is
|
|
98
|
+
positional and receives the signal of the run, and there is no lane, queue, dedupe or retirement — so no `inFlight`
|
|
99
|
+
to read, no fence, and two runs of one fixture overlap. A model opened with `openModel` takes it as a dependency,
|
|
100
|
+
which removes the second model a test used to open for one Call. `@opetope/react/testing` keeps its own `command`,
|
|
101
|
+
which is this one plus the contribution binding a mount reads, and also re-exports `yieldTurn`, `eventually` and
|
|
102
|
+
`CallTimeoutError`.
|
|
103
|
+
|
|
104
|
+
`settled` and `waitFor` accept the `ApplicationExecution` an `openApplication` returned. An application contributes
|
|
105
|
+
the models of every generation it holds right now and a fifth fact of its own: the instances whose retirement has
|
|
106
|
+
started and has not ended. No new type is exported — `SettleTarget` widened. `WaitTimeoutError.facts` gains
|
|
107
|
+
`retiringInstances` with it, so an assertion written as `expect(error.facts).toEqual({ … })` needs the new key;
|
|
108
|
+
reading one field (`error.facts.liveCalls`) is unaffected.
|
|
109
|
+
|
|
110
|
+
Migration: `flush()` (a `setImmediate` or `setTimeout(0)` helper) → `yieldTurn()`; a local generic
|
|
111
|
+
`waitFor<Value>(read, predicate)` → the library `waitFor`, which returns the value; a local `eventually` → the
|
|
112
|
+
library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `runCall` →
|
|
113
|
+
`{ timeoutMs: 500 }`; `command` from `@opetope/react/testing` in a test that renders nothing →
|
|
114
|
+
`command` from `@opetope/core/testing`; and a hand-written poll loop for an application → `settled(application)`.
|
|
115
|
+
|
|
116
|
+
One shape does not migrate to `waitFor`: a local wait whose predicate is awaited — `waitFor(async () => (await
|
|
117
|
+
auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block. `waitFor` reads synchronously and
|
|
118
|
+
refuses a thenable, because a promise is never «not yet» and such a wait would end on its first attempt with an
|
|
119
|
+
assertion that never ran.
|
|
120
|
+
|
|
121
|
+
- Updated dependencies [d7f346a]
|
|
122
|
+
- Updated dependencies [d7f346a]
|
|
123
|
+
- Updated dependencies [d7f346a]
|
|
124
|
+
- Updated dependencies [d7f346a]
|
|
125
|
+
- Updated dependencies [d7f346a]
|
|
126
|
+
- Updated dependencies [d7f346a]
|
|
127
|
+
- Updated dependencies [d7f346a]
|
|
128
|
+
- Updated dependencies [d7f346a]
|
|
129
|
+
- Updated dependencies [d7f346a]
|
|
130
|
+
- Updated dependencies [d7f346a]
|
|
131
|
+
- @opetope/runtime@0.9.1
|
|
132
|
+
- @opetope/core@0.9.1
|
|
133
|
+
|
|
3
134
|
## 0.9.0
|
|
4
135
|
|
|
5
136
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -31,15 +31,15 @@ const CounterSlot = defineSlot<{ readonly label: string }>('example.counter.slot
|
|
|
31
31
|
const CounterButton = requiresModels([CounterModel])(({ label }: { readonly label: string }) => {
|
|
32
32
|
const {
|
|
33
33
|
count,
|
|
34
|
-
increment: {
|
|
34
|
+
increment: { run, inFlight, outcome },
|
|
35
35
|
} = useModel(CounterModel, (model, { read }) => ({
|
|
36
36
|
count: read(model.count),
|
|
37
37
|
increment: model.increment,
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<button disabled={inFlight} onClick={
|
|
42
|
-
{
|
|
41
|
+
<button disabled={inFlight} onClick={() => void run()}>
|
|
42
|
+
{outcome?.status === 'failed' ? 'Retry' : `${label}: ${count}`}
|
|
43
43
|
</button>
|
|
44
44
|
);
|
|
45
45
|
});
|
|
@@ -50,18 +50,25 @@ function CounterArea() {
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
A component gets neither a service, nor a class, nor a runtime ref. The contribution mount grants its feature's `own` models implicitly; the component or hook that reads a per-mount
|
|
53
|
-
model declares it with `requiresModels`. `useModel` reads exactly that authority frame. `useCommand` returns
|
|
54
|
-
and always resolves the outcome, so a normal cancellation never becomes an unhandled
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
model declares it with `requiresModels`. `useModel` reads exactly that authority frame. `useCommand` returns
|
|
54
|
+
`{ run, inFlight, outcome }` and always resolves the outcome, so a normal cancellation never becomes an unhandled
|
|
55
|
+
rejection (D116, D292).
|
|
56
|
+
|
|
57
|
+
`outcome` is the last settled non-cancelled outcome of this consumer: `ok` takes the place of a previous `failed`
|
|
58
|
+
and a `failed` the place of a previous `ok`, a `cancelled` outcome moves nothing, and starting a run clears nothing,
|
|
59
|
+
so a failure stays readable while the retry is in flight. Before the first settle it is `undefined`. Read it through
|
|
60
|
+
its discriminant — `outcome?.status === 'failed' ? outcome.error : null`.
|
|
61
|
+
|
|
62
|
+
A command without input binds to an event through an arrow: `onClick={() => void logout.run()}`. The arrow is what
|
|
63
|
+
keeps the React event out of the Call and the promise from hanging; `onClick={logout.run}` does not compile, because
|
|
64
|
+
a `MouseEvent` is not a `void` input. Use `run(input, options?)` for data, an outcome, callbacks or a per-run signal.
|
|
65
|
+
Where a lint config bans an inline arrow prop (`react-perf/jsx-no-new-function-as-prop`, `react/jsx-no-bind`), hoist
|
|
66
|
+
it with `useCallback(() => void logout.run(), [logout.run])`: the dependency is `run`, never the hook object (D290).
|
|
67
|
+
|
|
68
|
+
`run` is stable while the invoker stays the same; the returned object is a snapshot of the render it was read in and
|
|
69
|
+
changes as `inFlight` or `outcome` changes, so an effect or a memo depends on `run` and never on the hook — the rule
|
|
70
|
+
`opetope/no-command-in-deps` holds this for the author (D290). A `run` captured from a replaced or unmounted
|
|
71
|
+
consumer is fenced; it does not start work on the replacement.
|
|
65
72
|
|
|
66
73
|
The hook schedules nothing. Every `run` reaches the call, and the policy the call was created with decides what
|
|
67
74
|
happens to it (D203). For an absolute value setter the model declares `policy: 'latest'` in `context.call`, and the
|
|
@@ -84,8 +91,11 @@ const saving = save.inFlight;
|
|
|
84
91
|
```
|
|
85
92
|
|
|
86
93
|
Import `useCommands` from `@opetope/react`. Each selected field returns the same `CommandHook` as `useCommand`,
|
|
87
|
-
with its own input/output types
|
|
88
|
-
different granted models. There is no second argument: each call carries its own policy.
|
|
94
|
+
with its own input/output types and its own `inFlight` and `outcome`. Keys are local UI names; commands may come from
|
|
95
|
+
different granted models. There is no second argument: each call carries its own policy. Its own case is a key set
|
|
96
|
+
the component does not know in advance — hooks are not called in a loop; a fixed set is `useCommand` per word or one
|
|
97
|
+
`useModel` selection. It is proven by the packaged consumer of `ci:pack` and is a candidate to leave this entry
|
|
98
|
+
until a second application is measured (D301).
|
|
89
99
|
|
|
90
100
|
An inline object needs no `useMemo`. Reordering keys or updating a sibling preserves an unchanged field's object
|
|
91
101
|
and `.run`. Adding or removing keys does not change the number of React hooks. Removing a key or replacing its
|
|
@@ -102,13 +112,12 @@ A model selection can use a named interface without an index signature (D217). I
|
|
|
102
112
|
arrays, functions, constructors and built-in collection/date/promise objects are not selection records.
|
|
103
113
|
`read(source)` returns its snapshot; `read(source, project)` returns a projection. Authentic Calls selected as record
|
|
104
114
|
fields become the same `CommandHook` as `useCommands`, including independent alias statuses and stable `.run`.
|
|
105
|
-
|
|
106
|
-
no nested hooks:
|
|
115
|
+
A single selected command needs no nested hooks:
|
|
107
116
|
|
|
108
117
|
```tsx
|
|
109
118
|
const { logout } = useModel(AuthModel, auth => ({ logout: auth.logout }));
|
|
110
119
|
return (
|
|
111
|
-
<button disabled={logout.inFlight} onClick={logout.
|
|
120
|
+
<button disabled={logout.inFlight} onClick={() => void logout.run()}>
|
|
112
121
|
Log out
|
|
113
122
|
</button>
|
|
114
123
|
);
|
|
@@ -171,6 +180,11 @@ callback. There is no separate hook for
|
|
|
171
180
|
reading the error: the host passed it into `error` itself, so it knows it without a second word (D142). UI enters a feature through `Slot`: there is no
|
|
172
181
|
root, no `ui` section and no second binding API any more (D85).
|
|
173
182
|
|
|
183
|
+
Both the boundary and the bare `useFeature` are exercised by the packaged consumer of `ci:pack`, which checks the
|
|
184
|
+
law they share: one lease per mount, kept across a settled `retry` and released on unmount. `useFeature` is the
|
|
185
|
+
hook half used where a host renders the readiness itself, and it is a candidate to leave this entry until a second
|
|
186
|
+
application is measured; `FeatureBoundary` is not a pair with `ContributionBoundary` and stays (D301).
|
|
187
|
+
|
|
174
188
|
## Contributions
|
|
175
189
|
|
|
176
190
|
```tsx
|
|
@@ -191,10 +205,14 @@ function HomePageEnd() {
|
|
|
191
205
|
`defineSlot` creates an authentic target with an ordered set of contributions; `defineSwitchSlot` lazily creates and
|
|
192
206
|
caches a stable target per route. `Slot` reads the atomic snapshot and renders every `{ Component }` in the authority
|
|
193
207
|
frame of the instance that gave the contribution. The order and the stable key come from the contribution entries of the core, so
|
|
194
|
-
React introduces neither a second comparator nor a second lifetime registry.
|
|
208
|
+
React introduces neither a second comparator nor a second lifetime registry. The packaged consumer of `ci:pack`
|
|
209
|
+
exercises that caching and routes one contribution through it (D301).
|
|
195
210
|
|
|
196
211
|
`useResource(resource)` reads the atomic snapshot of a resource through `useSyncExternalStore` and holds one lease per
|
|
197
212
|
mounted consumer. When the reference changes, the snapshot follows the new resource and effect cleanup releases the previous lease.
|
|
213
|
+
The lease is the half no other reader has: a `Resource` is a `Readable` of its own snapshot, so `useReadable` and a
|
|
214
|
+
`useModel` selection compile and subscribe to a request that `observer` retention never opens. The compile-checked
|
|
215
|
+
example of specification §2.13 shows both sides of that (D301).
|
|
198
216
|
|
|
199
217
|
Direct component props, a contribution's `props` adapter and its model's props `Readable` share the same
|
|
200
218
|
mount-owned snapshot. Incoming slot props publish in layout before paint, so direct and adapted renders cannot
|
|
@@ -256,7 +274,7 @@ application imports and internal renderer references. A failed cleanup promise c
|
|
|
256
274
|
capabilities; a caller that keeps `mounted.host` also keeps its own renderer result.
|
|
257
275
|
|
|
258
276
|
The inspection schemas are `opetope.devtools-graph/5` and `opetope.devtools-frame/4`, with optional
|
|
259
|
-
`opetope.runtime-activity/
|
|
277
|
+
`opetope.runtime-activity/2` snapshots (D266, D275). Within one session,
|
|
260
278
|
a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
|
|
261
279
|
(D216). Activity-bearing frames replace the previous activity in full.
|
|
262
280
|
Use matching runtime/devtools versions: previous graph/frame revisions are rejected; weak-edge presence requires graph `/5` and frame `/4`. Activity identifies the execution,
|
|
@@ -276,16 +294,20 @@ observation disabled. Graph frames remain bounded by the existing ring capacity.
|
|
|
276
294
|
(D285):
|
|
277
295
|
|
|
278
296
|
- `renderSlot(target, { contribution, models, props })` mounts the published target or one fixture contribution;
|
|
279
|
-
- `command(run)` produces an authentic `Call` for a model fixture;
|
|
297
|
+
- `command(run)` produces an authentic `Call` for a model fixture, plus the contribution binding a mount reads;
|
|
280
298
|
- `runCall(target, input?, options?)` executes an existing authentic `Call` without mounting React (D261);
|
|
281
|
-
- `testReadable(initial)`, `openModel`, `settled` and `
|
|
282
|
-
that has no UI in it.
|
|
299
|
+
- `testReadable(initial)`, `openModel`, `settled`, `waitFor`, `yieldTurn` and `eventually` come from the entries
|
|
300
|
+
below, for the part of a test that has no UI in it.
|
|
283
301
|
|
|
284
|
-
A test that renders nothing should import `@opetope/runtime/testing` directly: `
|
|
285
|
-
|
|
302
|
+
A test that renders nothing should import `@opetope/runtime/testing` directly: `renderSlot` and the binding half of
|
|
303
|
+
`command` are all that needs React here. The Call a fixture mints is the `command` of `@opetope/core/testing`
|
|
304
|
+
(D300), and this word is the one name of the re-export chain that does not pass through: the React `command`
|
|
305
|
+
shadows it with the stronger one.
|
|
286
306
|
|
|
287
307
|
The `renderSlot` harness returns `Slot` and `updateProps`: there are no roots and no fixtures for them, UI enters the
|
|
288
|
-
application as contributions (D85).
|
|
308
|
+
application as contributions (D85). It is not a test spelling of `<Slot>` — it mounts a fixture and takes a
|
|
309
|
+
`reporter`, because a component test has no feature to report to — and the packaged consumer of `ci:pack` mounts it
|
|
310
|
+
from the real archive (D301).
|
|
289
311
|
|
|
290
312
|
A test that previously mounted `useCommand` only to invoke a Call can use `runCall` directly. The result is the
|
|
291
313
|
Call's output, while a UI test still checks the `CommandOutcome` returned by the mounted consumer:
|
|
@@ -347,11 +369,13 @@ The constructors of the integration entry are not re-exported from the safe entr
|
|
|
347
369
|
- one committed contribution owns its model frame until unmount or retire;
|
|
348
370
|
- an abandoned concurrent render holds no frame;
|
|
349
371
|
- `useCommand` does not subscribe to the invoker: the observable state of a call lies in a `Readable` of the model;
|
|
350
|
-
- the `cancelled` outcome
|
|
372
|
+
- the `cancelled` outcome does not move `outcome`, and it is only the `CallError` codes `cancelled`
|
|
351
373
|
and `closed`; a call to a weak port with no provider (`unavailable`) and a rejected publication
|
|
352
|
-
(`publication-rejected`) arrive as the `failed` outcome and settle in `
|
|
374
|
+
(`publication-rejected`) arrive as the `failed` outcome and settle in `outcome` (D138, D292);
|
|
353
375
|
- a contribution that fails to render or to commit is contained by its own mount and reported to its feature;
|
|
354
|
-
- `useSelector` keeps the selected reference when something else changed;
|
|
376
|
+
- `useSelector` keeps the selected reference when something else changed; its identity case is `useReadable` and its
|
|
377
|
+
model case is a `useModel` selection, so it is proven by the memory gate and the packaged consumer rather than by a
|
|
378
|
+
specification example, and it is a candidate to leave this entry until a second application is measured (D301);
|
|
355
379
|
- closing and unmounting synchronously fence new calls and release the references of the frame.
|
|
356
380
|
|
|
357
381
|
## Compatibility contract
|
package/README.ru.md
CHANGED
|
@@ -31,15 +31,15 @@ const CounterSlot = defineSlot<{ readonly label: string }>('example.counter.slot
|
|
|
31
31
|
const CounterButton = requiresModels([CounterModel])(({ label }: { readonly label: string }) => {
|
|
32
32
|
const {
|
|
33
33
|
count,
|
|
34
|
-
increment: {
|
|
34
|
+
increment: { run, inFlight, outcome },
|
|
35
35
|
} = useModel(CounterModel, (model, { read }) => ({
|
|
36
36
|
count: read(model.count),
|
|
37
37
|
increment: model.increment,
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<button disabled={inFlight} onClick={
|
|
42
|
-
{
|
|
41
|
+
<button disabled={inFlight} onClick={() => void run()}>
|
|
42
|
+
{outcome?.status === 'failed' ? 'Retry' : `${label}: ${count}`}
|
|
43
43
|
</button>
|
|
44
44
|
);
|
|
45
45
|
});
|
|
@@ -51,17 +51,24 @@ function CounterArea() {
|
|
|
51
51
|
|
|
52
52
|
Компонент не получает ни сервис, ни класс, ни ref рантайма. Монтирование вклада неявно выдаёт модели `own` его фичи;
|
|
53
53
|
компонент или хук, читающий per-mount модель, объявляет её через `requiresModels`. `useModel` читает ровно этот кадр
|
|
54
|
-
авторитета. `useCommand` отдаёт `{ run, inFlight,
|
|
55
|
-
и всегда resolve-ит исход, поэтому штатная отмена не становится unhandled rejection (D116).
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`
|
|
54
|
+
авторитета. `useCommand` отдаёт `{ run, inFlight, outcome }`
|
|
55
|
+
и всегда resolve-ит исход, поэтому штатная отмена не становится unhandled rejection (D116, D292).
|
|
56
|
+
|
|
57
|
+
`outcome` — последний завершившийся не-отменённый исход этого потребителя: `ok` встаёт на место прежнего `failed`,
|
|
58
|
+
`failed` — на место прежнего `ok`, исход `cancelled` не двигает ничего, и старт прогона ничего не чистит, поэтому
|
|
59
|
+
отказ читается, пока идёт повтор. До первого завершения он `undefined`. Читают его по дискриминанту —
|
|
60
|
+
`outcome?.status === 'failed' ? outcome.error : null`.
|
|
61
|
+
|
|
62
|
+
Команда без входа привязывается к событию стрелкой: `onClick={() => void logout.run()}`. Именно стрелка не пускает
|
|
63
|
+
React-событие в Call и не оставляет промис висеть; `onClick={logout.run}` не компилируется, потому что `MouseEvent`
|
|
64
|
+
не ложится в `void`. Для данных, исхода, callbacks или сигнала отдельного вызова используйте `run(input, options?)`.
|
|
65
|
+
Там, где конфиг линтера запрещает стрелку прямо в пропсе (`react-perf/jsx-no-new-function-as-prop`,
|
|
66
|
+
`react/jsx-no-bind`), её поднимают через `useCallback(() => void logout.run(), [logout.run])`: зависимость — `run`,
|
|
67
|
+
а не объект хука (D290).
|
|
68
|
+
|
|
69
|
+
`run` стабилен, пока invoker тот же; возвращённый объект — снимок того рендера, в котором его прочитали, и меняется
|
|
70
|
+
вместе с `inFlight` или `outcome`, поэтому эффект или memo зависят от `run`, а не от хука, — правило
|
|
71
|
+
`opetope/no-command-in-deps` держит это за автора (D290). Сохранённый `run` заменённого или
|
|
65
72
|
размонтированного потребителя закрыт для новых вызовов и не перенаправляет работу на замену.
|
|
66
73
|
|
|
67
74
|
Хук ничего не планирует. Каждый `run` доходит до вызова, и решает политика, с которой вызов создан (D203). Для
|
|
@@ -84,8 +91,11 @@ const saving = save.inFlight;
|
|
|
84
91
|
```
|
|
85
92
|
|
|
86
93
|
Импортируйте `useCommands` из `@opetope/react`. Каждое поле возвращает прежний `CommandHook`, как `useCommand`,
|
|
87
|
-
со своими типами
|
|
88
|
-
из разных разрешённых моделей. Второго аргумента нет: политика есть у каждого вызова.
|
|
94
|
+
со своими типами входа/выхода и своими `inFlight` и `outcome`. Ключи — локальные имена UI; команды могут быть
|
|
95
|
+
из разных разрешённых моделей. Второго аргумента нет: политика есть у каждого вызова. Собственный случай у слова
|
|
96
|
+
один — набор ключей, которого компонент не знает заранее: хуки в цикле не вызывают, а фиксированный набор это
|
|
97
|
+
`useCommand` по слову или одна выборка `useModel`. Слово доказано упакованным потребителем `ci:pack` и остаётся
|
|
98
|
+
кандидатом на вывод с этого входа до второго измеренного приложения (D301).
|
|
89
99
|
|
|
90
100
|
Объекту прямо в рендере не нужен `useMemo`. Перестановка ключей и обновление соседнего поля сохраняют объект
|
|
91
101
|
неизменившегося поля и `.run`. Добавление и удаление ключей не меняет число React hooks. Удаление ключа или
|
|
@@ -102,13 +112,12 @@ const saving = save.inFlight;
|
|
|
102
112
|
arrays, functions, constructors и встроенные объекты коллекций, дат и promises не являются selection records.
|
|
103
113
|
`read(source)` возвращает снимок; `read(source, project)` — проекцию. Authentic Calls, выбранные полями record,
|
|
104
114
|
превращаются в тот же `CommandHook`, что у `useCommands`, с независимыми статусами aliases и стабильным `.run`.
|
|
105
|
-
|
|
106
|
-
вложенных hooks:
|
|
115
|
+
Одну команду можно выбрать без вложенных hooks:
|
|
107
116
|
|
|
108
117
|
```tsx
|
|
109
118
|
const { logout } = useModel(AuthModel, auth => ({ logout: auth.logout }));
|
|
110
119
|
return (
|
|
111
|
-
<button disabled={logout.inFlight} onClick={logout.
|
|
120
|
+
<button disabled={logout.inFlight} onClick={() => void logout.run()}>
|
|
112
121
|
Log out
|
|
113
122
|
</button>
|
|
114
123
|
);
|
|
@@ -170,6 +179,11 @@ render-колбэком: колбэк `children` получает готовый
|
|
|
170
179
|
нет: хост сам передал её в `error`, поэтому знает её без второго слова (D142). UI входит в фичу через `Slot`: ни
|
|
171
180
|
корня, ни секции `ui`, ни второго API привязки больше нет (D85).
|
|
172
181
|
|
|
182
|
+
И boundary, и голый `useFeature` проверяет упакованный потребитель `ci:pack` — на общем для них законе: одна аренда
|
|
183
|
+
на монтирование, сохраняется через севший `retry` и отпускается на размонтировании. `useFeature` это хуковая
|
|
184
|
+
половина для случая, когда готовность рендерит сам хост, и он кандидат на вывод с этого входа до второго
|
|
185
|
+
измеренного приложения; `FeatureBoundary` не пара `ContributionBoundary` и остаётся (D301).
|
|
186
|
+
|
|
173
187
|
## Contributions
|
|
174
188
|
|
|
175
189
|
```tsx
|
|
@@ -190,10 +204,14 @@ function HomePageEnd() {
|
|
|
190
204
|
`defineSlot` создаёт подлинную цель с упорядоченным множеством вкладов; `defineSwitchSlot` лениво создаёт и
|
|
191
205
|
кеширует стабильную цель на каждый маршрут. `Slot` читает атомарный снимок и рендерит каждый `{ Component }` в кадре
|
|
192
206
|
авторитета того экземпляра, который вклад дал. Порядок и стабильный ключ приходят из записей вкладов ядра, поэтому
|
|
193
|
-
React не вводит второй компаратор и второй реестр времени жизни.
|
|
207
|
+
React не вводит второй компаратор и второй реестр времени жизни. Упакованный потребитель `ci:pack` проверяет это
|
|
208
|
+
кеширование и проводит через него один вклад (D301).
|
|
194
209
|
|
|
195
210
|
`useResource(resource)` читает атомарный снимок ресурса через `useSyncExternalStore` и держит один lease на
|
|
196
211
|
смонтированного потребителя. При смене ссылки snapshot следует новому ресурсу, а cleanup эффекта освобождает предыдущий lease.
|
|
212
|
+
Аренда и есть та половина, которой нет ни у одного другого читателя: `Resource` — `Readable` собственного снимка,
|
|
213
|
+
поэтому `useReadable` и выборка `useModel` компилируются и подписываются на запрос, который при retention `observer`
|
|
214
|
+
никто не открыл. Обе стороны показывает компилируемый пример спецификации §2.13 (D301).
|
|
197
215
|
|
|
198
216
|
Прямые пропсы компонента, `props`-адаптер вклада и props-`Readable` его модели используют один снимок
|
|
199
217
|
монтирования. Входящие пропсы слота публикуются в layout до paint, поэтому прямой и адаптированный рендеры
|
|
@@ -256,7 +274,7 @@ host/UI/GC-утечек. Успешный cleanup очищает imports при
|
|
|
256
274
|
cleanup может удерживать исходные ошибки и retry capabilities; сохранённый пользователем `mounted.host` удерживает его renderer result.
|
|
257
275
|
|
|
258
276
|
Inspection использует схемы `opetope.devtools-graph/5` и `opetope.devtools-frame/4`, а также optional snapshots
|
|
259
|
-
`opetope.runtime-activity/
|
|
277
|
+
`opetope.runtime-activity/2` (D266, D275). В пределах одной session
|
|
260
278
|
frame без `activity` сохраняет предыдущую activity; полный snapshot/reset без этого поля очищает наблюдение (D216).
|
|
261
279
|
Frame с `activity` заменяет предыдущую activity целиком.
|
|
262
280
|
Используйте согласованные версии runtime/devtools: предыдущие ревизии графа и кадров отвергаются; presence слабого ребра требует graph `/5` и frame `/4`. Activity указывает execution,
|
|
@@ -276,16 +294,20 @@ idle executors могут требовать обхода, чтобы подтв
|
|
|
276
294
|
называет один вход (D285):
|
|
277
295
|
|
|
278
296
|
- `renderSlot(target, { contribution, models, props })` монтирует опубликованную цель либо один вклад-фикстуру;
|
|
279
|
-
- `command(run)` выдаёт подлинный `Call` для фикстуры
|
|
297
|
+
- `command(run)` выдаёт подлинный `Call` для фикстуры модели плюс привязку вклада, которую читает монтирование;
|
|
280
298
|
- `runCall(target, input?, options?)` запускает существующий подлинный `Call` без монтирования React (D261);
|
|
281
|
-
- `testReadable(initial)`, `openModel`, `settled` и `
|
|
282
|
-
которой UI нет.
|
|
299
|
+
- `testReadable(initial)`, `openModel`, `settled`, `waitFor`, `yieldTurn` и `eventually` приходят из входов ниже —
|
|
300
|
+
для той части теста, в которой UI нет.
|
|
283
301
|
|
|
284
|
-
Тест, который ничего не рендерит, должен импортировать `@opetope/runtime/testing` напрямую: React нужен
|
|
285
|
-
|
|
302
|
+
Тест, который ничего не рендерит, должен импортировать `@opetope/runtime/testing` напрямую: React здесь нужен
|
|
303
|
+
только `renderSlot` и привязочной половине `command`. Сам Call фикстуры чеканит `command` из
|
|
304
|
+
`@opetope/core/testing` (D300), и это единственное имя цепочки реэкспортов, которое насквозь не проходит:
|
|
305
|
+
React-`command` заслоняет его своим, более сильным.
|
|
286
306
|
|
|
287
307
|
Харнесс `renderSlot` отдаёт `Slot` и `updateProps`: корней и их фикстур больше нет, UI входит в приложение
|
|
288
|
-
вкладами (D85).
|
|
308
|
+
вкладами (D85). Это не тестовое написание `<Slot>`: он монтирует фикстуру и принимает `reporter`, потому что у
|
|
309
|
+
компонентного теста нет фичи, которой можно сообщить, — и упакованный потребитель `ci:pack` монтирует его из
|
|
310
|
+
настоящего архива (D301).
|
|
289
311
|
|
|
290
312
|
Тест, который раньше монтировал `useCommand` только ради вызова Call, может использовать `runCall` напрямую.
|
|
291
313
|
Результат — выход Call; тест UI по-прежнему проверяет `CommandOutcome`, возвращённый смонтированным consumer:
|
|
@@ -347,11 +369,14 @@ UI-статус, обёртку outcome, политику выполнения,
|
|
|
347
369
|
- один закоммиченный вклад владеет своим кадром моделей до размонтирования или retire;
|
|
348
370
|
- брошенный concurrent-рендер кадр не удерживает;
|
|
349
371
|
- `useCommand` не подписывается на invoker: наблюдаемое состояние вызова лежит в `Readable` модели;
|
|
350
|
-
- исход `cancelled` не двигает
|
|
372
|
+
- исход `cancelled` не двигает `outcome`, и это только коды `CallError` `cancelled`
|
|
351
373
|
и `closed`; вызов слабого порта без провайдера (`unavailable`) и отклонённая публикация
|
|
352
|
-
(`publication-rejected`) приходят исходом `failed` и оседают в `
|
|
374
|
+
(`publication-rejected`) приходят исходом `failed` и оседают в `outcome` (D138, D292);
|
|
353
375
|
- вклад, упавший в рендере или коммите, изолируется своим монтированием и сообщается своей фиче;
|
|
354
|
-
- `useSelector` сохраняет выбранную ссылку, когда изменилось что-то другое;
|
|
376
|
+
- `useSelector` сохраняет выбранную ссылку, когда изменилось что-то другое; его тождественный случай это
|
|
377
|
+
`useReadable`, а случай поля модели — выборка `useModel`, поэтому доказывают его гейт памяти и упакованный
|
|
378
|
+
потребитель, а не пример спецификации, и он кандидат на вывод с этого входа до второго измеренного
|
|
379
|
+
приложения (D301);
|
|
355
380
|
- закрытие и размонтирование синхронно фенсят новые вызовы и освобождают ссылки кадра.
|
|
356
381
|
|
|
357
382
|
## Compatibility contract
|
|
@@ -20,7 +20,6 @@ type CommandNotification<Output> = (inFlight: boolean, outcome?: CommandOutcome<
|
|
|
20
20
|
*/
|
|
21
21
|
declare class CommandHookController<Input, Output> {
|
|
22
22
|
private readonly invoker;
|
|
23
|
-
readonly handler: () => void;
|
|
24
23
|
readonly run: CommandRun<Input, Output>;
|
|
25
24
|
private inFlight;
|
|
26
25
|
private notify;
|
package/dist/command-hook.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { Call } from '@opetope/core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CommandOutcome } from './command.js';
|
|
3
3
|
import type { CommandRun } from './command-hook-controller.js';
|
|
4
|
+
/** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */
|
|
5
|
+
type SettledOutcome<Output> = Exclude<CommandOutcome<Output>, {
|
|
6
|
+
readonly status: 'cancelled';
|
|
7
|
+
}>;
|
|
4
8
|
interface CommandHookStatus<Output> {
|
|
5
9
|
readonly inFlight: boolean;
|
|
6
|
-
readonly
|
|
7
|
-
readonly lastResult: Output | undefined;
|
|
10
|
+
readonly outcome: SettledOutcome<Output> | undefined;
|
|
8
11
|
}
|
|
9
|
-
type CommandHandler<Input> = IsAny<Input> extends true ? object : [Input] extends [never] ? object : [Input] extends [void] ? {
|
|
10
|
-
readonly handler: () => void;
|
|
11
|
-
} : object;
|
|
12
12
|
type CommandHook<Input, Output> = CommandHookStatus<Output> & {
|
|
13
13
|
readonly run: CommandRun<Input, Output>;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
declare function useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output>;
|
|
16
16
|
export { useCommand };
|
|
17
17
|
export type { CommandHook };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useState as
|
|
1
|
+
import{useState as g,useInsertionEffect as h,useLayoutEffect as f,useMemo as S,useSyncExternalStore as m,useRef as b,useCallback as w,useEffect as I,createElement as v,Fragment as P}from"react";import{CallError as x,declarationId as K}from"@opetope/core";import{compatibleAbortReason as $,isCancellation as H,reportDetachedError as O,isCallTarget as T,isReadable as G,createAggregateError as U,createContributionTarget as D}from"@opetope/core/internal";import{C as l}from"./contribution-isolation-Bzfbt4iM.js";import{u as p,a as E,M as L,c as W}from"./contribution-frame-DXl7K0-B.js";import{b as ke}from"./contribution-frame-DXl7K0-B.js";function C(n,e){var t;if(H(e))return{reason:e,status:"cancelled"};try{const s=(t=n==null?void 0:n.onFailure)==null?void 0:t.call(n,e);s!==void 0&&Promise.resolve(s).catch(O)}catch(s){O(s)}return{error:e,status:"failed"}}function B(n){const e=n==null?void 0:n.onSuccess,t=n==null?void 0:n.signal;return e===void 0?t===void 0?void 0:{signal:t}:t===void 0?{onSuccess:e}:{onSuccess:e,signal:t}}function J(n,e){let t;const s=new Promise(i=>{t=i});return{input:n,options:e===void 0?void 0:{...e},promise:s,resolve:t}}function N(n,e){const t=n.resolve;n.input=void 0,n.options=void 0,n.resolve=void 0,t==null||t(e)}class M{invoker;run=((e,t)=>{if(this.notify===void 0)return Promise.resolve({reason:new l("inactive","Command consumer is not mounted."),status:"cancelled"});const s=this.cancelledInput(t);if(s!==void 0)return Promise.resolve(s);const i=J(e,t);return this.start(i),i.promise});inFlight=0;notify;constructor(e){this.invoker=e}attach(e){return this.notify=e,()=>{this.notify===e&&(this.notify=void 0)}}synchronize(){var e;(e=this.notify)==null||e.call(this,this.inFlight>0)}aborted(e){return{reason:new x("cancelled","Command input was cancelled.",$(e)),status:"cancelled"}}cancelledInput(e){var t;return((t=e==null?void 0:e.signal)==null?void 0:t.aborted)===!0?this.aborted(e.signal):void 0}finish(e,t){var s;e.resolve!==void 0&&(this.inFlight-=1,N(e,t),(s=this.notify)==null||s.call(this,this.inFlight>0,t))}invoke(e){let t;try{t=this.invoker.run(e.input,B(e.options)),e.input=void 0}catch(s){this.finish(e,C(e.options,s));return}t.then(s=>this.finish(e,{status:"ok",value:s}),s=>this.finish(e,C(e.options,s)))}start(e){var t;this.inFlight+=1,(t=this.notify)==null||t.call(this,!0),this.invoke(e)}}const Q={inFlight:!1,outcome:void 0};function V(n){return n===void 0||n.status==="cancelled"?void 0:n}function X(n,e,t,s){return s===void 0&&n.slot===e&&n.inFlight===t}function Y(n,e,t,s){const i=V(s);if(X(n,e,t,i))return n;const r=n.slot===e?n.outcome:void 0;return{inFlight:t,outcome:i??r,slot:e}}function Z(n){const[e]=g(()=>new WeakMap);let t=e.get(n);return t===void 0&&(t=new M(n),e.set(n,t)),t}function _(n){const t=p().commandRecords.get(n);if(t===void 0)throw new l("missing",`Command ${n.id} is not bound in this contribution.`);const s=Z(t.invoker),[i,r]=g(()=>({inFlight:!1,outcome:void 0,slot:s}));h(()=>s.attach((d,a)=>r(A=>Y(A,s,d,a))),[s]),f(()=>s.synchronize(),[s]);const{inFlight:o,outcome:u}=i.slot===s?i:Q,c=s.run;return S(()=>({inFlight:o,outcome:u,run:c}),[o,u,c])}const q=()=>{},j=()=>q,ee=()=>0,te=Object.freeze({});function ne(n){const e=new M(n);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:n,slot:e}}function se(n,e){return n.inFlight===e.inFlight&&Object.is(n.outcome,e.outcome)}function ie(n,e,t){const s=n.hook;if(t===void 0&&s.inFlight===e)return!1;const i=t===void 0||t.status==="cancelled"?void 0:t,r={inFlight:e,outcome:i??s.outcome,run:s.run};return se(s,r)?!1:(n.hook=r,!0)}function re(n,e){return(n==null?void 0:n.invoker)===e?n:ne(e)}class oe{entries=new Map;listeners=new Set;revision=0;close(){var e;for(const t of this.entries.values())(e=t.detach)==null||e.call(t),t.detach=void 0;this.entries.clear()}commit(e){var t;for(const[s,i]of this.entries)e.get(s)!==i&&((t=i.detach)==null||t.call(i),i.detach=void 0);this.entries=e;for(const s of e.values())s.detach??(s.detach=s.slot.attach((i,r)=>{if(ie(s,i,r)){this.revision+=1;for(const o of this.listeners)o()}}))}getSnapshot=()=>this.revision;prepare(e,t){const s=new Map;for(const i of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,i))continue;const r=e[i],o=r===void 0?void 0:t.commandRecords.get(r);if(o===void 0)throw new l("missing",`Command at ${String(i)} is not bound in this contribution.`);s.set(i,re(this.entries.get(i),o.invoker))}return s}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function ce(n){const e=b(void 0);return Reflect.ownKeys(n).some(s=>Object.prototype.propertyIsEnumerable.call(n,s))?e.current??(e.current=new oe):void 0}function R(n){const e=p(),t=ce(n),s=(t==null?void 0:t.getSnapshot)??ee;m((t==null?void 0:t.subscribe)??j,s,s);const i=t==null?void 0:t.prepare(n,e);return h(()=>()=>t==null?void 0:t.close(),[t]),h(()=>{i!==void 0&&(t==null||t.commit(i))}),f(()=>{if(i!==void 0)for(const r of i.values())r.slot.synchronize()}),i===void 0?te:Object.fromEntries([...i].map(([r,o])=>[r,o.hook]))}function ue(n,e){if(!G(n))throw new l("binding-invalid","Model selection read requires a Readable.");if(e.has(n))return e.get(n);const t=n.getSnapshot();return e.set(n,t),t}function de(n,e){const s=n({read:(i,r)=>{const o=ue(i,e);return r===void 0?o:r(o)}});if(typeof s!="object"||s===null||Array.isArray(s))throw new l("binding-invalid","Model selection requires a record.");return Object.fromEntries(Reflect.ownKeys(s).filter(i=>Object.prototype.propertyIsEnumerable.call(s,i)).map(i=>[i,s[i]]))}function ae(n,e,t){const s=Reflect.ownKeys(e),i=Reflect.ownKeys(n.values);if(s.length!==i.length||t.size!==n.sources.size)return!1;for(const r of t)if(!n.sources.has(r))return!1;return s.every((r,o)=>i[o]===r&&Object.is(n.values[r],e[r]))}const y={commands:{},sources:new Set,values:{}};function le(n){return Object.fromEntries(Reflect.ownKeys(n).filter(e=>T(n[e])).map(e=>[e,n[e]]))}function fe(n,e){let t,s,i=y;return()=>{const r=e.current;if(r===void 0)return y;if(t!==void 0&&s===r&&[...t].every(([d,a])=>Object.is(d.getSnapshot(),a)))return i;const o=new Map,u=de(d=>r(n,d),o),c=new Set(o.keys());return(t===void 0||!ae(i,u,c))&&(i={commands:le(u),sources:c,values:u}),t=o,s=r,i}}function k(n,e=[]){for(const t of n){const s=t.dispose;t.dispose=void 0;try{s==null||s()}catch(i){e.push(i)}}if(e.length===1)throw e[0];if(e.length>1)throw U(e,"Model selection sources failed to release.")}class he{enabled=!1;listeners=new Set;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.releaseAll()}commit(e){this.sources=e,this.enabled=!0;try{this.synchronize()}catch(t){this.releaseAll([t])}}subscribe=e=>{const t={notify:e};this.listeners.add(t);try{this.synchronize()}catch(s){this.listeners.delete(t),this.releaseAll([s])}return()=>{this.listeners.delete(t),this.listeners.size===0&&this.releaseAll()}};add(e){const t={dispose:void 0};this.subscriptions.set(e,t);const s=e.subscribe(this.notify);if(typeof s!="function")throw new TypeError("Model selection source must return a disposer.");this.subscriptions.get(e)===t?t.dispose=s:s()}notify=()=>{for(const e of[...this.listeners])this.listeners.has(e)&&e.notify()};releaseAll(e){const t=[...this.subscriptions.values()];this.subscriptions.clear(),k(t,e)}removeUnselected(){const e=[];for(const[t,s]of this.subscriptions)this.sources.has(t)||(this.subscriptions.delete(t),e.push(s));k(e)}synchronize(){this.removeUnselected();for(const e of this.sources){if(!this.enabled||this.listeners.size===0)return;this.sources.has(e)&&!this.subscriptions.has(e)&&this.add(e)}}}const me=()=>y;function be(n){const e=b(void 0);return n?e.current??(e.current=new he):void 0}function pe(n,e){const t=p();if(!t.models.has(n))throw new l("missing",`Model ${n.id} is not granted to this contribution.`);const s=t.models.get(n),i=be(e!==void 0),r=b(void 0);r.current=e;const o=e!==void 0,u=S(()=>o?fe(s,r):me,[s,o]),c=m((i==null?void 0:i.subscribe)??j,u,u),d=R(c.commands);return f(()=>()=>i==null?void 0:i.close(),[i]),f(()=>i==null?void 0:i.commit(c.sources),[c,i]),e===void 0?s:Object.fromEntries(Reflect.ownKeys(c.values).map(a=>[a,Object.prototype.hasOwnProperty.call(d,a)?d[a]:c.values[a]]))}function ye(n){const e=Object.freeze([...n]);return t=>{if(t.requires!==void 0)throw new l("duplicate","Component already declares the models it requires.");return Object.defineProperty(t,"requires",{enumerable:!1,value:e}),t}}function ge(n){const e=w(i=>n.subscribe(i),[n]),t=w(()=>n.getSnapshot(),[n]),s=m(e,t,t);return I(()=>n.retain(),[n]),s}const z=Object.freeze({});function F(n){return D(n,"slot")}function Se(n){const e=K(n),t=new Map,s=i=>{const r=t.get(i);if(r!==void 0)return r;const o=F(`${e}/${i}`);return t.set(i,o),o};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function we(n){const e=E(n.target.entries),t="props"in n?n.props??z:z;return e.length===0?null:e.map(s=>v(P,{key:s.id},v(L,{authority:W(s),contribution:s.value,contributionId:s.id,slotProps:t,targetId:n.target.id})))}export{l as ContributionError,we as Slot,F as defineSlot,Se as defineSwitchSlot,ye as requiresModels,_ as useCommand,R as useCommands,pe as useModel,E as useReadable,ge as useResource,ke as useSelector};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/command-hook-controller.ts","../src/command-hook.ts","../src/idle-subscription.ts","../src/commands-hook.ts","../src/model-selection-snapshot.ts","../src/model-selection-store.ts","../src/model-hook.ts","../src/requires-models.ts","../src/resource-hook.ts","../src/slot.tsx"],"sourcesContent":["import { CallError } from '@opetope/core';\nimport { compatibleAbortReason, isCancellation, reportDetachedError } from '@opetope/core/internal';\nimport type { CallInputArgs, CallRunOptions } from '@opetope/core/internal';\n\nimport type { CommandOutcome } from './command';\nimport { ContributionError } from './errors';\n\ntype CommandRunOptions<Output> = Readonly<{\n onFailure?: (error: unknown) => PromiseLike<void> | void;\n onSuccess?: (value: Output) => PromiseLike<void> | void;\n signal?: AbortSignal;\n}>;\n\ntype CommandRun<Input, Output> = {\n (input: Input, options?: CommandRunOptions<Output>): Promise<CommandOutcome<Output>>;\n (...args: [...CallInputArgs<Input>, options?: CommandRunOptions<Output>]): Promise<CommandOutcome<Output>>;\n};\n\ntype CommandInvoker = Readonly<{\n run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;\n}>;\n\ntype CommandNotification<Output> = (inFlight: boolean, outcome?: CommandOutcome<Output>) => void;\n\ntype CommandRequest<Input, Output> = {\n input: Input | undefined;\n options: CommandRunOptions<Output> | undefined;\n readonly promise: Promise<CommandOutcome<Output>>;\n resolve: ((outcome: CommandOutcome<Output>) => void) | undefined;\n};\n\nfunction failureOutcome<Output>(options: CommandRunOptions<Output> | undefined, error: unknown): CommandOutcome<never> {\n if (isCancellation(error)) return { reason: error, status: 'cancelled' };\n\n try {\n const notification = options?.onFailure?.(error);\n\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n } catch (callbackError) {\n reportDetachedError(callbackError);\n }\n\n return { error, status: 'failed' };\n}\n\nfunction runOptions<Output>(options: CommandRunOptions<Output> | undefined): CallRunOptions<Output> | undefined {\n const onSuccess = options?.onSuccess;\n const signal = options?.signal;\n\n if (onSuccess === undefined) return signal === undefined ? undefined : { signal };\n\n return signal === undefined ? { onSuccess } : { onSuccess, signal };\n}\n\nfunction requestOf<Input, Output>(\n input: Input,\n options: CommandRunOptions<Output> | undefined,\n): CommandRequest<Input, Output> {\n let resolve!: (outcome: CommandOutcome<Output>) => void;\n const promise = new Promise<CommandOutcome<Output>>(settle => {\n resolve = settle;\n });\n\n return { input, options: options === undefined ? undefined : { ...options }, promise, resolve };\n}\n\nfunction settle<Input, Output>(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n const resolve = request.resolve;\n request.input = undefined;\n request.options = undefined;\n request.resolve = undefined;\n resolve?.(outcome);\n}\n\n/**\n * D203: the consumer keeps a status, not a schedule. Every `run` reaches the call, and the policy the call was\n * created with — `queue`, `latest`, `parallel`, `once`, `dedupe` — decides what happens to it. What stays\n * local is what only this consumer knows: whether it is mounted, its own input signal, and the outcome it shows.\n */\nclass CommandHookController<Input, Output> {\n // React may supply an event; inputless handlers intentionally never forward it as Call data.\n readonly handler = (): void => {\n void this.run(undefined as Input);\n };\n readonly run: CommandRun<Input, Output> = ((input: Input, options?: CommandRunOptions<Output>) => {\n if (this.notify === undefined) {\n return Promise.resolve({\n reason: new ContributionError('inactive', 'Command consumer is not mounted.'),\n status: 'cancelled' as const,\n });\n }\n\n const aborted = this.cancelledInput(options);\n\n if (aborted !== undefined) return Promise.resolve(aborted);\n\n const request = requestOf(input, options);\n this.start(request);\n\n return request.promise;\n }) as CommandRun<Input, Output>;\n private inFlight = 0;\n private notify: CommandNotification<Output> | undefined;\n\n constructor(private readonly invoker: CommandInvoker) {}\n\n attach(notify: CommandNotification<Output>): () => void {\n this.notify = notify;\n\n return () => {\n if (this.notify !== notify) return;\n\n this.notify = undefined;\n };\n }\n\n synchronize(): void {\n this.notify?.(this.inFlight > 0);\n }\n\n private aborted(signal: AbortSignal): CommandOutcome<never> {\n return {\n reason: new CallError('cancelled', 'Command input was cancelled.', compatibleAbortReason(signal)),\n status: 'cancelled',\n };\n }\n\n private cancelledInput(options: CommandRunOptions<Output> | undefined): CommandOutcome<never> | undefined {\n return options?.signal?.aborted === true ? this.aborted(options.signal) : undefined;\n }\n\n private finish(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n if (request.resolve === undefined) return;\n\n this.inFlight -= 1;\n settle(request, outcome);\n this.notify?.(this.inFlight > 0, outcome);\n }\n\n private invoke(request: CommandRequest<Input, Output>): void {\n let invocation: Promise<Output>;\n\n try {\n invocation = this.invoker.run(request.input as never, runOptions(request.options) as never) as Promise<Output>;\n request.input = undefined;\n } catch (error) {\n this.finish(request, failureOutcome(request.options, error));\n\n return;\n }\n\n void invocation.then(\n value => this.finish(request, { status: 'ok', value }),\n (error: unknown) => this.finish(request, failureOutcome(request.options, error)),\n );\n }\n\n private start(request: CommandRequest<Input, Output>): void {\n this.inFlight += 1;\n // Reserve the flight before publishing: a reentrant run must already see this consumer as busy.\n this.notify?.(true);\n this.invoke(request);\n }\n}\n\nexport { CommandHookController };\nexport type { CommandInvoker, CommandRun };\n","import { useInsertionEffect, useLayoutEffect, useMemo, useState } from 'react';\n\nimport type { Call } from '@opetope/core';\nimport type { IsAny } from '@opetope/core/internal';\n\nimport type { CommandOutcome } from './command';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker, CommandRun } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { useFrame } from './mount-context';\n\ninterface CommandHookStatus<Output> {\n readonly inFlight: boolean;\n readonly lastError: unknown | null;\n readonly lastResult: Output | undefined;\n}\n\nconst emptyStatus: CommandHookStatus<never> = { inFlight: false, lastError: null, lastResult: undefined };\n\ntype CommandHandler<Input> =\n IsAny<Input> extends true\n ? object\n : [Input] extends [never]\n ? object\n : [Input] extends [void]\n ? { readonly handler: () => void }\n : object;\n\ntype CommandHook<Input, Output> = CommandHookStatus<Output> & {\n readonly run: CommandRun<Input, Output>;\n} & CommandHandler<Input>;\n\ntype CommandHookState<Input, Output> = CommandHookStatus<Output> & {\n readonly slot: CommandHookController<Input, Output>;\n};\n\nfunction unchangedState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n outcome: CommandOutcome<Output> | undefined,\n): boolean {\n return outcome === undefined && current.slot === slot && current.inFlight === inFlight;\n}\n\nfunction nextState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n outcome?: CommandOutcome<Output>,\n): CommandHookState<Input, Output> {\n if (unchangedState(current, slot, inFlight, outcome)) return current;\n\n const { lastError, lastResult } = current.slot === slot ? current : { lastError: null, lastResult: undefined };\n\n if (outcome?.status === 'ok') return { inFlight, lastError: null, lastResult: outcome.value, slot };\n\n if (outcome?.status === 'failed') return { inFlight, lastError: outcome.error, lastResult, slot };\n\n return { inFlight, lastError, lastResult, slot };\n}\n\nfunction useCommandSlot<Input, Output>(invoker: CommandInvoker): CommandHookController<Input, Output> {\n // Slots are inert until commit. An interrupted render cannot fence the committed source or admit its own work.\n const [slots] = useState(() => new WeakMap<object, CommandHookController<Input, Output>>());\n let slot = slots.get(invoker);\n\n if (slot === undefined) {\n slot = new CommandHookController(invoker);\n slots.set(invoker, slot);\n }\n\n return slot;\n}\n\nfunction useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output> {\n const frame = useFrame();\n const record = frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command ${command.id} is not bound in this contribution.`);\n }\n\n const currentSlot = useCommandSlot<Input, Output>(record.invoker);\n const [hookState, setHookState] = useState<CommandHookState<Input, Output>>(() => ({\n inFlight: false,\n lastError: null,\n lastResult: undefined,\n slot: currentSlot,\n }));\n useInsertionEffect(\n () =>\n currentSlot.attach((inFlight, outcome) =>\n setHookState(current => nextState(current, currentSlot, inFlight, outcome)),\n ),\n [currentSlot],\n );\n useLayoutEffect(() => currentSlot.synchronize(), [currentSlot]);\n const { inFlight, lastError, lastResult } = hookState.slot === currentSlot ? hookState : emptyStatus;\n const run = currentSlot.run;\n const handler = currentSlot.handler;\n\n return useMemo(\n () => ({ handler, inFlight, lastError, lastResult, run }),\n [handler, inFlight, lastError, lastResult, run],\n );\n}\n\nexport { useCommand };\nexport type { CommandHook };\n","const idleRelease = (): void => undefined;\nconst subscribeIdle = (): (() => void) => idleRelease;\nconst idleRevision = (): number => 0;\n\nexport { idleRevision, subscribeIdle };\n","import { useInsertionEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport type { CommandHook } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { idleRevision, subscribeIdle } from './idle-subscription';\nimport { useFrame } from './mount-context';\nimport type { Frame } from './mount-frame';\n\ntype CommandSelection = Readonly<Record<string, Call<never, unknown>>>;\ntype HookOf<Command> = Command extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : never;\ntype CommandsHook<Commands> = {\n readonly [Key in keyof Commands]: HookOf<Commands[Key]>;\n};\nconst emptyHooks = Object.freeze({});\n\ntype CommandEntry = {\n detach: (() => void) | undefined;\n hook: CommandHook<never, unknown> & { readonly handler: () => void };\n readonly invoker: CommandInvoker;\n readonly slot: CommandHookController<never, unknown>;\n};\n\nfunction createEntry(invoker: CommandInvoker): CommandEntry {\n const slot = new CommandHookController<never, unknown>(invoker);\n\n return {\n detach: undefined,\n hook: { handler: slot.handler, inFlight: false, lastError: null, lastResult: undefined, run: slot.run },\n invoker,\n slot,\n };\n}\n\nfunction unchangedHook(previous: CommandHook<never, unknown>, next: CommandHook<never, unknown>): boolean {\n return (\n previous.inFlight === next.inFlight &&\n Object.is(previous.lastResult, next.lastResult) &&\n Object.is(previous.lastError, next.lastError)\n );\n}\n\nfunction outcomeStatus(previous: CommandHook<never, unknown>, outcome: CommandOutcome<unknown> | undefined) {\n if (outcome?.status === 'ok') return { lastError: null, lastResult: outcome.value };\n\n if (outcome?.status === 'failed') return { lastError: outcome.error, lastResult: previous.lastResult };\n\n return previous;\n}\n\nfunction updateEntry(entry: CommandEntry, inFlight: boolean, outcome?: CommandOutcome<unknown>): boolean {\n const previous = entry.hook;\n\n if (outcome === undefined && previous.inFlight === inFlight) return false;\n\n const { lastError, lastResult } = outcomeStatus(previous, outcome);\n const next = { handler: entry.slot.handler, inFlight, lastError, lastResult, run: previous.run };\n\n if (unchangedHook(previous, next)) return false;\n\n entry.hook = next;\n\n return true;\n}\n\nfunction selectEntry(previous: CommandEntry | undefined, invoker: CommandInvoker): CommandEntry {\n return previous?.invoker === invoker ? previous : createEntry(invoker);\n}\n\n/** Selection is inert during render; only a committed selection can admit work or discard an old queue. */\nclass CommandGroup {\n private entries = new Map<PropertyKey, CommandEntry>();\n private readonly listeners = new Set<() => void>();\n private revision = 0;\n\n close(): void {\n for (const entry of this.entries.values()) {\n entry.detach?.();\n entry.detach = undefined;\n }\n\n this.entries.clear();\n }\n\n commit(selected: Map<PropertyKey, CommandEntry>): void {\n for (const [key, entry] of this.entries) {\n if (selected.get(key) === entry) continue;\n\n entry.detach?.();\n entry.detach = undefined;\n }\n\n this.entries = selected;\n\n for (const entry of selected.values()) {\n entry.detach ??= entry.slot.attach((inFlight, outcome) => {\n if (!updateEntry(entry, inFlight, outcome)) return;\n\n this.revision += 1;\n\n for (const listener of this.listeners) listener();\n });\n }\n }\n\n getSnapshot = (): number => this.revision;\n\n prepare(commands: CommandSelection, frame: Frame): Map<PropertyKey, CommandEntry> {\n const selected = new Map<PropertyKey, CommandEntry>();\n\n for (const key of Reflect.ownKeys(commands)) {\n if (!Object.prototype.propertyIsEnumerable.call(commands, key)) continue;\n\n const command = (commands as Readonly<Record<PropertyKey, Call<never, unknown>>>)[key];\n const record = command === undefined ? undefined : frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command at ${String(key)} is not bound in this contribution.`);\n }\n\n selected.set(key, selectEntry(this.entries.get(key), record.invoker));\n }\n\n return selected;\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n };\n}\n\nfunction useCommandGroup(commands: CommandSelection): CommandGroup | undefined {\n const groupRef = useRef<CommandGroup | undefined>(undefined);\n const populated = Reflect.ownKeys(commands).some(key => Object.prototype.propertyIsEnumerable.call(commands, key));\n\n return populated ? (groupRef.current ??= new CommandGroup()) : undefined;\n}\n\nfunction useCommands<Commands extends { readonly [Key in keyof Commands]: Call<never, unknown> }>(\n commands: Commands,\n): CommandsHook<Commands> {\n const frame = useFrame();\n const group = useCommandGroup(commands);\n const getSnapshot = group?.getSnapshot ?? idleRevision;\n useSyncExternalStore(group?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const selected = group?.prepare(commands, frame);\n useInsertionEffect(() => () => group?.close(), [group]);\n useInsertionEffect(() => {\n if (selected !== undefined) group?.commit(selected);\n });\n useLayoutEffect(() => {\n if (selected === undefined) return;\n\n for (const entry of selected.values()) entry.slot.synchronize();\n });\n\n return (\n selected === undefined ? emptyHooks : Object.fromEntries([...selected].map(([key, entry]) => [key, entry.hook]))\n ) as CommandsHook<Commands>;\n}\n\nexport { useCommands };\n","import type { Readable } from '@opetope/core';\nimport { isCallTarget, isReadable } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\nimport type { AnyCommand } from './model-binding';\n\ninterface SelectionRead {\n <Value>(source: Readable<Value>): Value;\n <Value, Selected>(source: Readable<Value>, select: (value: Value) => Selected): Selected;\n}\n\ntype SelectionContext = Readonly<{ read: SelectionRead }>;\ntype NonSelectionRecord =\n | readonly unknown[]\n | ((...arguments_: never[]) => unknown)\n | (abstract new (...arguments_: never[]) => unknown)\n | Date\n | PromiseLike<unknown>\n | ReadonlyMap<unknown, unknown>\n | ReadonlySet<unknown>\n | RegExp\n | WeakMap<object, unknown>\n | WeakSet<object>;\n/** A named interface needs no open index signature; container and callable results are not field selections. */\ntype SelectionShape<Selected extends object> = Selected extends NonSelectionRecord ? never : Selected;\ntype SelectionRecord = Readonly<Record<PropertyKey, unknown>>;\ntype SelectionSnapshot = Readonly<{\n commands: Readonly<Record<PropertyKey, AnyCommand>>;\n sources: ReadonlySet<Readable<unknown>>;\n values: SelectionRecord;\n}>;\n\nfunction readSource<Value>(source: Readable<Value>, sources: Map<Readable<unknown>, unknown>): Value {\n if (!isReadable(source)) {\n throw new ContributionError('binding-invalid', 'Model selection read requires a Readable.');\n }\n\n if (sources.has(source)) return sources.get(source) as Value;\n\n const value = source.getSnapshot();\n sources.set(source, value);\n\n return value;\n}\n\nfunction collectSelection(\n select: (context: SelectionContext) => object,\n sources: Map<Readable<unknown>, unknown>,\n): SelectionRecord {\n const read = <Value, Selected>(source: Readable<Value>, project?: (value: Value) => Selected) => {\n const value = readSource(source, sources);\n\n return project === undefined ? value : project(value);\n };\n const selected = select({ read });\n\n if (typeof selected !== 'object' || selected === null || Array.isArray(selected)) {\n throw new ContributionError('binding-invalid', 'Model selection requires a record.');\n }\n\n return Object.fromEntries(\n Reflect.ownKeys(selected)\n .filter(key => Object.prototype.propertyIsEnumerable.call(selected, key))\n .map(key => [key, (selected as SelectionRecord)[key]]),\n );\n}\n\nfunction equalSelection(previous: SelectionSnapshot, values: SelectionRecord, sources: ReadonlySet<Readable<unknown>>) {\n const keys = Reflect.ownKeys(values);\n const previousKeys = Reflect.ownKeys(previous.values);\n\n if (keys.length !== previousKeys.length || sources.size !== previous.sources.size) return false;\n\n for (const source of sources) if (!previous.sources.has(source)) return false;\n\n return keys.every((key, index) => previousKeys[index] === key && Object.is(previous.values[key], values[key]));\n}\n\ntype ModelSelector<Model> = (model: Model, context: SelectionContext) => object;\ntype SelectionReaderRef<Model> = { readonly current: ModelSelector<Model> | undefined };\n\nconst emptySelectionSnapshot: SelectionSnapshot = { commands: {}, sources: new Set(), values: {} };\n\nfunction selectionCommands(values: SelectionRecord): Readonly<Record<PropertyKey, AnyCommand>> {\n return Object.fromEntries(\n Reflect.ownKeys(values)\n .filter(key => isCallTarget(values[key]))\n .map(key => [key, values[key]]),\n ) as Readonly<Record<PropertyKey, AnyCommand>>;\n}\n\n/**\n * D214: one reader per model, not per render. The author's selector arrives through a ref, so an inline arrow does not\n * rebuild the reader; the fast path — nothing observed has moved — belongs to the selector that computed the snapshot,\n * so a new selector is always called again, its result compared field by field, and an equal selection keeps the\n * previous snapshot. A rejected or abandoned render still changes nothing: only a committed selection subscribes.\n */\nfunction createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>): () => SelectionSnapshot {\n let observed: Map<Readable<unknown>, unknown> | undefined;\n let observedSelect: ModelSelector<Model> | undefined;\n let snapshot: SelectionSnapshot = emptySelectionSnapshot;\n\n return () => {\n const select = selectRef.current;\n\n if (select === undefined) return emptySelectionSnapshot;\n\n if (\n observed !== undefined &&\n observedSelect === select &&\n [...observed].every(([source, value]) => Object.is(source.getSnapshot(), value))\n ) {\n return snapshot;\n }\n\n const nextObserved = new Map<Readable<unknown>, unknown>();\n const values = collectSelection(context => select(model, context), nextObserved);\n const sources = new Set(nextObserved.keys());\n\n if (observed === undefined || !equalSelection(snapshot, values, sources)) {\n snapshot = { commands: selectionCommands(values), sources, values };\n }\n\n observed = nextObserved;\n observedSelect = select;\n\n return snapshot;\n };\n}\n\nexport { createSelectionSnapshot, emptySelectionSnapshot };\nexport type { ModelSelector, SelectionContext, SelectionShape };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nfunction releaseSubscriptions(subscriptions: Iterable<Subscription>, failures: unknown[] = []): void {\n for (const subscription of subscriptions) {\n const dispose = subscription.dispose;\n subscription.dispose = undefined;\n\n try {\n dispose?.();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n\n if (failures.length > 1) throw createAggregateError(failures, 'Model selection sources failed to release.');\n}\n\n/** A subscription set, not a scheduler: source notifications use React's snapshot consistency check. */\nclass ModelSelectionStore {\n private enabled = false;\n private readonly listeners = new Set<Listener>();\n private sources: ReadonlySet<Readable<unknown>> = new Set();\n private readonly subscriptions = new Map<Readable<unknown>, Subscription>();\n\n close(): void {\n this.enabled = false;\n this.sources = new Set();\n this.releaseAll();\n }\n\n commit(sources: ReadonlySet<Readable<unknown>>): void {\n this.sources = sources;\n this.enabled = true;\n\n try {\n this.synchronize();\n } catch (error) {\n this.releaseAll([error]);\n }\n }\n\n subscribe = (notify: () => void): (() => void) => {\n const listener = { notify };\n this.listeners.add(listener);\n\n try {\n this.synchronize();\n } catch (error) {\n this.listeners.delete(listener);\n this.releaseAll([error]);\n }\n\n return () => {\n this.listeners.delete(listener);\n\n if (this.listeners.size === 0) this.releaseAll();\n };\n };\n\n private add(source: Readable<unknown>): void {\n const subscription: Subscription = { dispose: undefined };\n // Reserve before foreign code: a synchronous notification may replace or close this selection.\n this.subscriptions.set(source, subscription);\n const dispose = source.subscribe(this.notify);\n\n if (typeof dispose !== 'function') throw new TypeError('Model selection source must return a disposer.');\n\n if (this.subscriptions.get(source) === subscription) subscription.dispose = dispose;\n else dispose();\n }\n\n private notify = (): void => {\n for (const listener of [...this.listeners]) {\n if (this.listeners.has(listener)) listener.notify();\n }\n };\n\n private releaseAll(failures?: unknown[]): void {\n const subscriptions = [...this.subscriptions.values()];\n this.subscriptions.clear();\n releaseSubscriptions(subscriptions, failures);\n }\n\n private removeUnselected(): void {\n const removed: Subscription[] = [];\n\n for (const [source, subscription] of this.subscriptions) {\n if (this.sources.has(source)) continue;\n\n this.subscriptions.delete(source);\n removed.push(subscription);\n }\n\n releaseSubscriptions(removed);\n }\n\n private synchronize(): void {\n this.removeUnselected();\n\n for (const source of this.sources) {\n if (!this.enabled || this.listeners.size === 0) return;\n\n if (this.sources.has(source) && !this.subscriptions.has(source)) this.add(source);\n }\n }\n}\n\nexport { ModelSelectionStore };\n","import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call, ModelOf } from '@opetope/core';\n\nimport type { CommandHook } from './command-hook';\nimport { useCommands } from './commands-hook';\nimport { ContributionError } from './errors';\nimport { subscribeIdle } from './idle-subscription';\nimport type { AnyModel } from './model-binding';\nimport { createSelectionSnapshot, emptySelectionSnapshot } from './model-selection-snapshot';\nimport type { ModelSelector, SelectionContext, SelectionShape } from './model-selection-snapshot';\nimport { ModelSelectionStore } from './model-selection-store';\nimport { useFrame } from './mount-context';\n\ntype SelectedValue<Value> = Value extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : Value;\ntype ModelSelection<Selected> = { readonly [Key in keyof Selected]: SelectedValue<Selected[Key]> };\n\nconst readEmptySnapshot = () => emptySelectionSnapshot;\n\nfunction useSelectionStore(selected: boolean): ModelSelectionStore | undefined {\n const storeRef = useRef<ModelSelectionStore | undefined>(undefined);\n\n return selected ? (storeRef.current ??= new ModelSelectionStore()) : undefined;\n}\n\n/** D205: resolve only a granted model, explicitly read data, and reuse the ordinary per-key command consumers. */\nfunction useModel<Declaration extends AnyModel>(declaration: Declaration): ModelOf<Declaration>;\nfunction useModel<Declaration extends AnyModel, const Selected extends object>(\n declaration: Declaration,\n select: (model: ModelOf<Declaration>, context: SelectionContext) => Selected & SelectionShape<Selected>,\n): ModelSelection<Selected>;\nfunction useModel<Declaration extends AnyModel>(\n declaration: Declaration,\n select?: (model: ModelOf<Declaration>, context: SelectionContext) => object,\n): unknown {\n const frame = useFrame();\n\n if (!frame.models.has(declaration)) {\n throw new ContributionError('missing', `Model ${declaration.id} is not granted to this contribution.`);\n }\n\n const model = frame.models.get(declaration) as ModelOf<Declaration>;\n const store = useSelectionStore(select !== undefined);\n // D214: the selector of this render, read by a reader that belongs to the model. Writing the latest callback into\n // a ref is what `useSyncExternalStoreWithSelector` does: the reader only reads it, and never writes to it.\n const selectRef = useRef<ModelSelector<ModelOf<Declaration>> | undefined>(undefined);\n selectRef.current = select;\n const selected = select !== undefined;\n const getSnapshot = useMemo(\n () => (selected ? createSelectionSnapshot(model, selectRef) : readEmptySnapshot),\n [model, selected],\n );\n const snapshot = useSyncExternalStore(store?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const commands = useCommands(snapshot.commands);\n useLayoutEffect(() => () => store?.close(), [store]);\n useLayoutEffect(() => store?.commit(snapshot.sources), [snapshot, store]);\n\n if (select === undefined) return model;\n\n return Object.fromEntries(\n Reflect.ownKeys(snapshot.values).map(key => [\n key,\n Object.prototype.hasOwnProperty.call(commands, key) ? commands[key] : snapshot.values[key],\n ]),\n );\n}\n\nexport { useModel };\n","import type { FunctionComponent } from 'react';\n\nimport type { ModelIdentity } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\ntype AnyModel = ModelIdentity;\n\n/**\n * The marker a component uses to state which UI models of its contribution it reads. `slot` checks by type that the\n * contribution grants every model of this list — it may grant more, because the mount serves its whole instance.\n *\n * The runtime authority is the mount frame, not this marker: `useModel` resolves against what the mount was given\n * and refuses anything else. The lint rule `opetope/require-declared-models` checks visible contribution sites and\n * each reader of a per-mount model within the same module. Imported implementations and dynamic declaration lists\n * remain unknown to this syntactic check (D85, D158, D250).\n */\ntype ComponentRequiringModels<Props, Models extends readonly AnyModel[]> = FunctionComponent<Props> & {\n readonly requires: Models;\n};\n\nfunction requiresModels<const Models extends readonly AnyModel[]>(\n models: Models,\n): <Props>(component: FunctionComponent<Props>) => ComponentRequiringModels<Props, Models> {\n const declared = Object.freeze([...models]) as unknown as Models;\n\n return <Props>(component: FunctionComponent<Props>): ComponentRequiringModels<Props, Models> => {\n const existing = (component as { readonly requires?: readonly AnyModel[] }).requires;\n\n if (existing !== undefined) {\n throw new ContributionError('duplicate', 'Component already declares the models it requires.');\n }\n\n Object.defineProperty(component, 'requires', { enumerable: false, value: declared });\n\n return component as ComponentRequiringModels<Props, Models>;\n };\n}\n\nexport { requiresModels };\n","import { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { Resource, ResourceSnapshot } from '@opetope/runtime';\nimport type { ResourceRequestKey } from '@opetope/runtime/internal';\n\nfunction useResource<Data, Key extends ResourceRequestKey>(resource: Resource<Data, Key>): ResourceSnapshot<Data, Key> {\n const subscribe = useCallback((listener: () => void) => resource.subscribe(listener), [resource]);\n // D199: a resource is a structural contract, so it may be an object with methods. Reading through it keeps the\n // receiver the adapter expects; handing `resource.getSnapshot` to React would call it with none.\n const getSnapshot = useCallback(() => resource.getSnapshot(), [resource]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => resource.retain(), [resource]);\n\n return snapshot;\n}\n\nexport { useResource };\n","import type { ComponentType, ReactNode } from 'react';\nimport { createElement, Fragment } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport { declarationId } from '@opetope/core';\nimport type { ContributionTarget } from '@opetope/core/internal';\nimport { createContributionTarget } from '@opetope/core/internal';\nimport type { ContributionModel } from '@opetope/runtime/internal';\n\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\n\ntype SlotProperties = object | undefined;\ntype EmptySlotProperties = Readonly<Record<string, never>>;\ntype SlotComponentProperties<Props extends SlotProperties> = [Props] extends [undefined]\n ? EmptySlotProperties\n : Extract<Props, object>;\n\n/**\n * What a contribution publishes: the component, the optional props adapter and the optional UI models the mount\n * creates. Without an adapter the component takes the slot props; with one it takes exactly the adapter result (D85).\n */\ntype SlotContribution<Props extends SlotProperties = undefined> = {\n readonly Component: ComponentType<never>;\n readonly models?: readonly ContributionModel[];\n readonly props?: (slotProps: SlotComponentProperties<Props>) => object;\n};\n\ntype SlotTarget<Props extends SlotProperties = undefined> = ContributionTarget<SlotContribution<Props>>;\n\ntype SwitchSlotTarget<Route extends string, Props extends SlotProperties = undefined> = ((\n route: Route,\n) => SlotTarget<Props>) & {\n readonly id: DeclarationId;\n};\n\ntype SlotRenderProps<Props extends SlotProperties> = [Props] extends [undefined]\n ? {\n readonly props?: never;\n readonly target: SlotTarget<Props>;\n }\n : {\n readonly props: SlotComponentProperties<Props>;\n readonly target: SlotTarget<Props>;\n };\n\nconst emptySlotProperties: EmptySlotProperties = Object.freeze({});\n\n/** D280: a slot is a name, so it takes its id as the first argument. */\nfunction defineSlot<Props extends SlotProperties = undefined>(id: string): SlotTarget<Props> {\n return createContributionTarget<SlotContribution<Props>>(id, 'slot');\n}\n\nfunction defineSwitchSlot<Route extends string, Props extends SlotProperties = undefined>(\n id: string,\n): SwitchSlotTarget<Route, Props> {\n const declared = declarationId(id);\n const targets = new Map<Route, SlotTarget<Props>>();\n\n const switchSlot = (route: Route): SlotTarget<Props> => {\n const existing = targets.get(route);\n\n if (existing !== undefined) return existing;\n\n const target = defineSlot<Props>(`${declared}/${route}`);\n targets.set(route, target);\n\n return target;\n };\n\n Object.defineProperty(switchSlot, 'id', { enumerable: true, value: declared });\n\n return Object.freeze(switchSlot) as SwitchSlotTarget<Route, Props>;\n}\n\n/**\n * Every contribution renders inside its own mount: the models its generation owns plus the UI models it declared,\n * created on mount and closed on unmount (D70, D85).\n */\nfunction Slot<Props extends SlotProperties>(options: SlotRenderProps<Props>): ReactNode {\n const entries = useReadable(options.target.entries);\n const slotProps: object = 'props' in options ? (options.props ?? emptySlotProperties) : emptySlotProperties;\n\n if (entries.length === 0) return null;\n\n return entries.map(entry =>\n createElement(\n Fragment,\n { key: entry.id },\n createElement(ContributionMount, {\n authority: contributionAuthority(entry),\n contribution: entry.value,\n contributionId: entry.id,\n slotProps,\n targetId: options.target.id,\n }),\n ),\n );\n}\n\nexport { defineSlot, defineSwitchSlot, Slot };\nexport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget, SwitchSlotTarget };\n"],"names":["failureOutcome","options","error","isCancellation","notification","_a","reportDetachedError","callbackError","runOptions","onSuccess","signal","requestOf","input","resolve","promise","settle","request","outcome","CommandHookController","ContributionError","aborted","invoker","notify","CallError","compatibleAbortReason","invocation","value","emptyStatus","unchangedState","current","slot","inFlight","nextState","lastError","lastResult","useCommandSlot","slots","useState","useCommand","command","record","useFrame","currentSlot","hookState","setHookState","useInsertionEffect","useLayoutEffect","run","handler","useMemo","idleRelease","subscribeIdle","idleRevision","emptyHooks","createEntry","unchangedHook","previous","next","outcomeStatus","updateEntry","entry","selectEntry","CommandGroup","selected","key","listener","commands","frame","useCommandGroup","groupRef","useRef","useCommands","group","getSnapshot","useSyncExternalStore","readSource","source","sources","isReadable","collectSelection","select","project","equalSelection","values","keys","previousKeys","index","emptySelectionSnapshot","selectionCommands","isCallTarget","createSelectionSnapshot","model","selectRef","observed","observedSelect","snapshot","nextObserved","context","releaseSubscriptions","subscriptions","failures","subscription","dispose","createAggregateError","ModelSelectionStore","removed","readEmptySnapshot","useSelectionStore","storeRef","useModel","declaration","store","requiresModels","models","declared","component","useResource","resource","subscribe","useCallback","useEffect","emptySlotProperties","defineSlot","id","createContributionTarget","defineSwitchSlot","declarationId","targets","switchSlot","route","existing","target","Slot","entries","useReadable","slotProps","createElement","Fragment","ContributionMount","contributionAuthority"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/command-hook-controller.ts","../src/command-hook.ts","../src/idle-subscription.ts","../src/commands-hook.ts","../src/model-selection-snapshot.ts","../src/model-selection-store.ts","../src/model-hook.ts","../src/requires-models.ts","../src/resource-hook.ts","../src/slot.tsx"],"sourcesContent":["import { CallError } from '@opetope/core';\nimport { compatibleAbortReason, isCancellation, reportDetachedError } from '@opetope/core/internal';\nimport type { CallInputArgs, CallRunOptions } from '@opetope/core/internal';\n\nimport type { CommandOutcome } from './command';\nimport { ContributionError } from './errors';\n\ntype CommandRunOptions<Output> = Readonly<{\n onFailure?: (error: unknown) => PromiseLike<void> | void;\n onSuccess?: (value: Output) => PromiseLike<void> | void;\n signal?: AbortSignal;\n}>;\n\ntype CommandRun<Input, Output> = {\n (input: Input, options?: CommandRunOptions<Output>): Promise<CommandOutcome<Output>>;\n (...args: [...CallInputArgs<Input>, options?: CommandRunOptions<Output>]): Promise<CommandOutcome<Output>>;\n};\n\ntype CommandInvoker = Readonly<{\n run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;\n}>;\n\ntype CommandNotification<Output> = (inFlight: boolean, outcome?: CommandOutcome<Output>) => void;\n\ntype CommandRequest<Input, Output> = {\n input: Input | undefined;\n options: CommandRunOptions<Output> | undefined;\n readonly promise: Promise<CommandOutcome<Output>>;\n resolve: ((outcome: CommandOutcome<Output>) => void) | undefined;\n};\n\nfunction failureOutcome<Output>(options: CommandRunOptions<Output> | undefined, error: unknown): CommandOutcome<never> {\n if (isCancellation(error)) return { reason: error, status: 'cancelled' };\n\n try {\n const notification = options?.onFailure?.(error);\n\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n } catch (callbackError) {\n reportDetachedError(callbackError);\n }\n\n return { error, status: 'failed' };\n}\n\nfunction runOptions<Output>(options: CommandRunOptions<Output> | undefined): CallRunOptions<Output> | undefined {\n const onSuccess = options?.onSuccess;\n const signal = options?.signal;\n\n if (onSuccess === undefined) return signal === undefined ? undefined : { signal };\n\n return signal === undefined ? { onSuccess } : { onSuccess, signal };\n}\n\nfunction requestOf<Input, Output>(\n input: Input,\n options: CommandRunOptions<Output> | undefined,\n): CommandRequest<Input, Output> {\n let resolve!: (outcome: CommandOutcome<Output>) => void;\n const promise = new Promise<CommandOutcome<Output>>(settle => {\n resolve = settle;\n });\n\n return { input, options: options === undefined ? undefined : { ...options }, promise, resolve };\n}\n\nfunction settle<Input, Output>(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n const resolve = request.resolve;\n request.input = undefined;\n request.options = undefined;\n request.resolve = undefined;\n resolve?.(outcome);\n}\n\n/**\n * D203: the consumer keeps a status, not a schedule. Every `run` reaches the call, and the policy the call was\n * created with — `queue`, `latest`, `parallel`, `once`, `dedupe` — decides what happens to it. What stays\n * local is what only this consumer knows: whether it is mounted, its own input signal, and the outcome it shows.\n */\nclass CommandHookController<Input, Output> {\n readonly run: CommandRun<Input, Output> = ((input: Input, options?: CommandRunOptions<Output>) => {\n if (this.notify === undefined) {\n return Promise.resolve({\n reason: new ContributionError('inactive', 'Command consumer is not mounted.'),\n status: 'cancelled' as const,\n });\n }\n\n const aborted = this.cancelledInput(options);\n\n if (aborted !== undefined) return Promise.resolve(aborted);\n\n const request = requestOf(input, options);\n this.start(request);\n\n return request.promise;\n }) as CommandRun<Input, Output>;\n private inFlight = 0;\n private notify: CommandNotification<Output> | undefined;\n\n constructor(private readonly invoker: CommandInvoker) {}\n\n attach(notify: CommandNotification<Output>): () => void {\n this.notify = notify;\n\n return () => {\n if (this.notify !== notify) return;\n\n this.notify = undefined;\n };\n }\n\n synchronize(): void {\n this.notify?.(this.inFlight > 0);\n }\n\n private aborted(signal: AbortSignal): CommandOutcome<never> {\n return {\n reason: new CallError('cancelled', 'Command input was cancelled.', compatibleAbortReason(signal)),\n status: 'cancelled',\n };\n }\n\n private cancelledInput(options: CommandRunOptions<Output> | undefined): CommandOutcome<never> | undefined {\n return options?.signal?.aborted === true ? this.aborted(options.signal) : undefined;\n }\n\n private finish(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n if (request.resolve === undefined) return;\n\n this.inFlight -= 1;\n settle(request, outcome);\n this.notify?.(this.inFlight > 0, outcome);\n }\n\n private invoke(request: CommandRequest<Input, Output>): void {\n let invocation: Promise<Output>;\n\n try {\n invocation = this.invoker.run(request.input as never, runOptions(request.options) as never) as Promise<Output>;\n request.input = undefined;\n } catch (error) {\n this.finish(request, failureOutcome(request.options, error));\n\n return;\n }\n\n void invocation.then(\n value => this.finish(request, { status: 'ok', value }),\n (error: unknown) => this.finish(request, failureOutcome(request.options, error)),\n );\n }\n\n private start(request: CommandRequest<Input, Output>): void {\n this.inFlight += 1;\n // Reserve the flight before publishing: a reentrant run must already see this consumer as busy.\n this.notify?.(true);\n this.invoke(request);\n }\n}\n\nexport { CommandHookController };\nexport type { CommandInvoker, CommandRun };\n","import { useInsertionEffect, useLayoutEffect, useMemo, useState } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker, CommandRun } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { useFrame } from './mount-context';\n\n/** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */\ntype SettledOutcome<Output> = Exclude<CommandOutcome<Output>, { readonly status: 'cancelled' }>;\n\ninterface CommandHookStatus<Output> {\n readonly inFlight: boolean;\n readonly outcome: SettledOutcome<Output> | undefined;\n}\n\nconst emptyStatus: CommandHookStatus<never> = { inFlight: false, outcome: undefined };\n\ntype CommandHook<Input, Output> = CommandHookStatus<Output> & {\n readonly run: CommandRun<Input, Output>;\n};\n\ntype CommandHookState<Input, Output> = CommandHookStatus<Output> & {\n readonly slot: CommandHookController<Input, Output>;\n};\n\n/** D292: a cancellation moves nothing, so it is the same non-event as a notification without an outcome at all. */\nfunction settledOutcome<Output>(outcome: CommandOutcome<Output> | undefined): SettledOutcome<Output> | undefined {\n return outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n}\n\nfunction unchangedState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n settled: SettledOutcome<Output> | undefined,\n): boolean {\n return settled === undefined && current.slot === slot && current.inFlight === inFlight;\n}\n\nfunction nextState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n outcome?: CommandOutcome<Output>,\n): CommandHookState<Input, Output> {\n const settled = settledOutcome(outcome);\n\n if (unchangedState(current, slot, inFlight, settled)) return current;\n\n // A new run clears nothing: the previous answer stays readable while its retry is in flight.\n const previous = current.slot === slot ? current.outcome : undefined;\n\n return { inFlight, outcome: settled ?? previous, slot };\n}\n\nfunction useCommandSlot<Input, Output>(invoker: CommandInvoker): CommandHookController<Input, Output> {\n // Slots are inert until commit. An interrupted render cannot fence the committed source or admit its own work.\n const [slots] = useState(() => new WeakMap<object, CommandHookController<Input, Output>>());\n let slot = slots.get(invoker);\n\n if (slot === undefined) {\n slot = new CommandHookController(invoker);\n slots.set(invoker, slot);\n }\n\n return slot;\n}\n\nfunction useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output> {\n const frame = useFrame();\n const record = frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command ${command.id} is not bound in this contribution.`);\n }\n\n const currentSlot = useCommandSlot<Input, Output>(record.invoker);\n const [hookState, setHookState] = useState<CommandHookState<Input, Output>>(() => ({\n inFlight: false,\n outcome: undefined,\n slot: currentSlot,\n }));\n useInsertionEffect(\n () =>\n currentSlot.attach((inFlight, outcome) =>\n setHookState(current => nextState(current, currentSlot, inFlight, outcome)),\n ),\n [currentSlot],\n );\n useLayoutEffect(() => currentSlot.synchronize(), [currentSlot]);\n const { inFlight, outcome } = hookState.slot === currentSlot ? hookState : emptyStatus;\n const run = currentSlot.run;\n\n return useMemo(() => ({ inFlight, outcome, run }), [inFlight, outcome, run]);\n}\n\nexport { useCommand };\nexport type { CommandHook };\n","const idleRelease = (): void => undefined;\nconst subscribeIdle = (): (() => void) => idleRelease;\nconst idleRevision = (): number => 0;\n\nexport { idleRevision, subscribeIdle };\n","import { useInsertionEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport type { CommandHook } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { idleRevision, subscribeIdle } from './idle-subscription';\nimport { useFrame } from './mount-context';\nimport type { Frame } from './mount-frame';\n\ntype CommandSelection = Readonly<Record<string, Call<never, unknown>>>;\ntype HookOf<Command> = Command extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : never;\ntype CommandsHook<Commands> = {\n readonly [Key in keyof Commands]: HookOf<Commands[Key]>;\n};\nconst emptyHooks = Object.freeze({});\n\ntype CommandEntry = {\n detach: (() => void) | undefined;\n hook: CommandHook<never, unknown>;\n readonly invoker: CommandInvoker;\n readonly slot: CommandHookController<never, unknown>;\n};\n\nfunction createEntry(invoker: CommandInvoker): CommandEntry {\n const slot = new CommandHookController<never, unknown>(invoker);\n\n return { detach: undefined, hook: { inFlight: false, outcome: undefined, run: slot.run }, invoker, slot };\n}\n\nfunction unchangedHook(previous: CommandHook<never, unknown>, next: CommandHook<never, unknown>): boolean {\n return previous.inFlight === next.inFlight && Object.is(previous.outcome, next.outcome);\n}\n\nfunction updateEntry(entry: CommandEntry, inFlight: boolean, outcome?: CommandOutcome<unknown>): boolean {\n const previous = entry.hook;\n\n if (outcome === undefined && previous.inFlight === inFlight) return false;\n\n // D292: only a settled non-cancelled outcome replaces the one this key shows; a retry keeps the previous answer.\n const settled = outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n const next = { inFlight, outcome: settled ?? previous.outcome, run: previous.run };\n\n if (unchangedHook(previous, next)) return false;\n\n entry.hook = next;\n\n return true;\n}\n\nfunction selectEntry(previous: CommandEntry | undefined, invoker: CommandInvoker): CommandEntry {\n return previous?.invoker === invoker ? previous : createEntry(invoker);\n}\n\n/** Selection is inert during render; only a committed selection can admit work or discard an old queue. */\nclass CommandGroup {\n private entries = new Map<PropertyKey, CommandEntry>();\n private readonly listeners = new Set<() => void>();\n private revision = 0;\n\n close(): void {\n for (const entry of this.entries.values()) {\n entry.detach?.();\n entry.detach = undefined;\n }\n\n this.entries.clear();\n }\n\n commit(selected: Map<PropertyKey, CommandEntry>): void {\n for (const [key, entry] of this.entries) {\n if (selected.get(key) === entry) continue;\n\n entry.detach?.();\n entry.detach = undefined;\n }\n\n this.entries = selected;\n\n for (const entry of selected.values()) {\n entry.detach ??= entry.slot.attach((inFlight, outcome) => {\n if (!updateEntry(entry, inFlight, outcome)) return;\n\n this.revision += 1;\n\n for (const listener of this.listeners) listener();\n });\n }\n }\n\n getSnapshot = (): number => this.revision;\n\n prepare(commands: CommandSelection, frame: Frame): Map<PropertyKey, CommandEntry> {\n const selected = new Map<PropertyKey, CommandEntry>();\n\n for (const key of Reflect.ownKeys(commands)) {\n if (!Object.prototype.propertyIsEnumerable.call(commands, key)) continue;\n\n const command = (commands as Readonly<Record<PropertyKey, Call<never, unknown>>>)[key];\n const record = command === undefined ? undefined : frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command at ${String(key)} is not bound in this contribution.`);\n }\n\n selected.set(key, selectEntry(this.entries.get(key), record.invoker));\n }\n\n return selected;\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n };\n}\n\nfunction useCommandGroup(commands: CommandSelection): CommandGroup | undefined {\n const groupRef = useRef<CommandGroup | undefined>(undefined);\n const populated = Reflect.ownKeys(commands).some(key => Object.prototype.propertyIsEnumerable.call(commands, key));\n\n return populated ? (groupRef.current ??= new CommandGroup()) : undefined;\n}\n\nfunction useCommands<Commands extends { readonly [Key in keyof Commands]: Call<never, unknown> }>(\n commands: Commands,\n): CommandsHook<Commands> {\n const frame = useFrame();\n const group = useCommandGroup(commands);\n const getSnapshot = group?.getSnapshot ?? idleRevision;\n useSyncExternalStore(group?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const selected = group?.prepare(commands, frame);\n useInsertionEffect(() => () => group?.close(), [group]);\n useInsertionEffect(() => {\n if (selected !== undefined) group?.commit(selected);\n });\n useLayoutEffect(() => {\n if (selected === undefined) return;\n\n for (const entry of selected.values()) entry.slot.synchronize();\n });\n\n return (\n selected === undefined ? emptyHooks : Object.fromEntries([...selected].map(([key, entry]) => [key, entry.hook]))\n ) as CommandsHook<Commands>;\n}\n\nexport { useCommands };\n","import type { Readable } from '@opetope/core';\nimport { isCallTarget, isReadable } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\nimport type { AnyCommand } from './model-binding';\n\ninterface SelectionRead {\n <Value>(source: Readable<Value>): Value;\n <Value, Selected>(source: Readable<Value>, select: (value: Value) => Selected): Selected;\n}\n\ntype SelectionContext = Readonly<{ read: SelectionRead }>;\ntype NonSelectionRecord =\n | readonly unknown[]\n | ((...arguments_: never[]) => unknown)\n | (abstract new (...arguments_: never[]) => unknown)\n | Date\n | PromiseLike<unknown>\n | ReadonlyMap<unknown, unknown>\n | ReadonlySet<unknown>\n | RegExp\n | WeakMap<object, unknown>\n | WeakSet<object>;\n/** A named interface needs no open index signature; container and callable results are not field selections. */\ntype SelectionShape<Selected extends object> = Selected extends NonSelectionRecord ? never : Selected;\ntype SelectionRecord = Readonly<Record<PropertyKey, unknown>>;\ntype SelectionSnapshot = Readonly<{\n commands: Readonly<Record<PropertyKey, AnyCommand>>;\n sources: ReadonlySet<Readable<unknown>>;\n values: SelectionRecord;\n}>;\n\nfunction readSource<Value>(source: Readable<Value>, sources: Map<Readable<unknown>, unknown>): Value {\n if (!isReadable(source)) {\n throw new ContributionError('binding-invalid', 'Model selection read requires a Readable.');\n }\n\n if (sources.has(source)) return sources.get(source) as Value;\n\n const value = source.getSnapshot();\n sources.set(source, value);\n\n return value;\n}\n\nfunction collectSelection(\n select: (context: SelectionContext) => object,\n sources: Map<Readable<unknown>, unknown>,\n): SelectionRecord {\n const read = <Value, Selected>(source: Readable<Value>, project?: (value: Value) => Selected) => {\n const value = readSource(source, sources);\n\n return project === undefined ? value : project(value);\n };\n const selected = select({ read });\n\n if (typeof selected !== 'object' || selected === null || Array.isArray(selected)) {\n throw new ContributionError('binding-invalid', 'Model selection requires a record.');\n }\n\n return Object.fromEntries(\n Reflect.ownKeys(selected)\n .filter(key => Object.prototype.propertyIsEnumerable.call(selected, key))\n .map(key => [key, (selected as SelectionRecord)[key]]),\n );\n}\n\nfunction equalSelection(previous: SelectionSnapshot, values: SelectionRecord, sources: ReadonlySet<Readable<unknown>>) {\n const keys = Reflect.ownKeys(values);\n const previousKeys = Reflect.ownKeys(previous.values);\n\n if (keys.length !== previousKeys.length || sources.size !== previous.sources.size) return false;\n\n for (const source of sources) if (!previous.sources.has(source)) return false;\n\n return keys.every((key, index) => previousKeys[index] === key && Object.is(previous.values[key], values[key]));\n}\n\ntype ModelSelector<Model> = (model: Model, context: SelectionContext) => object;\ntype SelectionReaderRef<Model> = { readonly current: ModelSelector<Model> | undefined };\n\nconst emptySelectionSnapshot: SelectionSnapshot = { commands: {}, sources: new Set(), values: {} };\n\nfunction selectionCommands(values: SelectionRecord): Readonly<Record<PropertyKey, AnyCommand>> {\n return Object.fromEntries(\n Reflect.ownKeys(values)\n .filter(key => isCallTarget(values[key]))\n .map(key => [key, values[key]]),\n ) as Readonly<Record<PropertyKey, AnyCommand>>;\n}\n\n/**\n * D214: one reader per model, not per render. The author's selector arrives through a ref, so an inline arrow does not\n * rebuild the reader; the fast path — nothing observed has moved — belongs to the selector that computed the snapshot,\n * so a new selector is always called again, its result compared field by field, and an equal selection keeps the\n * previous snapshot. A rejected or abandoned render still changes nothing: only a committed selection subscribes.\n */\nfunction createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>): () => SelectionSnapshot {\n let observed: Map<Readable<unknown>, unknown> | undefined;\n let observedSelect: ModelSelector<Model> | undefined;\n let snapshot: SelectionSnapshot = emptySelectionSnapshot;\n\n return () => {\n const select = selectRef.current;\n\n if (select === undefined) return emptySelectionSnapshot;\n\n if (\n observed !== undefined &&\n observedSelect === select &&\n [...observed].every(([source, value]) => Object.is(source.getSnapshot(), value))\n ) {\n return snapshot;\n }\n\n const nextObserved = new Map<Readable<unknown>, unknown>();\n const values = collectSelection(context => select(model, context), nextObserved);\n const sources = new Set(nextObserved.keys());\n\n if (observed === undefined || !equalSelection(snapshot, values, sources)) {\n snapshot = { commands: selectionCommands(values), sources, values };\n }\n\n observed = nextObserved;\n observedSelect = select;\n\n return snapshot;\n };\n}\n\nexport { createSelectionSnapshot, emptySelectionSnapshot };\nexport type { ModelSelector, SelectionContext, SelectionShape };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nfunction releaseSubscriptions(subscriptions: Iterable<Subscription>, failures: unknown[] = []): void {\n for (const subscription of subscriptions) {\n const dispose = subscription.dispose;\n subscription.dispose = undefined;\n\n try {\n dispose?.();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n\n if (failures.length > 1) throw createAggregateError(failures, 'Model selection sources failed to release.');\n}\n\n/** A subscription set, not a scheduler: source notifications use React's snapshot consistency check. */\nclass ModelSelectionStore {\n private enabled = false;\n private readonly listeners = new Set<Listener>();\n private sources: ReadonlySet<Readable<unknown>> = new Set();\n private readonly subscriptions = new Map<Readable<unknown>, Subscription>();\n\n close(): void {\n this.enabled = false;\n this.sources = new Set();\n this.releaseAll();\n }\n\n commit(sources: ReadonlySet<Readable<unknown>>): void {\n this.sources = sources;\n this.enabled = true;\n\n try {\n this.synchronize();\n } catch (error) {\n this.releaseAll([error]);\n }\n }\n\n subscribe = (notify: () => void): (() => void) => {\n const listener = { notify };\n this.listeners.add(listener);\n\n try {\n this.synchronize();\n } catch (error) {\n this.listeners.delete(listener);\n this.releaseAll([error]);\n }\n\n return () => {\n this.listeners.delete(listener);\n\n if (this.listeners.size === 0) this.releaseAll();\n };\n };\n\n private add(source: Readable<unknown>): void {\n const subscription: Subscription = { dispose: undefined };\n // Reserve before foreign code: a synchronous notification may replace or close this selection.\n this.subscriptions.set(source, subscription);\n const dispose = source.subscribe(this.notify);\n\n if (typeof dispose !== 'function') throw new TypeError('Model selection source must return a disposer.');\n\n if (this.subscriptions.get(source) === subscription) subscription.dispose = dispose;\n else dispose();\n }\n\n private notify = (): void => {\n for (const listener of [...this.listeners]) {\n if (this.listeners.has(listener)) listener.notify();\n }\n };\n\n private releaseAll(failures?: unknown[]): void {\n const subscriptions = [...this.subscriptions.values()];\n this.subscriptions.clear();\n releaseSubscriptions(subscriptions, failures);\n }\n\n private removeUnselected(): void {\n const removed: Subscription[] = [];\n\n for (const [source, subscription] of this.subscriptions) {\n if (this.sources.has(source)) continue;\n\n this.subscriptions.delete(source);\n removed.push(subscription);\n }\n\n releaseSubscriptions(removed);\n }\n\n private synchronize(): void {\n this.removeUnselected();\n\n for (const source of this.sources) {\n if (!this.enabled || this.listeners.size === 0) return;\n\n if (this.sources.has(source) && !this.subscriptions.has(source)) this.add(source);\n }\n }\n}\n\nexport { ModelSelectionStore };\n","import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call, ModelOf } from '@opetope/core';\n\nimport type { CommandHook } from './command-hook';\nimport { useCommands } from './commands-hook';\nimport { ContributionError } from './errors';\nimport { subscribeIdle } from './idle-subscription';\nimport type { AnyModel } from './model-binding';\nimport { createSelectionSnapshot, emptySelectionSnapshot } from './model-selection-snapshot';\nimport type { ModelSelector, SelectionContext, SelectionShape } from './model-selection-snapshot';\nimport { ModelSelectionStore } from './model-selection-store';\nimport { useFrame } from './mount-context';\n\ntype SelectedValue<Value> = Value extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : Value;\ntype ModelSelection<Selected> = { readonly [Key in keyof Selected]: SelectedValue<Selected[Key]> };\n\nconst readEmptySnapshot = () => emptySelectionSnapshot;\n\nfunction useSelectionStore(selected: boolean): ModelSelectionStore | undefined {\n const storeRef = useRef<ModelSelectionStore | undefined>(undefined);\n\n return selected ? (storeRef.current ??= new ModelSelectionStore()) : undefined;\n}\n\n/** D205: resolve only a granted model, explicitly read data, and reuse the ordinary per-key command consumers. */\nfunction useModel<Declaration extends AnyModel>(declaration: Declaration): ModelOf<Declaration>;\nfunction useModel<Declaration extends AnyModel, const Selected extends object>(\n declaration: Declaration,\n select: (model: ModelOf<Declaration>, context: SelectionContext) => Selected & SelectionShape<Selected>,\n): ModelSelection<Selected>;\nfunction useModel<Declaration extends AnyModel>(\n declaration: Declaration,\n select?: (model: ModelOf<Declaration>, context: SelectionContext) => object,\n): unknown {\n const frame = useFrame();\n\n if (!frame.models.has(declaration)) {\n throw new ContributionError('missing', `Model ${declaration.id} is not granted to this contribution.`);\n }\n\n const model = frame.models.get(declaration) as ModelOf<Declaration>;\n const store = useSelectionStore(select !== undefined);\n // D214: the selector of this render, read by a reader that belongs to the model. Writing the latest callback into\n // a ref is what `useSyncExternalStoreWithSelector` does: the reader only reads it, and never writes to it.\n const selectRef = useRef<ModelSelector<ModelOf<Declaration>> | undefined>(undefined);\n selectRef.current = select;\n const selected = select !== undefined;\n const getSnapshot = useMemo(\n () => (selected ? createSelectionSnapshot(model, selectRef) : readEmptySnapshot),\n [model, selected],\n );\n const snapshot = useSyncExternalStore(store?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const commands = useCommands(snapshot.commands);\n useLayoutEffect(() => () => store?.close(), [store]);\n useLayoutEffect(() => store?.commit(snapshot.sources), [snapshot, store]);\n\n if (select === undefined) return model;\n\n return Object.fromEntries(\n Reflect.ownKeys(snapshot.values).map(key => [\n key,\n Object.prototype.hasOwnProperty.call(commands, key) ? commands[key] : snapshot.values[key],\n ]),\n );\n}\n\nexport { useModel };\n","import type { FunctionComponent } from 'react';\n\nimport type { ModelIdentity } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\ntype AnyModel = ModelIdentity;\n\n/**\n * The marker a component uses to state which UI models of its contribution it reads. `slot` checks by type that the\n * contribution grants every model of this list — it may grant more, because the mount serves its whole instance.\n *\n * The runtime authority is the mount frame, not this marker: `useModel` resolves against what the mount was given\n * and refuses anything else. The lint rule `opetope/require-declared-models` checks visible contribution sites and\n * each reader of a per-mount model within the same module. Imported implementations and dynamic declaration lists\n * remain unknown to this syntactic check (D85, D158, D250).\n */\ntype ComponentRequiringModels<Props, Models extends readonly AnyModel[]> = FunctionComponent<Props> & {\n readonly requires: Models;\n};\n\nfunction requiresModels<const Models extends readonly AnyModel[]>(\n models: Models,\n): <Props>(component: FunctionComponent<Props>) => ComponentRequiringModels<Props, Models> {\n const declared = Object.freeze([...models]) as unknown as Models;\n\n return <Props>(component: FunctionComponent<Props>): ComponentRequiringModels<Props, Models> => {\n const existing = (component as { readonly requires?: readonly AnyModel[] }).requires;\n\n if (existing !== undefined) {\n throw new ContributionError('duplicate', 'Component already declares the models it requires.');\n }\n\n Object.defineProperty(component, 'requires', { enumerable: false, value: declared });\n\n return component as ComponentRequiringModels<Props, Models>;\n };\n}\n\nexport { requiresModels };\n","import { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { Resource, ResourceSnapshot } from '@opetope/runtime';\nimport type { ResourceRequestKey } from '@opetope/runtime/internal';\n\nfunction useResource<Data, Key extends ResourceRequestKey>(resource: Resource<Data, Key>): ResourceSnapshot<Data, Key> {\n const subscribe = useCallback((listener: () => void) => resource.subscribe(listener), [resource]);\n // D199: a resource is a structural contract, so it may be an object with methods. Reading through it keeps the\n // receiver the adapter expects; handing `resource.getSnapshot` to React would call it with none.\n const getSnapshot = useCallback(() => resource.getSnapshot(), [resource]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => resource.retain(), [resource]);\n\n return snapshot;\n}\n\nexport { useResource };\n","import type { ComponentType, ReactNode } from 'react';\nimport { createElement, Fragment } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport { declarationId } from '@opetope/core';\nimport type { ContributionTarget } from '@opetope/core/internal';\nimport { createContributionTarget } from '@opetope/core/internal';\nimport type { ContributionModel } from '@opetope/runtime/internal';\n\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\n\ntype SlotProperties = object | undefined;\ntype EmptySlotProperties = Readonly<Record<string, never>>;\ntype SlotComponentProperties<Props extends SlotProperties> = [Props] extends [undefined]\n ? EmptySlotProperties\n : Extract<Props, object>;\n\n/**\n * What a contribution publishes: the component, the optional props adapter and the optional UI models the mount\n * creates. Without an adapter the component takes the slot props; with one it takes exactly the adapter result (D85).\n */\ntype SlotContribution<Props extends SlotProperties = undefined> = {\n readonly Component: ComponentType<never>;\n readonly models?: readonly ContributionModel[];\n readonly props?: (slotProps: SlotComponentProperties<Props>) => object;\n};\n\ntype SlotTarget<Props extends SlotProperties = undefined> = ContributionTarget<SlotContribution<Props>>;\n\ntype SwitchSlotTarget<Route extends string, Props extends SlotProperties = undefined> = ((\n route: Route,\n) => SlotTarget<Props>) & {\n readonly id: DeclarationId;\n};\n\ntype SlotRenderProps<Props extends SlotProperties> = [Props] extends [undefined]\n ? {\n readonly props?: never;\n readonly target: SlotTarget<Props>;\n }\n : {\n readonly props: SlotComponentProperties<Props>;\n readonly target: SlotTarget<Props>;\n };\n\nconst emptySlotProperties: EmptySlotProperties = Object.freeze({});\n\n/** D280: a slot is a name, so it takes its id as the first argument. */\nfunction defineSlot<Props extends SlotProperties = undefined>(id: string): SlotTarget<Props> {\n return createContributionTarget<SlotContribution<Props>>(id, 'slot');\n}\n\nfunction defineSwitchSlot<Route extends string, Props extends SlotProperties = undefined>(\n id: string,\n): SwitchSlotTarget<Route, Props> {\n const declared = declarationId(id);\n const targets = new Map<Route, SlotTarget<Props>>();\n\n const switchSlot = (route: Route): SlotTarget<Props> => {\n const existing = targets.get(route);\n\n if (existing !== undefined) return existing;\n\n const target = defineSlot<Props>(`${declared}/${route}`);\n targets.set(route, target);\n\n return target;\n };\n\n Object.defineProperty(switchSlot, 'id', { enumerable: true, value: declared });\n\n return Object.freeze(switchSlot) as SwitchSlotTarget<Route, Props>;\n}\n\n/**\n * Every contribution renders inside its own mount: the models its generation owns plus the UI models it declared,\n * created on mount and closed on unmount (D70, D85).\n */\nfunction Slot<Props extends SlotProperties>(options: SlotRenderProps<Props>): ReactNode {\n const entries = useReadable(options.target.entries);\n const slotProps: object = 'props' in options ? (options.props ?? emptySlotProperties) : emptySlotProperties;\n\n if (entries.length === 0) return null;\n\n return entries.map(entry =>\n createElement(\n Fragment,\n { key: entry.id },\n createElement(ContributionMount, {\n authority: contributionAuthority(entry),\n contribution: entry.value,\n contributionId: entry.id,\n slotProps,\n targetId: options.target.id,\n }),\n ),\n );\n}\n\nexport { defineSlot, defineSwitchSlot, Slot };\nexport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget, SwitchSlotTarget };\n"],"names":["failureOutcome","options","error","isCancellation","notification","_a","reportDetachedError","callbackError","runOptions","onSuccess","signal","requestOf","input","resolve","promise","settle","request","outcome","CommandHookController","ContributionError","aborted","invoker","notify","CallError","compatibleAbortReason","invocation","value","emptyStatus","settledOutcome","unchangedState","current","slot","inFlight","settled","nextState","previous","useCommandSlot","slots","useState","useCommand","command","record","useFrame","currentSlot","hookState","setHookState","useInsertionEffect","useLayoutEffect","run","useMemo","idleRelease","subscribeIdle","idleRevision","emptyHooks","createEntry","unchangedHook","next","updateEntry","entry","selectEntry","CommandGroup","selected","key","listener","commands","frame","useCommandGroup","groupRef","useRef","useCommands","group","getSnapshot","useSyncExternalStore","readSource","source","sources","isReadable","collectSelection","select","project","equalSelection","values","keys","previousKeys","index","emptySelectionSnapshot","selectionCommands","isCallTarget","createSelectionSnapshot","model","selectRef","observed","observedSelect","snapshot","nextObserved","context","releaseSubscriptions","subscriptions","failures","subscription","dispose","createAggregateError","ModelSelectionStore","removed","readEmptySnapshot","useSelectionStore","storeRef","useModel","declaration","store","requiresModels","models","declared","component","useResource","resource","subscribe","useCallback","useEffect","emptySlotProperties","defineSlot","id","createContributionTarget","defineSwitchSlot","declarationId","targets","switchSlot","route","existing","target","Slot","entries","useReadable","slotProps","createElement","Fragment","ContributionMount","contributionAuthority"],"mappings":"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"}
|
package/dist/scenario-slot.d.ts
CHANGED
|
@@ -20,7 +20,11 @@ interface SlotTestHarness<Props extends SlotProperties> {
|
|
|
20
20
|
readonly Slot: FC;
|
|
21
21
|
readonly updateProps: (props: SlotComponentProperties<Props>) => void;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* A command a fixture publishes: the test writes the body, the harness gives it the identity a model requires. The
|
|
25
|
+
* Call itself is the core fixture (D300); what this word adds, and what keeps it in the React package, is the
|
|
26
|
+
* contribution binding a mount reads (D285).
|
|
27
|
+
*/
|
|
24
28
|
declare function command<Input, Output>(run: (input: Input) => Output | PromiseLike<Output>): Call<Input, Output>;
|
|
25
29
|
/**
|
|
26
30
|
* Mounts the contributions of one slot the way a host does, with optional model fixtures for a component test. D19 and D85: the same
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Every word of the entry below, except one: `command` is shadowed here by the fixture that also registers the
|
|
3
|
+
* contribution binding a mount reads, so the React name keeps meaning the stronger thing it has always meant. The
|
|
4
|
+
* core fixture is the same Call without that binding, and a test that wants it names `@opetope/core/testing` (D300).
|
|
5
|
+
*/
|
|
6
|
+
export { CallTimeoutError, eventually, openModel, runCall, settled, testReadable, waitFor, WaitTimeoutError, yieldTurn, } from '@opetope/runtime/testing';
|
|
7
|
+
export type { CallInputArgs, EventuallyOptions, ModelInstance, OpenModelOptions, RunCallOptions, SettleTarget, WaitOptions, } from '@opetope/runtime/testing';
|
|
3
8
|
export { createScenario } from './scenario.js';
|
|
4
9
|
export { ScenarioTimeoutError } from './scenario-diagnostics.js';
|
|
5
10
|
export { command, renderSlot } from './scenario-slot.js';
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{WaitTimeoutError as ge,openModel as
|
|
1
|
+
import{CallTimeoutError as ye,WaitTimeoutError as Se,eventually as ge,openModel as ve,runCall as $e,settled as ke,testReadable as Me,waitFor as Ce,yieldTurn as je}from"@opetope/runtime/testing";import{hasOwn as F,createAggregateError as x}from"@opetope/core/internal";import{openApplication as _}from"@opetope/runtime";import{createInspectionSession as N}from"@opetope/runtime/internal";import{createElement as B}from"react";import{declarationId as H}from"@opetope/core";import{command as W}from"@opetope/core/testing";import{d as q,a as P,c as D,M as L}from"./contribution-frame-DXl7K0-B.js";function U(e){if(e.state.kind==="ready")return[];const t=e.state.kind==="waiting"?` (${e.state.reason})`:"";return[`Feature ${e.label}: ${e.state.kind}${t}.`]}function G(e){if(e.lane===null)return`Call ${e.declaration}: ${e.state}; lane parallel; blockers none (parallel).`;const t=e.lane.blockedBy==="unknown"?"unknown":e.lane.blockedBy.join(", ");return`Call ${e.declaration}: ${e.state}; lane ${e.lane.declaration}; blockers ${t}.`}function J(e){if(e===void 0)return["Runtime activity: unknown."];const t=[];return(e.freshness==="stale"||e.truncated)&&t.push(`Activity: ${e.freshness}${e.truncated?", truncated":""}; completeness unknown.`),[...t,...e.features.map(n=>`Feature ${n.feature}: ${n.phase}; body ${n.body}; host demand unknown.`),...e.calls.map(G),...e.resources.map(n=>`Resource ${n.id}: ${n.state}; retainers ${String(n.retainers)}.`)]}function K(e){const t=[...e.runtime.conditions.flatMap(n=>n.state.kind==="true"?[]:[`Condition ${n.label}: ${n.state.kind}.`]),...e.runtime.instances.flatMap(U),...J(e.activity)];return t.length===0?"No registered runtime blocker observed; external UI and host work are unknown.":t.join(`
|
|
2
2
|
`)}class T extends Error{label;timeoutMs;snapshot;history;code="timeout";constructor(t,n,r,o){super(`Scenario timed out after ${String(n)}ms: ${t}.
|
|
3
|
-
${
|
|
3
|
+
${K(r)}`),this.label=t,this.timeoutMs=n,this.snapshot=r,this.history=o,this.name="ScenarioTimeoutError"}}function Q(e,t){return!t||e===void 0?!1:e.closed&&!e.truncated}function V(e,t,n){if(e===void 0)return["Runtime activity is unavailable."];const r=[];return e.truncated&&r.push("The activity snapshot is truncated."),e.freshness==="stale"&&!t&&r.push("The activity snapshot is stale."),n||r.push("Successful application and mount cleanup has not been observed."),Object.freeze(r)}function X(e,t){return e===void 0||e.truncated||e.freshness==="stale"&&!t?{calls:"unknown",features:"unknown",resources:"unknown"}:{calls:e.calls.length,features:e.features.length,resources:e.resources.length}}function Y(e,t){const n=e.activity,r=Q(n,t);return Object.freeze({...X(n,r),closed:t,reasons:V(n,r,t),scope:"registered-runtime",status:r?"complete":"unknown"})}class Z{released;get closed(){return this.completion!==void 0}available=!1;completion;handle;reject;resolve;constructor(t){this.released=t}close=()=>this.completion!==void 0?this.completion:(this.completion=new Promise((t,n)=>{this.resolve=t,this.reject=n}),this.available&&this.releaseAvailable(),this.completion);provide(t){this.available=!0,this.handle=t,this.closed&&this.releaseAvailable()}finish(t,n){const r=this.resolve,o=this.reject;this.resolve=void 0,this.reject=void 0,this.released(t,n),t?o==null||o(n):r==null||r()}releaseAvailable(){const t=this.handle;this.handle=void 0,this.available=!1;try{Promise.resolve(t==null?void 0:t.unmount()).then(()=>this.finish(!1),n=>this.finish(!0,n))}catch(n){this.finish(!0,n)}}}const ee=H("testing.contribution");function te(e){return q(W(e))}function O(e,t={}){const n=t.models??[];let r=t.props??{};const o=new Set,w={getSnapshot:()=>r,subscribe:i=>(o.add(i),()=>o.delete(i))},c=t.contribution,s=t.reporter,l=s===void 0?void 0:{models:[],reporter:s};return Object.freeze({close:()=>{r={},o.clear()},Slot:()=>{const i=P(e.entries),d=P(w);return(c===void 0?i.map(p=>[p.id,p.value,D(p)??l]):[[ee,c,l]]).map(([p,f,k])=>B(L,{authority:k,contribution:f,contributionId:p,fixtures:n,key:p,slotProps:d,targetId:e.id}))},updateProps:i=>{r=i;for(const d of[...o])d()}})}function ne(e,t={}){const n=O(e,t);return Object.freeze({Slot:n.Slot,updateProps:n.updateProps})}function C(e,t){if(!Number.isFinite(e)||e<=0||e>2147483647)throw new RangeError(`${t} must be a finite duration from 0 (exclusive) to 2147483647ms.`);return e}class re{source;listeners=new Set;pending=new Set;constructor(t){this.source=t}cancel(){for(const t of[...this.pending])t(new Error("Scenario is closing."))}deadline(t,n,r=this.source.timeoutMs){return C(r,"Scenario timeoutMs"),new Promise((o,w)=>{const c=setTimeout(()=>w(this.timeout(n,r)),r);Promise.resolve(t).then(s=>{clearTimeout(c),o(s)},s=>{clearTimeout(c),w(s)})})}notify=()=>{for(const t of[...this.listeners])t()};waitFor(t,n){const r=C(n.timeoutMs??this.source.timeoutMs,"Scenario timeoutMs"),o=C(n.pollIntervalMs??10,"Scenario pollIntervalMs");return new Promise((w,c)=>{let s=!1,l=()=>{};const y=(f,k=f!==void 0)=>{s||(s=!0,l(),k?c(f):w())},i=()=>{if(!s)try{t(this.source.getSnapshot())&&y()}catch(f){y(f,!0)}},d=f=>y(f),v=setTimeout(()=>y(this.timeout(n.label??"waitFor",r)),r),p=setInterval(i,o);l=()=>{clearTimeout(v),clearInterval(p),this.listeners.delete(i),this.pending.delete(d)},this.listeners.add(i),this.pending.add(d),i()})}timeout(t,n){return new T(t,n,this.source.getSnapshot(),this.source.history())}}function I(e,t){if(!Number.isSafeInteger(e)||e<1||e>1e4)throw new RangeError(`${t} must be an integer from 1 to 10000.`);return e}function oe(e){if(e===null||typeof e!="object"||!("mount"in e)||typeof e.mount!="function")throw new TypeError("Scenario requires a host.mount(Component) adapter.")}function se(e){return{...e.cleanupFailure===void 0?{}:{cleanupFailure:e.cleanupFailure},...F(e,"conditions")?{conditions:e.conditions}:{},...F(e,"imports")?{imports:e.imports}:{},reporter:e.reporter}}function ie(e,t){const n=C(t.timeoutMs??1e3,"Scenario timeoutMs"),r=I(t.historyCapacity??64,"Scenario historyCapacity"),o=I(t.activityCapacity??256,"Scenario activityCapacity");let w=t.host;oe(w);let c=_(e,se(t)),s;try{s=N(c,{activityCapacity:o,ringCapacity:r})}catch(u){throw c.ready.catch(()=>{}),c.close().catch(()=>{}),u}let l="open",y=!1,i=!1;const d=[],v=new Set,p=[],f=()=>s.getSnapshot(),k=()=>Object.freeze([...d]),$=new re({getSnapshot:f,history:k,timeoutMs:n}),j=()=>{if(!i){i=!0;try{const u=f(),m=d[d.length-1];((m==null?void 0:m.stateRevision)!==u.stateRevision||m.activity!==u.activity)&&(d.push(u),d.length>r&&d.shift())}finally{i=!1}$.notify()}},R=s.subscribe(j);j();const E=$.deadline(c.ready,"application ready");E.catch(()=>{});let M,S;const A=()=>{if(M!==void 0)return M;let u,m;M=new Promise((a,h)=>{u=a,m=h}),l="closing",$.cancel();const g=[];try{g.push(c.close())}catch(a){g.push(Promise.reject(a))}return g.push(...[...v].map(a=>a.close())),Promise.allSettled(g).then(a=>{const h=[...new Set([...p,...a.flatMap(b=>b.status==="rejected"?[b.reason]:[])])];p.length=0,l="closed";try{j()}catch(b){h.push(b)}finally{R(),s.close(),v.clear(),c=void 0,w=void 0}y=h.length===0,h.length===0?u():m(x(h,"Scenario cleanup failed."))}),M};return Object.freeze({close:()=>{if(S!==void 0)return S;const u=A();return S!==void 0||(S=$.deadline(u,"scenario close"),S.catch(()=>{l==="closing"&&(S=void 0)})),S},getSnapshot:f,history:k,mount:(u,m={})=>{if(l!=="open")throw new Error("Scenario is closing.");const g=O(u,m),a=new Z((b,z)=>{v.delete(a),g.close(),b&&p.push(z)});v.add(a);let h;try{if(h=w.mount(g.Slot),h===null||typeof h!="object"||typeof h.unmount!="function")throw new TypeError("Scenario host.mount must return an unmount() handle.");a.provide(h)}catch(b){throw a.provide(void 0),a.close().catch(()=>{}),b}return Object.freeze({host:h,unmount:a.close,updateProps:b=>{if(l!=="open"||a.closed)throw new Error("Scenario mount is closed.");g.updateProps(b),$.notify()}})},notify:()=>{l==="open"&&$.notify()},ownership:()=>Y(f(),y),ready:E,waitFor:(u,m={})=>l!=="open"?Promise.reject(new Error("Scenario is closing.")):$.waitFor(u,m)})}export{ye as CallTimeoutError,T as ScenarioTimeoutError,Se as WaitTimeoutError,te as command,ie as createScenario,ge as eventually,ve as openModel,ne as renderSlot,$e as runCall,ke as settled,Me as testReadable,Ce as waitFor,je as yieldTurn};
|
|
4
4
|
//# sourceMappingURL=testing.js.map
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sources":["../src/scenario-diagnostics.ts","../src/scenario-mount.ts","../src/scenario-slot.tsx","../src/scenario-wait.ts","../src/scenario.ts"],"sourcesContent":["import type { RuntimeActivitySnapshot, RuntimeGraphSnapshot } from '@opetope/runtime/internal';\n\nimport type { ScenarioOwnership } from './scenario-types';\n\nfunction instanceFacts(instance: RuntimeGraphSnapshot['runtime']['instances'][number]): readonly string[] {\n if (instance.state.kind === 'ready') return [];\n\n const waiting = instance.state.kind === 'waiting' ? ` (${instance.state.reason})` : '';\n\n return [`Feature ${instance.label}: ${instance.state.kind}${waiting}.`];\n}\n\nfunction callFact(call: RuntimeActivitySnapshot['calls'][number]): string {\n if (call.lane === null) return `Call ${call.declaration}: ${call.state}; lane parallel; blockers none (parallel).`;\n\n const blockers = call.lane.blockedBy === 'unknown' ? 'unknown' : call.lane.blockedBy.join(', ');\n\n return `Call ${call.declaration}: ${call.state}; lane ${call.lane.declaration}; blockers ${blockers}.`;\n}\n\nfunction activityFacts(activity: RuntimeActivitySnapshot | undefined): readonly string[] {\n if (activity === undefined) return ['Runtime activity: unknown.'];\n\n const facts: string[] = [];\n\n if (activity.freshness === 'stale' || activity.truncated) {\n facts.push(`Activity: ${activity.freshness}${activity.truncated ? ', truncated' : ''}; completeness unknown.`);\n }\n\n return [\n ...facts,\n ...activity.features.map(\n feature => `Feature ${feature.feature}: ${feature.phase}; body ${feature.body}; host demand unknown.`,\n ),\n ...activity.calls.map(callFact),\n ...activity.resources.map(\n resource => `Resource ${resource.id}: ${resource.state}; retainers ${String(resource.retainers)}.`,\n ),\n ];\n}\n\nfunction explainSnapshot(snapshot: RuntimeGraphSnapshot): string {\n const facts = [\n ...snapshot.runtime.conditions.flatMap(condition =>\n condition.state.kind === 'true' ? [] : [`Condition ${condition.label}: ${condition.state.kind}.`],\n ),\n ...snapshot.runtime.instances.flatMap(instanceFacts),\n ...activityFacts(snapshot.activity),\n ];\n\n return facts.length === 0\n ? 'No registered runtime blocker observed; external UI and host work are unknown.'\n : facts.join('\\n');\n}\n\n/** D206: the failure carries the producer's same data-only graph/activity facts, without product payloads. */\nclass ScenarioTimeoutError extends Error {\n readonly code = 'timeout';\n\n constructor(\n readonly label: string,\n readonly timeoutMs: number,\n readonly snapshot: RuntimeGraphSnapshot,\n readonly history: readonly RuntimeGraphSnapshot[],\n ) {\n super(`Scenario timed out after ${String(timeoutMs)}ms: ${label}.\\n${explainSnapshot(snapshot)}`);\n this.name = 'ScenarioTimeoutError';\n }\n}\n\nfunction ownershipComplete(activity: RuntimeActivitySnapshot | undefined, successfullyClosed: boolean): boolean {\n if (!successfullyClosed || activity === undefined) return false;\n\n return activity.closed && !activity.truncated;\n}\n\nfunction ownershipReasons(\n activity: RuntimeActivitySnapshot | undefined,\n complete: boolean,\n successfullyClosed: boolean,\n): readonly string[] {\n if (activity === undefined) return ['Runtime activity is unavailable.'];\n\n const reasons: string[] = [];\n\n if (activity.truncated) reasons.push('The activity snapshot is truncated.');\n\n if (activity.freshness === 'stale' && !complete) reasons.push('The activity snapshot is stale.');\n\n if (!successfullyClosed) reasons.push('Successful application and mount cleanup has not been observed.');\n\n return Object.freeze(reasons);\n}\n\nfunction ownershipCounts(\n activity: RuntimeActivitySnapshot | undefined,\n complete: boolean,\n): Pick<ScenarioOwnership, 'calls' | 'features' | 'resources'> {\n if (activity === undefined || activity.truncated || (activity.freshness === 'stale' && !complete)) {\n return { calls: 'unknown', features: 'unknown', resources: 'unknown' };\n }\n\n return { calls: activity.calls.length, features: activity.features.length, resources: activity.resources.length };\n}\n\nfunction scenarioOwnership(snapshot: RuntimeGraphSnapshot, successfullyClosed: boolean): ScenarioOwnership {\n const activity = snapshot.activity;\n const complete = ownershipComplete(activity, successfullyClosed);\n\n return Object.freeze({\n ...ownershipCounts(activity, complete),\n closed: successfullyClosed,\n reasons: ownershipReasons(activity, complete, successfullyClosed),\n scope: 'registered-runtime',\n status: complete ? 'complete' : 'unknown',\n });\n}\n\nexport { scenarioOwnership, ScenarioTimeoutError };\n","import type { ScenarioHostMount } from './scenario-types';\n\n/** A reservation exists before the host callback, so reentrant close also owns a mount returned later. */\nclass ScenarioMountLease<Mounted extends ScenarioHostMount> {\n get closed(): boolean {\n return this.completion !== undefined;\n }\n private available = false;\n private completion: Promise<void> | undefined;\n private handle: Mounted | undefined;\n private reject: ((error: unknown) => void) | undefined;\n\n private resolve: (() => void) | undefined;\n\n constructor(private readonly released: (failed: boolean, error?: unknown) => void) {}\n\n readonly close = (): Promise<void> => {\n if (this.completion !== undefined) return this.completion;\n\n this.completion = new Promise<void>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n\n if (this.available) this.releaseAvailable();\n\n return this.completion;\n };\n\n provide(handle: Mounted | undefined): void {\n this.available = true;\n this.handle = handle;\n\n if (this.closed) this.releaseAvailable();\n }\n\n private finish(failed: boolean, error?: unknown): void {\n const resolve = this.resolve;\n const reject = this.reject;\n this.resolve = undefined;\n this.reject = undefined;\n this.released(failed, error);\n\n if (!failed) resolve?.();\n else reject?.(error);\n }\n\n private releaseAvailable(): void {\n const handle = this.handle;\n this.handle = undefined;\n this.available = false;\n try {\n Promise.resolve(handle?.unmount()).then(\n () => this.finish(false),\n (error: unknown) => this.finish(true, error),\n );\n } catch (error) {\n this.finish(true, error);\n }\n }\n}\n\nexport { ScenarioMountLease };\n","import { createElement } from 'react';\nimport type { FC, ReactNode } from 'react';\n\nimport { declarationId } from '@opetope/core';\nimport type { Call } from '@opetope/core';\nimport { defineCallTarget } from '@opetope/core/internal';\n\nimport { bindCommand } from './command';\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport type { ContributionValue, ModelFixture } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\nimport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget } from './slot';\n\n/**\n * Two forms, as `renderRoot` had (D19): without `contribution` the harness mounts what the target published, with it\n * the harness mounts one fixture contribution so a component test needs no generation.\n */\ntype SlotTestFixture<Props extends SlotProperties> = {\n readonly contribution?: SlotContribution<Props>;\n readonly models?: readonly ModelFixture[];\n readonly props?: SlotComponentProperties<Props>;\n /**\n * Where a contained mount failure is reported (D256). A component test has no feature to report to, so without it\n * the failure is detached and the assertion has nothing to read.\n */\n readonly reporter?: (error: unknown) => void;\n};\n\ninterface SlotTestHarness<Props extends SlotProperties> {\n readonly Slot: FC;\n readonly updateProps: (props: SlotComponentProperties<Props>) => void;\n}\n\nconst testingContributionId = declarationId('testing.contribution');\n\nlet fixtureSequence = 0;\n\n/** A command a fixture publishes: the test writes the body, the harness gives it the identity a model requires. */\nfunction command<Input, Output>(run: (input: Input) => Output | PromiseLike<Output>): Call<Input, Output> {\n fixtureSequence += 1;\n\n return bindCommand(defineCallTarget<Input, Output>({ id: `testing.command.${String(fixtureSequence)}`, run }));\n}\n\n/**\n * Mounts the contributions of one slot the way a host does, with optional model fixtures for a component test. D19 and D85: the same\n * ContributionMount is shared by component tests and the application scenario harness (D206).\n */\nfunction createSlotHarness<Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: SlotTestFixture<Props> = {},\n): SlotTestHarness<Props> & { readonly close: () => void } {\n const fixtures = fixture.models ?? [];\n let current: object = fixture.props ?? {};\n const listeners = new Set<() => void>();\n const props = {\n getSnapshot: (): object => current,\n subscribe: (listener: () => void): (() => void) => {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n\n const supplied = fixture.contribution;\n const reported = fixture.reporter;\n const fixtureAuthority = reported === undefined ? undefined : { models: [], reporter: reported };\n const Mounted: FC = (): ReactNode => {\n const published = useReadable(target.entries);\n const slotProps = useReadable(props);\n const entries =\n supplied === undefined\n ? published.map(\n entry =>\n [entry.id, entry.value as ContributionValue, contributionAuthority(entry) ?? fixtureAuthority] as const,\n )\n : [[testingContributionId, supplied as unknown as ContributionValue, fixtureAuthority] as const];\n\n return entries.map(([contributionId, contribution, authority]) =>\n createElement(ContributionMount, {\n authority,\n contribution,\n contributionId,\n fixtures,\n key: contributionId,\n slotProps,\n targetId: target.id,\n }),\n );\n };\n\n return Object.freeze({\n close: (): void => {\n current = {};\n listeners.clear();\n },\n Slot: Mounted,\n updateProps: (next: SlotComponentProperties<Props>): void => {\n current = next;\n\n for (const listener of [...listeners]) listener();\n },\n });\n}\n\nfunction renderSlot<Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: SlotTestFixture<Props> = {},\n): SlotTestHarness<Props> {\n const harness = createSlotHarness(target, fixture);\n\n return Object.freeze({ Slot: harness.Slot, updateProps: harness.updateProps });\n}\n\nexport { command, createSlotHarness, renderSlot };\nexport type { SlotTestFixture, SlotTestHarness };\n","import type { RuntimeGraphSnapshot } from '@opetope/runtime/internal';\n\nimport { ScenarioTimeoutError } from './scenario-diagnostics';\nimport type { ScenarioWaitOptions } from './scenario-types';\n\nfunction positiveDuration(value: number, label: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > 2_147_483_647) {\n throw new RangeError(`${label} must be a finite duration from 0 (exclusive) to 2147483647ms.`);\n }\n\n return value;\n}\n\ninterface ScenarioWaitSource {\n readonly getSnapshot: () => RuntimeGraphSnapshot;\n readonly history: () => readonly RuntimeGraphSnapshot[];\n readonly timeoutMs: number;\n}\n\nclass ScenarioWaiters {\n private readonly listeners = new Set<() => void>();\n private readonly pending = new Set<(error: Error) => void>();\n\n constructor(private readonly source: ScenarioWaitSource) {}\n\n cancel(): void {\n for (const reject of [...this.pending]) reject(new Error('Scenario is closing.'));\n }\n\n deadline<Value>(result: PromiseLike<Value>, label: string, timeoutMs = this.source.timeoutMs): Promise<Value> {\n positiveDuration(timeoutMs, 'Scenario timeoutMs');\n\n return new Promise<Value>((resolve, reject) => {\n const timer = setTimeout(() => reject(this.timeout(label, timeoutMs)), timeoutMs);\n Promise.resolve(result).then(\n value => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n }\n\n readonly notify = (): void => {\n for (const listener of [...this.listeners]) listener();\n };\n\n waitFor(predicate: (snapshot: RuntimeGraphSnapshot) => boolean, options: ScenarioWaitOptions): Promise<void> {\n const timeoutMs = positiveDuration(options.timeoutMs ?? this.source.timeoutMs, 'Scenario timeoutMs');\n const pollIntervalMs = positiveDuration(options.pollIntervalMs ?? 10, 'Scenario pollIntervalMs');\n\n return new Promise<void>((resolve, reject) => {\n let done = false;\n let dispose = (): void => undefined;\n const finish = (error?: unknown, failed = error !== undefined): void => {\n if (done) return;\n\n done = true;\n dispose();\n\n if (!failed) resolve();\n else reject(error);\n };\n const check = (): void => {\n if (done) return;\n\n try {\n if (predicate(this.source.getSnapshot())) finish();\n } catch (error) {\n finish(error, true);\n }\n };\n const cancel = (error: Error): void => finish(error);\n const deadline = setTimeout(() => finish(this.timeout(options.label ?? 'waitFor', timeoutMs)), timeoutMs);\n const poll = setInterval(check, pollIntervalMs);\n dispose = (): void => {\n clearTimeout(deadline);\n clearInterval(poll);\n this.listeners.delete(check);\n this.pending.delete(cancel);\n };\n this.listeners.add(check);\n this.pending.add(cancel);\n check();\n });\n }\n\n private timeout(label: string, timeoutMs: number): ScenarioTimeoutError {\n return new ScenarioTimeoutError(label, timeoutMs, this.source.getSnapshot(), this.source.history());\n }\n}\n\nexport { positiveDuration, ScenarioWaiters };\n","import { createAggregateError, hasOwn } from '@opetope/core/internal';\nimport { openApplication } from '@opetope/runtime';\nimport type { Application, ApplicationExecution } from '@opetope/runtime';\nimport { createInspectionSession } from '@opetope/runtime/internal';\nimport type {\n ApplicationConditionBinding,\n OpenApplicationOptions,\n RuntimeGraphSnapshot,\n RuntimeInspectionSession,\n} from '@opetope/runtime/internal';\n\nimport { scenarioOwnership } from './scenario-diagnostics';\nimport { ScenarioMountLease } from './scenario-mount';\nimport { createSlotHarness } from './scenario-slot';\nimport type {\n Scenario,\n ScenarioFeatures,\n ScenarioHost,\n ScenarioHostMount,\n ScenarioMount,\n ScenarioOptions,\n ScenarioWaitOptions,\n} from './scenario-types';\nimport { positiveDuration, ScenarioWaiters } from './scenario-wait';\nimport type { SlotComponentProperties, SlotProperties, SlotTarget } from './slot';\n\nfunction boundedCapacity(value: number, label: string): number {\n if (!Number.isSafeInteger(value) || value < 1 || value > 10_000) {\n throw new RangeError(`${label} must be an integer from 1 to 10000.`);\n }\n\n return value;\n}\n\nfunction validateHost(host: unknown): void {\n if (host === null || typeof host !== 'object' || !('mount' in host) || typeof host.mount !== 'function') {\n throw new TypeError('Scenario requires a host.mount(Component) adapter.');\n }\n}\n\n/**\n * What the scenario hands to `openApplication`: absence is preserved separately from an explicitly invalid value,\n * so the condition-dependent type and the D281 omissions behave exactly as they do for a host.\n */\nfunction applicationOpenOptions<const Features extends ScenarioFeatures>(\n options: ScenarioOptions<Features, ScenarioHostMount>,\n): OpenApplicationOptions<Features> {\n return {\n ...(options.cleanupFailure === undefined ? {} : { cleanupFailure: options.cleanupFailure }),\n ...(hasOwn(options, 'conditions') ? { conditions: options.conditions } : {}),\n ...(hasOwn(options, 'imports') ? { imports: options.imports } : {}),\n reporter: options.reporter,\n };\n}\n\n/** D206: one real application, its existing inspection session and renderer-owned Slot ingress. */\nfunction createScenario<\n const Features extends ScenarioFeatures,\n Mounted extends ScenarioHostMount,\n const Bindings extends readonly ApplicationConditionBinding[] = readonly [],\n>(\n application: Application<Features>,\n options: ScenarioOptions<NoInfer<Features>, Mounted, Bindings>,\n): Scenario<Mounted> {\n const timeoutMs = positiveDuration(options.timeoutMs ?? 1_000, 'Scenario timeoutMs');\n const historyCapacity = boundedCapacity(options.historyCapacity ?? 64, 'Scenario historyCapacity');\n const activityCapacity = boundedCapacity(options.activityCapacity ?? 256, 'Scenario activityCapacity');\n let host: ScenarioHost<Mounted> | undefined = options.host;\n\n validateHost(host);\n\n let execution: ApplicationExecution | undefined = openApplication(application, applicationOpenOptions(options));\n let session: RuntimeInspectionSession;\n try {\n session = createInspectionSession(execution, { activityCapacity, ringCapacity: historyCapacity });\n } catch (error) {\n void execution.ready.catch(() => undefined);\n void execution.close().catch(() => undefined);\n throw error;\n }\n\n let phase: 'closed' | 'closing' | 'open' = 'open';\n let successfullyClosed = false;\n let capturing = false;\n const records: RuntimeGraphSnapshot[] = [];\n const mounts = new Set<ScenarioMountLease<Mounted>>();\n const mountFailures: unknown[] = [];\n const getSnapshot = (): RuntimeGraphSnapshot => session.getSnapshot();\n const history = (): readonly RuntimeGraphSnapshot[] => Object.freeze([...records]);\n const waiters = new ScenarioWaiters({ getSnapshot, history, timeoutMs });\n const capture = (): void => {\n if (capturing) return;\n\n capturing = true;\n try {\n const snapshot = getSnapshot();\n const previous = records[records.length - 1];\n\n if (previous?.stateRevision !== snapshot.stateRevision || previous.activity !== snapshot.activity) {\n records.push(snapshot);\n\n if (records.length > historyCapacity) records.shift();\n }\n } finally {\n capturing = false;\n }\n waiters.notify();\n };\n const release = session.subscribe(capture);\n capture();\n const ready = waiters.deadline(execution.ready, 'application ready');\n // A test may deliberately inspect loading without awaiting ready; rejection remains observable to that caller.\n void ready.catch(() => undefined);\n\n let physicalClose: Promise<void> | undefined;\n let closeAttempt: Promise<void> | undefined;\n const startClose = (): Promise<void> => {\n if (physicalClose !== undefined) return physicalClose;\n\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n physicalClose = new Promise<void>((accept, refuse) => {\n resolve = accept;\n reject = refuse;\n });\n phase = 'closing';\n waiters.cancel();\n const drains: Promise<void>[] = [];\n // Fence before any foreign unmount callback can reenter UI ingress; join all physical drains afterwards.\n try {\n drains.push(execution!.close());\n } catch (error) {\n drains.push(Promise.reject(error));\n }\n drains.push(...[...mounts].map(mount => mount.close()));\n void Promise.allSettled(drains).then(results => {\n const failures = [\n ...new Set([\n ...mountFailures,\n ...results.flatMap(result => (result.status === 'rejected' ? [result.reason as unknown] : [])),\n ]),\n ];\n mountFailures.length = 0;\n phase = 'closed';\n try {\n capture();\n } catch (error) {\n failures.push(error);\n } finally {\n release();\n session.close();\n mounts.clear();\n execution = undefined;\n host = undefined;\n }\n successfullyClosed = failures.length === 0;\n\n if (failures.length === 0) resolve();\n else reject(createAggregateError(failures, 'Scenario cleanup failed.'));\n });\n\n return physicalClose;\n };\n\n const close = (): Promise<void> => {\n if (closeAttempt !== undefined) return closeAttempt;\n\n const drain = startClose();\n\n // A nested close during unmount may have installed the same attempt already.\n if (closeAttempt !== undefined) return closeAttempt;\n\n closeAttempt = waiters.deadline(drain, 'scenario close');\n void closeAttempt.catch(() => {\n // A deadline cannot cancel physical work. A later close can await that same drain with a new deadline.\n if (phase === 'closing') closeAttempt = undefined;\n });\n\n return closeAttempt;\n };\n\n const mount: Scenario<Mounted>['mount'] = <Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: { readonly props?: SlotComponentProperties<Props> } = {},\n ): ScenarioMount<Props, Mounted> => {\n if (phase !== 'open') throw new Error('Scenario is closing.');\n\n const slot = createSlotHarness(target, fixture);\n const lease = new ScenarioMountLease<Mounted>((failed, error) => {\n mounts.delete(lease);\n slot.close();\n\n if (failed) mountFailures.push(error);\n });\n mounts.add(lease);\n let mounted: Mounted;\n try {\n mounted = host!.mount(slot.Slot);\n\n if (mounted === null || typeof mounted !== 'object' || typeof mounted.unmount !== 'function') {\n throw new TypeError('Scenario host.mount must return an unmount() handle.');\n }\n\n lease.provide(mounted);\n } catch (error) {\n lease.provide(undefined);\n void lease.close().catch(() => undefined);\n throw error;\n }\n\n return Object.freeze({\n host: mounted,\n unmount: lease.close,\n updateProps: (props: SlotComponentProperties<Props>): void => {\n if (phase !== 'open' || lease.closed) throw new Error('Scenario mount is closed.');\n\n slot.updateProps(props);\n waiters.notify();\n },\n });\n };\n\n return Object.freeze({\n close,\n getSnapshot,\n history,\n mount,\n notify: (): void => {\n if (phase === 'open') waiters.notify();\n },\n ownership: () => scenarioOwnership(getSnapshot(), successfullyClosed),\n ready,\n waitFor: (predicate: (snapshot: RuntimeGraphSnapshot) => boolean, waitOptions: ScenarioWaitOptions = {}) => {\n if (phase !== 'open') return Promise.reject(new Error('Scenario is closing.'));\n\n return waiters.waitFor(predicate, waitOptions);\n },\n });\n}\n\nexport { createScenario };\n"],"names":["instanceFacts","instance","waiting","callFact","call","blockers","activityFacts","activity","facts","feature","resource","explainSnapshot","snapshot","condition","ScenarioTimeoutError","label","timeoutMs","history","ownershipComplete","successfullyClosed","ownershipReasons","complete","reasons","ownershipCounts","scenarioOwnership","ScenarioMountLease","released","resolve","reject","handle","failed","error","testingContributionId","declarationId","fixtureSequence","command","run","bindCommand","defineCallTarget","createSlotHarness","target","fixture","fixtures","current","listeners","props","listener","supplied","reported","fixtureAuthority","published","useReadable","slotProps","entry","contributionAuthority","contributionId","contribution","authority","createElement","ContributionMount","next","renderSlot","harness","positiveDuration","value","ScenarioWaiters","source","result","timer","predicate","options","pollIntervalMs","done","dispose","finish","check","cancel","deadline","poll","boundedCapacity","validateHost","host","applicationOpenOptions","hasOwn","createScenario","application","historyCapacity","activityCapacity","execution","openApplication","session","createInspectionSession","phase","capturing","records","mounts","mountFailures","getSnapshot","waiters","capture","previous","release","ready","physicalClose","closeAttempt","startClose","accept","refuse","drains","mount","results","failures","createAggregateError","drain","slot","lease","mounted","waitOptions"],"mappings":"+fAIA,SAASA,EAAcC,EAA8D,CACnF,GAAIA,EAAS,MAAM,OAAS,QAAS,MAAO,CAAA,EAE5C,MAAMC,EAAUD,EAAS,MAAM,OAAS,UAAY,KAAKA,EAAS,MAAM,MAAM,IAAM,GAEpF,MAAO,CAAC,WAAWA,EAAS,KAAK,KAAKA,EAAS,MAAM,IAAI,GAAGC,CAAO,GAAG,CACxE,CAEA,SAASC,EAASC,EAA8C,CAC9D,GAAIA,EAAK,OAAS,KAAM,MAAO,QAAQA,EAAK,WAAW,KAAKA,EAAK,KAAK,6CAEtE,MAAMC,EAAWD,EAAK,KAAK,YAAc,UAAY,UAAYA,EAAK,KAAK,UAAU,KAAK,IAAI,EAE9F,MAAO,QAAQA,EAAK,WAAW,KAAKA,EAAK,KAAK,UAAUA,EAAK,KAAK,WAAW,cAAcC,CAAQ,GACrG,CAEA,SAASC,EAAcC,EAA6C,CAClE,GAAIA,IAAa,OAAW,MAAO,CAAC,4BAA4B,EAEhE,MAAMC,EAAkB,CAAA,EAExB,OAAID,EAAS,YAAc,SAAWA,EAAS,YAC7CC,EAAM,KAAK,aAAaD,EAAS,SAAS,GAAGA,EAAS,UAAY,cAAgB,EAAE,yBAAyB,EAGxG,CACL,GAAGC,EACH,GAAGD,EAAS,SAAS,IACnBE,GAAW,WAAWA,EAAQ,OAAO,KAAKA,EAAQ,KAAK,UAAUA,EAAQ,IAAI,wBAAwB,EAEvG,GAAGF,EAAS,MAAM,IAAIJ,CAAQ,EAC9B,GAAGI,EAAS,UAAU,IACpBG,GAAY,YAAYA,EAAS,EAAE,KAAKA,EAAS,KAAK,eAAe,OAAOA,EAAS,SAAS,CAAC,GAAG,EAGxG,CAEA,SAASC,EAAgBC,EAA8B,CACrD,MAAMJ,EAAQ,CACZ,GAAGI,EAAS,QAAQ,WAAW,QAAQC,GACrCA,EAAU,MAAM,OAAS,OAAS,CAAA,EAAK,CAAC,aAAaA,EAAU,KAAK,KAAKA,EAAU,MAAM,IAAI,GAAG,CAAC,EAEnG,GAAGD,EAAS,QAAQ,UAAU,QAAQZ,CAAa,EACnD,GAAGM,EAAcM,EAAS,QAAQ,GAGpC,OAAOJ,EAAM,SAAW,EACpB,iFACAA,EAAM,KAAK;AAAA,CAAI,CACrB,CAGA,MAAMM,UAA6B,KAAK,CAI3B,MACA,UACA,SACA,QANF,KAAO,UAEhB,YACWC,EACAC,EACAJ,EACAK,EAAwC,CAEjD,MAAM,4BAA4B,OAAOD,CAAS,CAAC,OAAOD,CAAK;AAAA,EAAMJ,EAAgBC,CAAQ,CAAC,EAAE,EALvF,KAAA,MAAAG,EACA,KAAA,UAAAC,EACA,KAAA,SAAAJ,EACA,KAAA,QAAAK,EAGT,KAAK,KAAO,sBACd,CACD,CAED,SAASC,EAAkBX,EAA+CY,EAA2B,CACnG,MAAI,CAACA,GAAsBZ,IAAa,OAAkB,GAEnDA,EAAS,QAAU,CAACA,EAAS,SACtC,CAEA,SAASa,EACPb,EACAc,EACAF,EAA2B,CAE3B,GAAIZ,IAAa,OAAW,MAAO,CAAC,kCAAkC,EAEtE,MAAMe,EAAoB,CAAA,EAE1B,OAAIf,EAAS,WAAWe,EAAQ,KAAK,qCAAqC,EAEtEf,EAAS,YAAc,SAAW,CAACc,GAAUC,EAAQ,KAAK,iCAAiC,EAE1FH,GAAoBG,EAAQ,KAAK,iEAAiE,EAEhG,OAAO,OAAOA,CAAO,CAC9B,CAEA,SAASC,EACPhB,EACAc,EAAiB,CAEjB,OAAId,IAAa,QAAaA,EAAS,WAAcA,EAAS,YAAc,SAAW,CAACc,EAC/E,CAAE,MAAO,UAAW,SAAU,UAAW,UAAW,SAAS,EAG/D,CAAE,MAAOd,EAAS,MAAM,OAAQ,SAAUA,EAAS,SAAS,OAAQ,UAAWA,EAAS,UAAU,MAAM,CACjH,CAEA,SAASiB,EAAkBZ,EAAgCO,EAA2B,CACpF,MAAMZ,EAAWK,EAAS,SACpBS,EAAWH,EAAkBX,EAAUY,CAAkB,EAE/D,OAAO,OAAO,OAAO,CACnB,GAAGI,EAAgBhB,EAAUc,CAAQ,EACrC,OAAQF,EACR,QAASC,EAAiBb,EAAUc,EAAUF,CAAkB,EAChE,MAAO,qBACP,OAAQE,EAAW,WAAa,SACjC,CAAA,CACH,CCjHA,MAAMI,EAAkB,CAWO,SAV7B,IAAI,QAAM,CACR,OAAO,KAAK,aAAe,MAC7B,CACQ,UAAY,GACZ,WACA,OACA,OAEA,QAER,YAA6BC,EAAoD,CAApD,KAAA,SAAAA,CAAuD,CAE3E,MAAQ,IACX,KAAK,aAAe,OAAkB,KAAK,YAE/C,KAAK,WAAa,IAAI,QAAc,CAACC,EAASC,IAAU,CACtD,KAAK,QAAUD,EACf,KAAK,OAASC,CAChB,CAAC,EAEG,KAAK,WAAW,KAAK,iBAAgB,EAElC,KAAK,YAGd,QAAQC,EAA2B,CACjC,KAAK,UAAY,GACjB,KAAK,OAASA,EAEV,KAAK,QAAQ,KAAK,iBAAgB,CACxC,CAEQ,OAAOC,EAAiBC,EAAe,CAC7C,MAAMJ,EAAU,KAAK,QACfC,EAAS,KAAK,OACpB,KAAK,QAAU,OACf,KAAK,OAAS,OACd,KAAK,SAASE,EAAQC,CAAK,EAEtBD,EACAF,GAAA,MAAAA,EAASG,GADDJ,GAAA,MAAAA,GAEf,CAEQ,kBAAgB,CACtB,MAAME,EAAS,KAAK,OACpB,KAAK,OAAS,OACd,KAAK,UAAY,GACjB,GAAI,CACF,QAAQ,QAAQA,GAAA,YAAAA,EAAQ,SAAS,EAAE,KACjC,IAAM,KAAK,OAAO,EAAK,EACtBE,GAAmB,KAAK,OAAO,GAAMA,CAAK,CAAC,CAEhD,OAASA,EAAO,CACd,KAAK,OAAO,GAAMA,CAAK,CACzB,CACF,CACD,CC3BD,MAAMC,GAAwBC,EAAc,sBAAsB,EAElE,IAAIC,EAAkB,EAGtB,SAASC,GAAuBC,EAAmD,CACjF,OAAAF,GAAmB,EAEZG,EAAYC,EAAgC,CAAE,GAAI,mBAAmB,OAAOJ,CAAe,CAAC,GAAI,IAAAE,CAAG,CAAE,CAAC,CAC/G,CAMA,SAASG,EACPC,EACAC,EAAkC,GAAE,CAEpC,MAAMC,EAAWD,EAAQ,QAAU,CAAA,EACnC,IAAIE,EAAkBF,EAAQ,OAAS,CAAA,EACvC,MAAMG,EAAY,IAAI,IAChBC,EAAQ,CACZ,YAAa,IAAcF,EAC3B,UAAYG,IACVF,EAAU,IAAIE,CAAQ,EAEf,IAAMF,EAAU,OAAOE,CAAQ,IAIpCC,EAAWN,EAAQ,aACnBO,EAAWP,EAAQ,SACnBQ,EAAmBD,IAAa,OAAY,OAAY,CAAE,OAAQ,CAAA,EAAI,SAAUA,CAAQ,EAyB9F,OAAO,OAAO,OAAO,CACnB,MAAO,IAAW,CAChBL,EAAU,CAAA,EACVC,EAAU,MAAK,CACjB,EACA,KA7BkB,IAAgB,CAClC,MAAMM,EAAYC,EAAYX,EAAO,OAAO,EACtCY,EAAYD,EAAYN,CAAK,EASnC,OAPEE,IAAa,OACTG,EAAU,IACRG,GACE,CAACA,EAAM,GAAIA,EAAM,MAA4BC,EAAsBD,CAAK,GAAKJ,CAAgB,CAAU,EAE3G,CAAC,CAACjB,GAAuBe,EAA0CE,CAAgB,CAAU,GAEpF,IAAI,CAAC,CAACM,EAAgBC,EAAcC,CAAS,IAC1DC,EAAcC,EAAmB,CAC/B,UAAAF,EACA,aAAAD,EACA,eAAAD,EACA,SAAAb,EACA,IAAKa,EACL,UAAAH,EACA,SAAUZ,EAAO,EAClB,CAAA,CAAC,CAEN,EAQE,YAAcoB,GAA8C,CAC1DjB,EAAUiB,EAEV,UAAWd,IAAY,CAAC,GAAGF,CAAS,EAAGE,EAAQ,CACjD,CACD,CAAA,CACH,CAEA,SAASe,GACPrB,EACAC,EAAkC,GAAE,CAEpC,MAAMqB,EAAUvB,EAAkBC,EAAQC,CAAO,EAEjD,OAAO,OAAO,OAAO,CAAE,KAAMqB,EAAQ,KAAM,YAAaA,EAAQ,YAAa,CAC/E,CC3GA,SAASC,EAAiBC,EAAejD,EAAa,CACpD,GAAI,CAAC,OAAO,SAASiD,CAAK,GAAKA,GAAS,GAAKA,EAAQ,WACnD,MAAM,IAAI,WAAW,GAAGjD,CAAK,gEAAgE,EAG/F,OAAOiD,CACT,CAQA,MAAMC,EAAe,CAIU,OAHZ,UAAY,IAAI,IAChB,QAAU,IAAI,IAE/B,YAA6BC,EAA0B,CAA1B,KAAA,OAAAA,CAA6B,CAE1D,QAAM,CACJ,UAAWtC,IAAU,CAAC,GAAG,KAAK,OAAO,EAAGA,EAAO,IAAI,MAAM,sBAAsB,CAAC,CAClF,CAEA,SAAgBuC,EAA4BpD,EAAeC,EAAY,KAAK,OAAO,UAAS,CAC1F,OAAA+C,EAAiB/C,EAAW,oBAAoB,EAEzC,IAAI,QAAe,CAACW,EAASC,IAAU,CAC5C,MAAMwC,EAAQ,WAAW,IAAMxC,EAAO,KAAK,QAAQb,EAAOC,CAAS,CAAC,EAAGA,CAAS,EAChF,QAAQ,QAAQmD,CAAM,EAAE,KACtBH,GAAQ,CACN,aAAaI,CAAK,EAClBzC,EAAQqC,CAAK,CACf,EACCjC,GAAkB,CACjB,aAAaqC,CAAK,EAClBxC,EAAOG,CAAK,CACd,CAAC,CAEL,CAAC,CACH,CAES,OAAS,IAAW,CAC3B,UAAWe,IAAY,CAAC,GAAG,KAAK,SAAS,EAAGA,EAAQ,CACtD,EAEA,QAAQuB,EAAwDC,EAA4B,CAC1F,MAAMtD,EAAY+C,EAAiBO,EAAQ,WAAa,KAAK,OAAO,UAAW,oBAAoB,EAC7FC,EAAiBR,EAAiBO,EAAQ,gBAAkB,GAAI,yBAAyB,EAE/F,OAAO,IAAI,QAAc,CAAC3C,EAASC,IAAU,CAC3C,IAAI4C,EAAO,GACPC,EAAU,IAAA,GACd,MAAMC,EAAS,CAAC3C,EAAiBD,EAASC,IAAU,SAAmB,CACjEyC,IAEJA,EAAO,GACPC,EAAO,EAEF3C,EACAF,EAAOG,CAAK,EADJJ,EAAO,EAEtB,EACMgD,EAAQ,IAAW,CACvB,GAAI,CAAAH,EAEJ,GAAI,CACEH,EAAU,KAAK,OAAO,YAAW,CAAE,GAAGK,EAAM,CAClD,OAAS3C,EAAO,CACd2C,EAAO3C,EAAO,EAAI,CACpB,CACF,EACM6C,EAAU7C,GAAuB2C,EAAO3C,CAAK,EAC7C8C,EAAW,WAAW,IAAMH,EAAO,KAAK,QAAQJ,EAAQ,OAAS,UAAWtD,CAAS,CAAC,EAAGA,CAAS,EAClG8D,EAAO,YAAYH,EAAOJ,CAAc,EAC9CE,EAAU,IAAW,CACnB,aAAaI,CAAQ,EACrB,cAAcC,CAAI,EAClB,KAAK,UAAU,OAAOH,CAAK,EAC3B,KAAK,QAAQ,OAAOC,CAAM,CAC5B,EACA,KAAK,UAAU,IAAID,CAAK,EACxB,KAAK,QAAQ,IAAIC,CAAM,EACvBD,EAAK,CACP,CAAC,CACH,CAEQ,QAAQ5D,EAAeC,EAAiB,CAC9C,OAAO,IAAIF,EAAqBC,EAAOC,EAAW,KAAK,OAAO,cAAe,KAAK,OAAO,QAAO,CAAE,CACpG,CACD,CCpED,SAAS+D,EAAgBf,EAAejD,EAAa,CACnD,GAAI,CAAC,OAAO,cAAciD,CAAK,GAAKA,EAAQ,GAAKA,EAAQ,IACvD,MAAM,IAAI,WAAW,GAAGjD,CAAK,sCAAsC,EAGrE,OAAOiD,CACT,CAEA,SAASgB,GAAaC,EAAa,CACjC,GAAIA,IAAS,MAAQ,OAAOA,GAAS,UAAY,EAAE,UAAWA,IAAS,OAAOA,EAAK,OAAU,WAC3F,MAAM,IAAI,UAAU,oDAAoD,CAE5E,CAMA,SAASC,GACPZ,EAAqD,CAErD,MAAO,CACL,GAAIA,EAAQ,iBAAmB,OAAY,CAAA,EAAK,CAAE,eAAgBA,EAAQ,gBAC1E,GAAIa,EAAOb,EAAS,YAAY,EAAI,CAAE,WAAYA,EAAQ,UAAU,EAAK,GACzE,GAAIa,EAAOb,EAAS,SAAS,EAAI,CAAE,QAASA,EAAQ,OAAO,EAAK,GAChE,SAAUA,EAAQ,SAEtB,CAGA,SAASc,GAKPC,EACAf,EAA8D,CAE9D,MAAMtD,EAAY+C,EAAiBO,EAAQ,WAAa,IAAO,oBAAoB,EAC7EgB,EAAkBP,EAAgBT,EAAQ,iBAAmB,GAAI,0BAA0B,EAC3FiB,EAAmBR,EAAgBT,EAAQ,kBAAoB,IAAK,2BAA2B,EACrG,IAAIW,EAA0CX,EAAQ,KAEtDU,GAAaC,CAAI,EAEjB,IAAIO,EAA8CC,EAAgBJ,EAAaH,GAAuBZ,CAAO,CAAC,EAC1GoB,EACJ,GAAI,CACFA,EAAUC,EAAwBH,EAAW,CAAE,iBAAAD,EAAkB,aAAcD,EAAiB,CAClG,OAASvD,EAAO,CACd,MAAKyD,EAAU,MAAM,MAAM,IAAA,EAAe,EACrCA,EAAU,MAAK,EAAG,MAAM,IAAA,EAAe,EACtCzD,CACR,CAEA,IAAI6D,EAAuC,OACvCzE,EAAqB,GACrB0E,EAAY,GAChB,MAAMC,EAAkC,CAAA,EAClCC,EAAS,IAAI,IACbC,EAA2B,CAAA,EAC3BC,EAAc,IAA4BP,EAAQ,YAAW,EAC7DzE,EAAU,IAAuC,OAAO,OAAO,CAAC,GAAG6E,CAAO,CAAC,EAC3EI,EAAU,IAAIjC,GAAgB,CAAE,YAAAgC,EAAa,QAAAhF,EAAS,UAAAD,EAAW,EACjEmF,EAAU,IAAW,CACzB,GAAI,CAAAN,EAEJ,CAAAA,EAAY,GACZ,GAAI,CACF,MAAMjF,EAAWqF,EAAW,EACtBG,EAAWN,EAAQA,EAAQ,OAAS,CAAC,IAEvCM,GAAA,YAAAA,EAAU,iBAAkBxF,EAAS,eAAiBwF,EAAS,WAAaxF,EAAS,YACvFkF,EAAQ,KAAKlF,CAAQ,EAEjBkF,EAAQ,OAASR,GAAiBQ,EAAQ,MAAK,EAEvD,SACED,EAAY,EACd,CACAK,EAAQ,OAAM,EAChB,EACMG,EAAUX,EAAQ,UAAUS,CAAO,EACzCA,EAAO,EACP,MAAMG,EAAQJ,EAAQ,SAASV,EAAU,MAAO,mBAAmB,EAE9Dc,EAAM,MAAM,IAAA,EAAe,EAEhC,IAAIC,EACAC,EACJ,MAAMC,EAAa,IAAoB,CACrC,GAAIF,IAAkB,OAAW,OAAOA,EAExC,IAAI5E,EACAC,EACJ2E,EAAgB,IAAI,QAAc,CAACG,EAAQC,IAAU,CACnDhF,EAAU+E,EACV9E,EAAS+E,CACX,CAAC,EACDf,EAAQ,UACRM,EAAQ,OAAM,EACd,MAAMU,EAA0B,CAAA,EAEhC,GAAI,CACFA,EAAO,KAAKpB,EAAW,OAAO,CAChC,OAASzD,EAAO,CACd6E,EAAO,KAAK,QAAQ,OAAO7E,CAAK,CAAC,CACnC,CACA,OAAA6E,EAAO,KAAK,GAAG,CAAC,GAAGb,CAAM,EAAE,IAAIc,GAASA,EAAM,MAAK,CAAE,CAAC,EACjD,QAAQ,WAAWD,CAAM,EAAE,KAAKE,GAAU,CAC7C,MAAMC,EAAW,CACf,GAAG,IAAI,IAAI,CACT,GAAGf,EACH,GAAGc,EAAQ,QAAQ3C,GAAWA,EAAO,SAAW,WAAa,CAACA,EAAO,MAAiB,EAAI,CAAA,CAAG,EAC9F,GAEH6B,EAAc,OAAS,EACvBJ,EAAQ,SACR,GAAI,CACFO,EAAO,CACT,OAASpE,EAAO,CACdgF,EAAS,KAAKhF,CAAK,CACrB,SACEsE,EAAO,EACPX,EAAQ,MAAK,EACbK,EAAO,MAAK,EACZP,EAAY,OACZP,EAAO,MACT,CACA9D,EAAqB4F,EAAS,SAAW,EAErCA,EAAS,SAAW,EAAGpF,EAAO,EAC7BC,EAAOoF,EAAqBD,EAAU,0BAA0B,CAAC,CACxE,CAAC,EAEMR,CACT,EA4DA,OAAO,OAAO,OAAO,CACnB,MA3DY,IAAoB,CAChC,GAAIC,IAAiB,OAAW,OAAOA,EAEvC,MAAMS,EAAQR,EAAU,EAGxB,OAAID,IAAiB,SAErBA,EAAeN,EAAQ,SAASe,EAAO,gBAAgB,EAClDT,EAAa,MAAM,IAAK,CAEvBZ,IAAU,YAAWY,EAAe,OAC1C,CAAC,GAEMA,CACT,EA6CE,YAAAP,EACA,QAAAhF,EACA,MA7CwC,CACxCuB,EACAC,EAA+D,CAAA,IAC9B,CACjC,GAAImD,IAAU,OAAQ,MAAM,IAAI,MAAM,sBAAsB,EAE5D,MAAMsB,EAAO3E,EAAkBC,EAAQC,CAAO,EACxC0E,EAAQ,IAAI1F,GAA4B,CAACK,EAAQC,IAAS,CAC9DgE,EAAO,OAAOoB,CAAK,EACnBD,EAAK,MAAK,EAENpF,GAAQkE,EAAc,KAAKjE,CAAK,CACtC,CAAC,EACDgE,EAAO,IAAIoB,CAAK,EAChB,IAAIC,EACJ,GAAI,CAGF,GAFAA,EAAUnC,EAAM,MAAMiC,EAAK,IAAI,EAE3BE,IAAY,MAAQ,OAAOA,GAAY,UAAY,OAAOA,EAAQ,SAAY,WAChF,MAAM,IAAI,UAAU,sDAAsD,EAG5ED,EAAM,QAAQC,CAAO,CACvB,OAASrF,EAAO,CACd,MAAAoF,EAAM,QAAQ,MAAS,EAClBA,EAAM,MAAK,EAAG,MAAM,IAAA,EAAe,EAClCpF,CACR,CAEA,OAAO,OAAO,OAAO,CACnB,KAAMqF,EACN,QAASD,EAAM,MACf,YAActE,GAA+C,CAC3D,GAAI+C,IAAU,QAAUuB,EAAM,OAAQ,MAAM,IAAI,MAAM,2BAA2B,EAEjFD,EAAK,YAAYrE,CAAK,EACtBqD,EAAQ,OAAM,CAChB,CACD,CAAA,CACH,EAOE,OAAQ,IAAW,CACbN,IAAU,QAAQM,EAAQ,OAAM,CACtC,EACA,UAAW,IAAM1E,EAAkByE,EAAW,EAAI9E,CAAkB,EACpE,MAAAmF,EACA,QAAS,CAACjC,EAAwDgD,EAAmC,KAC/FzB,IAAU,OAAe,QAAQ,OAAO,IAAI,MAAM,sBAAsB,CAAC,EAEtEM,EAAQ,QAAQ7B,EAAWgD,CAAW,CAEhD,CAAA,CACH"}
|
|
1
|
+
{"version":3,"file":"testing.js","sources":["../src/scenario-diagnostics.ts","../src/scenario-mount.ts","../src/scenario-slot.tsx","../src/scenario-wait.ts","../src/scenario.ts"],"sourcesContent":["import type { RuntimeActivitySnapshot, RuntimeGraphSnapshot } from '@opetope/runtime/internal';\n\nimport type { ScenarioOwnership } from './scenario-types';\n\nfunction instanceFacts(instance: RuntimeGraphSnapshot['runtime']['instances'][number]): readonly string[] {\n if (instance.state.kind === 'ready') return [];\n\n const waiting = instance.state.kind === 'waiting' ? ` (${instance.state.reason})` : '';\n\n return [`Feature ${instance.label}: ${instance.state.kind}${waiting}.`];\n}\n\nfunction callFact(call: RuntimeActivitySnapshot['calls'][number]): string {\n if (call.lane === null) return `Call ${call.declaration}: ${call.state}; lane parallel; blockers none (parallel).`;\n\n const blockers = call.lane.blockedBy === 'unknown' ? 'unknown' : call.lane.blockedBy.join(', ');\n\n return `Call ${call.declaration}: ${call.state}; lane ${call.lane.declaration}; blockers ${blockers}.`;\n}\n\nfunction activityFacts(activity: RuntimeActivitySnapshot | undefined): readonly string[] {\n if (activity === undefined) return ['Runtime activity: unknown.'];\n\n const facts: string[] = [];\n\n if (activity.freshness === 'stale' || activity.truncated) {\n facts.push(`Activity: ${activity.freshness}${activity.truncated ? ', truncated' : ''}; completeness unknown.`);\n }\n\n return [\n ...facts,\n ...activity.features.map(\n feature => `Feature ${feature.feature}: ${feature.phase}; body ${feature.body}; host demand unknown.`,\n ),\n ...activity.calls.map(callFact),\n ...activity.resources.map(\n resource => `Resource ${resource.id}: ${resource.state}; retainers ${String(resource.retainers)}.`,\n ),\n ];\n}\n\nfunction explainSnapshot(snapshot: RuntimeGraphSnapshot): string {\n const facts = [\n ...snapshot.runtime.conditions.flatMap(condition =>\n condition.state.kind === 'true' ? [] : [`Condition ${condition.label}: ${condition.state.kind}.`],\n ),\n ...snapshot.runtime.instances.flatMap(instanceFacts),\n ...activityFacts(snapshot.activity),\n ];\n\n return facts.length === 0\n ? 'No registered runtime blocker observed; external UI and host work are unknown.'\n : facts.join('\\n');\n}\n\n/** D206: the failure carries the producer's same data-only graph/activity facts, without product payloads. */\nclass ScenarioTimeoutError extends Error {\n readonly code = 'timeout';\n\n constructor(\n readonly label: string,\n readonly timeoutMs: number,\n readonly snapshot: RuntimeGraphSnapshot,\n readonly history: readonly RuntimeGraphSnapshot[],\n ) {\n super(`Scenario timed out after ${String(timeoutMs)}ms: ${label}.\\n${explainSnapshot(snapshot)}`);\n this.name = 'ScenarioTimeoutError';\n }\n}\n\nfunction ownershipComplete(activity: RuntimeActivitySnapshot | undefined, successfullyClosed: boolean): boolean {\n if (!successfullyClosed || activity === undefined) return false;\n\n return activity.closed && !activity.truncated;\n}\n\nfunction ownershipReasons(\n activity: RuntimeActivitySnapshot | undefined,\n complete: boolean,\n successfullyClosed: boolean,\n): readonly string[] {\n if (activity === undefined) return ['Runtime activity is unavailable.'];\n\n const reasons: string[] = [];\n\n if (activity.truncated) reasons.push('The activity snapshot is truncated.');\n\n if (activity.freshness === 'stale' && !complete) reasons.push('The activity snapshot is stale.');\n\n if (!successfullyClosed) reasons.push('Successful application and mount cleanup has not been observed.');\n\n return Object.freeze(reasons);\n}\n\nfunction ownershipCounts(\n activity: RuntimeActivitySnapshot | undefined,\n complete: boolean,\n): Pick<ScenarioOwnership, 'calls' | 'features' | 'resources'> {\n if (activity === undefined || activity.truncated || (activity.freshness === 'stale' && !complete)) {\n return { calls: 'unknown', features: 'unknown', resources: 'unknown' };\n }\n\n return { calls: activity.calls.length, features: activity.features.length, resources: activity.resources.length };\n}\n\nfunction scenarioOwnership(snapshot: RuntimeGraphSnapshot, successfullyClosed: boolean): ScenarioOwnership {\n const activity = snapshot.activity;\n const complete = ownershipComplete(activity, successfullyClosed);\n\n return Object.freeze({\n ...ownershipCounts(activity, complete),\n closed: successfullyClosed,\n reasons: ownershipReasons(activity, complete, successfullyClosed),\n scope: 'registered-runtime',\n status: complete ? 'complete' : 'unknown',\n });\n}\n\nexport { scenarioOwnership, ScenarioTimeoutError };\n","import type { ScenarioHostMount } from './scenario-types';\n\n/** A reservation exists before the host callback, so reentrant close also owns a mount returned later. */\nclass ScenarioMountLease<Mounted extends ScenarioHostMount> {\n get closed(): boolean {\n return this.completion !== undefined;\n }\n private available = false;\n private completion: Promise<void> | undefined;\n private handle: Mounted | undefined;\n private reject: ((error: unknown) => void) | undefined;\n\n private resolve: (() => void) | undefined;\n\n constructor(private readonly released: (failed: boolean, error?: unknown) => void) {}\n\n readonly close = (): Promise<void> => {\n if (this.completion !== undefined) return this.completion;\n\n this.completion = new Promise<void>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n\n if (this.available) this.releaseAvailable();\n\n return this.completion;\n };\n\n provide(handle: Mounted | undefined): void {\n this.available = true;\n this.handle = handle;\n\n if (this.closed) this.releaseAvailable();\n }\n\n private finish(failed: boolean, error?: unknown): void {\n const resolve = this.resolve;\n const reject = this.reject;\n this.resolve = undefined;\n this.reject = undefined;\n this.released(failed, error);\n\n if (!failed) resolve?.();\n else reject?.(error);\n }\n\n private releaseAvailable(): void {\n const handle = this.handle;\n this.handle = undefined;\n this.available = false;\n try {\n Promise.resolve(handle?.unmount()).then(\n () => this.finish(false),\n (error: unknown) => this.finish(true, error),\n );\n } catch (error) {\n this.finish(true, error);\n }\n }\n}\n\nexport { ScenarioMountLease };\n","import { createElement } from 'react';\nimport type { FC, ReactNode } from 'react';\n\nimport { declarationId } from '@opetope/core';\nimport type { Call } from '@opetope/core';\nimport { command as callFixture } from '@opetope/core/testing';\n\nimport { bindCommand } from './command';\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport type { ContributionValue, ModelFixture } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\nimport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget } from './slot';\n\n/**\n * Two forms, as `renderRoot` had (D19): without `contribution` the harness mounts what the target published, with it\n * the harness mounts one fixture contribution so a component test needs no generation.\n */\ntype SlotTestFixture<Props extends SlotProperties> = {\n readonly contribution?: SlotContribution<Props>;\n readonly models?: readonly ModelFixture[];\n readonly props?: SlotComponentProperties<Props>;\n /**\n * Where a contained mount failure is reported (D256). A component test has no feature to report to, so without it\n * the failure is detached and the assertion has nothing to read.\n */\n readonly reporter?: (error: unknown) => void;\n};\n\ninterface SlotTestHarness<Props extends SlotProperties> {\n readonly Slot: FC;\n readonly updateProps: (props: SlotComponentProperties<Props>) => void;\n}\n\nconst testingContributionId = declarationId('testing.contribution');\n\n/**\n * A command a fixture publishes: the test writes the body, the harness gives it the identity a model requires. The\n * Call itself is the core fixture (D300); what this word adds, and what keeps it in the React package, is the\n * contribution binding a mount reads (D285).\n */\nfunction command<Input, Output>(run: (input: Input) => Output | PromiseLike<Output>): Call<Input, Output> {\n return bindCommand(callFixture<Input, Output>(run));\n}\n\n/**\n * Mounts the contributions of one slot the way a host does, with optional model fixtures for a component test. D19 and D85: the same\n * ContributionMount is shared by component tests and the application scenario harness (D206).\n */\nfunction createSlotHarness<Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: SlotTestFixture<Props> = {},\n): SlotTestHarness<Props> & { readonly close: () => void } {\n const fixtures = fixture.models ?? [];\n let current: object = fixture.props ?? {};\n const listeners = new Set<() => void>();\n const props = {\n getSnapshot: (): object => current,\n subscribe: (listener: () => void): (() => void) => {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n\n const supplied = fixture.contribution;\n const reported = fixture.reporter;\n const fixtureAuthority = reported === undefined ? undefined : { models: [], reporter: reported };\n const Mounted: FC = (): ReactNode => {\n const published = useReadable(target.entries);\n const slotProps = useReadable(props);\n const entries =\n supplied === undefined\n ? published.map(\n entry =>\n [entry.id, entry.value as ContributionValue, contributionAuthority(entry) ?? fixtureAuthority] as const,\n )\n : [[testingContributionId, supplied as unknown as ContributionValue, fixtureAuthority] as const];\n\n return entries.map(([contributionId, contribution, authority]) =>\n createElement(ContributionMount, {\n authority,\n contribution,\n contributionId,\n fixtures,\n key: contributionId,\n slotProps,\n targetId: target.id,\n }),\n );\n };\n\n return Object.freeze({\n close: (): void => {\n current = {};\n listeners.clear();\n },\n Slot: Mounted,\n updateProps: (next: SlotComponentProperties<Props>): void => {\n current = next;\n\n for (const listener of [...listeners]) listener();\n },\n });\n}\n\nfunction renderSlot<Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: SlotTestFixture<Props> = {},\n): SlotTestHarness<Props> {\n const harness = createSlotHarness(target, fixture);\n\n return Object.freeze({ Slot: harness.Slot, updateProps: harness.updateProps });\n}\n\nexport { command, createSlotHarness, renderSlot };\nexport type { SlotTestFixture, SlotTestHarness };\n","import type { RuntimeGraphSnapshot } from '@opetope/runtime/internal';\n\nimport { ScenarioTimeoutError } from './scenario-diagnostics';\nimport type { ScenarioWaitOptions } from './scenario-types';\n\nfunction positiveDuration(value: number, label: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > 2_147_483_647) {\n throw new RangeError(`${label} must be a finite duration from 0 (exclusive) to 2147483647ms.`);\n }\n\n return value;\n}\n\ninterface ScenarioWaitSource {\n readonly getSnapshot: () => RuntimeGraphSnapshot;\n readonly history: () => readonly RuntimeGraphSnapshot[];\n readonly timeoutMs: number;\n}\n\nclass ScenarioWaiters {\n private readonly listeners = new Set<() => void>();\n private readonly pending = new Set<(error: Error) => void>();\n\n constructor(private readonly source: ScenarioWaitSource) {}\n\n cancel(): void {\n for (const reject of [...this.pending]) reject(new Error('Scenario is closing.'));\n }\n\n deadline<Value>(result: PromiseLike<Value>, label: string, timeoutMs = this.source.timeoutMs): Promise<Value> {\n positiveDuration(timeoutMs, 'Scenario timeoutMs');\n\n return new Promise<Value>((resolve, reject) => {\n const timer = setTimeout(() => reject(this.timeout(label, timeoutMs)), timeoutMs);\n Promise.resolve(result).then(\n value => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n }\n\n readonly notify = (): void => {\n for (const listener of [...this.listeners]) listener();\n };\n\n waitFor(predicate: (snapshot: RuntimeGraphSnapshot) => boolean, options: ScenarioWaitOptions): Promise<void> {\n const timeoutMs = positiveDuration(options.timeoutMs ?? this.source.timeoutMs, 'Scenario timeoutMs');\n const pollIntervalMs = positiveDuration(options.pollIntervalMs ?? 10, 'Scenario pollIntervalMs');\n\n return new Promise<void>((resolve, reject) => {\n let done = false;\n let dispose = (): void => undefined;\n const finish = (error?: unknown, failed = error !== undefined): void => {\n if (done) return;\n\n done = true;\n dispose();\n\n if (!failed) resolve();\n else reject(error);\n };\n const check = (): void => {\n if (done) return;\n\n try {\n if (predicate(this.source.getSnapshot())) finish();\n } catch (error) {\n finish(error, true);\n }\n };\n const cancel = (error: Error): void => finish(error);\n const deadline = setTimeout(() => finish(this.timeout(options.label ?? 'waitFor', timeoutMs)), timeoutMs);\n const poll = setInterval(check, pollIntervalMs);\n dispose = (): void => {\n clearTimeout(deadline);\n clearInterval(poll);\n this.listeners.delete(check);\n this.pending.delete(cancel);\n };\n this.listeners.add(check);\n this.pending.add(cancel);\n check();\n });\n }\n\n private timeout(label: string, timeoutMs: number): ScenarioTimeoutError {\n return new ScenarioTimeoutError(label, timeoutMs, this.source.getSnapshot(), this.source.history());\n }\n}\n\nexport { positiveDuration, ScenarioWaiters };\n","import { createAggregateError, hasOwn } from '@opetope/core/internal';\nimport { openApplication } from '@opetope/runtime';\nimport type { Application, ApplicationExecution } from '@opetope/runtime';\nimport { createInspectionSession } from '@opetope/runtime/internal';\nimport type {\n ApplicationConditionBinding,\n OpenApplicationOptions,\n RuntimeGraphSnapshot,\n RuntimeInspectionSession,\n} from '@opetope/runtime/internal';\n\nimport { scenarioOwnership } from './scenario-diagnostics';\nimport { ScenarioMountLease } from './scenario-mount';\nimport { createSlotHarness } from './scenario-slot';\nimport type {\n Scenario,\n ScenarioFeatures,\n ScenarioHost,\n ScenarioHostMount,\n ScenarioMount,\n ScenarioOptions,\n ScenarioWaitOptions,\n} from './scenario-types';\nimport { positiveDuration, ScenarioWaiters } from './scenario-wait';\nimport type { SlotComponentProperties, SlotProperties, SlotTarget } from './slot';\n\nfunction boundedCapacity(value: number, label: string): number {\n if (!Number.isSafeInteger(value) || value < 1 || value > 10_000) {\n throw new RangeError(`${label} must be an integer from 1 to 10000.`);\n }\n\n return value;\n}\n\nfunction validateHost(host: unknown): void {\n if (host === null || typeof host !== 'object' || !('mount' in host) || typeof host.mount !== 'function') {\n throw new TypeError('Scenario requires a host.mount(Component) adapter.');\n }\n}\n\n/**\n * What the scenario hands to `openApplication`: absence is preserved separately from an explicitly invalid value,\n * so the condition-dependent type and the D281 omissions behave exactly as they do for a host.\n */\nfunction applicationOpenOptions<const Features extends ScenarioFeatures>(\n options: ScenarioOptions<Features, ScenarioHostMount>,\n): OpenApplicationOptions<Features> {\n return {\n ...(options.cleanupFailure === undefined ? {} : { cleanupFailure: options.cleanupFailure }),\n ...(hasOwn(options, 'conditions') ? { conditions: options.conditions } : {}),\n ...(hasOwn(options, 'imports') ? { imports: options.imports } : {}),\n reporter: options.reporter,\n };\n}\n\n/** D206: one real application, its existing inspection session and renderer-owned Slot ingress. */\nfunction createScenario<\n const Features extends ScenarioFeatures,\n Mounted extends ScenarioHostMount,\n const Bindings extends readonly ApplicationConditionBinding[] = readonly [],\n>(\n application: Application<Features>,\n options: ScenarioOptions<NoInfer<Features>, Mounted, Bindings>,\n): Scenario<Mounted> {\n const timeoutMs = positiveDuration(options.timeoutMs ?? 1_000, 'Scenario timeoutMs');\n const historyCapacity = boundedCapacity(options.historyCapacity ?? 64, 'Scenario historyCapacity');\n const activityCapacity = boundedCapacity(options.activityCapacity ?? 256, 'Scenario activityCapacity');\n let host: ScenarioHost<Mounted> | undefined = options.host;\n\n validateHost(host);\n\n let execution: ApplicationExecution | undefined = openApplication(application, applicationOpenOptions(options));\n let session: RuntimeInspectionSession;\n try {\n session = createInspectionSession(execution, { activityCapacity, ringCapacity: historyCapacity });\n } catch (error) {\n void execution.ready.catch(() => undefined);\n void execution.close().catch(() => undefined);\n throw error;\n }\n\n let phase: 'closed' | 'closing' | 'open' = 'open';\n let successfullyClosed = false;\n let capturing = false;\n const records: RuntimeGraphSnapshot[] = [];\n const mounts = new Set<ScenarioMountLease<Mounted>>();\n const mountFailures: unknown[] = [];\n const getSnapshot = (): RuntimeGraphSnapshot => session.getSnapshot();\n const history = (): readonly RuntimeGraphSnapshot[] => Object.freeze([...records]);\n const waiters = new ScenarioWaiters({ getSnapshot, history, timeoutMs });\n const capture = (): void => {\n if (capturing) return;\n\n capturing = true;\n try {\n const snapshot = getSnapshot();\n const previous = records[records.length - 1];\n\n if (previous?.stateRevision !== snapshot.stateRevision || previous.activity !== snapshot.activity) {\n records.push(snapshot);\n\n if (records.length > historyCapacity) records.shift();\n }\n } finally {\n capturing = false;\n }\n waiters.notify();\n };\n const release = session.subscribe(capture);\n capture();\n const ready = waiters.deadline(execution.ready, 'application ready');\n // A test may deliberately inspect loading without awaiting ready; rejection remains observable to that caller.\n void ready.catch(() => undefined);\n\n let physicalClose: Promise<void> | undefined;\n let closeAttempt: Promise<void> | undefined;\n const startClose = (): Promise<void> => {\n if (physicalClose !== undefined) return physicalClose;\n\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n physicalClose = new Promise<void>((accept, refuse) => {\n resolve = accept;\n reject = refuse;\n });\n phase = 'closing';\n waiters.cancel();\n const drains: Promise<void>[] = [];\n // Fence before any foreign unmount callback can reenter UI ingress; join all physical drains afterwards.\n try {\n drains.push(execution!.close());\n } catch (error) {\n drains.push(Promise.reject(error));\n }\n drains.push(...[...mounts].map(mount => mount.close()));\n void Promise.allSettled(drains).then(results => {\n const failures = [\n ...new Set([\n ...mountFailures,\n ...results.flatMap(result => (result.status === 'rejected' ? [result.reason as unknown] : [])),\n ]),\n ];\n mountFailures.length = 0;\n phase = 'closed';\n try {\n capture();\n } catch (error) {\n failures.push(error);\n } finally {\n release();\n session.close();\n mounts.clear();\n execution = undefined;\n host = undefined;\n }\n successfullyClosed = failures.length === 0;\n\n if (failures.length === 0) resolve();\n else reject(createAggregateError(failures, 'Scenario cleanup failed.'));\n });\n\n return physicalClose;\n };\n\n const close = (): Promise<void> => {\n if (closeAttempt !== undefined) return closeAttempt;\n\n const drain = startClose();\n\n // A nested close during unmount may have installed the same attempt already.\n if (closeAttempt !== undefined) return closeAttempt;\n\n closeAttempt = waiters.deadline(drain, 'scenario close');\n void closeAttempt.catch(() => {\n // A deadline cannot cancel physical work. A later close can await that same drain with a new deadline.\n if (phase === 'closing') closeAttempt = undefined;\n });\n\n return closeAttempt;\n };\n\n const mount: Scenario<Mounted>['mount'] = <Props extends SlotProperties>(\n target: SlotTarget<Props>,\n fixture: { readonly props?: SlotComponentProperties<Props> } = {},\n ): ScenarioMount<Props, Mounted> => {\n if (phase !== 'open') throw new Error('Scenario is closing.');\n\n const slot = createSlotHarness(target, fixture);\n const lease = new ScenarioMountLease<Mounted>((failed, error) => {\n mounts.delete(lease);\n slot.close();\n\n if (failed) mountFailures.push(error);\n });\n mounts.add(lease);\n let mounted: Mounted;\n try {\n mounted = host!.mount(slot.Slot);\n\n if (mounted === null || typeof mounted !== 'object' || typeof mounted.unmount !== 'function') {\n throw new TypeError('Scenario host.mount must return an unmount() handle.');\n }\n\n lease.provide(mounted);\n } catch (error) {\n lease.provide(undefined);\n void lease.close().catch(() => undefined);\n throw error;\n }\n\n return Object.freeze({\n host: mounted,\n unmount: lease.close,\n updateProps: (props: SlotComponentProperties<Props>): void => {\n if (phase !== 'open' || lease.closed) throw new Error('Scenario mount is closed.');\n\n slot.updateProps(props);\n waiters.notify();\n },\n });\n };\n\n return Object.freeze({\n close,\n getSnapshot,\n history,\n mount,\n notify: (): void => {\n if (phase === 'open') waiters.notify();\n },\n ownership: () => scenarioOwnership(getSnapshot(), successfullyClosed),\n ready,\n waitFor: (predicate: (snapshot: RuntimeGraphSnapshot) => boolean, waitOptions: ScenarioWaitOptions = {}) => {\n if (phase !== 'open') return Promise.reject(new Error('Scenario is closing.'));\n\n return waiters.waitFor(predicate, waitOptions);\n },\n });\n}\n\nexport { createScenario };\n"],"names":["instanceFacts","instance","waiting","callFact","call","blockers","activityFacts","activity","facts","feature","resource","explainSnapshot","snapshot","condition","ScenarioTimeoutError","label","timeoutMs","history","ownershipComplete","successfullyClosed","ownershipReasons","complete","reasons","ownershipCounts","scenarioOwnership","ScenarioMountLease","released","resolve","reject","handle","failed","error","testingContributionId","declarationId","command","run","bindCommand","callFixture","createSlotHarness","target","fixture","fixtures","current","listeners","props","listener","supplied","reported","fixtureAuthority","published","useReadable","slotProps","entry","contributionAuthority","contributionId","contribution","authority","createElement","ContributionMount","next","renderSlot","harness","positiveDuration","value","ScenarioWaiters","source","result","timer","predicate","options","pollIntervalMs","done","dispose","finish","check","cancel","deadline","poll","boundedCapacity","validateHost","host","applicationOpenOptions","hasOwn","createScenario","application","historyCapacity","activityCapacity","execution","openApplication","session","createInspectionSession","phase","capturing","records","mounts","mountFailures","getSnapshot","waiters","capture","previous","release","ready","physicalClose","closeAttempt","startClose","accept","refuse","drains","mount","results","failures","createAggregateError","drain","slot","lease","mounted","waitOptions"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opetope/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20.19.0"
|
|
6
6
|
},
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
}
|
|
51
51
|
],
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@opetope/core": "0.9.
|
|
54
|
-
"@opetope/runtime": "0.9.
|
|
53
|
+
"@opetope/core": "0.9.2",
|
|
54
|
+
"@opetope/runtime": "0.9.2",
|
|
55
55
|
"@testing-library/react": "16.3.3",
|
|
56
56
|
"@types/react": "19.2.18",
|
|
57
57
|
"react": "19.2.8",
|
|
58
58
|
"react-dom": "19.2.8"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@opetope/core": "0.9.
|
|
62
|
-
"@opetope/runtime": "0.9.
|
|
61
|
+
"@opetope/core": "0.9.2",
|
|
62
|
+
"@opetope/runtime": "0.9.2",
|
|
63
63
|
"react": ">=19.0.0 <20"
|
|
64
64
|
},
|
|
65
65
|
"sideEffects": false,
|