@opetope/react 0.9.4 → 0.9.6
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 +76 -0
- package/README.md +19 -9
- package/README.ru.md +19 -9
- package/dist/command-hook.d.ts +8 -1
- package/dist/commands-hook.d.ts +14 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model-hook.d.ts +8 -2
- package/dist/model-selection-snapshot.d.ts +10 -1
- package/dist/model-selection-store.d.ts +15 -1
- package/dist/resource-command.d.ts +50 -0
- package/dist/resource-hook.d.ts +7 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# @opetope/react
|
|
2
2
|
|
|
3
|
+
## 0.9.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [fd42586]
|
|
8
|
+
- @opetope/core@0.9.6
|
|
9
|
+
- @opetope/runtime@0.9.6
|
|
10
|
+
|
|
11
|
+
## 0.9.5
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- bab00b8: Five words an application had been writing by hand, and the two lifetime laws under them.
|
|
16
|
+
|
|
17
|
+
**`constant(value)` (D316).** A value known when the model is built and never written again is a fact, not a cell.
|
|
18
|
+
It used to be written `ctx.state(<literal>)` — a writer nobody uses and a close nobody needs — six times in one
|
|
19
|
+
consuming application. `constant` is a `Readable` whose `subscribe` registers nothing and never notifies, so a
|
|
20
|
+
`derive` over it computes once and an `effect` over it runs once. It is a free function and not a member of
|
|
21
|
+
`ModelContext`, because every member of that interface creates an owned node, and the vocabulary pays for a word
|
|
22
|
+
once: values move 39 → 40 at the target of 40.
|
|
23
|
+
|
|
24
|
+
**`ModelStateWriter` (D317).** A helper that only writes state had been declaring the structural type of the writer
|
|
25
|
+
itself, and two models of one application disagreed about its overloads. The narrow writer is exported, not the
|
|
26
|
+
execution context: a helper that needs `capture` is already a command.
|
|
27
|
+
|
|
28
|
+
**Stream inference (D318).** `accumulate.reduce` is an inference site now: `State` comes from the return of
|
|
29
|
+
`initial` and from a named reducer, `Item` from that reducer's second parameter. Four explicit type arguments and
|
|
30
|
+
an annotation on `initial` leave the call. The limit is written down and has a negative typetest: inference reads
|
|
31
|
+
the reducer only when it is not context-sensitive, so an inline reducer that annotates only its item collapses
|
|
32
|
+
`Item` to `unknown`.
|
|
33
|
+
|
|
34
|
+
**`execution.capture(state)` (D319).** The confirmation of a mutation the host already accepted has to be written
|
|
35
|
+
down after the caller is gone, and an ordinary `update` after cancellation is dropped — which is why an application
|
|
36
|
+
kept a dummy resource with a constant key and an empty loader just to reach `capture`. The same word now takes a
|
|
37
|
+
cell: `applied` while the model is alive, `cancelled` after its fence, no `superseded` at all, and the owner is
|
|
38
|
+
re-checked after the reducer, so a reducer that retires the model answers `cancelled` instead of leaking a
|
|
39
|
+
`ReadableError`. The right belongs to a command, an effect run and the `run` of an event; the other contexts that
|
|
40
|
+
carry a writer refuse it. The context of an event's `subscribe` is now typed as the writer and a signal, because the
|
|
41
|
+
runtime has never put a `capture` on that object.
|
|
42
|
+
|
|
43
|
+
**`execution.capture(stream)` (D320).** `ctx.stream` answers `OwnedResource`, and a REST answer is laid over what the
|
|
44
|
+
connection accumulates instead of over a hand-written overlay of a cell, a purity check, an effect and a `derive`.
|
|
45
|
+
`refresh()` does not revoke the right — it is the same opening, and the commit rebases the accumulator of whatever
|
|
46
|
+
connection is serving it — while a reconnect does, because it replaces that connection and accumulation there begins
|
|
47
|
+
from `initial`. The commit publishes `refreshing` and supersedes a pending `refresh()`, and it is ordered against
|
|
48
|
+
consumption: the next delta folds from the confirmation while a queued snapshot frame still replaces the table. A
|
|
49
|
+
`consume` stream accumulates nothing, so `capture` on it is a `TypeError`.
|
|
50
|
+
|
|
51
|
+
Alpha, and this is why the wave is a minor: `useResource` and a selected `Resource` field change shape without an
|
|
52
|
+
alias, and the old paths are deleted.
|
|
53
|
+
|
|
54
|
+
The runtime `public feature consumer` ceiling moves 35 → 36 kB with that machinery, by the record in D320; every
|
|
55
|
+
other size budget is re-measured and unchanged.
|
|
56
|
+
|
|
57
|
+
**`ResourceHook` (D321).** `useResource(resource)` answers `{ snapshot, refresh, retry }`, and a field of a
|
|
58
|
+
`useModel` selection whose value is a request answers the same hook and takes the same lease. Nine models of one
|
|
59
|
+
application had wrapped `resource.refresh()` in a `Call<void, void>` for nothing but the `inFlight` of a button.
|
|
60
|
+
Recognition asks the same question the type asks: `Resource` is a structural contract (D199), so a host adapter that
|
|
61
|
+
satisfies it is classified and leased exactly like a materialization the runtime built, which stays the fast path.
|
|
62
|
+
`read(resource)` still answers the snapshot and leases nothing. One pair of controllers per resource identity keeps
|
|
63
|
+
`run` stable across renders, the value of a key that did not move is the same object again, and leases are diffed by
|
|
64
|
+
identity, reserved before the reentrant `retain()` and taken in commit rather than in a render.
|
|
65
|
+
|
|
66
|
+
**`invoke` at the `run` of an event (D322).** The relay of `event` → state → `effect` that an external callback had
|
|
67
|
+
to go through to execute a command is gone: `run` receives `invoke` and `rethrowIfCancelled`, the serial queue is the
|
|
68
|
+
handshake, and the executor belongs to the declaration under the signal of the model. Two defects go with it: the
|
|
69
|
+
cancellation of a nested call no longer reaches the reporter of the owner, and the executor is closed after the queue
|
|
70
|
+
has drained rather than before it. It is registered among the calls of the model, so a child nobody awaited is
|
|
71
|
+
visible to `settled`.
|
|
72
|
+
|
|
73
|
+
- Updated dependencies [bab00b8]
|
|
74
|
+
- Updated dependencies [bab00b8]
|
|
75
|
+
- Updated dependencies [fa553e6]
|
|
76
|
+
- @opetope/core@0.9.5
|
|
77
|
+
- @opetope/runtime@0.9.5
|
|
78
|
+
|
|
3
79
|
## 0.9.4
|
|
4
80
|
|
|
5
81
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -131,9 +131,11 @@ references. A newly allocated nested object is a changed field. The callback is
|
|
|
131
131
|
A read or selector error reaches the nearest React error boundary. Changing the selected sources or command keys
|
|
132
132
|
changes their subscriptions/consumers at commit; an abandoned render cannot replace committed authority.
|
|
133
133
|
|
|
134
|
-
The hook neither creates a model nor acquires a feature
|
|
135
|
-
|
|
136
|
-
`
|
|
134
|
+
The hook neither creates a model nor acquires a feature. It retains exactly the resources its selection names as
|
|
135
|
+
values — that field answers a `ResourceHook` and takes one lease for the mount (D321) — and nothing else: a
|
|
136
|
+
`read(resource)` leases nothing, and neither does the one-argument form. Combining hooks does not promise fewer
|
|
137
|
+
source subscriptions or faster renders; a raw `useModel` import now also includes the selection implementation.
|
|
138
|
+
There is no additional Call scheduler.
|
|
137
139
|
|
|
138
140
|
## Application host
|
|
139
141
|
|
|
@@ -208,11 +210,19 @@ frame of the instance that gave the contribution. The order and the stable key c
|
|
|
208
210
|
React introduces neither a second comparator nor a second lifetime registry. The packaged consumer of `ci:pack`
|
|
209
211
|
exercises that caching and routes one contribution through it (D301).
|
|
210
212
|
|
|
211
|
-
`useResource(resource)`
|
|
212
|
-
mounted consumer
|
|
213
|
-
|
|
214
|
-
`useModel` selection
|
|
215
|
-
|
|
213
|
+
`useResource(resource)` answers `{ snapshot, refresh, retry }`: the atomic snapshot through `useSyncExternalStore`,
|
|
214
|
+
one lease per mounted consumer, and the two verbs of the request as ordinary `CommandHook` consumers (D321). When the
|
|
215
|
+
reference changes, the snapshot follows the new resource and effect cleanup releases the previous lease. A field of a
|
|
216
|
+
`useModel` selection whose value is a resource answers the same hook and takes the same lease, so a model declares no
|
|
217
|
+
`Call<void, void>` around `resource.refresh()` to give a button its `inFlight`. `read(resource)` in the same selector
|
|
218
|
+
still answers the snapshot and leases nothing — the two are independent questions, and naming one request both ways
|
|
219
|
+
registers it twice. The compile-checked example of specification §2.13 shows both sides of that (D301, D321).
|
|
220
|
+
|
|
221
|
+
`refresh.run()` and `retry.run()` reach the resource, which is owned by the model that declared it: `inFlight` is true
|
|
222
|
+
until the outcome of the operation settles, `outcome` carries that `ResourceOperationOutcome` under `ok`, and
|
|
223
|
+
`run(undefined, { signal })` cancels the wait of this consumer and not the load. Two consumers of one resource keep
|
|
224
|
+
separate statuses, and `run` keeps its identity while the resource does. `invalidate` is not in the hook: it is a word
|
|
225
|
+
of cache policy, not of a button (D305).
|
|
216
226
|
|
|
217
227
|
Direct component props, a contribution's `props` adapter and its model's props `Readable` share the same
|
|
218
228
|
mount-owned snapshot. Incoming slot props publish in layout before paint, so direct and adapted renders cannot
|
|
@@ -353,7 +363,7 @@ Generic helpers may forward the target and its explicit input without losing the
|
|
|
353
363
|
|
|
354
364
|
| Entry | What it holds |
|
|
355
365
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
356
|
-
| `@opetope/react` | `useModel`, `useCommand`, `useCommands`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; types `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`
|
|
366
|
+
| `@opetope/react` | `useModel`, `useCommand`, `useCommands`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; types `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`, `ResourceHook` |
|
|
357
367
|
| `@opetope/react/integration` | `FeatureBoundary`, `ContributionBoundary`, `useFeature`, `useFeatureRetry`, `FeatureBoundaryError` for the integration layer; types `FeatureBoundaryProps`, `ContributionBoundaryProps`, `ContributionErrorContent`, `ContributionFailure`, `FeatureDemandSource`, `FeatureDemandState`, `FeatureDemandResult`, `FeatureDemandLease` |
|
|
358
368
|
| `@opetope/react/testing` | `renderSlot`, `command`, `createScenario`, `ScenarioTimeoutError` and fixture/scenario types, plus everything `@opetope/runtime/testing` and `@opetope/core/testing` publish; tests only |
|
|
359
369
|
|
package/README.ru.md
CHANGED
|
@@ -131,9 +131,11 @@ return (
|
|
|
131
131
|
без hooks, команд и side effects. Ошибка read/selector попадает в ближайший React error boundary.
|
|
132
132
|
Изменение источников и ключей команд применяется в commit; abandoned render не меняет действующие полномочия.
|
|
133
133
|
|
|
134
|
-
Hook не создаёт
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
Hook не создаёт модель и не приобретает feature. Он удерживает ровно те ресурсы, которые выборка назвала
|
|
135
|
+
значениями: такое поле отвечает `ResourceHook` и берёт одну аренду на время монтирования (D321). Больше ничего:
|
|
136
|
+
`read(resource)` не арендует, и однопараметрическая форма тоже. Объединение hooks не обещает меньше подписок или
|
|
137
|
+
более быстрый render; импорт обычного `useModel` теперь также включает реализацию selection. Дополнительного
|
|
138
|
+
scheduler для Call нет.
|
|
137
139
|
|
|
138
140
|
## Application host
|
|
139
141
|
|
|
@@ -207,11 +209,19 @@ function HomePageEnd() {
|
|
|
207
209
|
React не вводит второй компаратор и второй реестр времени жизни. Упакованный потребитель `ci:pack` проверяет это
|
|
208
210
|
кеширование и проводит через него один вклад (D301).
|
|
209
211
|
|
|
210
|
-
`useResource(resource)`
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
`useResource(resource)` отвечает `{ snapshot, refresh, retry }`: атомарным снимком через `useSyncExternalStore`,
|
|
213
|
+
одной арендой на смонтированного потребителя и двумя глаголами запроса как обычными потребителями `CommandHook`
|
|
214
|
+
(D321). При смене ссылки snapshot следует новому ресурсу, а cleanup эффекта освобождает предыдущий lease. Поле
|
|
215
|
+
выборки `useModel`, значением которого является ресурс, отвечает тем же хуком и берёт ту же аренду, поэтому модель
|
|
216
|
+
не объявляет `Call<void, void>` вокруг `resource.refresh()` ради `inFlight` в кнопке. `read(resource)` в том же
|
|
217
|
+
селекторе по-прежнему отдаёт снимок и не арендует ничего — это независимые вопросы, и названный обоими способами
|
|
218
|
+
запрос регистрируется дважды. Обе стороны показывает компилируемый пример спецификации §2.13 (D301, D321).
|
|
219
|
+
|
|
220
|
+
`refresh.run()` и `retry.run()` доходят до ресурса, которым владеет объявившая его модель: `inFlight` истинен, пока
|
|
221
|
+
не осел исход операции, `outcome` несёт этот `ResourceOperationOutcome` под `ok`, а `run(undefined, { signal })`
|
|
222
|
+
отменяет ожидание этого потребителя, а не загрузку. Два потребителя одного ресурса держат раздельные статусы, а
|
|
223
|
+
`run` сохраняет ссылку, пока та же идентичность ресурса. `invalidate` в хук не входит: это слово политики кэша, а не
|
|
224
|
+
кнопки (D305).
|
|
215
225
|
|
|
216
226
|
Прямые пропсы компонента, `props`-адаптер вклада и props-`Readable` его модели используют один снимок
|
|
217
227
|
монтирования. Входящие пропсы слота публикуются в layout до paint, поэтому прямой и адаптированный рендеры
|
|
@@ -353,7 +363,7 @@ UI-статус, обёртку outcome, политику выполнения,
|
|
|
353
363
|
|
|
354
364
|
| Вход | Что содержит |
|
|
355
365
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
356
|
-
| `@opetope/react` | `useModel`, `useCommand`, `useCommands`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; типы `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`
|
|
366
|
+
| `@opetope/react` | `useModel`, `useCommand`, `useCommands`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; типы `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`, `ResourceHook` |
|
|
357
367
|
| `@opetope/react/integration` | `FeatureBoundary`, `ContributionBoundary`, `useFeature`, `useFeatureRetry`, `FeatureBoundaryError` для integration-слоя; типы `FeatureBoundaryProps`, `ContributionBoundaryProps`, `ContributionErrorContent`, `ContributionFailure`, `FeatureDemandSource`, `FeatureDemandState`, `FeatureDemandResult`, `FeatureDemandLease` |
|
|
358
368
|
| `@opetope/react/testing` | `renderSlot`, `command`, `createScenario`, `ScenarioTimeoutError` и типы fixtures/scenarios плюс всё, что публикуют `@opetope/runtime/testing` и `@opetope/core/testing`; только для тестов |
|
|
359
369
|
|
package/dist/command-hook.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Call } from '@opetope/core';
|
|
2
2
|
import type { CommandOutcome } from './command.js';
|
|
3
|
+
import { CommandHookController } from './command-hook-controller.js';
|
|
3
4
|
import type { CommandRun } from './command-hook-controller.js';
|
|
4
5
|
/** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */
|
|
5
6
|
type SettledOutcome<Output> = Exclude<CommandOutcome<Output>, {
|
|
@@ -12,6 +13,12 @@ interface CommandHookStatus<Output> {
|
|
|
12
13
|
type CommandHook<Input, Output> = CommandHookStatus<Output> & {
|
|
13
14
|
readonly run: CommandRun<Input, Output>;
|
|
14
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* The status half of a consumer, apart from what it is a consumer of. D321 gives a resource the same two verbs a
|
|
18
|
+
* command has, so the same three React hooks answer for them: the state of this consumer, the attachment that moves
|
|
19
|
+
* it, and the synchronisation of a slot that was already busy when this mount arrived.
|
|
20
|
+
*/
|
|
21
|
+
declare function useCommandStatus<Input, Output>(currentSlot: CommandHookController<Input, Output>): CommandHook<Input, Output>;
|
|
15
22
|
declare function useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output>;
|
|
16
|
-
export { useCommand };
|
|
23
|
+
export { useCommand, useCommandStatus };
|
|
17
24
|
export type { CommandHook };
|
package/dist/commands-hook.d.ts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import type { Call } from '@opetope/core';
|
|
2
2
|
import type { CommandHook } from './command-hook.js';
|
|
3
|
+
import type { SelectedResource } from './model-selection-snapshot.js';
|
|
4
|
+
type CommandSelection = Readonly<Record<string, Call<never, unknown>>>;
|
|
5
|
+
type ResourceSelection = ReadonlyMap<PropertyKey, SelectedResource>;
|
|
3
6
|
type HookOf<Command> = Command extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : never;
|
|
4
7
|
type CommandsHook<Commands> = {
|
|
5
8
|
readonly [Key in keyof Commands]: HookOf<Commands[Key]>;
|
|
6
9
|
};
|
|
10
|
+
/** D321: what one selected resource answers — the two verbs and the snapshot this selection read from it. */
|
|
11
|
+
type SelectedResourceHook = Readonly<{
|
|
12
|
+
refresh: CommandHook<never, unknown>;
|
|
13
|
+
retry: CommandHook<never, unknown>;
|
|
14
|
+
snapshot: unknown;
|
|
15
|
+
}>;
|
|
16
|
+
type SelectionHooks = Readonly<Record<PropertyKey, CommandHook<never, unknown> | SelectedResourceHook>>;
|
|
17
|
+
/** The consumers of one selection: a command per key, and the two verbs of a resource per key (D321). */
|
|
18
|
+
declare function useSelectionHooks(commands: CommandSelection, resources: ResourceSelection): SelectionHooks;
|
|
7
19
|
declare function useCommands<Commands extends {
|
|
8
20
|
readonly [Key in keyof Commands]: Call<never, unknown>;
|
|
9
21
|
}>(commands: Commands): CommandsHook<Commands>;
|
|
10
|
-
export { useCommands };
|
|
22
|
+
export { useCommands, useSelectionHooks };
|
|
23
|
+
export type { SelectionHooks };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export { useModel } from './model-hook.js';
|
|
|
7
7
|
export { useReadable, useSelector } from './readable-hooks.js';
|
|
8
8
|
export { requiresModels } from './requires-models.js';
|
|
9
9
|
export { useResource } from './resource-hook.js';
|
|
10
|
+
export type { ResourceHook } from './resource-command.js';
|
|
10
11
|
export { defineSlot, defineSwitchSlot, Slot } from './slot.js';
|
|
11
12
|
export type { SlotContribution, SlotTarget, SwitchSlotTarget } from './slot.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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-qP1uPkaO.js";import{b as ke}from"./contribution-frame-qP1uPkaO.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};
|
|
1
|
+
import{useState as b,useInsertionEffect as y,useLayoutEffect as l,useMemo as p,useSyncExternalStore as w,useRef as g,useCallback as O,useEffect as B,createElement as M,Fragment as J}from"react";import{CallError as z,declarationId as N}from"@opetope/core";import{compatibleAbortReason as j,isCancellation as Q,reportDetachedError as S,isReadable as F,isCallTarget as V,createAggregateError as X,createContributionTarget as Y}from"@opetope/core/internal";import{C as a}from"./contribution-isolation-Bzfbt4iM.js";import{u as v,a as I,M as Z,c as q}from"./contribution-frame-qP1uPkaO.js";import{b as et}from"./contribution-frame-qP1uPkaO.js";import{isResource as ee}from"@opetope/runtime/internal";function P(t,e){var n;if(Q(e))return{reason:e,status:"cancelled"};try{const s=(n=t==null?void 0:t.onFailure)==null?void 0:n.call(t,e);s!==void 0&&Promise.resolve(s).catch(S)}catch(s){S(s)}return{error:e,status:"failed"}}function te(t){const e=t==null?void 0:t.onSuccess,n=t==null?void 0:t.signal;return e===void 0?n===void 0?void 0:{signal:n}:n===void 0?{onSuccess:e}:{onSuccess:e,signal:n}}function ne(t,e){let n;const s=new Promise(r=>{n=r});return{input:t,options:e===void 0?void 0:{...e},promise:s,resolve:n}}function se(t,e){const n=t.resolve;t.input=void 0,t.options=void 0,t.resolve=void 0,n==null||n(e)}class h{invoker;run=((e,n)=>{if(this.notify===void 0)return Promise.resolve({reason:new a("inactive","Command consumer is not mounted."),status:"cancelled"});const s=this.cancelledInput(n);if(s!==void 0)return Promise.resolve(s);const r=ne(e,n);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 z("cancelled","Command input was cancelled.",j(e)),status:"cancelled"}}cancelledInput(e){var n;return((n=e==null?void 0:e.signal)==null?void 0:n.aborted)===!0?this.aborted(e.signal):void 0}finish(e,n){var s;e.resolve!==void 0&&(this.inFlight-=1,se(e,n),(s=this.notify)==null||s.call(this,this.inFlight>0,n))}invoke(e){let n;try{n=this.invoker.run(e.input,te(e.options)),e.input=void 0}catch(s){this.finish(e,P(e.options,s));return}n.then(s=>this.finish(e,{status:"ok",value:s}),s=>this.finish(e,P(e.options,s)))}start(e){var n;this.inFlight+=1,(n=this.notify)==null||n.call(this,!0),this.invoke(e)}}const re={inFlight:!1,outcome:void 0};function ie(t){return t===void 0||t.status==="cancelled"?void 0:t}function oe(t,e,n,s){return s===void 0&&t.slot===e&&t.inFlight===n}function ce(t,e,n,s){const r=ie(s);if(oe(t,e,n,r))return t;const i=t.slot===e?t.outcome:void 0;return{inFlight:n,outcome:r??i,slot:e}}function ue(t){const[e]=b(()=>new WeakMap);let n=e.get(t);return n===void 0&&(n=new h(t),e.set(t,n)),n}function k(t){const[e,n]=b(()=>({inFlight:!1,outcome:void 0,slot:t}));y(()=>t.attach((o,c)=>n(u=>ce(u,t,o,c))),[t]),l(()=>t.synchronize(),[t]);const{inFlight:s,outcome:r}=e.slot===t?e:re,i=t.run;return p(()=>({inFlight:s,outcome:r,run:i}),[s,r,i])}function ae(t){const n=v().commandRecords.get(t);if(n===void 0)throw new a("missing",`Command ${t.id} is not bound in this contribution.`);return k(ue(n.invoker))}const fe=()=>{},A=()=>fe,de=()=>0;function le(t){if(ee(t))return!0;if(!F(t))return!1;const e=t;return typeof e.refresh=="function"&&typeof e.retry=="function"&&typeof e.retain=="function"&&typeof e.invalidate=="function"}function he(t,e){return new Promise((n,s)=>{const r=()=>{s(new z("cancelled","Resource operation wait was cancelled.",j(e)))};if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0}),t.then(i=>{e.removeEventListener("abort",r),n(i)},i=>{e.removeEventListener("abort",r),s(i)})})}function me(t,e){var s;const n=(s=e==null?void 0:e.onSuccess)==null?void 0:s.call(e,t);return n!==void 0&&Promise.resolve(n).catch(S),t}function x(t){return{run:(e,n)=>{let s;try{s=Promise.resolve(t())}catch(i){return Promise.reject(i)}const r=n==null?void 0:n.signal;return(r===void 0?s:he(s,r)).then(i=>me(i,n))}}}function K(t){return{refresh:x(()=>t.refresh()),retry:x(()=>t.retry())}}function be(t,e){const n=e.get(t);if(n!==void 0)return n;const s=K(t);return e.set(t,s),s}const ye=Object.freeze({}),pe=new Map;function f(t){return"kind"in t}function E(t){const e=new h(t);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:t,slot:e}}function we(t,e){return t.inFlight===e.inFlight&&Object.is(t.outcome,e.outcome)}function ge(t,e,n){const s=t.hook;if(n===void 0&&s.inFlight===e)return!1;const r=n===void 0||n.status==="cancelled"?void 0:n,i={inFlight:e,outcome:r??s.outcome,run:s.run};return we(s,i)?!1:(t.hook=i,!0)}function Se(t,e){return t!==void 0&&!f(t)&&t.invoker===e?t:E(e)}function ve(t,e){const n=E(t.refresh),s=E(t.retry);return{hook:{refresh:n.hook,retry:s.hook,snapshot:e},kind:"resource",refresh:n,retry:s}}function ke(t,e,n){if(t===void 0||!f(t)||t.refresh.invoker!==e.refresh)return ve(e,n);const{hook:s,refresh:r,retry:i}=t;return(s.refresh!==r.hook||s.retry!==i.hook||!Object.is(s.snapshot,n))&&(t.hook={refresh:r.hook,retry:i.hook,snapshot:n}),t}function Ee(t){return f(t)?[t.refresh,t.retry]:[t]}function $(t){var e;for(const n of Ee(t))(e=n.detach)==null||e.call(n),n.detach=void 0}function Re(t){return t.hook}class Ce{entries=new Map;invokers=new WeakMap;listeners=new Set;revision=0;close(){for(const e of this.entries.values())$(e);this.entries.clear()}commit(e){for(const[n,s]of this.entries)e.get(n)!==s&&$(s);this.entries=e;for(const n of e.values())this.attachEntry(n)}attachEntry(e){if(f(e)){this.attachCommand(e.refresh),this.attachCommand(e.retry);return}this.attachCommand(e)}attachCommand(e){e.detach??(e.detach=e.slot.attach((n,s)=>{if(ge(e,n,s)){this.revision+=1;for(const r of this.listeners)r()}}))}getSnapshot=()=>this.revision;prepare(e,n,s){const r=new Map;for(const i of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,i))continue;const o=e[i],c=o===void 0?void 0:s.commandRecords.get(o);if(c===void 0)throw new a("missing",`Command at ${String(i)} is not bound in this contribution.`);r.set(i,Se(this.entries.get(i),c.invoker))}for(const[i,o]of n){const c=be(o.resource,this.invokers);r.set(i,ke(this.entries.get(i),c,o.snapshot))}return r}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function Oe(t,e){const n=g(void 0);return e.size>0||Reflect.ownKeys(t).some(r=>Object.prototype.propertyIsEnumerable.call(t,r))?n.current??(n.current=new Ce):void 0}function L(t,e){const n=v(),s=Oe(t,e),r=(s==null?void 0:s.getSnapshot)??de;w((s==null?void 0:s.subscribe)??A,r,r);const i=s==null?void 0:s.prepare(t,e,n);return y(()=>()=>s==null?void 0:s.close(),[s]),y(()=>{i!==void 0&&(s==null||s.commit(i))}),l(()=>{if(i!==void 0)for(const o of i.values())f(o)?(o.refresh.slot.synchronize(),o.retry.slot.synchronize()):o.slot.synchronize()}),i===void 0?ye:Object.fromEntries([...i].map(([o,c])=>[o,Re(c)]))}function Me(t){return L(t,pe)}const H=new Map,T=new Set;function W(t,e){if(!F(t))throw new a("binding-invalid","Model selection read requires a Readable.");if(e.has(t))return e.get(t);const n=t.getSnapshot();return e.set(t,n),n}function ze(t,e,n,s){if(!le(e))return;const r=e;n.set(t,{resource:r,snapshot:W(r,s)})}function je(t){return t.size===0?H:t}function Fe(t,e){const s=t({read:(o,c)=>{const u=W(o,e);return c===void 0?u:c(u)}});if(typeof s!="object"||s===null||Array.isArray(s))throw new a("binding-invalid","Model selection requires a record.");const r=[],i=new Map;for(const o of Reflect.ownKeys(s)){if(!Object.prototype.propertyIsEnumerable.call(s,o))continue;const c=s[o];r.push([o,c]),ze(o,c,i,e)}return{resources:je(i),values:Object.fromEntries(r)}}function Ie(t,e){if(t.size!==e.size)return!1;for(const[n,s]of e){const r=t.get(n);if((r==null?void 0:r.resource)!==s.resource||!Object.is(r.snapshot,s.snapshot))return!1}return!0}function Pe(t,e,n,s){const r=Reflect.ownKeys(e),i=Reflect.ownKeys(t.values);if(r.length!==i.length||n.size!==t.sources.size)return!1;for(const o of n)if(!t.sources.has(o))return!1;return Ie(t.resources,s)?r.every((o,c)=>i[c]===o&&Object.is(t.values[o],e[o])):!1}const R={commands:{},leases:T,resources:H,sources:new Set,values:{}};function Ae(t){return Object.fromEntries(Reflect.ownKeys(t).filter(e=>V(t[e])).map(e=>[e,t[e]]))}function xe(t){if(t.size===0)return T;const e=new Set;for(const n of t.values())e.add(n.resource);return e}function Ke(t,e){let n,s,r=R;return()=>{const i=e.current;if(i===void 0)return R;if(n!==void 0&&s===i&&[...n].every(([m,_])=>Object.is(m.getSnapshot(),_)))return r;const o=new Map,{resources:c,values:u}=Fe(m=>i(t,m),o),d=new Set(o.keys());return(n===void 0||!Pe(r,u,d,c))&&(r={commands:Ae(u),leases:xe(c),resources:c,sources:d,values:u}),n=o,s=i,r}}const C=new Set;function G(t,e=[]){for(const n of t){const s=n.dispose;n.dispose=void 0;try{s==null||s()}catch(r){e.push(r)}}if(e.length===1)throw e[0];if(e.length>1)throw X(e,"Model selection sources failed to release.")}class $e{enabled=!1;leases=new Map;listeners=new Set;retained=C;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.retained=C,this.releaseAll()}commit(e,n=C){this.sources=e,this.retained=n,this.enabled=!0;try{this.synchronize()}catch(s){this.releaseAll([s])}}subscribe=e=>{const n={notify:e};this.listeners.add(n);try{this.synchronize()}catch(s){this.listeners.delete(n),this.releaseAll([s])}return()=>{this.listeners.delete(n),this.listeners.size===0&&this.releaseAll()}};add(e){const n={dispose:void 0};this.subscriptions.set(e,n);const s=e.subscribe(this.notify);if(typeof s!="function")throw new TypeError("Model selection source must return a disposer.");this.subscriptions.get(e)===n?n.dispose=s:s()}notify=()=>{for(const e of[...this.listeners])this.listeners.has(e)&&e.notify()};releaseAll(e){const n=[...this.subscriptions.values(),...this.leases.values()];this.subscriptions.clear(),this.leases.clear(),G(n,e)}removeUnselected(){const e=[];for(const[n,s]of this.subscriptions)this.sources.has(n)||(this.subscriptions.delete(n),e.push(s));for(const[n,s]of this.leases)this.retained.has(n)||(this.leases.delete(n),e.push(s));G(e)}retain(e){const n={dispose:void 0};this.leases.set(e,n);const s=e.retain();if(typeof s!="function")throw new TypeError("Model selection resource must return a disposer.");this.leases.get(e)===n?n.dispose=s:s()}ready(){return this.enabled&&this.listeners.size>0}retainSelected(){for(const e of this.retained){if(!this.ready())return;this.retained.has(e)&&!this.leases.has(e)&&this.retain(e)}}subscribeSources(){for(const e of this.sources){if(!this.ready())return;this.sources.has(e)&&!this.subscriptions.has(e)&&this.add(e)}}synchronize(){this.removeUnselected(),this.subscribeSources(),this.retainSelected()}}const Le=()=>R;function He(t){const e=g(void 0);return t?e.current??(e.current=new $e):void 0}function Te(t,e){return Object.fromEntries(Reflect.ownKeys(t.values).map(n=>[n,Object.prototype.hasOwnProperty.call(e,n)?e[n]:t.values[n]]))}function We(t,e){const n=v();if(!n.models.has(t))throw new a("missing",`Model ${t.id} is not granted to this contribution.`);const s=n.models.get(t),r=He(e!==void 0),i=g(void 0);i.current=e;const o=e!==void 0,c=p(()=>o?Ke(s,i):Le,[s,o]),u=w((r==null?void 0:r.subscribe)??A,c,c),d=L(u.commands,u.resources);return l(()=>()=>r==null?void 0:r.close(),[r]),l(()=>r==null?void 0:r.commit(u.sources,u.leases),[u,r]),e===void 0?s:Te(u,d)}function Ge(t){const e=Object.freeze([...t]);return n=>{if(n.requires!==void 0)throw new a("duplicate","Component already declares the models it requires.");return Object.defineProperty(n,"requires",{enumerable:!1,value:e}),n}}function Ue(t){const[e]=b(()=>new WeakMap),n=e.get(t);if(n!==void 0)return n;const s=K(t),r={refresh:new h(s.refresh),retry:new h(s.retry)};return e.set(t,r),r}function De(t){const e=O(c=>t.subscribe(c),[t]),n=O(()=>t.getSnapshot(),[t]),s=w(e,n,n);B(()=>t.retain(),[t]);const r=Ue(t),i=k(r.refresh),o=k(r.retry);return p(()=>({refresh:i,retry:o,snapshot:s}),[i,o,s])}const U=Object.freeze({});function D(t){return Y(t,"slot")}function _e(t){const e=N(t),n=new Map,s=r=>{const i=n.get(r);if(i!==void 0)return i;const o=D(`${e}/${r}`);return n.set(r,o),o};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function Be(t){const e=I(t.target.entries),n="props"in t?t.props??U:U;return e.length===0?null:e.map(s=>M(J,{key:s.id},M(Z,{authority:q(s),contribution:s.value,contributionId:s.id,slotProps:n,targetId:t.target.id})))}export{a as ContributionError,Be as Slot,D as defineSlot,_e as defineSwitchSlot,Ge as requiresModels,ae as useCommand,Me as useCommands,We as useModel,I as useReadable,De as useResource,et 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 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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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/command-hook-controller.ts","../src/command-hook.ts","../src/idle-subscription.ts","../src/resource-command.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\n/**\n * The status half of a consumer, apart from what it is a consumer of. D321 gives a resource the same two verbs a\n * command has, so the same three React hooks answer for them: the state of this consumer, the attachment that moves\n * it, and the synchronisation of a slot that was already busy when this mount arrived.\n */\nfunction useCommandStatus<Input, Output>(\n currentSlot: CommandHookController<Input, Output>,\n): CommandHook<Input, Output> {\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\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 return useCommandStatus(useCommandSlot<Input, Output>(record.invoker));\n}\n\nexport { useCommand, useCommandStatus };\nexport type { CommandHook };\n","const idleRelease = (): void => undefined;\nconst subscribeIdle = (): (() => void) => idleRelease;\nconst idleRevision = (): number => 0;\n\nexport { idleRevision, subscribeIdle };\n","/**\n * D321: the two verbs of a resource, dressed as the commands a button already knows. A consumer keeps a status —\n * `inFlight` until the operation settles, the settled `outcome` — while the resource stays owned by the model that\n * declared it: cancelling this wait cancels the wait and not the load.\n */\n\nimport { CallError } from '@opetope/core';\nimport type { ResourceOperationOutcome } from '@opetope/core';\nimport { compatibleAbortReason, isReadable, reportDetachedError } from '@opetope/core/internal';\nimport type { CallRunOptions } from '@opetope/core/internal';\nimport type { Resource, ResourceSnapshot } from '@opetope/runtime';\nimport { isResource as isRuntimeMaterialization } from '@opetope/runtime/internal';\nimport type { ResourceRequestKey } from '@opetope/runtime/internal';\n\nimport type { CommandHook } from './command-hook';\nimport type { CommandInvoker } from './command-hook-controller';\n\ntype AnyResource = Resource<never, ResourceRequestKey>;\n\n/**\n * D199/D321: the type of a selected field promises `ResourceHook` for every value that satisfies `Resource`, and\n * `Resource` is a structural contract a host may implement with an ordinary object whose members are methods — the\n * fixture of `useResource` is exactly such an object. So the runtime answers the question the type asked, by the same\n * contract: the three verbs and the lease beside a `Readable`. Recognising less would type a host adapter as a hook\n * and then hand the raw object to the render, where `snapshot` is `undefined` and `refresh.run()` throws.\n *\n * The registry of materializations this runtime built stays the fast path: a model's own resource is the common case,\n * and it answers without reading four properties off a foreign object.\n */\nfunction isResource(value: unknown): boolean {\n if (isRuntimeMaterialization(value)) return true;\n // A `Readable` alone is not a request: the verbs are what tell one from a value that merely moves.\n if (!isReadable(value)) return false;\n\n const candidate = value as Partial<AnyResource>;\n\n return (\n typeof candidate.refresh === 'function' &&\n typeof candidate.retry === 'function' &&\n typeof candidate.retain === 'function' &&\n typeof candidate.invalidate === 'function'\n );\n}\n\n/**\n * What `useResource` answers, and what a field of a `useModel` selection whose value is a resource answers. The two\n * verbs never reject (`resource.ts` answers an outcome), so the ordinary path of `outcome` is\n * `{ status: 'ok', value }`; `failed` stays in the type because the controller turns a synchronous throw of an\n * invoker into it, and the wrapper below carries its own test that it never throws synchronously.\n */\ntype ResourceHook<Data, Key extends ResourceRequestKey = ResourceRequestKey> = Readonly<{\n refresh: CommandHook<void, ResourceOperationOutcome<Data, Key>>;\n retry: CommandHook<void, ResourceOperationOutcome<Data, Key>>;\n snapshot: ResourceSnapshot<Data, Key>;\n}>;\n\ntype ResourceInvokers = Readonly<{ refresh: CommandInvoker; retry: CommandInvoker }>;\n\n/** The consumer's own cancellation: it ends this wait, and the load the model owns keeps running. */\nfunction awaitCancellable(operation: Promise<unknown>, signal: AbortSignal): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n const cancel = (): void => {\n reject(new CallError('cancelled', 'Resource operation wait was cancelled.', compatibleAbortReason(signal)));\n };\n\n if (signal.aborted) {\n cancel();\n\n return;\n }\n\n signal.addEventListener('abort', cancel, { once: true });\n operation.then(\n value => {\n signal.removeEventListener('abort', cancel);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', cancel);\n reject(error);\n },\n );\n });\n}\n\nfunction notifySuccess(value: unknown, options: CallRunOptions<unknown> | undefined): unknown {\n const notification = options?.onSuccess?.(value);\n\n // The failure of a consumer's own callback is detached from the operation it watched, as it is for a command.\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n\n return value;\n}\n\n/**\n * The invariant of this wrapper: it never throws synchronously, so the `failed` outcome a consumer can see is the\n * one the verb itself produced and never one this layer invented in the middle of a render commit.\n */\nfunction operationInvoker(start: () => Promise<unknown>): CommandInvoker {\n return {\n run: (_input: never, options?: CallRunOptions<unknown>): Promise<unknown> => {\n let operation: Promise<unknown>;\n\n try {\n operation = Promise.resolve(start());\n } catch (error) {\n return Promise.reject(error);\n }\n\n const signal = options?.signal;\n\n return (signal === undefined ? operation : awaitCancellable(operation, signal)).then(value =>\n notifySuccess(value, options),\n );\n },\n };\n}\n\n/**\n * One pair per resource identity. `selectEntry` and `useCommandSlot` both pick their controller by the identity of\n * the invoker, so a pair rebuilt on every render would reset `inFlight`, drop `outcome` and change `run` — which is\n * why the pair is memoised against the resource itself and never assembled during a render.\n */\nfunction createResourceInvokers(resource: AnyResource): ResourceInvokers {\n // D199: a resource is a structural contract, so both verbs are called through it and never as detached functions.\n return { refresh: operationInvoker(() => resource.refresh()), retry: operationInvoker(() => resource.retry()) };\n}\n\nfunction resourceInvokers(resource: AnyResource, cache: WeakMap<object, ResourceInvokers>): ResourceInvokers {\n const existing = cache.get(resource);\n\n if (existing !== undefined) return existing;\n\n const invokers = createResourceInvokers(resource);\n cache.set(resource, invokers);\n\n return invokers;\n}\n\n/**\n * The one module of this package that talks to the runtime about a materialization, so the rest of React reads the\n * recognition and the request key from here instead of routing to `@opetope/runtime/internal` of its own.\n */\nexport { createResourceInvokers, isResource, resourceInvokers };\nexport type { AnyResource, ResourceHook, ResourceInvokers, ResourceRequestKey };\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 type { SelectedResource } from './model-selection-snapshot';\nimport { useFrame } from './mount-context';\nimport type { Frame } from './mount-frame';\nimport { resourceInvokers } from './resource-command';\nimport type { ResourceInvokers } from './resource-command';\n\ntype CommandSelection = Readonly<Record<string, Call<never, unknown>>>;\ntype ResourceSelection = ReadonlyMap<PropertyKey, SelectedResource>;\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};\n/** D321: what one selected resource answers — the two verbs and the snapshot this selection read from it. */\ntype SelectedResourceHook = Readonly<{\n refresh: CommandHook<never, unknown>;\n retry: CommandHook<never, unknown>;\n snapshot: unknown;\n}>;\ntype SelectionHooks = Readonly<Record<PropertyKey, CommandHook<never, unknown> | SelectedResourceHook>>;\n\nconst emptyHooks: SelectionHooks = Object.freeze({});\nconst noResources: ResourceSelection = new Map();\n\ntype CommandEntry = {\n detach: (() => void) | undefined;\n hook: CommandHook<never, unknown>;\n readonly invoker: CommandInvoker;\n readonly slot: CommandHookController<never, unknown>;\n};\n\n/** D321: one key of a selection, two consumers. The pair is the record; `entries` still holds one slot per key. */\ntype ResourceEntry = {\n /** D321: the value this key hands the render, kept on the record so a still frame is the same object again. */\n hook: SelectedResourceHook;\n readonly kind: 'resource';\n readonly refresh: CommandEntry;\n readonly retry: CommandEntry;\n};\n\ntype SelectionEntry = CommandEntry | ResourceEntry;\n\nfunction isResourceEntry(entry: SelectionEntry): entry is ResourceEntry {\n return 'kind' in entry;\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: SelectionEntry | undefined, invoker: CommandInvoker): CommandEntry {\n return previous !== undefined && !isResourceEntry(previous) && previous.invoker === invoker\n ? previous\n : createEntry(invoker);\n}\n\nfunction createResourceEntry(invokers: ResourceInvokers, snapshot: unknown): ResourceEntry {\n const refresh = createEntry(invokers.refresh);\n const retry = createEntry(invokers.retry);\n\n return {\n hook: { refresh: refresh.hook, retry: retry.hook, snapshot },\n kind: 'resource',\n refresh,\n retry,\n };\n}\n\n/**\n * S6/D321: the same resource that did not move answers the same object, so a child under `memo` is not re-rendered\n * by the mere act of reading it. The three parts are all render inputs, so this is a memo and not a piece of state.\n */\nfunction selectResourceEntry(\n previous: SelectionEntry | undefined,\n invokers: ResourceInvokers,\n snapshot: unknown,\n): ResourceEntry {\n if (previous === undefined || !isResourceEntry(previous) || previous.refresh.invoker !== invokers.refresh) {\n return createResourceEntry(invokers, snapshot);\n }\n\n const { hook, refresh, retry } = previous;\n\n if (hook.refresh !== refresh.hook || hook.retry !== retry.hook || !Object.is(hook.snapshot, snapshot)) {\n previous.hook = { refresh: refresh.hook, retry: retry.hook, snapshot };\n }\n\n return previous;\n}\n\nfunction commandEntries(entry: SelectionEntry): readonly CommandEntry[] {\n return isResourceEntry(entry) ? [entry.refresh, entry.retry] : [entry];\n}\n\nfunction detachEntry(entry: SelectionEntry): void {\n for (const command of commandEntries(entry)) {\n command.detach?.();\n command.detach = undefined;\n }\n}\n\nfunction hookOf(entry: SelectionEntry): CommandHook<never, unknown> | SelectedResourceHook {\n return entry.hook;\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, SelectionEntry>();\n /** One pair of invokers per resource identity, so `run` keeps its reference across renders (D321). */\n private readonly invokers = new WeakMap<object, ResourceInvokers>();\n private readonly listeners = new Set<() => void>();\n private revision = 0;\n\n close(): void {\n for (const entry of this.entries.values()) detachEntry(entry);\n\n this.entries.clear();\n }\n\n commit(selected: Map<PropertyKey, SelectionEntry>): void {\n for (const [key, entry] of this.entries) {\n if (selected.get(key) !== entry) detachEntry(entry);\n }\n\n this.entries = selected;\n\n for (const entry of selected.values()) this.attachEntry(entry);\n }\n\n /** S7: the attach runs on every commit, so the two verbs are named rather than gathered into an array. */\n private attachEntry(entry: SelectionEntry): void {\n if (isResourceEntry(entry)) {\n this.attachCommand(entry.refresh);\n this.attachCommand(entry.retry);\n\n return;\n }\n\n this.attachCommand(entry);\n }\n\n private attachCommand(command: CommandEntry): void {\n command.detach ??= command.slot.attach((inFlight, outcome) => {\n if (!updateEntry(command, inFlight, outcome)) return;\n\n this.revision += 1;\n\n for (const listener of this.listeners) listener();\n });\n }\n\n getSnapshot = (): number => this.revision;\n\n /**\n * D321: two outcomes no longer, but two kinds. A command that is not bound is still a `ContributionError`; a\n * resource never appears in `frame.commandRecords`, so it is prepared from its own identity instead.\n */\n prepare(commands: CommandSelection, resources: ResourceSelection, frame: Frame): Map<PropertyKey, SelectionEntry> {\n const selected = new Map<PropertyKey, SelectionEntry>();\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 for (const [key, resource] of resources) {\n const invokers = resourceInvokers(resource.resource, this.invokers);\n\n selected.set(key, selectResourceEntry(this.entries.get(key), invokers, resource.snapshot));\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, resources: ResourceSelection): CommandGroup | undefined {\n const groupRef = useRef<CommandGroup | undefined>(undefined);\n const populated =\n resources.size > 0 ||\n Reflect.ownKeys(commands).some(key => Object.prototype.propertyIsEnumerable.call(commands, key));\n\n return populated ? (groupRef.current ??= new CommandGroup()) : undefined;\n}\n\n/** The consumers of one selection: a command per key, and the two verbs of a resource per key (D321). */\nfunction useSelectionHooks(commands: CommandSelection, resources: ResourceSelection): SelectionHooks {\n const frame = useFrame();\n const group = useCommandGroup(commands, resources);\n const getSnapshot = group?.getSnapshot ?? idleRevision;\n useSyncExternalStore(group?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const selected = group?.prepare(commands, resources, 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 // S7: the same branch as the attach — this runs after every commit, and the arrays were pure allocation.\n for (const entry of selected.values()) {\n if (isResourceEntry(entry)) {\n entry.refresh.slot.synchronize();\n entry.retry.slot.synchronize();\n } else entry.slot.synchronize();\n }\n });\n\n return selected === undefined\n ? emptyHooks\n : Object.fromEntries([...selected].map(([key, entry]) => [key, hookOf(entry)]));\n}\n\nfunction useCommands<Commands extends { readonly [Key in keyof Commands]: Call<never, unknown> }>(\n commands: Commands,\n): CommandsHook<Commands> {\n return useSelectionHooks(commands, noResources) as CommandsHook<Commands>;\n}\n\nexport { useCommands, useSelectionHooks };\nexport type { SelectionHooks };\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';\nimport { isResource } from './resource-command';\nimport type { AnyResource } from './resource-command';\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>>;\n/** D321: the resource a field selected, and the snapshot this selection read from it. */\ntype SelectedResource = Readonly<{ resource: AnyResource; snapshot: unknown }>;\ntype SelectionSnapshot = Readonly<{\n commands: Readonly<Record<PropertyKey, AnyCommand>>;\n /** The set the mount leases; two keys naming one resource are one lease. */\n leases: ReadonlySet<AnyResource>;\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n sources: ReadonlySet<Readable<unknown>>;\n values: SelectionRecord;\n}>;\n\nconst noResources: ReadonlyMap<PropertyKey, SelectedResource> = new Map();\nconst noLeases: ReadonlySet<AnyResource> = new Set();\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\n/** D321: the predicate that answers the question the type of the field asked, and the read that leases nothing. */\nfunction classifyResource(\n key: PropertyKey,\n value: unknown,\n resources: Map<PropertyKey, SelectedResource>,\n sources: Map<Readable<unknown>, unknown>,\n): void {\n if (!isResource(value)) return;\n\n const resource = value as AnyResource;\n\n resources.set(key, { resource, snapshot: readSource(resource, sources) });\n}\n\nfunction selectedResources(resources: Map<PropertyKey, SelectedResource>): ReadonlyMap<PropertyKey, SelectedResource> {\n return resources.size === 0 ? noResources : resources;\n}\n\ntype CollectedSelection = Readonly<{\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n values: SelectionRecord;\n}>;\n\n/**\n * D321: recognition happens in the one pass that already walks the selection. `Resource` is a structural contract\n * (D199) and the type of a selected field promises `ResourceHook` for anything that satisfies it, so the predicate\n * this uses answers the same question the type asked. Reading the resource here also puts it among `sources`, so its\n * publication invalidates the fast path of this reader exactly as a read source does.\n */\nfunction collectSelection(\n select: (context: SelectionContext) => object,\n sources: Map<Readable<unknown>, unknown>,\n): CollectedSelection {\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 const entries: (readonly [PropertyKey, unknown])[] = [];\n const resources = new Map<PropertyKey, SelectedResource>();\n\n for (const key of Reflect.ownKeys(selected)) {\n if (!Object.prototype.propertyIsEnumerable.call(selected, key)) continue;\n\n const value = (selected as SelectionRecord)[key];\n\n entries.push([key, value]);\n classifyResource(key, value, resources, sources);\n }\n\n // `__proto__` is a key like any other here, and only a defined property keeps it one (D214).\n return { resources: selectedResources(resources), values: Object.fromEntries(entries) };\n}\n\n/**\n * D321: `values` keeps the resource itself — a stable reference — so a selection that holds one is compared by\n * `Object.is` exactly as before. What moves is the snapshot behind it, and that is compared here: without this the\n * selection would be «equal», the previous snapshot would be kept, and a published request would reach no render.\n */\nfunction equalResources(\n previous: ReadonlyMap<PropertyKey, SelectedResource>,\n next: ReadonlyMap<PropertyKey, SelectedResource>,\n): boolean {\n if (previous.size !== next.size) return false;\n\n for (const [key, selected] of next) {\n const before = previous.get(key);\n\n if (before?.resource !== selected.resource || !Object.is(before.snapshot, selected.snapshot)) return false;\n }\n\n return true;\n}\n\nfunction equalSelection(\n previous: SelectionSnapshot,\n values: SelectionRecord,\n sources: ReadonlySet<Readable<unknown>>,\n resources: ReadonlyMap<PropertyKey, SelectedResource>,\n) {\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 if (!equalResources(previous.resources, resources)) 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 = {\n commands: {},\n leases: noLeases,\n resources: noResources,\n sources: new Set(),\n values: {},\n};\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\nfunction selectionLeases(resources: ReadonlyMap<PropertyKey, SelectedResource>): ReadonlySet<AnyResource> {\n if (resources.size === 0) return noLeases;\n\n const leased = new Set<AnyResource>();\n\n for (const selected of resources.values()) leased.add(selected.resource);\n\n return leased;\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 { resources, values } = collectSelection(context => select(model, context), nextObserved);\n const sources = new Set(nextObserved.keys());\n\n if (observed === undefined || !equalSelection(snapshot, values, sources, resources)) {\n snapshot = {\n commands: selectionCommands(values),\n leases: selectionLeases(resources),\n resources,\n sources,\n values,\n };\n }\n\n observed = nextObserved;\n observedSelect = select;\n\n return snapshot;\n };\n}\n\nexport { createSelectionSnapshot, emptySelectionSnapshot };\nexport type { ModelSelector, SelectedResource, SelectionContext, SelectionShape, SelectionSnapshot };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\nimport type { AnyResource } from './resource-command';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nconst noLeases: ReadonlySet<AnyResource> = new Set();\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 /** D321: one lease per resource identity, diffed like a subscription, so an unchanged selection takes none. */\n private readonly leases = new Map<AnyResource, Subscription>();\n private readonly listeners = new Set<Listener>();\n private retained: ReadonlySet<AnyResource> = noLeases;\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.retained = noLeases;\n this.releaseAll();\n }\n\n commit(sources: ReadonlySet<Readable<unknown>>, retained: ReadonlySet<AnyResource> = noLeases): void {\n this.sources = sources;\n this.retained = retained;\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 held = [...this.subscriptions.values(), ...this.leases.values()];\n this.subscriptions.clear();\n this.leases.clear();\n releaseSubscriptions(held, 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 for (const [resource, lease] of this.leases) {\n if (this.retained.has(resource)) continue;\n\n this.leases.delete(resource);\n removed.push(lease);\n }\n\n releaseSubscriptions(removed);\n }\n\n /**\n * D321: `retain()` is reentrant — it reaches `activate()`, the target subscription, the first read and the\n * publication that follows, all synchronously — and a listener woken there may rebuild or close this selection.\n * The record is reserved first, the lease is taken second, and it is kept only if the record is still this one.\n */\n private retain(resource: AnyResource): void {\n const lease: Subscription = { dispose: undefined };\n this.leases.set(resource, lease);\n const release = resource.retain();\n\n if (typeof release !== 'function') throw new TypeError('Model selection resource must return a disposer.');\n\n if (this.leases.get(resource) === lease) lease.dispose = release;\n else release();\n }\n\n /** Foreign code inside a subscription or a lease may replace this selection, so both loops ask again. */\n private ready(): boolean {\n return this.enabled && this.listeners.size > 0;\n }\n\n private retainSelected(): void {\n for (const resource of this.retained) {\n if (!this.ready()) return;\n\n if (this.retained.has(resource) && !this.leases.has(resource)) this.retain(resource);\n }\n }\n\n private subscribeSources(): void {\n for (const source of this.sources) {\n if (!this.ready()) return;\n\n if (this.sources.has(source) && !this.subscriptions.has(source)) this.add(source);\n }\n }\n\n private synchronize(): void {\n this.removeUnselected();\n this.subscribeSources();\n this.retainSelected();\n }\n}\n\nexport { ModelSelectionStore };\n","import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call, ModelOf, Resource } from '@opetope/core';\n\nimport type { CommandHook } from './command-hook';\nimport { useSelectionHooks } from './commands-hook';\nimport type { SelectionHooks } 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, SelectionSnapshot } from './model-selection-snapshot';\nimport { ModelSelectionStore } from './model-selection-store';\nimport { useFrame } from './mount-context';\nimport type { ResourceHook, ResourceRequestKey } from './resource-command';\n\n/**\n * D321: a field whose value is a resource answers the hook `useResource` answers, and the mount leases that request\n * exactly as `useResource` does. `read(resource)` is unchanged — it hands out the snapshot and leases nothing — so\n * naming one request both ways registers it twice: the subscription and the lease are independent.\n */\ntype SelectedValue<Value> =\n Value extends Call<infer Input, infer Output>\n ? CommandHook<Input, Output>\n : Value extends Resource<infer Data, infer Key extends ResourceRequestKey>\n ? ResourceHook<Data, Key>\n : 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/**\n * Two branches of one key: the consumer this selection prepared — a bound command, or the two verbs and the snapshot\n * of a selected resource — and otherwise the value the selector wrote. S6/D321: the resource branch is assembled on\n * the record and not here, so a key whose request did not move hands back the very same object.\n */\nfunction mergeSelection(snapshot: SelectionSnapshot, hooks: SelectionHooks): unknown {\n return Object.fromEntries(\n Reflect.ownKeys(snapshot.values).map(key => [\n key,\n Object.prototype.hasOwnProperty.call(hooks, key) ? hooks[key] : snapshot.values[key],\n ]),\n );\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 hooks = useSelectionHooks(snapshot.commands, snapshot.resources);\n useLayoutEffect(() => () => store?.close(), [store]);\n // D321: a lease is taken in commit and never in a render, and the store diffs the set by identity.\n useLayoutEffect(() => store?.commit(snapshot.sources, snapshot.leases), [snapshot, store]);\n\n if (select === undefined) return model;\n\n return mergeSelection(snapshot, hooks);\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, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport type { Resource } from '@opetope/runtime';\nimport type { ResourceRequestKey } from '@opetope/runtime/internal';\n\nimport { useCommandStatus } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport { createResourceInvokers } from './resource-command';\nimport type { AnyResource, ResourceHook } from './resource-command';\n\ntype ResourceSlots = Readonly<{\n refresh: CommandHookController<void, unknown>;\n retry: CommandHookController<void, unknown>;\n}>;\n\n/**\n * D321: one pair of controllers per resource identity, exactly as `useCommandSlot` keeps one per invoker. The slots\n * are what the pair of invokers is kept alive by, so one map answers for both. Slots are inert until commit, so an\n * interrupted render neither admits work nor resets the status of the committed one.\n */\nfunction useResourceSlots(resource: AnyResource): ResourceSlots {\n const [cache] = useState(() => new WeakMap<object, ResourceSlots>());\n const existing = cache.get(resource);\n\n if (existing !== undefined) return existing;\n\n const invokers = createResourceInvokers(resource);\n const slots: ResourceSlots = {\n refresh: new CommandHookController<void, unknown>(invokers.refresh),\n retry: new CommandHookController<void, unknown>(invokers.retry),\n };\n cache.set(resource, slots);\n\n return slots;\n}\n\n/**\n * The atomic snapshot of a request, the lease that keeps it open for as long as this component is mounted, and the\n * two verbs of the resource as commands (D321). The number of React hooks here does not depend on the resource.\n */\nfunction useResource<Data, Key extends ResourceRequestKey>(resource: Resource<Data, Key>): ResourceHook<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 const slots = useResourceSlots(resource as unknown as AnyResource);\n const refresh = useCommandStatus(slots.refresh);\n const retry = useCommandStatus(slots.retry);\n\n return useMemo(() => ({ refresh, retry, snapshot }) as ResourceHook<Data, Key>, [refresh, retry, 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","useCommandStatus","currentSlot","hookState","setHookState","useInsertionEffect","useLayoutEffect","run","useMemo","useCommand","command","record","useFrame","idleRelease","subscribeIdle","idleRevision","isResource","isRuntimeMaterialization","isReadable","candidate","awaitCancellable","operation","reject","cancel","notifySuccess","operationInvoker","start","_input","createResourceInvokers","resource","resourceInvokers","cache","existing","invokers","emptyHooks","noResources","isResourceEntry","entry","createEntry","unchangedHook","next","updateEntry","selectEntry","createResourceEntry","snapshot","refresh","retry","selectResourceEntry","hook","commandEntries","detachEntry","hookOf","CommandGroup","selected","key","listener","commands","resources","frame","useCommandGroup","groupRef","useRef","useSelectionHooks","group","getSnapshot","useSyncExternalStore","useCommands","noLeases","readSource","source","sources","classifyResource","selectedResources","collectSelection","select","project","entries","equalResources","before","equalSelection","values","keys","previousKeys","index","emptySelectionSnapshot","selectionCommands","isCallTarget","selectionLeases","leased","createSelectionSnapshot","model","selectRef","observed","observedSelect","nextObserved","context","releaseSubscriptions","subscriptions","failures","subscription","dispose","createAggregateError","ModelSelectionStore","retained","held","removed","lease","release","readEmptySnapshot","useSelectionStore","storeRef","mergeSelection","hooks","useModel","declaration","store","requiresModels","models","declared","component","useResourceSlots","useResource","subscribe","useCallback","useEffect","emptySlotProperties","defineSlot","id","createContributionTarget","defineSwitchSlot","declarationId","targets","switchSlot","route","target","Slot","useReadable","slotProps","createElement","Fragment","ContributionMount","contributionAuthority"],"mappings":"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"}
|
package/dist/model-hook.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type { Call, ModelOf } from '@opetope/core';
|
|
1
|
+
import type { Call, ModelOf, Resource } from '@opetope/core';
|
|
2
2
|
import type { CommandHook } from './command-hook.js';
|
|
3
3
|
import type { AnyModel } from './model-binding.js';
|
|
4
4
|
import type { SelectionContext, SelectionShape } from './model-selection-snapshot.js';
|
|
5
|
-
type
|
|
5
|
+
import type { ResourceHook, ResourceRequestKey } from './resource-command.js';
|
|
6
|
+
/**
|
|
7
|
+
* D321: a field whose value is a resource answers the hook `useResource` answers, and the mount leases that request
|
|
8
|
+
* exactly as `useResource` does. `read(resource)` is unchanged — it hands out the snapshot and leases nothing — so
|
|
9
|
+
* naming one request both ways registers it twice: the subscription and the lease are independent.
|
|
10
|
+
*/
|
|
11
|
+
type SelectedValue<Value> = Value extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : Value extends Resource<infer Data, infer Key extends ResourceRequestKey> ? ResourceHook<Data, Key> : Value;
|
|
6
12
|
type ModelSelection<Selected> = {
|
|
7
13
|
readonly [Key in keyof Selected]: SelectedValue<Selected[Key]>;
|
|
8
14
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Readable } from '@opetope/core';
|
|
2
2
|
import type { AnyCommand } from './model-binding.js';
|
|
3
|
+
import type { AnyResource } from './resource-command.js';
|
|
3
4
|
interface SelectionRead {
|
|
4
5
|
<Value>(source: Readable<Value>): Value;
|
|
5
6
|
<Value, Selected>(source: Readable<Value>, select: (value: Value) => Selected): Selected;
|
|
@@ -11,8 +12,16 @@ type NonSelectionRecord = readonly unknown[] | ((...arguments_: never[]) => unkn
|
|
|
11
12
|
/** A named interface needs no open index signature; container and callable results are not field selections. */
|
|
12
13
|
type SelectionShape<Selected extends object> = Selected extends NonSelectionRecord ? never : Selected;
|
|
13
14
|
type SelectionRecord = Readonly<Record<PropertyKey, unknown>>;
|
|
15
|
+
/** D321: the resource a field selected, and the snapshot this selection read from it. */
|
|
16
|
+
type SelectedResource = Readonly<{
|
|
17
|
+
resource: AnyResource;
|
|
18
|
+
snapshot: unknown;
|
|
19
|
+
}>;
|
|
14
20
|
type SelectionSnapshot = Readonly<{
|
|
15
21
|
commands: Readonly<Record<PropertyKey, AnyCommand>>;
|
|
22
|
+
/** The set the mount leases; two keys naming one resource are one lease. */
|
|
23
|
+
leases: ReadonlySet<AnyResource>;
|
|
24
|
+
resources: ReadonlyMap<PropertyKey, SelectedResource>;
|
|
16
25
|
sources: ReadonlySet<Readable<unknown>>;
|
|
17
26
|
values: SelectionRecord;
|
|
18
27
|
}>;
|
|
@@ -29,4 +38,4 @@ declare const emptySelectionSnapshot: SelectionSnapshot;
|
|
|
29
38
|
*/
|
|
30
39
|
declare function createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>): () => SelectionSnapshot;
|
|
31
40
|
export { createSelectionSnapshot, emptySelectionSnapshot };
|
|
32
|
-
export type { ModelSelector, SelectionContext, SelectionShape };
|
|
41
|
+
export type { ModelSelector, SelectedResource, SelectionContext, SelectionShape, SelectionSnapshot };
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import type { Readable } from '@opetope/core';
|
|
2
|
+
import type { AnyResource } from './resource-command.js';
|
|
2
3
|
/** A subscription set, not a scheduler: source notifications use React's snapshot consistency check. */
|
|
3
4
|
declare class ModelSelectionStore {
|
|
4
5
|
private enabled;
|
|
6
|
+
/** D321: one lease per resource identity, diffed like a subscription, so an unchanged selection takes none. */
|
|
7
|
+
private readonly leases;
|
|
5
8
|
private readonly listeners;
|
|
9
|
+
private retained;
|
|
6
10
|
private sources;
|
|
7
11
|
private readonly subscriptions;
|
|
8
12
|
close(): void;
|
|
9
|
-
commit(sources: ReadonlySet<Readable<unknown
|
|
13
|
+
commit(sources: ReadonlySet<Readable<unknown>>, retained?: ReadonlySet<AnyResource>): void;
|
|
10
14
|
subscribe: (notify: () => void) => (() => void);
|
|
11
15
|
private add;
|
|
12
16
|
private notify;
|
|
13
17
|
private releaseAll;
|
|
14
18
|
private removeUnselected;
|
|
19
|
+
/**
|
|
20
|
+
* D321: `retain()` is reentrant — it reaches `activate()`, the target subscription, the first read and the
|
|
21
|
+
* publication that follows, all synchronously — and a listener woken there may rebuild or close this selection.
|
|
22
|
+
* The record is reserved first, the lease is taken second, and it is kept only if the record is still this one.
|
|
23
|
+
*/
|
|
24
|
+
private retain;
|
|
25
|
+
/** Foreign code inside a subscription or a lease may replace this selection, so both loops ask again. */
|
|
26
|
+
private ready;
|
|
27
|
+
private retainSelected;
|
|
28
|
+
private subscribeSources;
|
|
15
29
|
private synchronize;
|
|
16
30
|
}
|
|
17
31
|
export { ModelSelectionStore };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D321: the two verbs of a resource, dressed as the commands a button already knows. A consumer keeps a status —
|
|
3
|
+
* `inFlight` until the operation settles, the settled `outcome` — while the resource stays owned by the model that
|
|
4
|
+
* declared it: cancelling this wait cancels the wait and not the load.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResourceOperationOutcome } from '@opetope/core';
|
|
7
|
+
import type { Resource, ResourceSnapshot } from '@opetope/runtime';
|
|
8
|
+
import type { ResourceRequestKey } from '@opetope/runtime/internal';
|
|
9
|
+
import type { CommandHook } from './command-hook.js';
|
|
10
|
+
import type { CommandInvoker } from './command-hook-controller.js';
|
|
11
|
+
type AnyResource = Resource<never, ResourceRequestKey>;
|
|
12
|
+
/**
|
|
13
|
+
* D199/D321: the type of a selected field promises `ResourceHook` for every value that satisfies `Resource`, and
|
|
14
|
+
* `Resource` is a structural contract a host may implement with an ordinary object whose members are methods — the
|
|
15
|
+
* fixture of `useResource` is exactly such an object. So the runtime answers the question the type asked, by the same
|
|
16
|
+
* contract: the three verbs and the lease beside a `Readable`. Recognising less would type a host adapter as a hook
|
|
17
|
+
* and then hand the raw object to the render, where `snapshot` is `undefined` and `refresh.run()` throws.
|
|
18
|
+
*
|
|
19
|
+
* The registry of materializations this runtime built stays the fast path: a model's own resource is the common case,
|
|
20
|
+
* and it answers without reading four properties off a foreign object.
|
|
21
|
+
*/
|
|
22
|
+
declare function isResource(value: unknown): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* What `useResource` answers, and what a field of a `useModel` selection whose value is a resource answers. The two
|
|
25
|
+
* verbs never reject (`resource.ts` answers an outcome), so the ordinary path of `outcome` is
|
|
26
|
+
* `{ status: 'ok', value }`; `failed` stays in the type because the controller turns a synchronous throw of an
|
|
27
|
+
* invoker into it, and the wrapper below carries its own test that it never throws synchronously.
|
|
28
|
+
*/
|
|
29
|
+
type ResourceHook<Data, Key extends ResourceRequestKey = ResourceRequestKey> = Readonly<{
|
|
30
|
+
refresh: CommandHook<void, ResourceOperationOutcome<Data, Key>>;
|
|
31
|
+
retry: CommandHook<void, ResourceOperationOutcome<Data, Key>>;
|
|
32
|
+
snapshot: ResourceSnapshot<Data, Key>;
|
|
33
|
+
}>;
|
|
34
|
+
type ResourceInvokers = Readonly<{
|
|
35
|
+
refresh: CommandInvoker;
|
|
36
|
+
retry: CommandInvoker;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* One pair per resource identity. `selectEntry` and `useCommandSlot` both pick their controller by the identity of
|
|
40
|
+
* the invoker, so a pair rebuilt on every render would reset `inFlight`, drop `outcome` and change `run` — which is
|
|
41
|
+
* why the pair is memoised against the resource itself and never assembled during a render.
|
|
42
|
+
*/
|
|
43
|
+
declare function createResourceInvokers(resource: AnyResource): ResourceInvokers;
|
|
44
|
+
declare function resourceInvokers(resource: AnyResource, cache: WeakMap<object, ResourceInvokers>): ResourceInvokers;
|
|
45
|
+
/**
|
|
46
|
+
* The one module of this package that talks to the runtime about a materialization, so the rest of React reads the
|
|
47
|
+
* recognition and the request key from here instead of routing to `@opetope/runtime/internal` of its own.
|
|
48
|
+
*/
|
|
49
|
+
export { createResourceInvokers, isResource, resourceInvokers };
|
|
50
|
+
export type { AnyResource, ResourceHook, ResourceInvokers, ResourceRequestKey };
|
package/dist/resource-hook.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { Resource
|
|
1
|
+
import type { Resource } from '@opetope/runtime';
|
|
2
2
|
import type { ResourceRequestKey } from '@opetope/runtime/internal';
|
|
3
|
-
|
|
3
|
+
import type { ResourceHook } from './resource-command.js';
|
|
4
|
+
/**
|
|
5
|
+
* The atomic snapshot of a request, the lease that keeps it open for as long as this component is mounted, and the
|
|
6
|
+
* two verbs of the resource as commands (D321). The number of React hooks here does not depend on the resource.
|
|
7
|
+
*/
|
|
8
|
+
declare function useResource<Data, Key extends ResourceRequestKey>(resource: Resource<Data, Key>): ResourceHook<Data, Key>;
|
|
4
9
|
export { useResource };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opetope/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20.19.0"
|
|
6
6
|
},
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
}
|
|
51
51
|
],
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@opetope/core": "0.9.
|
|
54
|
-
"@opetope/runtime": "0.9.
|
|
53
|
+
"@opetope/core": "0.9.6",
|
|
54
|
+
"@opetope/runtime": "0.9.6",
|
|
55
55
|
"@testing-library/react": "16.3.3",
|
|
56
56
|
"@types/react": "19.2.18",
|
|
57
57
|
"react": "19.2.8",
|
|
58
58
|
"react-dom": "19.2.8"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@opetope/core": "0.9.
|
|
62
|
-
"@opetope/runtime": "0.9.
|
|
61
|
+
"@opetope/core": "0.9.6",
|
|
62
|
+
"@opetope/runtime": "0.9.6",
|
|
63
63
|
"react": ">=19.0.0 <20"
|
|
64
64
|
},
|
|
65
65
|
"sideEffects": false,
|