@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 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: { handler, inFlight, lastError },
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={handler}>
42
- {lastError ? 'Retry' : `${label}: ${count}`}
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 `{ run, inFlight, lastResult, lastError }`
54
- and always resolves the outcome, so a normal cancellation never becomes an unhandled rejection (D116).
55
-
56
- For `Call<void, Output>` and `Call<undefined, Output>`, the hook also exposes `handler: () => void` (D278).
57
- Use `onClick={logout.handler}`: it ignores the React event and invokes the Call with `undefined`.
58
- Commands with data, including `T | undefined`, `unknown`, `any` and `never`, do not expose a handler in their type.
59
- Use `run(input, options?)` for data, an outcome, callbacks or a per-run signal. The handler reports failures through
60
- `lastError` and preserves the same cancellation behavior; it returns no promise or outcome.
61
-
62
- `run` and `handler` are stable while the invoker stays the same; the returned object changes as `inFlight`, `lastResult`
63
- or `lastError` changes. Destructure the function for effect dependencies. A handler captured from a replaced or
64
- unmounted consumer is fenced; it does not start work on the replacement.
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, status, result and error. Keys are local UI names; commands may come from
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
- The same inputless `.handler` is available in `useCommands` and model selections. A single selected command needs
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.handler}>
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/1` snapshots (D266, D275). Within one session,
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 `waitFor` come from the entries below, for the part of a test
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: `command` and `renderSlot` are the
285
- only two words here that need React.
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 moves neither `lastResult` nor `lastError`, and it is only the `CallError` codes `cancelled`
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 `lastError` (D138);
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: { handler, inFlight, lastError },
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={handler}>
42
- {lastError ? 'Retry' : `${label}: ${count}`}
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, lastResult, lastError }`
55
- и всегда resolve-ит исход, поэтому штатная отмена не становится unhandled rejection (D116).
56
-
57
- Для `Call<void, Output>` и `Call<undefined, Output>` хук также предоставляет `handler: () => void` (D278).
58
- Пишите `onClick={logout.handler}`: обработчик игнорирует React event и вызывает Call с `undefined`.
59
- У команд с данными, включая `T | undefined`, `unknown`, `any` и `never`, handler недоступен в типе.
60
- Для данных, исхода, callbacks или сигнала отдельного вызова используйте `run(input, options?)`. Handler показывает
61
- ошибки через `lastError` и сохраняет обработку отмены; promise или исход он не возвращает.
62
-
63
- `run` и `handler` стабильны, пока invoker тот же; возвращённый объект меняется вместе с `inFlight`, `lastResult` или
64
- `lastError`. Для зависимостей эффекта берите функцию деструктуризацией. Сохранённый handler заменённого или
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
- со своими типами входа/выхода, статусом, результатом и ошибкой. Ключи — локальные имена UI; команды могут быть
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
- Тот же `.handler` для команды без входа доступен в `useCommands` и выборе модели. Одну команду можно выбрать без
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.handler}>
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/1` (D266, D275). В пределах одной session
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` и `waitFor` приходят из входов ниже — для той части теста, в
282
- которой UI нет.
299
+ - `testReadable(initial)`, `openModel`, `settled`, `waitFor`, `yieldTurn` и `eventually` приходят из входов ниже —
300
+ для той части теста, в которой UI нет.
283
301
 
284
- Тест, который ничего не рендерит, должен импортировать `@opetope/runtime/testing` напрямую: React нужен только
285
- двум словам отсюда — `command` и `renderSlot`.
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` не двигает ни `lastResult`, ни `lastError`, и это только коды `CallError` `cancelled`
372
+ - исход `cancelled` не двигает `outcome`, и это только коды `CallError` `cancelled`
351
373
  и `closed`; вызов слабого порта без провайдера (`unavailable`) и отклонённая публикация
352
- (`publication-rejected`) приходят исходом `failed` и оседают в `lastError` (D138);
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;
@@ -1,17 +1,17 @@
1
1
  import type { Call } from '@opetope/core';
2
- import type { IsAny } from '@opetope/core/internal';
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 lastError: unknown | null;
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
- } & CommandHandler<Input>;
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 S,useInsertionEffect as h,useLayoutEffect as f,useMemo as g,useSyncExternalStore as b,useRef as m,useCallback as w,useEffect as K,createElement as E,Fragment as x}from"react";import{CallError as $,declarationId as H}from"@opetope/core";import{compatibleAbortReason as T,isCancellation as G,reportDetachedError as v,isCallTarget as U,isReadable as D,createAggregateError as L,createContributionTarget as W}from"@opetope/core/internal";import{C as d}from"./contribution-isolation-Bzfbt4iM.js";import{u as p,a as R,M as B,c as J}from"./contribution-frame-DXl7K0-B.js";import{b as Fe}from"./contribution-frame-DXl7K0-B.js";function O(n,e){var t;if(G(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(v)}catch(s){v(s)}return{error:e,status:"failed"}}function N(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 Q(n,e){let t;const s=new Promise(r=>{t=r});return{input:n,options:e===void 0?void 0:{...e},promise:s,resolve:t}}function V(n,e){const t=n.resolve;n.input=void 0,n.options=void 0,n.resolve=void 0,t==null||t(e)}class C{invoker;handler=()=>{this.run(void 0)};run=((e,t)=>{if(this.notify===void 0)return Promise.resolve({reason:new d("inactive","Command consumer is not mounted."),status:"cancelled"});const s=this.cancelledInput(t);if(s!==void 0)return Promise.resolve(s);const r=Q(e,t);return this.start(r),r.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 $("cancelled","Command input was cancelled.",T(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,V(e,t),(s=this.notify)==null||s.call(this,this.inFlight>0,t))}invoke(e){let t;try{t=this.invoker.run(e.input,N(e.options)),e.input=void 0}catch(s){this.finish(e,O(e.options,s));return}t.then(s=>this.finish(e,{status:"ok",value:s}),s=>this.finish(e,O(e.options,s)))}start(e){var t;this.inFlight+=1,(t=this.notify)==null||t.call(this,!0),this.invoke(e)}}const X={inFlight:!1,lastError:null,lastResult:void 0};function Y(n,e,t,s){return s===void 0&&n.slot===e&&n.inFlight===t}function Z(n,e,t,s){if(Y(n,e,t,s))return n;const{lastError:r,lastResult:i}=n.slot===e?n:{lastError:null,lastResult:void 0};return(s==null?void 0:s.status)==="ok"?{inFlight:t,lastError:null,lastResult:s.value,slot:e}:(s==null?void 0:s.status)==="failed"?{inFlight:t,lastError:s.error,lastResult:i,slot:e}:{inFlight:t,lastError:r,lastResult:i,slot:e}}function _(n){const[e]=S(()=>new WeakMap);let t=e.get(n);return t===void 0&&(t=new C(n),e.set(n,t)),t}function q(n){const t=p().commandRecords.get(n);if(t===void 0)throw new d("missing",`Command ${n.id} is not bound in this contribution.`);const s=_(t.invoker),[r,i]=S(()=>({inFlight:!1,lastError:null,lastResult:void 0,slot:s}));h(()=>s.attach((A,I)=>i(P=>Z(P,s,A,I))),[s]),f(()=>s.synchronize(),[s]);const{inFlight:o,lastError:a,lastResult:l}=r.slot===s?r:X,c=s.run,u=s.handler;return g(()=>({handler:u,inFlight:o,lastError:a,lastResult:l,run:c}),[u,o,a,l,c])}const ee=()=>{},j=()=>ee,te=()=>0,ne=Object.freeze({});function se(n){const e=new C(n);return{detach:void 0,hook:{handler:e.handler,inFlight:!1,lastError:null,lastResult:void 0,run:e.run},invoker:n,slot:e}}function re(n,e){return n.inFlight===e.inFlight&&Object.is(n.lastResult,e.lastResult)&&Object.is(n.lastError,e.lastError)}function ie(n,e){return(e==null?void 0:e.status)==="ok"?{lastError:null,lastResult:e.value}:(e==null?void 0:e.status)==="failed"?{lastError:e.error,lastResult:n.lastResult}:n}function oe(n,e,t){const s=n.hook;if(t===void 0&&s.inFlight===e)return!1;const{lastError:r,lastResult:i}=ie(s,t),o={handler:n.slot.handler,inFlight:e,lastError:r,lastResult:i,run:s.run};return re(s,o)?!1:(n.hook=o,!0)}function le(n,e){return(n==null?void 0:n.invoker)===e?n:se(e)}class ae{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,r]of this.entries)e.get(s)!==r&&((t=r.detach)==null||t.call(r),r.detach=void 0);this.entries=e;for(const s of e.values())s.detach??(s.detach=s.slot.attach((r,i)=>{if(oe(s,r,i)){this.revision+=1;for(const o of this.listeners)o()}}))}getSnapshot=()=>this.revision;prepare(e,t){const s=new Map;for(const r of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,r))continue;const i=e[r],o=i===void 0?void 0:t.commandRecords.get(i);if(o===void 0)throw new d("missing",`Command at ${String(r)} is not bound in this contribution.`);s.set(r,le(this.entries.get(r),o.invoker))}return s}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function ce(n){const e=m(void 0);return Reflect.ownKeys(n).some(s=>Object.prototype.propertyIsEnumerable.call(n,s))?e.current??(e.current=new ae):void 0}function M(n){const e=p(),t=ce(n),s=(t==null?void 0:t.getSnapshot)??te;b((t==null?void 0:t.subscribe)??j,s,s);const r=t==null?void 0:t.prepare(n,e);return h(()=>()=>t==null?void 0:t.close(),[t]),h(()=>{r!==void 0&&(t==null||t.commit(r))}),f(()=>{if(r!==void 0)for(const i of r.values())i.slot.synchronize()}),r===void 0?ne:Object.fromEntries([...r].map(([i,o])=>[i,o.hook]))}function ue(n,e){if(!D(n))throw new d("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:(r,i)=>{const o=ue(r,e);return i===void 0?o:i(o)}});if(typeof s!="object"||s===null||Array.isArray(s))throw new d("binding-invalid","Model selection requires a record.");return Object.fromEntries(Reflect.ownKeys(s).filter(r=>Object.prototype.propertyIsEnumerable.call(s,r)).map(r=>[r,s[r]]))}function fe(n,e,t){const s=Reflect.ownKeys(e),r=Reflect.ownKeys(n.values);if(s.length!==r.length||t.size!==n.sources.size)return!1;for(const i of t)if(!n.sources.has(i))return!1;return s.every((i,o)=>r[o]===i&&Object.is(n.values[i],e[i]))}const y={commands:{},sources:new Set,values:{}};function he(n){return Object.fromEntries(Reflect.ownKeys(n).filter(e=>U(n[e])).map(e=>[e,n[e]]))}function be(n,e){let t,s,r=y;return()=>{const i=e.current;if(i===void 0)return y;if(t!==void 0&&s===i&&[...t].every(([c,u])=>Object.is(c.getSnapshot(),u)))return r;const o=new Map,a=de(c=>i(n,c),o),l=new Set(o.keys());return(t===void 0||!fe(r,a,l))&&(r={commands:he(a),sources:l,values:a}),t=o,s=i,r}}function k(n,e=[]){for(const t of n){const s=t.dispose;t.dispose=void 0;try{s==null||s()}catch(r){e.push(r)}}if(e.length===1)throw e[0];if(e.length>1)throw L(e,"Model selection sources failed to release.")}class me{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 pe=()=>y;function ye(n){const e=m(void 0);return n?e.current??(e.current=new me):void 0}function Se(n,e){const t=p();if(!t.models.has(n))throw new d("missing",`Model ${n.id} is not granted to this contribution.`);const s=t.models.get(n),r=ye(e!==void 0),i=m(void 0);i.current=e;const o=e!==void 0,a=g(()=>o?be(s,i):pe,[s,o]),l=b((r==null?void 0:r.subscribe)??j,a,a),c=M(l.commands);return f(()=>()=>r==null?void 0:r.close(),[r]),f(()=>r==null?void 0:r.commit(l.sources),[l,r]),e===void 0?s:Object.fromEntries(Reflect.ownKeys(l.values).map(u=>[u,Object.prototype.hasOwnProperty.call(c,u)?c[u]:l.values[u]]))}function ge(n){const e=Object.freeze([...n]);return t=>{if(t.requires!==void 0)throw new d("duplicate","Component already declares the models it requires.");return Object.defineProperty(t,"requires",{enumerable:!1,value:e}),t}}function we(n){const e=w(r=>n.subscribe(r),[n]),t=w(()=>n.getSnapshot(),[n]),s=b(e,t,t);return K(()=>n.retain(),[n]),s}const z=Object.freeze({});function F(n){return W(n,"slot")}function Ee(n){const e=H(n),t=new Map,s=r=>{const i=t.get(r);if(i!==void 0)return i;const o=F(`${e}/${r}`);return t.set(r,o),o};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function ve(n){const e=R(n.target.entries),t="props"in n?n.props??z:z;return e.length===0?null:e.map(s=>E(x,{key:s.id},E(B,{authority:J(s),contribution:s.value,contributionId:s.id,slotProps:t,targetId:n.target.id})))}export{d as ContributionError,ve as Slot,F as defineSlot,Ee as defineSwitchSlot,ge as requiresModels,q as useCommand,M as useCommands,Se as useModel,R as useReadable,we as useResource,Fe as useSelector};
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":"8nBA+BA,SAASA,EAAuBC,EAAgDC,EAAc,OAC5F,GAAIC,EAAeD,CAAK,EAAG,MAAO,CAAE,OAAQA,EAAO,OAAQ,WAAW,EAEtE,GAAI,CACF,MAAME,GAAeC,EAAAJ,GAAA,YAAAA,EAAS,YAAT,YAAAI,EAAA,KAAAJ,EAAqBC,GAEtCE,IAAiB,QAAgB,QAAQ,QAAQA,CAAY,EAAE,MAAME,CAAmB,CAC9F,OAASC,EAAe,CACtBD,EAAoBC,CAAa,CACnC,CAEA,MAAO,CAAE,MAAAL,EAAO,OAAQ,QAAQ,CAClC,CAEA,SAASM,EAAmBP,EAA8C,CACxE,MAAMQ,EAAYR,GAAA,YAAAA,EAAS,UACrBS,EAAST,GAAA,YAAAA,EAAS,OAExB,OAAIQ,IAAc,OAAkBC,IAAW,OAAY,OAAY,CAAE,OAAAA,CAAM,EAExEA,IAAW,OAAY,CAAE,UAAAD,CAAS,EAAK,CAAE,UAAAA,EAAW,OAAAC,CAAM,CACnE,CAEA,SAASC,EACPC,EACAX,EAA8C,CAE9C,IAAIY,EACJ,MAAMC,EAAU,IAAI,QAAgCC,GAAS,CAC3DF,EAAUE,CACZ,CAAC,EAED,MAAO,CAAE,MAAAH,EAAO,QAASX,IAAY,OAAY,OAAY,CAAE,GAAGA,CAAO,EAAI,QAAAa,EAAS,QAAAD,CAAO,CAC/F,CAEA,SAASE,EAAsBC,EAAwCC,EAA+B,CACpG,MAAMJ,EAAUG,EAAQ,QACxBA,EAAQ,MAAQ,OAChBA,EAAQ,QAAU,OAClBA,EAAQ,QAAU,OAClBH,GAAA,MAAAA,EAAUI,EACZ,CAOA,MAAMC,CAAqB,CAyBI,QAvBpB,QAAU,IAAW,CACvB,KAAK,IAAI,MAAkB,CAClC,EACS,KAAkC,CAACN,EAAcX,IAAuC,CAC/F,GAAI,KAAK,SAAW,OAClB,OAAO,QAAQ,QAAQ,CACrB,OAAQ,IAAIkB,EAAkB,WAAY,kCAAkC,EAC5E,OAAQ,WACT,CAAA,EAGH,MAAMC,EAAU,KAAK,eAAenB,CAAO,EAE3C,GAAImB,IAAY,OAAW,OAAO,QAAQ,QAAQA,CAAO,EAEzD,MAAMJ,EAAUL,EAAUC,EAAOX,CAAO,EACxC,YAAK,MAAMe,CAAO,EAEXA,EAAQ,OACjB,GACQ,SAAW,EACX,OAER,YAA6BK,EAAuB,CAAvB,KAAA,QAAAA,CAA0B,CAEvD,OAAOC,EAAmC,CACxC,YAAK,OAASA,EAEP,IAAK,CACN,KAAK,SAAWA,IAEpB,KAAK,OAAS,OAChB,CACF,CAEA,aAAW,QACTjB,EAAA,KAAK,SAAL,MAAAA,EAAA,UAAc,KAAK,SAAW,EAChC,CAEQ,QAAQK,EAAmB,CACjC,MAAO,CACL,OAAQ,IAAIa,EAAU,YAAa,+BAAgCC,EAAsBd,CAAM,CAAC,EAChG,OAAQ,YAEZ,CAEQ,eAAeT,EAA8C,OACnE,QAAOI,EAAAJ,GAAA,YAAAA,EAAS,SAAT,YAAAI,EAAiB,WAAY,GAAO,KAAK,QAAQJ,EAAQ,MAAM,EAAI,MAC5E,CAEQ,OAAOe,EAAwCC,EAA+B,OAChFD,EAAQ,UAAY,SAExB,KAAK,UAAY,EACjBD,EAAOC,EAASC,CAAO,GACvBZ,EAAA,KAAK,SAAL,MAAAA,EAAA,UAAc,KAAK,SAAW,EAAGY,GACnC,CAEQ,OAAOD,EAAsC,CACnD,IAAIS,EAEJ,GAAI,CACFA,EAAa,KAAK,QAAQ,IAAIT,EAAQ,MAAgBR,EAAWQ,EAAQ,OAAO,CAAU,EAC1FA,EAAQ,MAAQ,MAClB,OAASd,EAAO,CACd,KAAK,OAAOc,EAAShB,EAAegB,EAAQ,QAASd,CAAK,CAAC,EAE3D,MACF,CAEKuB,EAAW,KACdC,GAAS,KAAK,OAAOV,EAAS,CAAE,OAAQ,KAAM,MAAAU,CAAK,CAAE,EACpDxB,GAAmB,KAAK,OAAOc,EAAShB,EAAegB,EAAQ,QAASd,CAAK,CAAC,CAAC,CAEpF,CAEQ,MAAMc,EAAsC,OAClD,KAAK,UAAY,GAEjBX,EAAA,KAAK,SAAL,MAAAA,EAAA,UAAc,IACd,KAAK,OAAOW,CAAO,CACrB,CACD,CClJD,MAAMW,EAAwC,CAAE,SAAU,GAAO,UAAW,KAAM,WAAY,MAAS,EAmBvG,SAASC,EACPC,EACAC,EACAC,EACAd,EAA2C,CAE3C,OAAOA,IAAY,QAAaY,EAAQ,OAASC,GAAQD,EAAQ,WAAaE,CAChF,CAEA,SAASC,EACPH,EACAC,EACAC,EACAd,EAAgC,CAEhC,GAAIW,EAAeC,EAASC,EAAMC,EAAUd,CAAO,EAAG,OAAOY,EAE7D,KAAM,CAAE,UAAAI,EAAW,WAAAC,CAAU,EAAKL,EAAQ,OAASC,EAAOD,EAAU,CAAE,UAAW,KAAM,WAAY,MAAS,EAE5G,OAAIZ,GAAA,YAAAA,EAAS,UAAW,KAAa,CAAE,SAAAc,EAAU,UAAW,KAAM,WAAYd,EAAQ,MAAO,KAAAa,CAAI,GAE7Fb,GAAA,YAAAA,EAAS,UAAW,SAAiB,CAAE,SAAAc,EAAU,UAAWd,EAAQ,MAAO,WAAAiB,EAAY,KAAAJ,CAAI,EAExF,CAAE,SAAAC,EAAU,UAAAE,EAAW,WAAAC,EAAY,KAAAJ,CAAI,CAChD,CAEA,SAASK,EAA8Bd,EAAuB,CAE5D,KAAM,CAACe,CAAK,EAAIC,EAAS,IAAM,IAAI,OAAuD,EAC1F,IAAIP,EAAOM,EAAM,IAAIf,CAAO,EAE5B,OAAIS,IAAS,SACXA,EAAO,IAAIZ,EAAsBG,CAAO,EACxCe,EAAM,IAAIf,EAASS,CAAI,GAGlBA,CACT,CAEA,SAASQ,EAA0BC,EAA4B,CAE7D,MAAMC,EADQC,EAAQ,EACD,eAAe,IAAIF,CAAO,EAE/C,GAAIC,IAAW,OACb,MAAM,IAAIrB,EAAkB,UAAW,WAAWoB,EAAQ,EAAE,qCAAqC,EAGnG,MAAMG,EAAcP,EAA8BK,EAAO,OAAO,EAC1D,CAACG,EAAWC,CAAY,EAAIP,EAA0C,KAAO,CACjF,SAAU,GACV,UAAW,KACX,WAAY,OACZ,KAAMK,CACP,EAAC,EACFG,EACE,IACEH,EAAY,OAAO,CAACX,EAAUd,IAC5B2B,EAAaf,GAAWG,EAAUH,EAASa,EAAaX,EAAUd,CAAO,CAAC,CAAC,EAE/E,CAACyB,CAAW,CAAC,EAEfI,EAAgB,IAAMJ,EAAY,YAAW,EAAI,CAACA,CAAW,CAAC,EAC9D,KAAM,CAAE,SAAAX,EAAU,UAAAE,EAAW,WAAAC,CAAU,EAAKS,EAAU,OAASD,EAAcC,EAAYhB,EACnFoB,EAAML,EAAY,IAClBM,EAAUN,EAAY,QAE5B,OAAOO,EACL,KAAO,CAAE,QAAAD,EAAS,SAAAjB,EAAU,UAAAE,EAAW,WAAAC,EAAY,IAAAa,CAAG,GACtD,CAACC,EAASjB,EAAUE,EAAWC,EAAYa,CAAG,CAAC,CAEnD,CC1GA,MAAMG,GAAc,IAAA,GACdC,EAAgB,IAAoBD,GACpCE,GAAe,IAAc,ECgB7BC,GAAa,OAAO,OAAO,EAAE,EASnC,SAASC,GAAYjC,EAAuB,CAC1C,MAAMS,EAAO,IAAIZ,EAAsCG,CAAO,EAE9D,MAAO,CACL,OAAQ,OACR,KAAM,CAAE,QAASS,EAAK,QAAS,SAAU,GAAO,UAAW,KAAM,WAAY,OAAW,IAAKA,EAAK,GAAG,EACrG,QAAAT,EACA,KAAAS,EAEJ,CAEA,SAASyB,GAAcC,EAAuCC,EAAiC,CAC7F,OACED,EAAS,WAAaC,EAAK,UAC3B,OAAO,GAAGD,EAAS,WAAYC,EAAK,UAAU,GAC9C,OAAO,GAAGD,EAAS,UAAWC,EAAK,SAAS,CAEhD,CAEA,SAASC,GAAcF,EAAuCvC,EAA4C,CACxG,OAAIA,GAAA,YAAAA,EAAS,UAAW,KAAa,CAAE,UAAW,KAAM,WAAYA,EAAQ,KAAK,GAE7EA,GAAA,YAAAA,EAAS,UAAW,SAAiB,CAAE,UAAWA,EAAQ,MAAO,WAAYuC,EAAS,UAAU,EAE7FA,CACT,CAEA,SAASG,GAAYC,EAAqB7B,EAAmBd,EAAiC,CAC5F,MAAMuC,EAAWI,EAAM,KAEvB,GAAI3C,IAAY,QAAauC,EAAS,WAAazB,EAAU,MAAO,GAEpE,KAAM,CAAE,UAAAE,EAAW,WAAAC,CAAU,EAAKwB,GAAcF,EAAUvC,CAAO,EAC3DwC,EAAO,CAAE,QAASG,EAAM,KAAK,QAAS,SAAA7B,EAAU,UAAAE,EAAW,WAAAC,EAAY,IAAKsB,EAAS,GAAG,EAE9F,OAAID,GAAcC,EAAUC,CAAI,EAAU,IAE1CG,EAAM,KAAOH,EAEN,GACT,CAEA,SAASI,GAAYL,EAAoCnC,EAAuB,CAC9E,OAAOmC,GAAA,YAAAA,EAAU,WAAYnC,EAAUmC,EAAWF,GAAYjC,CAAO,CACvE,CAGA,MAAMyC,EAAY,CACR,QAAU,IAAI,IACL,UAAY,IAAI,IACzB,SAAW,EAEnB,OAAK,OACH,UAAWF,KAAS,KAAK,QAAQ,OAAM,GACrCvD,EAAAuD,EAAM,SAAN,MAAAvD,EAAA,KAAAuD,GACAA,EAAM,OAAS,OAGjB,KAAK,QAAQ,MAAK,CACpB,CAEA,OAAOG,EAAwC,OAC7C,SAAW,CAACC,EAAKJ,CAAK,IAAK,KAAK,QAC1BG,EAAS,IAAIC,CAAG,IAAMJ,KAE1BvD,EAAAuD,EAAM,SAAN,MAAAvD,EAAA,KAAAuD,GACAA,EAAM,OAAS,QAGjB,KAAK,QAAUG,EAEf,UAAWH,KAASG,EAAS,SAC3BH,EAAM,SAANA,EAAM,OAAWA,EAAM,KAAK,OAAO,CAAC7B,EAAUd,IAAW,CACvD,GAAK0C,GAAYC,EAAO7B,EAAUd,CAAO,EAEzC,MAAK,UAAY,EAEjB,UAAWgD,KAAY,KAAK,UAAWA,EAAQ,EACjD,CAAC,EAEL,CAEA,YAAc,IAAc,KAAK,SAEjC,QAAQC,EAA4BC,EAAY,CAC9C,MAAMJ,EAAW,IAAI,IAErB,UAAWC,KAAO,QAAQ,QAAQE,CAAQ,EAAG,CAC3C,GAAI,CAAC,OAAO,UAAU,qBAAqB,KAAKA,EAAUF,CAAG,EAAG,SAEhE,MAAMzB,EAAW2B,EAAiEF,CAAG,EAC/ExB,EAASD,IAAY,OAAY,OAAY4B,EAAM,eAAe,IAAI5B,CAAO,EAEnF,GAAIC,IAAW,OACb,MAAM,IAAIrB,EAAkB,UAAW,cAAc,OAAO6C,CAAG,CAAC,qCAAqC,EAGvGD,EAAS,IAAIC,EAAKH,GAAY,KAAK,QAAQ,IAAIG,CAAG,EAAGxB,EAAO,OAAO,CAAC,CACtE,CAEA,OAAOuB,CACT,CAEA,UAAaE,IACX,KAAK,UAAU,IAAIA,CAAQ,EAEpB,IAAK,CACV,KAAK,UAAU,OAAOA,CAAQ,CAChC,EAEH,CAED,SAASG,GAAgBF,EAA0B,CACjD,MAAMG,EAAWC,EAAiC,MAAS,EAG3D,OAFkB,QAAQ,QAAQJ,CAAQ,EAAE,KAAKF,GAAO,OAAO,UAAU,qBAAqB,KAAKE,EAAUF,CAAG,CAAC,EAE7FK,EAAS,UAATA,EAAS,QAAY,IAAIP,IAAkB,MACjE,CAEA,SAASS,EACPL,EAAkB,CAElB,MAAMC,EAAQ1B,EAAQ,EAChB+B,EAAQJ,GAAgBF,CAAQ,EAChCO,GAAcD,GAAA,YAAAA,EAAO,cAAepB,GAC1CsB,GAAqBF,GAAA,YAAAA,EAAO,YAAarB,EAAesB,EAAaA,CAAW,EAChF,MAAMV,EAAWS,GAAA,YAAAA,EAAO,QAAQN,EAAUC,GAC1C,OAAAtB,EAAmB,IAAM,IAAM2B,GAAA,YAAAA,EAAO,QAAS,CAACA,CAAK,CAAC,EACtD3B,EAAmB,IAAK,CAClBkB,IAAa,SAAWS,GAAA,MAAAA,EAAO,OAAOT,GAC5C,CAAC,EACDjB,EAAgB,IAAK,CACnB,GAAIiB,IAAa,OAEjB,UAAWH,KAASG,EAAS,OAAM,EAAIH,EAAM,KAAK,YAAW,CAC/D,CAAC,EAGCG,IAAa,OAAYV,GAAa,OAAO,YAAY,CAAC,GAAGU,CAAQ,EAAE,IAAI,CAAC,CAACC,EAAKJ,CAAK,IAAM,CAACI,EAAKJ,EAAM,IAAI,CAAC,CAAC,CAEnH,CCvIA,SAASe,GAAkBC,EAAyBC,EAAwC,CAC1F,GAAI,CAACC,EAAWF,CAAM,EACpB,MAAM,IAAIzD,EAAkB,kBAAmB,2CAA2C,EAG5F,GAAI0D,EAAQ,IAAID,CAAM,EAAG,OAAOC,EAAQ,IAAID,CAAM,EAElD,MAAMlD,EAAQkD,EAAO,YAAW,EAChC,OAAAC,EAAQ,IAAID,EAAQlD,CAAK,EAElBA,CACT,CAEA,SAASqD,GACPC,EACAH,EAAwC,CAOxC,MAAMd,EAAWiB,EAAO,CAAE,KALb,CAAkBJ,EAAyBK,IAAwC,CAC9F,MAAMvD,EAAQiD,GAAWC,EAAQC,CAAO,EAExC,OAAOI,IAAY,OAAYvD,EAAQuD,EAAQvD,CAAK,CACtD,EACgC,EAEhC,GAAI,OAAOqC,GAAa,UAAYA,IAAa,MAAQ,MAAM,QAAQA,CAAQ,EAC7E,MAAM,IAAI5C,EAAkB,kBAAmB,oCAAoC,EAGrF,OAAO,OAAO,YACZ,QAAQ,QAAQ4C,CAAQ,EACrB,OAAOC,GAAO,OAAO,UAAU,qBAAqB,KAAKD,EAAUC,CAAG,CAAC,EACvE,IAAIA,GAAO,CAACA,EAAMD,EAA6BC,CAAG,CAAC,CAAC,CAAC,CAE5D,CAEA,SAASkB,GAAe1B,EAA6B2B,EAAyBN,EAAuC,CACnH,MAAMO,EAAO,QAAQ,QAAQD,CAAM,EAC7BE,EAAe,QAAQ,QAAQ7B,EAAS,MAAM,EAEpD,GAAI4B,EAAK,SAAWC,EAAa,QAAUR,EAAQ,OAASrB,EAAS,QAAQ,KAAM,MAAO,GAE1F,UAAWoB,KAAUC,EAAS,GAAI,CAACrB,EAAS,QAAQ,IAAIoB,CAAM,EAAG,MAAO,GAExE,OAAOQ,EAAK,MAAM,CAACpB,EAAKsB,IAAUD,EAAaC,CAAK,IAAMtB,GAAO,OAAO,GAAGR,EAAS,OAAOQ,CAAG,EAAGmB,EAAOnB,CAAG,CAAC,CAAC,CAC/G,CAKA,MAAMuB,EAA4C,CAAE,SAAU,GAAI,QAAS,IAAI,IAAO,OAAQ,EAAE,EAEhG,SAASC,GAAkBL,EAAuB,CAChD,OAAO,OAAO,YACZ,QAAQ,QAAQA,CAAM,EACnB,OAAOnB,GAAOyB,EAAaN,EAAOnB,CAAG,CAAC,CAAC,EACvC,IAAIA,GAAO,CAACA,EAAKmB,EAAOnB,CAAG,CAAC,CAAC,CAAC,CAErC,CAQA,SAAS0B,GAA+BC,EAAcC,EAAoC,CACxF,IAAIC,EACAC,EACAC,EAA8BR,EAElC,MAAO,IAAK,CACV,MAAMP,EAASY,EAAU,QAEzB,GAAIZ,IAAW,OAAW,OAAOO,EAEjC,GACEM,IAAa,QACbC,IAAmBd,GACnB,CAAC,GAAGa,CAAQ,EAAE,MAAM,CAAC,CAACjB,EAAQlD,CAAK,IAAM,OAAO,GAAGkD,EAAO,YAAW,EAAIlD,CAAK,CAAC,EAE/E,OAAOqE,EAGT,MAAMC,EAAe,IAAI,IACnBb,EAASJ,GAAiBkB,GAAWjB,EAAOW,EAAOM,CAAO,EAAGD,CAAY,EACzEnB,EAAU,IAAI,IAAImB,EAAa,KAAI,CAAE,EAE3C,OAAIH,IAAa,QAAa,CAACX,GAAea,EAAUZ,EAAQN,CAAO,KACrEkB,EAAW,CAAE,SAAUP,GAAkBL,CAAM,EAAG,QAAAN,EAAS,OAAAM,CAAM,GAGnEU,EAAWG,EACXF,EAAiBd,EAEVe,CACT,CACF,CC1HA,SAASG,EAAqBC,EAAuCC,EAAsB,GAAE,CAC3F,UAAWC,KAAgBF,EAAe,CACxC,MAAMG,EAAUD,EAAa,QAC7BA,EAAa,QAAU,OAEvB,GAAI,CACFC,GAAA,MAAAA,GACF,OAASpG,EAAO,CACdkG,EAAS,KAAKlG,CAAK,CACrB,CACF,CAEA,GAAIkG,EAAS,SAAW,EAAG,MAAMA,EAAS,CAAC,EAE3C,GAAIA,EAAS,OAAS,EAAG,MAAMG,EAAqBH,EAAU,4CAA4C,CAC5G,CAGA,MAAMI,EAAmB,CACf,QAAU,GACD,UAAY,IAAI,IACzB,QAA0C,IAAI,IACrC,cAAgB,IAAI,IAErC,OAAK,CACH,KAAK,QAAU,GACf,KAAK,QAAU,IAAI,IACnB,KAAK,WAAU,CACjB,CAEA,OAAO3B,EAAuC,CAC5C,KAAK,QAAUA,EACf,KAAK,QAAU,GAEf,GAAI,CACF,KAAK,YAAW,CAClB,OAAS3E,EAAO,CACd,KAAK,WAAW,CAACA,CAAK,CAAC,CACzB,CACF,CAEA,UAAaoB,GAAoC,CAC/C,MAAM2C,EAAW,CAAE,OAAA3C,CAAM,EACzB,KAAK,UAAU,IAAI2C,CAAQ,EAE3B,GAAI,CACF,KAAK,YAAW,CAClB,OAAS/D,EAAO,CACd,KAAK,UAAU,OAAO+D,CAAQ,EAC9B,KAAK,WAAW,CAAC/D,CAAK,CAAC,CACzB,CAEA,MAAO,IAAK,CACV,KAAK,UAAU,OAAO+D,CAAQ,EAE1B,KAAK,UAAU,OAAS,GAAG,KAAK,WAAU,CAChD,CACF,EAEQ,IAAIW,EAAyB,CACnC,MAAMyB,EAA6B,CAAE,QAAS,MAAS,EAEvD,KAAK,cAAc,IAAIzB,EAAQyB,CAAY,EAC3C,MAAMC,EAAU1B,EAAO,UAAU,KAAK,MAAM,EAE5C,GAAI,OAAO0B,GAAY,WAAY,MAAM,IAAI,UAAU,gDAAgD,EAEnG,KAAK,cAAc,IAAI1B,CAAM,IAAMyB,EAAcA,EAAa,QAAUC,EACvEA,EAAO,CACd,CAEQ,OAAS,IAAW,CAC1B,UAAWrC,IAAY,CAAC,GAAG,KAAK,SAAS,EACnC,KAAK,UAAU,IAAIA,CAAQ,GAAGA,EAAS,OAAM,CAErD,EAEQ,WAAWmC,EAAoB,CACrC,MAAMD,EAAgB,CAAC,GAAG,KAAK,cAAc,OAAM,CAAE,EACrD,KAAK,cAAc,MAAK,EACxBD,EAAqBC,EAAeC,CAAQ,CAC9C,CAEQ,kBAAgB,CACtB,MAAMK,EAA0B,CAAA,EAEhC,SAAW,CAAC7B,EAAQyB,CAAY,IAAK,KAAK,cACpC,KAAK,QAAQ,IAAIzB,CAAM,IAE3B,KAAK,cAAc,OAAOA,CAAM,EAChC6B,EAAQ,KAAKJ,CAAY,GAG3BH,EAAqBO,CAAO,CAC9B,CAEQ,aAAW,CACjB,KAAK,iBAAgB,EAErB,UAAW7B,KAAU,KAAK,QAAS,CACjC,GAAI,CAAC,KAAK,SAAW,KAAK,UAAU,OAAS,EAAG,OAE5C,KAAK,QAAQ,IAAIA,CAAM,GAAK,CAAC,KAAK,cAAc,IAAIA,CAAM,GAAG,KAAK,IAAIA,CAAM,CAClF,CACF,CACD,CC9FD,MAAM8B,GAAoB,IAAMnB,EAEhC,SAASoB,GAAkB5C,EAAiB,CAC1C,MAAM6C,EAAWtC,EAAwC,MAAS,EAElE,OAAOP,EAAY6C,EAAS,UAATA,EAAS,QAAY,IAAIJ,IAAyB,MACvE,CAQA,SAASK,GACPC,EACA9B,EAA2E,CAE3E,MAAMb,EAAQ1B,EAAQ,EAEtB,GAAI,CAAC0B,EAAM,OAAO,IAAI2C,CAAW,EAC/B,MAAM,IAAI3F,EAAkB,UAAW,SAAS2F,EAAY,EAAE,uCAAuC,EAGvG,MAAMnB,EAAQxB,EAAM,OAAO,IAAI2C,CAAW,EACpCC,EAAQJ,GAAkB3B,IAAW,MAAS,EAG9CY,EAAYtB,EAAwD,MAAS,EACnFsB,EAAU,QAAUZ,EACpB,MAAMjB,EAAWiB,IAAW,OACtBP,EAAcxB,EAClB,IAAOc,EAAW2B,GAAwBC,EAAOC,CAAS,EAAIc,GAC9D,CAACf,EAAO5B,CAAQ,CAAC,EAEbgC,EAAWrB,GAAqBqC,GAAA,YAAAA,EAAO,YAAa5D,EAAesB,EAAaA,CAAW,EAC3FP,EAAWK,EAAYwB,EAAS,QAAQ,EAI9C,OAHAjD,EAAgB,IAAM,IAAMiE,GAAA,YAAAA,EAAO,QAAS,CAACA,CAAK,CAAC,EACnDjE,EAAgB,IAAMiE,GAAA,YAAAA,EAAO,OAAOhB,EAAS,SAAU,CAACA,EAAUgB,CAAK,CAAC,EAEpE/B,IAAW,OAAkBW,EAE1B,OAAO,YACZ,QAAQ,QAAQI,EAAS,MAAM,EAAE,IAAI/B,GAAO,CAC1CA,EACA,OAAO,UAAU,eAAe,KAAKE,EAAUF,CAAG,EAAIE,EAASF,CAAG,EAAI+B,EAAS,OAAO/B,CAAG,CAC1F,CAAA,CAAC,CAEN,CC5CA,SAASgD,GACPC,EAAc,CAEd,MAAMC,EAAW,OAAO,OAAO,CAAC,GAAGD,CAAM,CAAC,EAE1C,OAAeE,GAAgF,CAG7F,GAFkBA,EAA0D,WAE3D,OACf,MAAM,IAAIhG,EAAkB,YAAa,oDAAoD,EAG/F,cAAO,eAAegG,EAAW,WAAY,CAAE,WAAY,GAAO,MAAOD,EAAU,EAE5EC,CACT,CACF,CChCA,SAASC,GAAkDC,EAA6B,CACtF,MAAMC,EAAYC,EAAatD,GAAyBoD,EAAS,UAAUpD,CAAQ,EAAG,CAACoD,CAAQ,CAAC,EAG1F5C,EAAc8C,EAAY,IAAMF,EAAS,YAAW,EAAI,CAACA,CAAQ,CAAC,EAClEtB,EAAWrB,EAAqB4C,EAAW7C,EAAaA,CAAW,EAEzE,OAAA+C,EAAU,IAAMH,EAAS,OAAM,EAAI,CAACA,CAAQ,CAAC,EAEtCtB,CACT,CC+BA,MAAM0B,EAA2C,OAAO,OAAO,EAAE,EAGjE,SAASC,EAAqDC,EAAU,CACtE,OAAOC,EAAkDD,EAAI,MAAM,CACrE,CAEA,SAASE,GACPF,EAAU,CAEV,MAAMT,EAAWY,EAAcH,CAAE,EAC3BI,EAAU,IAAI,IAEdC,EAAcC,GAAmC,CACrD,MAAMC,EAAWH,EAAQ,IAAIE,CAAK,EAElC,GAAIC,IAAa,OAAW,OAAOA,EAEnC,MAAMC,EAAST,EAAkB,GAAGR,CAAQ,IAAIe,CAAK,EAAE,EACvD,OAAAF,EAAQ,IAAIE,EAAOE,CAAM,EAElBA,CACT,EAEA,cAAO,eAAeH,EAAY,KAAM,CAAE,WAAY,GAAM,MAAOd,EAAU,EAEtE,OAAO,OAAOc,CAAU,CACjC,CAMA,SAASI,GAAmCnI,EAA+B,CACzE,MAAMoI,EAAUC,EAAYrI,EAAQ,OAAO,OAAO,EAC5CsI,EAAoB,UAAWtI,EAAWA,EAAQ,OAASwH,EAAuBA,EAExF,OAAIY,EAAQ,SAAW,EAAU,KAE1BA,EAAQ,IAAIzE,GACjB4E,EACEC,EACA,CAAE,IAAK7E,EAAM,IACb4E,EAAcE,EAAmB,CAC/B,UAAWC,EAAsB/E,CAAK,EACtC,aAAcA,EAAM,MACpB,eAAgBA,EAAM,GACtB,UAAA2E,EACA,SAAUtI,EAAQ,OAAO,GAC1B,CAAC,CACH,CAEL"}
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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"}
@@ -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
- /** A command a fixture publishes: the test writes the body, the harness gives it the identity a model requires. */
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
- export { openModel, runCall, settled, testReadable, waitFor, WaitTimeoutError } from '@opetope/runtime/testing';
2
- export type { CallInputArgs, ModelInstance, OpenModelOptions, RunCallOptions, SettleTarget, WaitOptions, } from '@opetope/runtime/testing';
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 Se,runCall as ye,settled as ve,testReadable as $e,waitFor as ke}from"@opetope/runtime/testing";import{defineCallTarget as _,hasOwn as P,createAggregateError as N}from"@opetope/core/internal";import{openApplication as q}from"@opetope/runtime";import{createInspectionSession as B}from"@opetope/runtime/internal";import{createElement as H}from"react";import{declarationId as W}from"@opetope/core";import{d as D,a as E,c as L,M as U}from"./contribution-frame-DXl7K0-B.js";function G(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 J(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 K(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(J),...e.resources.map(n=>`Resource ${n.id}: ${n.state}; retainers ${String(n.retainers)}.`)]}function Q(e){const t=[...e.runtime.conditions.flatMap(n=>n.state.kind==="true"?[]:[`Condition ${n.label}: ${n.state.kind}.`]),...e.runtime.instances.flatMap(G),...K(e.activity)];return t.length===0?"No registered runtime blocker observed; external UI and host work are unknown.":t.join(`
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
- ${Q(r)}`),this.label=t,this.timeoutMs=n,this.snapshot=r,this.history=o,this.name="ScenarioTimeoutError"}}function V(e,t){return!t||e===void 0?!1:e.closed&&!e.truncated}function X(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 Y(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 Z(e,t){const n=e.activity,r=V(n,t);return Object.freeze({...Y(n,r),closed:t,reasons:X(n,r,t),scope:"registered-runtime",status:r?"complete":"unknown"})}class ee{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 te=W("testing.contribution");let O=0;function ne(e){return O+=1,D(_({id:`testing.command.${String(O)}`,run:e}))}function I(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=E(e.entries),d=E(w);return(c===void 0?i.map(p=>[p.id,p.value,L(p)??l]):[[te,c,l]]).map(([p,f,k])=>H(U,{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 re(e,t={}){const n=I(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 oe{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 g=(f,k=f!==void 0)=>{s||(s=!0,l(),k?c(f):w())},i=()=>{if(!s)try{t(this.source.getSnapshot())&&g()}catch(f){g(f,!0)}},d=f=>g(f),v=setTimeout(()=>g(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 R(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 se(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 ie(e){return{...e.cleanupFailure===void 0?{}:{cleanupFailure:e.cleanupFailure},...P(e,"conditions")?{conditions:e.conditions}:{},...P(e,"imports")?{imports:e.imports}:{},reporter:e.reporter}}function ae(e,t){const n=C(t.timeoutMs??1e3,"Scenario timeoutMs"),r=R(t.historyCapacity??64,"Scenario historyCapacity"),o=R(t.activityCapacity??256,"Scenario activityCapacity");let w=t.host;se(w);let c=q(e,ie(t)),s;try{s=B(c,{activityCapacity:o,ringCapacity:r})}catch(u){throw c.ready.catch(()=>{}),c.close().catch(()=>{}),u}let l="open",g=!1,i=!1;const d=[],v=new Set,p=[],f=()=>s.getSnapshot(),k=()=>Object.freeze([...d]),$=new oe({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()}},A=s.subscribe(j);j();const F=$.deadline(c.ready,"application ready");F.catch(()=>{});let M,S;const z=()=>{if(M!==void 0)return M;let u,m;M=new Promise((a,h)=>{u=a,m=h}),l="closing",$.cancel();const y=[];try{y.push(c.close())}catch(a){y.push(Promise.reject(a))}return y.push(...[...v].map(a=>a.close())),Promise.allSettled(y).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{A(),s.close(),v.clear(),c=void 0,w=void 0}g=h.length===0,h.length===0?u():m(N(h,"Scenario cleanup failed."))}),M};return Object.freeze({close:()=>{if(S!==void 0)return S;const u=z();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 y=I(u,m),a=new ee((b,x)=>{v.delete(a),y.close(),b&&p.push(x)});v.add(a);let h;try{if(h=w.mount(y.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.");y.updateProps(b),$.notify()}})},notify:()=>{l==="open"&&$.notify()},ownership:()=>Z(f(),g),ready:F,waitFor:(u,m={})=>l!=="open"?Promise.reject(new Error("Scenario is closing.")):$.waitFor(u,m)})}export{T as ScenarioTimeoutError,ge as WaitTimeoutError,ne as command,ae as createScenario,Se as openModel,re as renderSlot,ye as runCall,ve as settled,$e as testReadable,ke as waitFor};
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
@@ -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":"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"}
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.0",
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.0",
54
- "@opetope/runtime": "0.9.0",
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.0",
62
- "@opetope/runtime": "0.9.0",
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,