@opetope/react 0.13.0 → 0.15.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +5 -3
  3. package/dist/command-hook-controller.d.ts +13 -3
  4. package/dist/command-hook.d.ts +1 -8
  5. package/dist/contribution-frame-D7_DN6Y2.js +2 -0
  6. package/dist/contribution-frame-D7_DN6Y2.js.map +1 -0
  7. package/dist/{contribution-isolation-Bzfbt4iM.js → contribution-isolation-Ig70xFiC.js} +2 -2
  8. package/dist/contribution-isolation-Ig70xFiC.js.map +1 -0
  9. package/dist/contribution-isolation.d.ts +3 -3
  10. package/dist/errors.d.ts +4 -3
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/integration.d.ts +0 -4
  15. package/dist/integration.js +1 -1
  16. package/dist/integration.js.map +1 -1
  17. package/dist/model-hook.d.ts +5 -4
  18. package/dist/model-selection-snapshot.d.ts +6 -11
  19. package/dist/readable-hooks.d.ts +5 -8
  20. package/dist/requires-models.d.ts +4 -7
  21. package/dist/resource-command.d.ts +25 -26
  22. package/dist/resource-hook.d.ts +3 -7
  23. package/dist/resource-verb-hook.d.ts +28 -0
  24. package/dist/scenario-diagnostics.d.ts +3 -1
  25. package/dist/scenario-slot.d.ts +6 -15
  26. package/dist/scenario-types.d.ts +64 -11
  27. package/dist/scenario-wait.d.ts +8 -1
  28. package/dist/scenario.d.ts +5 -3
  29. package/dist/selection-hooks.d.ts +6 -4
  30. package/dist/selector-sources.d.ts +64 -0
  31. package/dist/slot.d.ts +9 -7
  32. package/dist/testing.d.ts +2 -8
  33. package/dist/testing.js +3 -3
  34. package/dist/testing.js.map +1 -1
  35. package/package.json +5 -5
  36. package/dist/contribution-frame-Bh104T5Z.js +0 -2
  37. package/dist/contribution-frame-Bh104T5Z.js.map +0 -1
  38. package/dist/contribution-isolation-Bzfbt4iM.js.map +0 -1
  39. package/dist/feature-boundary.d.ts +0 -29
  40. package/dist/feature-demand.d.ts +0 -21
package/CHANGELOG.md CHANGED
@@ -1,5 +1,112 @@
1
1
  # @opetope/react
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fe97978: `onSuccess` and `onFailure` of a `CommandHook` run may return anything: the answer is not awaited and never becomes the outcome, so `onSuccess: () => close.run()` compiles, and a throw or a rejected answer of either callback is reported apart from the run (D474). The verbs of a `ResourceHook` — `refresh`, `reset` and `retry` — take no options any more and answer the Resource operation outcome itself: read `await refresh.run()` or `refresh.outcome` instead of `outcome.value`, and write the callback as code after `await refresh.run()` (D520).
8
+
9
+ Behavior change: a `refresh`, `reset` or `retry` of a `ResourceHook` still in flight when its component unmounted, or its Resource was replaced, answered the outcome of the operation; now that consumer's wait answers `{ kind: 'cancelled' }`, while the Resource's work goes on and the hook keeps its last settled outcome. Notice it where code after `await refresh.run()` expects `ready` for a component that is gone (D520).
10
+
11
+ - 544bdd9: Feature `own` keeps composition and host lifetimes only — `imports`, `requires`, `model`, `select`, `attach` and `command` — and a feature command takes `parallel`, `queue` or `{ pending: 'latest' }`. `own.effect`, `own.event`, `own.resource`, `own.scope` and `own.lane` are removed: declare them in a model's `ModelContext` and compose the model through `own.model` (D501). `FeatureInstance.instance` is no longer public: pass the feature handle to `settled(instance)` or use its `ready` and `close` (D517). Runtime no longer re-exports Core's Resource declaration and state types and `skipChange` — import them from `@opetope/core`; `ResourceView` stays a shared boundary type. React removes `useSelector` and, from `@opetope/react/integration`, the `FeatureBoundary` demand family with `useFeature` and `useFeatureRetry`: select model data with `read(source, project)` in `useModel`, or create a stable `derive(source, project, { equals })` for a standalone projection and pass it to `useReadable` (D500). `opetope/no-retired-vocabulary` reports the removed words with the instruction and moves the Resource type imports with `--fix`.
12
+
13
+ Behavior change: an event of feature `own` whose subscription failed asynchronously, or a scope of feature `own` whose first child failed to open, rejected the feature's `ready`; moved into a model, the failure goes to the reporter — a scope's as `ModelScopeOpenError` — and `ready` can resolve. A synchronous throw in an event `subscribe` still rejects opening, as `preparation failed` instead of `attachment <key> failed to open`. Notice it where a test or a host waits for `ready` to reject, or matches that text rather than `name` and `code` (D501).
14
+
15
+ Behavior change: after close, a Resource declared in feature `own` and exported answered `idle('unleased')`; moved into a model the feature owns, it answers `idle('retired')`, and `settled(instance)` waits for that model's effect and event runs, so one still running ends the wait with `WaitTimeoutError`. Notice it where a test asserts the state of a closed feature's Resource or settles an instance while a run is in flight (D501).
16
+
17
+ - dac0543: `openApplication` and `createScenario` check `imports` by type, as they already check `conditions`: an application whose enabled features import a host contract that `imports` does not bind no longer compiles, and the error names the import and its feature, ``the host contract imported as `api` by `shop.browse` requires a binding``. Pass `bind(contract, value)` for every hard and `onDemand` import; an `optional` one may stay unbound, and a binding that no enabled feature imports is refused too. A binding written by hand as `{ contract, value }`, a list annotated `ApplicationImportBinding[]` and an options record annotated `OpenApplicationOptions<Features>` or `ScenarioOptions<Features, Mount>` are refused: let the call infer the list. `ApplicationImportBinding<Value>` and `HostContract<Value>` are invariant now, so a binding of any contract is written `ApplicationImportBinding`, without an argument. Types only (D478).
18
+ - b6471b3: Pagination is applied to a built Resource instead of written in its declaration: `ctx.resource.load({ …, pagination: { load, merge, nextCursor } })` becomes `ctx.resource.paginate(ctx.resource.load({ … }), { cursor, load, merge })` in the model that declares it, and answers the same handle with `pagination` on it; a feature Resource has no pagination, so declare it in a model the feature owns. `ConsumerResource<Data, Key, Cursor>` loses its third parameter: a paginated `consumers` field is `PaginatedResource<Data, Key, Cursor, 'consumers'>`. In React `pagination.loadNext` is a plain function answering `Promise<PaginationOutcome>`, so `loadNext.run()`, `.inFlight` and `.outcome` are gone and the status is read from `pagination.state`. `loadNext({ retry: false })` starts nothing over a failed cursor, and `testPagination` from `@opetope/runtime/testing` stands in for the capability. `opetope/no-retired-vocabulary` and the compiler name the replacement of `pagination` in the record of `ctx.resource.load` or `ctx.resource.live` and of `nextCursor` in the record of `ctx.resource.paginate` (D486, D487, D488).
19
+
20
+ Behavior change: a `merge` that threw, or answered `skipChange` or a thenable, left `loadNext()` pending forever with nothing reported; now the reporter hears it once and every caller of that `loadNext()` is rejected with it. Notice it where a caller waited for a page that never came (D486, D487).
21
+
22
+ Behavior change: `pagination.state` was a new record on every write of the data, a tuple cursor a new array each time; now it keeps its record while its kind, its cursor and its error stand. Notice it where an effect or a memo keyed on it ran on every data write and now runs when the page moves (D487).
23
+
24
+ Behavior change: a page that answered but left its cursor where it was, or whose `merge` was refused, kept the state `failed` with the error of the page before; now the answer takes that failure away and the state is `ready`, and an operation that ended where it started publishes the record it left through `loading` again. Notice it where a view shows the error of a page (D487).
25
+
26
+ Behavior change: a page whose operation was superseded — by a `refresh()` or `reset()`, from inside its own `merge` included — was still written and published; now it does not land, the data stay as they were and `loadNext()` answers `superseded`. Notice it where a page was expected after a refresh started (D489).
27
+
28
+ - 3e0dc0b: `useReadable` reads a `ResourceView` too, with its demand held by committed mounts only, as `read` of a model selection holds it; the demand is committed in a layout effect, so when releasing one view synchronously changes another, the hook reads the new snapshot before paint. Its explicit type argument names the source now, not its value: `useReadable<number>(count)` is refused, so write `useReadable<Readable<number>>(count)` or let the call infer (D469).
29
+ - 54397dd: `lifetime: 'owner'` is now `lifetime: 'model'`, a model context's `acquire(resource)` is `hold(resource)`, a Resource view's `initial` is `fallback` and `identity.scope` is `identity.partition`; `opetope/no-retired-vocabulary` fixes each where it proves the context — the factories of `model`, `openModel` and a slot contribution's `model` included — and names the rest. Every Resource and its pagination, `model` and `consumers` alike, exposes a passive `observe()`, and `acquire()` answers `{ state, release }` instead of a disposer: call `lease.release()` (D514).
30
+
31
+ A Resource verb called inside a captured update supersedes the update, keeping the verb's work and leaving no loading state stuck. Each grace window of a `consumers` Resource compares its inputs with the request its last lease selected: returning to the parked identity reveals its latest state without restarting it, work already admitted goes on until its normal release, and when the window ends the data a lease read are released instead of staying cached until the next lease (D516).
32
+
33
+ Behavior change: a `reset`, `refresh` or `retry` that a confirmed write's reducer calls used to let that write land; now the write answers `superseded` and the verb's work goes on. Notice it where a confirmed write reports `superseded` although no key or request moved.
34
+
35
+ Behavior change: inside the grace window of a `consumers` Resource, a request whose key or partition differs from the parked one used to keep showing the old data; now the Resource shows `idle('unleased')` and its pagination `unavailable`, and `refresh`, `retry` and `reset` answer `skipped: unleased` only while the record is hidden — a visible parked record is served. Notice it where a passive reader or a projection expects data after the last lease.
36
+
37
+ Behavior change: a confirmed commit that superseded the load in flight of a one-shot Resource left `pending: 'refresh'` on its state for good; now nothing is pending after the commit, which answered the epoch that load was fetching. Notice it where a spinner reads `state.pending` (D489).
38
+
39
+ Behavior change: runtime activity inspection published `opetope.runtime-activity/5`; now it publishes `/6`, because the Resource lifetime literal changed, while graph and frame stay `/5` and model state `/1`. Notice it where a custom inspection adapter or a recorded fixture checks the revision (D514).
40
+
41
+ - 86c7d69: Testing fixtures have one non-React entry, `@opetope/runtime/testing`: Core no longer exports `/testing`, and React `/testing` keeps only scenarios and Slot rendering. `command` becomes `testCommand`, Navigation's `fakeLinkSource` and `fakeLinkCodec` become `testLinkSource` and `testLinkCodec`, Kit `ExternalSource.publish` becomes `set`, and the public Core `State` becomes `TestReadable` beside `testReadable`. `CommandInputArgs` is no longer exported: a wrapper forwards `RunCommandArgs` or `Parameters<CommandHook<Input, Output>['run']>`. `Scenario.waitFor` answers the accepted value, with type-guard narrowing. `opetope/no-retired-vocabulary` moves the imports with aliases that keep local and public names, and rewrites a Kit receiver only where its origin is proved (D502, D517).
42
+
43
+ Behavior change: `scenario.waitFor(read)` finished when `read` answered any truthy value; like Runtime `waitFor`, it now waits only while `read` answers `false`, `null` or `undefined`, so `0` and `''` finish at once, and it resolves with the value. Notice it where a test waits on a count or a text from JavaScript — pass an `accept` predicate for the condition it meant (D502).
44
+
45
+ - 5e7c654: A `useModel` selector reads a source only through `read`, from its second argument, now exported as `SelectionContext`: `model.count.getSnapshot()`, which read once and never updated the component, no longer compiles, and neither do `subscribe`, `acquire` or a write to a `State` (D476, D518). The types no longer check what a selector returns, so a hook generic in its model or in its value compiles without an annotation and a selector costs about half as much to check; the result is checked at run time instead (D525). The `read` of a `SelectionContext` called after the selector returned throws `TypeError: A selector's read was called after the selector returned: read inside the selector.` `opetope/no-snapshot-read-in-render`, `error` in `recommended` and in the shipped `oxlintrc.json`, now reads the inline selector as render code, so it reports more after the upgrade: a `getSnapshot()` in the selector or in a projection handed to `read(source, project)`, on any receiver, and a selector that answers the whole model, `model => model`; select the fields the component uses (D476).
46
+
47
+ Behavior change: a selector that left a `Readable` or a `ResourceView` in a field of its result used to hand the component the source object; now the selection throws `TypeError: A selector returned a Readable in field "x": read(source) inside the selector.` (or its `ResourceView` form) in development and in production. A Resource or a Command field still becomes its hook, and a source one level down passes. A result that is an array, a tuple, a promise or another container throws `Model selection requires a record of named fields, not an array, a promise or another container.` Notice it where a component throws while rendering after the upgrade.
48
+
49
+ Behavior change: a `useModel` selector that writes a `State` it closed over used to write; now it throws `TypeError: a selector reads; write in a command` before the write. Notice it where a component throws while rendering after the upgrade.
50
+
51
+ ### Patch Changes
52
+
53
+ - 5e7c654: Type diagnostics name a concrete remedy for invalid model, Resource, React selector, host binding and Navigation calls, and valid calls infer as strictly as before. A host contract union cannot bind a value of only one of its declarations: narrow to one host contract or condition. An untyped `derive` source no longer picks a diagnostic overload silently; a Resource data or key mismatch names the key at the declaration without an absolute import path, and a mismatched data, key or lifetime is an ordinary TS2322 without the exact-optional advice. The instruction a command body reads when it uses an input nothing named names the inline form first, `(ctx): ModelOf<typeof M> => ...`, and the refusal of `grace` on a model Resource says that only `consumers` has a grace window.
54
+ - 5e7c654: The hover of every public declaration gives a self-contained description of what it does and how its ownership works.
55
+ - 5e7c654: Documentation only. Migration guides are removed; the changelog lists behavior changes: `@opetope/runtime/docs` no longer ships `migrations/` or the `wave-migration.md` and `resource-migration.md` link pages. A change that neither the compiler nor the lint reports is a `Behavior change` line of the changelog, a mechanical rename is fixed by `opetope/no-retired-vocabulary`, and the compiler names the replacement of any other removed form.
56
+
57
+ `@opetope/runtime/docs` no longer ships the decision archive or the maintainer pages: a shipped document keeps the text of a link into the repository and drops its destination, and cites a decision by its number alone; the unpacked package is about 2.3 MB smaller (D492). The shipped reference pages follow one template — how to do it with a compiled example, the signature from the built declarations, the laws, and why. The README and the guides explain React 19 notification timing and inline model authoring, and declare a component as a top-level function wrapped after, `const CounterButton = requiresModels([CounterModel])(CounterButtonView)`, which React Compiler and the compiler rules of oxlint can see. The references say that every caller of a Command whose owner is fenced while its body runs is answered at the fence, not when the body returns, and call the `consumers` lifetime by that name instead of `lease`.
58
+
59
+ The guides are recipes: the task, a compiled example, why it holds and the reference pages that own its laws; the longest ones split into focused pages such as choosing a node, a source adapter, delivering live changes, showing screens on a surface and handling deep links. The link pages `spec.md`, `primitives.md`, `cookbook.md`, `how-it-works.md`, `agent-guide.md`, `devtools.md` and `releases.md` are removed; `reference/index.md` is the entry point.
60
+
61
+ - f9f6459: New: `ctx.resource.list` declares a feed of keyed items with the record of `ctx.resource.load` and `items`, `key`, `next`, and optionally `order`, `refresh` and `resolve`. One loader reads every page, the library keeps the cursor beside the data, pages fold by the key of their items, `refresh: 'head'` sews a fresh first page onto the scrolled tail, and the pages follow the law of `paginate`. `capture(list)` answers `upsert(items)` and `remove(keys)` beside `update`; `upsert` folds the repeats of a key in its batch through `resolve(earlier, later)`, or keeps the later item, where the key first came, while a page or an answer of `update` that repeats a key is refused. `ListCommit`, `OwnedListResource` and `ListLoadContext` are public Core types, so exported list helpers and hoisted loaders emit portable declarations. `testResource` from `@opetope/runtime/testing` is a Resource a test sets by hand, paginated or not (D489, D490).
62
+ - 0f102d6: Behavior change: `reset()` of a live Resource, and `retry()` of one that was `failed` or `loading` without data, answered `ready` as soon as the new connection was healthy, while the Resource was still `loading`; now they answer on the first data of the epoch, and `failed` when a `load` of that epoch fails before any data. Notice it on a quiet stream: `await reset()` waits for its first message, and a React `reset` stays `inFlight` until then (D355).
63
+ - c153f4f: A `ModelStateWriter`, also a helper parameter typed with it, reads with the same passive `read(source)` as every execution, so a hand-written writer now needs `read` beside `update` (D526). `@opetope/runtime/testing` exports `read(source)` for fixtures; Runtime `waitFor` and React `Scenario.waitFor` take a readable source directly, with an optional `accept`, and answer the accepted value, while the callback form of Runtime `waitFor` now also refuses a callback that answers a promise on some calls only, and `settled` and `waitFor` refuse a spread copy of a handle. The new `prefer-read` rule warns in the recommended config on a passive `getSnapshot()` inside an execution and fixes only the forms it proves equivalent, so a project whose lint admits no warning sees it at once.
64
+ - 1aa622e: A flush that changes nothing no longer allocates: a `derive` or `computed` that reads the same sources in the same order as last time — a source read again after another one, as in `read(a) + read(b) + read(a)` or `derive([a, b, a], …)`, included — reuses what it recorded then, and the values a flush changed are published without building a table. Every derived value and state cell retains less memory, most of all one that nobody or a single listener observes; what listeners and reactions hear, and in which order, is unchanged. `useModel` no longer repeats its commit effect when a render selects the same result.
65
+ - 86c7d69: New in `@opetope/runtime/testing`: `configureTesting({ timeoutMs })` supplies a process-wide default deadline for Runtime waits and React scenarios, which explicit options override, and its restore cancels only its own setting, in any order. `openModel` and `createScenario` with `reporter: 'collect'` collect reports in a readonly `reported`, and `expectNoReports` asserts on that list; a constructor that fails synchronously returns no handle, so inspect its rollback reports with a callback reporter. `preloadAll(application)` warms every lazy feature body without opening an instance, waits for every load and then rejects with the first observed failure, `undefined` included (D503).
66
+ - Updated dependencies [5e7c654]
67
+ - Updated dependencies [ac23b91]
68
+ - Updated dependencies [0f102d6]
69
+ - Updated dependencies [1f600e2]
70
+ - Updated dependencies [5e7c654]
71
+ - Updated dependencies [5e7c654]
72
+ - Updated dependencies [bd6ec20]
73
+ - Updated dependencies [5e7c654]
74
+ - Updated dependencies [0f102d6]
75
+ - Updated dependencies [544bdd9]
76
+ - Updated dependencies [0286832]
77
+ - Updated dependencies [b6a6564]
78
+ - Updated dependencies [dac0543]
79
+ - Updated dependencies [5e7c654]
80
+ - Updated dependencies [f9f6459]
81
+ - Updated dependencies [0f102d6]
82
+ - Updated dependencies [0f102d6]
83
+ - Updated dependencies [b6471b3]
84
+ - Updated dependencies [c153f4f]
85
+ - Updated dependencies [54397dd]
86
+ - Updated dependencies [dfc7ced]
87
+ - Updated dependencies [0286832]
88
+ - Updated dependencies [5e7c654]
89
+ - Updated dependencies [3e0dc0b]
90
+ - Updated dependencies [1aa622e]
91
+ - Updated dependencies [5e7c654]
92
+ - Updated dependencies [86c7d69]
93
+ - Updated dependencies [86c7d69]
94
+ - Updated dependencies [4df6a8b]
95
+ - @opetope/core@0.15.0
96
+ - @opetope/runtime@0.15.0
97
+
98
+ ## 0.14.0
99
+
100
+ ### Minor Changes
101
+
102
+ - 712352a: Use one model-selection `read` for ordinary readables and resource views. Reading a view carries its demand until the committed selection changes or unmounts; `read(view.observe())` remains passive. Remove the former selection `acquire` method.
103
+
104
+ ### Patch Changes
105
+
106
+ - 712352a: Keep the whole `useModel` selection result referentially stable across committed renders when its exposed fields and ordered keys are unchanged. Inline selectors can pass the result directly to memoized children and effects; source subscriptions and resource demand still follow the committed selection.
107
+ - @opetope/core@0.14.0
108
+ - @opetope/runtime@0.14.0
109
+
3
110
  ## 0.13.0
4
111
 
5
112
  ### Minor Changes
package/README.md CHANGED
@@ -31,7 +31,7 @@ const CounterModel = defineModel<{
31
31
  }>('example.counter.model');
32
32
  const CounterSlot = defineSlot<{ readonly label: string }>('example.counter.slot');
33
33
 
34
- const CounterButton = requiresModels([CounterModel])(({ label }: { readonly label: string }) => {
34
+ function CounterButtonView({ label }: { readonly label: string }) {
35
35
  const {
36
36
  count,
37
37
  increment: { run, inFlight, outcome },
@@ -45,7 +45,9 @@ const CounterButton = requiresModels([CounterModel])(({ label }: { readonly labe
45
45
  {outcome?.kind === 'failed' ? 'Retry' : `${label}: ${count}`}
46
46
  </button>
47
47
  );
48
- });
48
+ }
49
+
50
+ const CounterButton = requiresModels([CounterModel])(CounterButtonView);
49
51
 
50
52
  function CounterArea() {
51
53
  return <Slot props={{ label: 'Count' }} target={CounterSlot} />;
@@ -60,7 +62,7 @@ The [reference](../runtime/docs/reference/react.md) owns the detailed contract,
60
62
 
61
63
  For a contributor checkout, use `npm run ci:type --workspace @opetope/react` and
62
64
  `npm run ci:test --workspace @opetope/react` where provided. The root `npm run check` performs full acceptance;
63
- [contributor commands](../runtime/docs/maintainers/contributing.md) describe the build and package checks.
65
+ `docs/maintainers/contributing.md` in the checkout describes the build and package checks.
64
66
 
65
67
  <a id="hello-ui"></a>
66
68
  [See Hello UI](../runtime/docs/reference/react.md#react-hello-ui).
@@ -1,13 +1,23 @@
1
1
  import type { CommandInputArgs, CallRunOptions } from '@opetope/core/internal';
2
2
  import type { CommandOutcome } from './command.js';
3
+ /**
4
+ * D474: a callback's answer is not awaited and changes nothing — the run has already settled — so any value is
5
+ * accepted, and a rejected promise among them is reported like a throw, detached from the outcome it watched.
6
+ */
3
7
  type CommandRunOptions<Output> = Readonly<{
4
- onFailure?: (error: unknown) => PromiseLike<void> | void;
5
- onSuccess?: (value: Output) => PromiseLike<void> | void;
8
+ /** Called with a failure that is not a cancellation; its answer is not awaited, and a throw or rejection is reported. */
9
+ onFailure?: (error: unknown) => unknown;
10
+ /** Called with the body's value; its answer is not awaited and changes no outcome, and a throw or rejection is reported. */
11
+ onSuccess?: (value: Output) => unknown;
12
+ /** Cancels this run's wait: the run answers `cancelled`, and an already-aborted signal never reaches the command. */
6
13
  signal?: AbortSignal;
7
14
  }>;
15
+ /**
16
+ * The options element of the spread form is `CommandRunOptions<Output> | undefined`.
17
+ */
8
18
  type CommandRun<Input, Output> = {
9
19
  (input: Input, options?: CommandRunOptions<Output>): Promise<CommandOutcome<Output>>;
10
- (...args: [...CommandInputArgs<Input>, options?: CommandRunOptions<Output>]): Promise<CommandOutcome<Output>>;
20
+ (...args: [...CommandInputArgs<Input>, options?: CommandRunOptions<Output> | undefined]): Promise<CommandOutcome<Output>>;
11
21
  };
12
22
  type CommandInvoker = Readonly<{
13
23
  run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;
@@ -1,6 +1,5 @@
1
1
  import type { Command } from '@opetope/core';
2
2
  import type { CommandOutcome } from './command.js';
3
- import { CommandHookController } from './command-hook-controller.js';
4
3
  import type { CommandRun } from './command-hook-controller.js';
5
4
  /** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */
6
5
  type SettledOutcome<Output> = Exclude<CommandOutcome<Output>, {
@@ -13,12 +12,6 @@ interface CommandHookStatus<Output> {
13
12
  type CommandHook<Input, Output> = CommandHookStatus<Output> & {
14
13
  readonly run: CommandRun<Input, Output>;
15
14
  };
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>;
22
15
  declare function useCommand<Input, Output>(command: Command<Input, Output>): CommandHook<Input, Output>;
23
- export { useCommand, useCommandStatus };
16
+ export { useCommand };
24
17
  export type { CommandHook };
@@ -0,0 +1,2 @@
1
+ import{jsx as h}from"react/jsx-runtime";import{useContext as D,createContext as P,useRef as M,useCallback as p,useSyncExternalStore as S,useLayoutEffect as f,useInsertionEffect as R,memo as W,useMemo as m,useState as N}from"react";import{createAggregateError as A,isResourceView as g,resourceViewDemands as U,isCallTarget as K,isModel as G,isReadable as O,createLifetimeController as H,createCallController as J,reportDetachedError as w,getContributionOwner as Q,setStateFromOwner as X,createState as Y,closeStateFromOwner as k}from"@opetope/core/internal";import{isResourceValue as Z,reportRuntimeFailure as _,requireContributionModelPlans as ee,openContributionModels as te}from"@opetope/runtime/internal";import{a as d,u as ne,b as se}from"./contribution-isolation-Ig70xFiC.js";const F=P(void 0);function ie(){const n=D(F);if(n===void 0)throw new d("missing","Opetope hook was called outside a contribution mount.");return n}function C(n){return Z(n)}function j(n){const e=n.pagination;if(!(typeof e!="object"||e===null))return e}function z(n){return{loadNext:e=>j(n).loadNext(e),refresh:()=>n.refresh(),reset:()=>n.reset(),retry:()=>n.retry()}}function re(n,e){const t=e.get(n);if(t!==void 0)return t;const s=z(n);return e.set(n,s),s}const v=new Set;function x(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 A(e,"Model selection sources failed to release.")}class I{enabled=!1;leases=new Map;listeners=new Set;retained=v;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.retained=v,this.releaseAll()}commit(e,t=v){this.sources=e,this.retained=t,this.enabled=!0;try{this.synchronize()}catch(s){this.enabled=!1,this.releaseAll([s])}}subscribe=e=>{const t={notify:e};this.listeners.add(t);try{this.subscribeSources()}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.leases.values()];this.subscriptions.clear(),this.leases.clear(),x(t,e)}removeUnselected(){const e=[];for(const[t,s]of this.subscriptions)this.sources.has(t)||(this.subscriptions.delete(t),e.push(s));for(const[t,s]of this.leases)this.retained.has(t)||(this.leases.delete(t),e.push(s));x(e)}retain(e){const t={dispose:void 0};this.leases.set(e,t);const s=e.acquire();if(typeof s!="function")throw new TypeError("Model selection resource must answer a release disposer.");this.leases.get(e)===t?t.dispose=s:s()}ready(){return this.enabled&&this.listeners.size>0}retainSelected(){for(const e of this.retained){if(!this.enabled)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()}}class oe{store=new I;observations=new WeakMap;observe(e){let t=this.observations.get(e);return t===void 0&&(t=e.observe(),this.observations.set(e,t)),t}commit(e){const t=this.observations.get(e);t===void 0?this.store.close():this.store.commit(new Set([t]),new Set(U(e)))}}function ae(n){const e=M(void 0);let t=e.current;t===void 0&&(t={views:void 0},e.current=t);let s,i;if(g(n)){const a=t.views??(t.views=new oe);s=a.observe(n),i=a.store}else s=n,i=s;const r=p(a=>i.subscribe(a),[i]),c=p(()=>s.getSnapshot(),[s]),o=S(r,c,c);return f(()=>{var a;(a=t.views)==null||a.commit(n)},[t,n]),o}function E(n){return ae(n)}const y=new WeakMap;function ce(n){if(arguments.length>1)throw new d("binding-invalid","Command follows the target it binds; binding options are forbidden.");if(!K(n))throw new d("binding-invalid","Command target is not authentic.");return y.set(n,{target:n}),n}function de(n){const e=y.get(n);if(e===void 0)throw new d("binding-invalid","Command binding is not authentic.");return e}function $(n){return typeof n=="object"&&n!==null&&y.has(n)}const ue=Symbol("opetope.model-binding"),le=Symbol("opetope.model-binding-identity"),he=Object.freeze({}),B=new WeakMap;function fe(n,e,t){if(typeof t!="string")throw new d("binding-invalid",`Model ${n.id} accepts only string fields.`);const s=Object.getOwnPropertyDescriptor(e,t);if((s==null?void 0:s.enumerable)!==!0||!("value"in s))throw new d("binding-invalid",`Model ${n.id}.${t} requires an enumerable data binding.`);return s.value}function me(n){return O(n)||C(n)||g(n)||typeof n=="function"}function be(n,e){const t=[],s=Reflect.ownKeys(e);if(s.length===0)throw new d("binding-invalid",`Model ${n.id} requires at least one field.`);for(const i of s){const r=fe(n,e,i);if(!me(r)){if(!$(r))throw new d("binding-invalid",`Model ${n.id}.${String(i)} requires a Readable, a Resource, a ResourceView or a bound command.`);t.push(r)}}return Object.freeze(t)}function T(n,e){if(!G(n))throw new d("binding-invalid","Model declaration is not authentic.");if(typeof e!="object"||e===null)throw new d("binding-invalid",`Model ${n.id} requires a record binding.`);const t=be(n,e),s=Object.freeze({[ue]:he,[le]:!0});return B.set(s,{commands:t,declaration:n,model:e}),s}function pe(n){const e=B.get(n);if(e===void 0)throw new d("binding-invalid","Model binding is not authentic.");return e}let ge=0;function we(n){const e=new Map,t=[],s=new Set;for(const i of n){const r=pe(i);if(e.has(r.declaration))throw new d("duplicate",`Model ${r.declaration.id} is bound twice in one mount.`);e.set(r.declaration,r.model);for(const c of r.commands)s.has(c)||(s.add(c),t.push(c))}return{commands:Object.freeze(t),frame:{commandRecords:new Map,models:e}}}class Ce{frame;authority;boundCommands;closeTicket=0;isActive;lifetime;onClose;released=!1;rendererAttached=!1;renderListeners=new Set;state="detached";constructor(e,t){const s=++ge;this.lifetime=H({id:`contribution.${String(s)}`}),this.isActive=t==null?void 0:t.isActive,this.onClose=t==null?void 0:t.onClose;const{commands:i,frame:r}=we(e);this.frame=r,this.boundCommands=i,this.buildCommandRecords(s)}attach(){if(this.state==="closing")return this.closeTicket+=1,this.state="active",this.rendererAttached=!0,!0;if(this.state==="closed")return!1;if(this.state==="active")throw new d("duplicate","One contribution mount cannot mount twice.");const e=this.createAuthority(this.lifetime.open());try{for(const t of this.frame.commandRecords.values())t.controller.activate()}catch(t){throw this.authority=e,this.state="active",this.close(),t}return this.authority=e,this.rendererAttached=!0,this.state="active",!0}close(){if(this.state==="closed")return;this.state="closed",this.closeTicket+=1,this.lifetime.close(),this.authority=void 0;for(const t of this.frame.commandRecords.values())t.controller.close();const e=this.onClose;this.onClose=void 0,e==null||e(),this.publishRenderable(),this.rendererAttached||this.releaseFrame()}detach(){if(!this.rendererAttached)return;if(this.rendererAttached=!1,this.state==="closed"){this.releaseFrame();return}if(this.state!=="active")return;this.state="closing";const e=++this.closeTicket;queueMicrotask(()=>{this.state==="closing"&&e===this.closeTicket&&this.close()})}diagnostics(){const e=[...this.frame.commandRecords.values()].map(t=>t.controller.diagnostics());return Object.freeze({active:this.state==="active",calls:Object.freeze(e),commandRecords:this.frame.commandRecords.size,models:this.frame.models.size})}getRenderableSnapshot=()=>this.state!=="closed"&&(this.isActive===void 0||this.isActive());releaseClosedFrame(){this.state==="closed"&&this.releaseFrame()}subscribeRenderable=e=>{if(this.state==="closed")return()=>{};const t={listener:e};this.renderListeners.add(t);let s=!0;return()=>{s&&(s=!1,this.renderListeners.delete(t))}};buildCommandRecords(e){let t=0;for(const s of this.boundCommands){de(s);const i=J(s,{captureAuthority:this.captureAuthority,id:`contributionCommand.${String(e)}.${String(++t)}`,scheduling:{policy:"parallel"}});this.frame.commandRecords.set(s,{controller:i,invoker:Object.freeze({run:i.run})})}}captureAuthority=()=>{const e=this.authority;if(this.state!=="active"||e===void 0)throw new d("inactive","Contribution mount is not active.");return e.assertCurrent(),e};createAuthority(e){return Object.freeze({assertCurrent:()=>{if(this.state!=="active")throw new d("inactive","Contribution mount is not active.");e.assertCurrent()},isCurrent:()=>this.state==="active"&&e.isCurrent()})}publishRenderable(){for(const{listener:e}of[...this.renderListeners])try{e()}catch(t){w(t)}}releaseFrame(){this.released||(this.released=!0,this.frame.commandRecords.clear(),this.frame.models.clear(),this.boundCommands=[],this.renderListeners.clear())}}function ve({children:n,mount:e}){const t=S(e.subscribeRenderable,e.getRenderableSnapshot,e.getRenderableSnapshot);return R(()=>{if(e.attach())return()=>e.detach()},[e]),f(()=>{t||e.releaseClosedFrame()},[e,t]),t?h(F,{value:e.frame,children:n}):null}function q(n){const e=n.bundle;n.bundle=void 0,e==null||e.frame.close()}function ye(n,e){if(typeof e!="object"||e===null)return T(n,e);const t=i=>O(i)||C(i)||g(i)||typeof i=="function"||$(i),s=Object.entries(e).map(([i,r])=>[i,t(r)?r:ce(r)]);return T(n,Object.fromEntries(s))}function Me(n){return Q(n)}const Se={models:[]};function L(n,e){try{n()}catch(t){throw A([e,t],"Contribution mount setup and cleanup failed.",{cause:e})}throw e}function Re(n,e,t,s){const i=Y(e),{models:r,reporter:c}=s??Se;try{const o=ee(n.models),a=o.length===0?void 0:te(o,i,c);return{bundle:a,models:[...r,...(a==null?void 0:a.models)??[],...t],props:i}}catch(o){return L(()=>k(i),o)}}function Ae(n,e,t,s){const{bundle:i,models:r,props:c}=Re(n,e,t,s);let o=!1;const a=()=>{if(!o){o=!0;try{i==null||i.close().catch(w)}finally{k(c)}}};try{return{frame:new Ce(r.map(([u,b])=>ye(u,b)),{isActive:()=>!o,onClose:a}),isOpen:()=>!o,props:c}}catch(l){return L(a,l)}}function Oe(n,e){return h(n,{...e})}function ke({bundle:n,contribution:e}){const t=E(n.props),s=m(()=>e.props===void 0?t:e.props(t),[t,e]);return h(ve,{mount:n.frame,children:Oe(e.Component,s)})}function Fe({authority:n,contribution:e,fixtures:t,slotProps:s}){const i=m(()=>({authority:n,bundle:void 0,contribution:e,fixtures:t,retired:!1}),[n,e,t]),[r,c]=N(void 0),o=(r==null?void 0:r.owner)===i?r.bundle:void 0,a=M(s);return f(()=>{a.current=s}),R(()=>()=>{i.retired=!0,queueMicrotask(()=>{try{q(i)}catch(l){w(l)}})},[i]),f(()=>{const l=i.bundle??(i.bundle=Ae(i.contribution,a.current,i.fixtures??[],i.authority));return c(u=>(u==null?void 0:u.owner)===i?u:{bundle:l,owner:i}),()=>{i.retired&&q(i)}},[i]),f(()=>{(o==null?void 0:o.isOpen())===!0&&X(o.props,s)},[o,s]),o===void 0?null:h(ke,{bundle:o,contribution:e})}function je(n){const{authority:e,contribution:t,contributionId:s,fixtures:i,targetId:r}=n,c=ne(),o=m(()=>({authority:e,contribution:t,fixtures:i}),[e,t,i]),a=e==null?void 0:e.feature,l=m(()=>({contribution:s,feature:a,target:r}),[s,a,r]),u=e==null?void 0:e.reporter,b=p(V=>_(V,u),[u]);return h(se,{content:c,identity:o,report:b,source:l,children:h(Fe,{...n})})}const ze=W(je);export{ze as M,ie as a,I as b,Me as c,z as d,C as i,j as p,re as r,E as u};
2
+ //# sourceMappingURL=contribution-frame-D7_DN6Y2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contribution-frame-D7_DN6Y2.js","sources":["../src/mount-context.tsx","../src/resource-command.ts","../src/model-selection-store.ts","../src/readable-hooks.ts","../src/command.ts","../src/model-binding.ts","../src/mount-frame.ts","../src/mount-provider.tsx","../src/contribution-frame.tsx"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nimport { ContributionError } from './errors';\nimport type { Frame } from './mount-frame';\n\nconst FrameContext = createContext<Frame | undefined>(undefined);\n\nfunction useFrame(): Frame {\n const frame = useContext(FrameContext);\n\n if (frame === undefined) {\n throw new ContributionError('missing', 'Opetope hook was called outside a contribution mount.');\n }\n\n return frame;\n}\n\nexport { FrameContext, useFrame };\n","/*\n * D359: the verbs of a Resource, exposed with their own outcome, and the recognition that answers\n * «is this selected field a Resource». A consumer keeps a status — `inFlight` until the operation settles, the\n * settled `outcome` — while the Resource stays owned by the model that declared it: cancelling this wait cancels the\n * wait and not the load.\n */\n\nimport type {\n PaginationOutcome,\n PaginationState,\n Resource,\n ResourceOperationOutcome,\n ResourcePagination,\n ResourceState,\n} from '@opetope/core';\nimport type { ResourceKey } from '@opetope/core/internal';\nimport { isResourceValue } from '@opetope/runtime/internal';\n\nimport type { ResourceVerbHook } from './resource-verb-hook';\n\n/**\n * D439: the widest Resource, and the name says so. `Resource<never, ResourceKey>` said the opposite in both\n * arguments — `never` is the narrowest `Data` a reader can be handed and a named `Key` *requires* `state.key`, so\n * hardly any member of the family fitted it. Nothing was refused, because every value reaches this name through a\n * cast, which is exactly why the name could drift. `ResourceKeyField<never>` requires no field, so\n * `Resource<unknown, never, 'consumers' | 'model'>` is the one form every Resource satisfies — keyed or not, paginated or not.\n */\ntype AnyResource = Resource<unknown, never, 'consumers' | 'model'>;\n\n/**\n * D349, D359: a Resource is a structural contract a host may implement with an ordinary object, so the question the\n * type of a selected field asked is answered by the runtime that owns it — the registry of the materializations it\n * built, and the structural contract for everything else. Asking it here a second time would let the two answers\n * drift, and a host adapter typed as a hook reaches the render where `state` is `undefined` and `refresh.run()`\n * throws.\n */\nfunction isResource(value: unknown): boolean {\n return isResourceValue(value);\n}\n\n/*\n * D356, D486: the nested capability — the projected pagination state and a stable `loadNext`. `state` is the one\n * source of the status a view shows: it is shared by every consumer and survives a remount, which a command's own\n * `inFlight` and `outcome` did not, and it keeps its identity until the cursor, the kind or the error moves, so it is\n * also the key a list asks for the next page by. `loadNext` is a plain function that stays the same while the\n * component holds the same Resource, and it answers the outcome of the page it asked for.\n */\n/**\n * The nested capability — the projected pagination state and a stable `loadNext`.\n */\ntype ResourcePaginationHook<Cursor extends ResourceKey> = Readonly<{\n loadNext: (options?: { readonly retry?: boolean }) => Promise<PaginationOutcome<Cursor>>;\n state: PaginationState<Cursor>;\n}>;\n\n/*\n * D359: what `useResource` answers, and what a field of a `useModel` selection whose value is a Resource answers.\n * Producer failures are settled Resource outcomes; the consumer's cancellation of a wait is `cancelled` directly.\n */\n/**\n * What `useResource` answers, and what a field of a `useModel` selection whose value is a Resource answers.\n */\ntype ResourceHook<Data, Key extends ResourceKey = never> = Readonly<{\n /**\n * Present when the Resource was paginated (`ctx.resource.paginate`); `undefined` for every other Resource.\n */\n pagination: ResourcePaginationHook<ResourceKey> | undefined;\n refresh: ResourceVerbHook;\n reset: ResourceVerbHook;\n retry: ResourceVerbHook;\n state: ResourceState<Data, Key>;\n}>;\n\ntype PaginatedResourceHook<Data, Key extends ResourceKey, Cursor extends ResourceKey> = Omit<\n ResourceHook<Data, Key>,\n 'pagination'\n> &\n Readonly<{ pagination: ResourcePaginationHook<Cursor> }>;\n\ntype ResourceInvokers = Readonly<{\n /** D486: not a command — the stable function `pagination.loadNext` is, one per Resource identity. */\n loadNext: (options?: { readonly retry?: boolean }) => Promise<unknown>;\n refresh: () => Promise<ResourceOperationOutcome>;\n reset: () => Promise<ResourceOperationOutcome>;\n retry: () => Promise<ResourceOperationOutcome>;\n}>;\n\nfunction paginationOf(resource: AnyResource): ResourcePagination<ResourceKey, 'consumers' | 'model'> | undefined {\n const capability = (resource as { readonly pagination?: unknown }).pagination;\n\n if (typeof capability !== 'object' || capability === null) return undefined;\n\n return capability as ResourcePagination<ResourceKey, 'consumers' | 'model'>;\n}\n\n/**\n * One set per Resource identity. Selected and standalone hooks both pick their controller by the identity of the\n * invoker, so a set rebuilt on every render would reset `inFlight`, drop `outcome` and change `run` — which is why it\n * is memoised against the Resource itself and never assembled during a render.\n */\nfunction createResourceInvokers(resource: AnyResource): ResourceInvokers {\n // D349: a Resource is a structural contract, so every verb is called through it and never as a detached function.\n return {\n // Only a paginated Resource hands this out, and the capability it calls through is the one it was selected with.\n loadNext: options => paginationOf(resource)!.loadNext(options),\n refresh: () => resource.refresh(),\n reset: () => resource.reset(),\n retry: () => resource.retry(),\n };\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 key type from here instead of routing to `@opetope/runtime/internal` of its own.\n */\nexport { createResourceInvokers, isResource, paginationOf, resourceInvokers };\nexport type { AnyResource, PaginatedResourceHook, ResourceHook, ResourceInvokers, ResourceKey, ResourcePaginationHook };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\nimport type { ResourceDemand } from '@opetope/core/internal';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nconst noLeases: ReadonlySet<ResourceDemand> = 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 /** D359: one lease per Resource identity, diffed like a subscription, so an unchanged selection takes none. */\n private readonly leases = new Map<ResourceDemand, Subscription>();\n private readonly listeners = new Set<Listener>();\n private retained: ReadonlySet<ResourceDemand> = 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<ResourceDemand> = 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.enabled = false;\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.subscribeSources();\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: ResourceDemand): void {\n const lease: Subscription = { dispose: undefined };\n this.leases.set(resource, lease);\n const release = resource.acquire();\n\n if (typeof release !== 'function') throw new TypeError('Model selection resource must answer a release 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 // A committed selection owns demand before React installs its passive subscription.\n if (!this.enabled) 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 { useCallback, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\n\nimport type { Readable, Resource, ResourceKey, ResourceView } from '@opetope/core';\nimport { isResourceView, resourceViewDemands } from '@opetope/core/internal';\nimport type { ResourceDemand } from '@opetope/core/internal';\n\nimport { ModelSelectionStore } from './model-selection-store';\n\ntype SourceValue<Source> =\n Source extends ResourceView<infer Value> ? Value : Source extends Readable<infer Value> ? Value : never;\n\n/**\n * D469: what a mount that has read a view keeps for the rest of its life. `observe()` is asked once per view, so a\n * structural view whose `observe()` answers a new Readable each time is not resubscribed by every render, and the\n * one store holds the demand roots of the committed view — two views over one root hand it over without a release.\n */\nclass ViewDemand {\n readonly store = new ModelSelectionStore();\n private readonly observations = new WeakMap<object, Readable<unknown>>();\n\n observe(view: ResourceView<unknown>): Readable<unknown> {\n let observed = this.observations.get(view);\n\n if (observed === undefined) {\n observed = view.observe();\n this.observations.set(view, observed);\n }\n\n return observed;\n }\n\n /** Demand follows the committed source: a view's roots, or none at all for a plain Readable. */\n commit(source: object): void {\n const observed = this.observations.get(source);\n\n if (observed === undefined) this.store.close();\n else\n this.store.commit(\n new Set([observed]),\n new Set<ResourceDemand>(resourceViewDemands(source as ResourceView<unknown>)),\n );\n }\n}\n\n/**\n * D469: demand follows only the source that committed. A mount may switch between a ResourceView and a plain\n * Readable; the store exists only after it has read a view.\n */\ninterface SelectionInstance {\n views: ViewDemand | undefined;\n}\n\nfunction useSourceValue<Value>(source: Readable<Value> | ResourceView<Value>): Value {\n const instanceRef = useRef<SelectionInstance | undefined>(undefined);\n let instance = instanceRef.current;\n\n if (instance === undefined) {\n instance = { views: undefined };\n instanceRef.current = instance;\n }\n\n // A valid dual-protocol host is a view first, as in the read of a model selection (D466).\n let readable: Readable<Value>;\n let target: Readable<unknown> | ModelSelectionStore;\n\n if (isResourceView(source)) {\n const demand = (instance.views ??= new ViewDemand());\n readable = demand.observe(source) as Readable<Value>;\n target = demand.store;\n } else {\n readable = source;\n target = readable;\n }\n\n const subscribe = useCallback((listener: () => void) => target.subscribe(listener), [target]);\n const getSnapshot = useCallback(() => readable.getSnapshot(), [readable]);\n const selected = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useLayoutEffect(() => {\n // Commit in layout: releasing A can synchronously mutate B. React must see that write before its\n // post-commit snapshot check, or B's displayed value can remain stale.\n // React replays layout setup before restoring passive subscriptions in StrictMode and Activity.\n instance.views?.commit(source);\n }, [instance, source]);\n\n return selected;\n}\n\n/**\n * Subscribe to a Readable or lease a ResourceView for this committed mount.\n */\nexport function useReadable<\n Source extends Readable<unknown> | Resource<unknown, ResourceKey, 'consumers' | 'model'> | ResourceView<unknown>,\n>(\n source: Source extends Readable<unknown> | ResourceView<unknown>\n ? Source\n : 'read resource.state, or select the Resource in useModel',\n): SourceValue<Source> {\n return useSourceValue(source as Readable<SourceValue<Source>> | ResourceView<SourceValue<Source>>);\n}\n","import type { Command } from '@opetope/core';\nimport { isCallTarget } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\n/**\n * A bound command is the authentic `Command` the model field declares, so the common path adds no wrapper. Observable\n * call state belongs to the model as a `Readable` field (D71), so a binding carries no mode.\n */\ntype CommandBindingIdentity = Command<never, unknown>;\ntype CommandBinding<Input, Output> = Command<Input, Output>;\n\ntype CommandOutcome<Output> =\n | { readonly error: unknown; readonly kind: 'failed' }\n | { readonly reason: unknown; readonly kind: 'cancelled' }\n | { readonly kind: 'ok'; readonly value: Output };\n\ntype AnyCommandBinding = CommandBinding<never, unknown>;\n\ntype CommandBindingDefinition<Input, Output> = {\n readonly target: Command<Input, Output>;\n};\n\nconst bindingDefinitions = new WeakMap<CommandBindingIdentity, CommandBindingDefinition<never, unknown>>();\n\nfunction bindCommand<Input, Output>(target: Command<Input, Output>): CommandBinding<Input, Output> {\n if (arguments.length > 1) {\n throw new ContributionError(\n 'binding-invalid',\n 'Command follows the target it binds; binding options are forbidden.',\n );\n }\n\n if (!isCallTarget(target)) throw new ContributionError('binding-invalid', 'Command target is not authentic.');\n\n bindingDefinitions.set(target, { target });\n\n return target;\n}\n\nfunction getCommandBindingDefinition<Input, Output>(\n binding: Command<Input, Output>,\n): CommandBindingDefinition<Input, Output> {\n const definition = bindingDefinitions.get(binding);\n\n if (definition === undefined) throw new ContributionError('binding-invalid', 'Command binding is not authentic.');\n\n return definition as CommandBindingDefinition<Input, Output>;\n}\n\nfunction isCommandBinding(value: unknown): value is AnyCommandBinding {\n return typeof value === 'object' && value !== null && bindingDefinitions.has(value as CommandBindingIdentity);\n}\n\nexport { bindCommand, getCommandBindingDefinition, isCommandBinding };\nexport type { CommandOutcome };\n","import type { Command, ModelOf } from '@opetope/core';\nimport { isModel, isReadable, isResourceView } from '@opetope/core/internal';\nimport type { ExactKeys, ModelIdentity } from '@opetope/core/internal';\n\nimport { isCommandBinding } from './command';\nimport { ContributionError } from './errors';\nimport { isResource } from './resource-command';\n\nconst modelBindingBrand = Symbol('opetope.model-binding');\nconst modelBindingIdentityBrand = Symbol('opetope.model-binding-identity');\nconst modelBindingMarker = Object.freeze({});\n\ntype AnyModel = ModelIdentity;\ntype AnyCommand = Command<never, unknown>;\n\ninterface ModelBindingIdentity {\n readonly [modelBindingIdentityBrand]: true;\n}\n\ninterface ModelBinding<Declaration extends AnyModel> extends ModelBindingIdentity {\n readonly [modelBindingBrand]: (declaration: Declaration) => Declaration;\n}\n\ntype ModelBindingMetadata = {\n readonly commands: readonly AnyCommand[];\n readonly declaration: AnyModel;\n readonly model: object;\n};\n\nconst registry = new WeakMap<ModelBindingIdentity, ModelBindingMetadata>();\n\n/**\n * The declaration carries the field shape in its type; binding also validates fields supplied by host code.\n * ResourceViews travel unchanged, so observing a model never acquires its dependencies.\n */\nfunction modelFieldValue(declaration: AnyModel, model: object, field: PropertyKey): unknown {\n if (typeof field !== 'string') {\n throw new ContributionError('binding-invalid', `Model ${declaration.id} accepts only string fields.`);\n }\n\n const descriptor = Object.getOwnPropertyDescriptor(model, field);\n\n if (descriptor?.enumerable !== true || !('value' in descriptor)) {\n throw new ContributionError(\n 'binding-invalid',\n `Model ${declaration.id}.${field} requires an enumerable data binding.`,\n );\n }\n\n return descriptor.value;\n}\n\nfunction isObservedField(value: unknown): boolean {\n return isReadable(value) || isResource(value) || isResourceView(value) || typeof value === 'function';\n}\n\nfunction snapshotModelFields(declaration: AnyModel, model: object): readonly AnyCommand[] {\n const commands: AnyCommand[] = [];\n const fields = Reflect.ownKeys(model);\n\n if (fields.length === 0)\n throw new ContributionError('binding-invalid', `Model ${declaration.id} requires at least one field.`);\n\n for (const field of fields) {\n const value = modelFieldValue(declaration, model, field);\n\n // D349: a Resource is a field a model publishes, and it is not a `Readable`, so it is recognised by name.\n if (isObservedField(value)) continue;\n\n if (!isCommandBinding(value)) {\n throw new ContributionError(\n 'binding-invalid',\n `Model ${declaration.id}.${String(field)} requires a Readable, a Resource, a ResourceView or a bound command.`,\n );\n }\n\n commands.push(value);\n }\n\n return Object.freeze(commands);\n}\n\nfunction bindModel<Declaration extends AnyModel, const Keys extends PropertyKey = never>(\n declaration: Declaration,\n model: ExactKeys<Keys, ModelOf<Declaration>> & ModelOf<Declaration>,\n): ModelBinding<Declaration> {\n if (!isModel(declaration)) throw new ContributionError('binding-invalid', 'Model declaration is not authentic.');\n\n if (typeof model !== 'object' || model === null) {\n throw new ContributionError('binding-invalid', `Model ${declaration.id} requires a record binding.`);\n }\n\n const commands = snapshotModelFields(declaration, model);\n const binding = Object.freeze({\n [modelBindingBrand]: modelBindingMarker as unknown as (value: Declaration) => Declaration,\n [modelBindingIdentityBrand]: true as const,\n });\n registry.set(binding, { commands, declaration, model });\n\n return binding;\n}\n\nfunction getModelBindingMetadata(binding: ModelBindingIdentity): ModelBindingMetadata {\n const metadata = registry.get(binding);\n\n if (metadata === undefined) throw new ContributionError('binding-invalid', 'Model binding is not authentic.');\n\n return metadata;\n}\n\nexport { bindModel, getModelBindingMetadata };\nexport type { AnyCommand, AnyModel, ModelBindingIdentity };\n","import type {\n CallAuthority,\n CallController,\n CallDiagnostics,\n CallRunOptions,\n LifetimeLease,\n} from '@opetope/core/internal';\nimport { createCallController, createLifetimeController, reportDetachedError } from '@opetope/core/internal';\n\nimport { getCommandBindingDefinition } from './command';\nimport { ContributionError } from './errors';\nimport { getModelBindingMetadata } from './model-binding';\nimport type { AnyCommand, AnyModel, ModelBindingIdentity } from './model-binding';\n\ntype RuntimeCommandInvoker = {\n readonly run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;\n};\n\ntype CommandRecord = {\n readonly controller: CallController<never, unknown>;\n readonly invoker: RuntimeCommandInvoker;\n};\n\n/** What a mounted contribution grants its components: the models it may read and the commands it may invoke. */\ntype Frame = {\n readonly commandRecords: Map<AnyCommand, CommandRecord>;\n readonly models: Map<AnyModel, unknown>;\n};\n\ntype MountFrameLifecycle = {\n readonly isActive: () => boolean;\n readonly onClose: () => void;\n};\n\ntype MountFrameDiagnostics = {\n readonly active: boolean;\n readonly calls: readonly CallDiagnostics[];\n readonly commandRecords: number;\n readonly models: number;\n};\n\nlet nextMountId = 0;\n\nfunction buildFrame(supplied: readonly ModelBindingIdentity[]): {\n readonly commands: readonly AnyCommand[];\n readonly frame: Frame;\n} {\n const models = new Map<AnyModel, unknown>();\n const commands: AnyCommand[] = [];\n const bound = new Set<AnyCommand>();\n\n for (const candidate of supplied) {\n const binding = getModelBindingMetadata(candidate);\n\n if (models.has(binding.declaration)) {\n throw new ContributionError('duplicate', `Model ${binding.declaration.id} is bound twice in one mount.`);\n }\n\n models.set(binding.declaration, binding.model);\n\n for (const command of binding.commands) {\n // D197: one Command is one command of the mount, however many fields of the models name it. A second name is an\n // alias, not a second command, so it shares the record, the activation and the fence.\n if (bound.has(command)) continue;\n\n bound.add(command);\n commands.push(command);\n }\n }\n\n return { commands: Object.freeze(commands), frame: { commandRecords: new Map(), models } };\n}\n\n/**\n * The lifetime of one mounted contribution. `attach` claims it for the renderer, `detach` schedules the close on the\n * next microtask so a StrictMode remount reuses the same mount, and `close` fences every command it granted (D85).\n */\nclass MountFrameImpl {\n readonly frame: Frame;\n private authority: CallAuthority | undefined;\n private boundCommands: readonly AnyCommand[];\n private closeTicket = 0;\n private readonly isActive: (() => boolean) | undefined;\n private readonly lifetime: ReturnType<typeof createLifetimeController>;\n private onClose: (() => void) | undefined;\n private released = false;\n private rendererAttached = false;\n private readonly renderListeners = new Set<{ readonly listener: () => void }>();\n private state: 'active' | 'closed' | 'closing' | 'detached' = 'detached';\n\n constructor(models: readonly ModelBindingIdentity[], lifecycle?: MountFrameLifecycle) {\n const mountId = ++nextMountId;\n this.lifetime = createLifetimeController({ id: `contribution.${String(mountId)}` });\n this.isActive = lifecycle?.isActive;\n this.onClose = lifecycle?.onClose;\n const { commands, frame } = buildFrame(models);\n this.frame = frame;\n this.boundCommands = commands;\n this.buildCommandRecords(mountId);\n }\n\n attach(): boolean {\n if (this.state === 'closing') {\n this.closeTicket += 1;\n this.state = 'active';\n this.rendererAttached = true;\n\n return true;\n }\n\n if (this.state === 'closed') return false;\n\n if (this.state === 'active') throw new ContributionError('duplicate', 'One contribution mount cannot mount twice.');\n\n const authority = this.createAuthority(this.lifetime.open());\n\n try {\n for (const record of this.frame.commandRecords.values()) record.controller.activate();\n } catch (error) {\n this.authority = authority;\n this.state = 'active';\n this.close();\n\n throw error;\n }\n\n this.authority = authority;\n this.rendererAttached = true;\n this.state = 'active';\n\n return true;\n }\n\n close(): void {\n if (this.state === 'closed') return;\n\n this.state = 'closed';\n this.closeTicket += 1;\n this.lifetime.close();\n this.authority = undefined;\n\n for (const record of this.frame.commandRecords.values()) record.controller.close();\n\n const onClose = this.onClose;\n this.onClose = undefined;\n onClose?.();\n this.publishRenderable();\n\n if (!this.rendererAttached) this.releaseFrame();\n }\n\n detach(): void {\n if (!this.rendererAttached) return;\n\n this.rendererAttached = false;\n\n if (this.state === 'closed') {\n this.releaseFrame();\n\n return;\n }\n\n if (this.state !== 'active') return;\n\n this.state = 'closing';\n const ticket = ++this.closeTicket;\n\n queueMicrotask(() => {\n if (this.state === 'closing' && ticket === this.closeTicket) this.close();\n });\n }\n\n diagnostics(): MountFrameDiagnostics {\n const calls = [...this.frame.commandRecords.values()].map(record => record.controller.diagnostics());\n\n return Object.freeze({\n active: this.state === 'active',\n calls: Object.freeze(calls),\n commandRecords: this.frame.commandRecords.size,\n models: this.frame.models.size,\n });\n }\n\n getRenderableSnapshot = (): boolean => this.state !== 'closed' && (this.isActive === undefined || this.isActive());\n\n releaseClosedFrame(): void {\n if (this.state === 'closed') this.releaseFrame();\n }\n\n subscribeRenderable = (listener: () => void): (() => void) => {\n if (this.state === 'closed') return (): void => undefined;\n\n const registration = { listener };\n this.renderListeners.add(registration);\n let subscribed = true;\n\n return (): void => {\n if (!subscribed) return;\n\n subscribed = false;\n this.renderListeners.delete(registration);\n };\n };\n\n private buildCommandRecords(mountId: number): void {\n let commandIndex = 0;\n\n // `buildFrame` already reduced the models to their distinct Commands, so every command here gets its own record.\n for (const command of this.boundCommands) {\n // Authenticity check only: the binding carries no mode since observe was removed (D71).\n getCommandBindingDefinition(command);\n const controller = createCallController(command, {\n captureAuthority: this.captureAuthority,\n id: `contributionCommand.${String(mountId)}.${String(++commandIndex)}`,\n scheduling: { policy: 'parallel' },\n });\n this.frame.commandRecords.set(command, { controller, invoker: Object.freeze({ run: controller.run }) });\n }\n }\n\n private captureAuthority = (): CallAuthority => {\n const authority = this.authority;\n\n if (this.state !== 'active' || authority === undefined) {\n throw new ContributionError('inactive', 'Contribution mount is not active.');\n }\n\n authority.assertCurrent();\n\n return authority;\n };\n\n private createAuthority(lease: LifetimeLease): CallAuthority {\n return Object.freeze({\n assertCurrent: (): void => {\n if (this.state !== 'active') throw new ContributionError('inactive', 'Contribution mount is not active.');\n\n lease.assertCurrent();\n },\n isCurrent: (): boolean => this.state === 'active' && lease.isCurrent(),\n });\n }\n\n private publishRenderable(): void {\n for (const { listener } of [...this.renderListeners]) {\n try {\n listener();\n } catch (error) {\n reportDetachedError(error);\n }\n }\n }\n\n private releaseFrame(): void {\n if (this.released) return;\n\n this.released = true;\n this.frame.commandRecords.clear();\n this.frame.models.clear();\n this.boundCommands = [];\n this.renderListeners.clear();\n }\n}\n\nexport { MountFrameImpl };\nexport type { Frame };\n","import { useInsertionEffect, useLayoutEffect, useSyncExternalStore } from 'react';\nimport type { PropsWithChildren, ReactElement } from 'react';\n\nimport { FrameContext } from './mount-context';\nimport type { MountFrameImpl } from './mount-frame';\n\nfunction MountProvider({\n children,\n mount,\n}: PropsWithChildren<{ readonly mount: MountFrameImpl }>): ReactElement | null {\n const renderable = useSyncExternalStore(\n mount.subscribeRenderable,\n mount.getRenderableSnapshot,\n mount.getRenderableSnapshot,\n );\n\n useInsertionEffect(() => {\n const attached = mount.attach();\n\n if (!attached) return undefined;\n\n return () => mount.detach();\n }, [mount]);\n\n useLayoutEffect(() => {\n if (!renderable) mount.releaseClosedFrame();\n }, [mount, renderable]);\n\n return renderable ? <FrameContext value={mount.frame}>{children}</FrameContext> : null;\n}\n\nexport { MountProvider };\n","import { memo, useCallback, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { ComponentType, ReactElement } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport {\n closeStateFromOwner,\n createAggregateError,\n createState,\n getContributionOwner,\n isReadable,\n isResourceView,\n reportDetachedError,\n setStateFromOwner,\n} from '@opetope/core/internal';\nimport type { ModelIdentity, State } from '@opetope/core/internal';\nimport { openContributionModels, reportRuntimeFailure, requireContributionModelPlans } from '@opetope/runtime/internal';\nimport type { ContributionModelBundle, RuntimeErrorReporter } from '@opetope/runtime/internal';\n\nimport { bindCommand, isCommandBinding } from './command';\nimport { ContributionIsolation, useContributionErrorContent } from './contribution-isolation';\nimport { bindModel } from './model-binding';\nimport { isResource } from './resource-command';\nimport type { ModelBindingIdentity } from './model-binding';\nimport { MountFrameImpl } from './mount-frame';\nimport { MountProvider } from './mount-provider';\nimport { useReadable } from './readable-hooks';\n\ntype AnyModel = ModelIdentity;\ntype ContributionAuthority = {\n /** The feature whose generation published this contribution (D257). */\n readonly feature?: DeclarationId;\n readonly models: readonly (readonly [AnyModel, unknown])[];\n /** The reporter of the feature that published this contribution: a contained failure is its diagnostic, not ours. */\n readonly reporter?: RuntimeErrorReporter;\n};\ntype ModelFixture = readonly [declaration: AnyModel, model: unknown];\n\ntype ContributionValue = {\n readonly Component: ComponentType<never>;\n readonly models?: readonly object[] | undefined;\n readonly props?: ((slotProps: never) => object) | undefined;\n};\n\ntype ContributionMountProps = {\n readonly authority?: ContributionAuthority | undefined;\n readonly contribution: ContributionValue;\n /** The published entry this mount renders, and the slot it renders in: the identity of a contained failure (D257). */\n readonly contributionId: DeclarationId;\n readonly fixtures?: readonly ModelFixture[];\n readonly slotProps: object;\n readonly targetId: DeclarationId;\n};\n\ntype MountBundle = {\n readonly frame: MountFrameImpl;\n readonly isOpen: () => boolean;\n readonly props: State<object>;\n};\n\ntype MountOwner = {\n readonly authority: ContributionAuthority | undefined;\n bundle: MountBundle | undefined;\n readonly contribution: ContributionValue;\n readonly fixtures: readonly ModelFixture[] | undefined;\n retired: boolean;\n};\n\ntype CommittedMount = {\n readonly bundle: MountBundle;\n readonly owner: MountOwner;\n};\n\nfunction closeOwnedMount(owner: MountOwner): void {\n const bundle = owner.bundle;\n owner.bundle = undefined;\n bundle?.frame.close();\n}\n\n/**\n * Model fields arrive as authentic kernel calls; binding them registers the command records the frame needs. D349: a\n * Resource is none of those and needs none of it — it is a field the selection reads on its own terms, so it travels\n * to the frame unchanged instead of being offered to `bindCommand`, which would refuse it as inauthentic.\n */\nfunction bindFrameModel(declaration: AnyModel, model: unknown): ModelBindingIdentity {\n if (typeof model !== 'object' || model === null) return bindModel(declaration as never, model as never);\n\n const passesThrough = (value: unknown): boolean =>\n isReadable(value) ||\n isResource(value) ||\n isResourceView(value) ||\n typeof value === 'function' ||\n isCommandBinding(value);\n const fields = Object.entries(model).map(([key, value]): readonly [string, unknown] => [\n key,\n passesThrough(value) ? value : bindCommand(value as never),\n ]);\n\n return bindModel(declaration as never, Object.fromEntries(fields) as never);\n}\n\n/**\n * D70: the authority of a contribution belongs to the entry a feature published, not to the value it wrote. One\n * static contribution object may be published by two features, and each mount must read the models of its own.\n */\nfunction contributionAuthority(entry: unknown): ContributionAuthority | undefined {\n return getContributionOwner(entry) as ContributionAuthority | undefined;\n}\n\n/** A mount with no publishing generation behind it: a fixture of a component test grants nothing and reports nowhere. */\nconst unownedContribution: ContributionAuthority = { models: [] };\n\nfunction cleanupFailedMount(close: () => void, cause: unknown): never {\n try {\n close();\n } catch (error) {\n throw createAggregateError([cause, error], 'Contribution mount setup and cleanup failed.', { cause });\n }\n\n throw cause;\n}\n\nfunction contributionModels(\n contribution: ContributionValue,\n slotProps: object,\n fixtures: readonly ModelFixture[],\n owner: ContributionAuthority | undefined,\n): {\n readonly bundle: ContributionModelBundle | undefined;\n readonly models: readonly ModelFixture[];\n readonly props: State<object>;\n} {\n const props = createState(slotProps);\n const { models: granted, reporter } = owner ?? unownedContribution;\n\n try {\n const plans = requireContributionModelPlans(contribution.models);\n const bundle = plans.length === 0 ? undefined : openContributionModels(plans, props, reporter);\n\n return {\n bundle,\n models: [...granted, ...(bundle?.models ?? []), ...fixtures],\n props,\n };\n } catch (error) {\n return cleanupFailedMount(() => closeStateFromOwner(props), error);\n }\n}\n\nfunction createMountBundle(\n contribution: ContributionValue,\n slotProps: object,\n fixtures: readonly ModelFixture[],\n authority: ContributionAuthority | undefined,\n): MountBundle {\n const { bundle, models, props } = contributionModels(contribution, slotProps, fixtures, authority);\n let closed = false;\n const close = (): void => {\n if (closed) return;\n\n closed = true;\n\n try {\n void bundle?.close().catch(reportDetachedError);\n } finally {\n closeStateFromOwner(props);\n }\n };\n try {\n const frame = new MountFrameImpl(\n models.map(([declaration, model]) => bindFrameModel(declaration, model)),\n { isActive: () => !closed, onClose: close },\n );\n\n return { frame, isOpen: () => !closed, props };\n } catch (error) {\n return cleanupFailedMount(close, error);\n }\n}\n\nfunction createComponentElement(Component: ComponentType<never>, props: object): ReactElement {\n const Rendered = Component as ComponentType<object>;\n\n return <Rendered {...props} />;\n}\n\ntype MountedContributionProps = {\n readonly bundle: MountBundle;\n readonly contribution: ContributionValue;\n};\n\n/** Reads the committed props of one live mount: every hook here sees a bundle that already exists. */\nfunction MountedContribution({ bundle, contribution }: MountedContributionProps): ReactElement {\n const committedProps = useReadable(bundle.props);\n const componentProps = useMemo(\n () => (contribution.props === undefined ? committedProps : contribution.props(committedProps as never)),\n [committedProps, contribution],\n );\n\n return (\n <MountProvider mount={bundle.frame}>{createComponentElement(contribution.Component, componentProps)}</MountProvider>\n );\n}\n\n/**\n * D188: a mount is created in commit, never in render. The first pass renders nothing, the layout effect builds the\n * bundle from the props that committed and publishes it, and React flushes that update before paint. A render that\n * is thrown away — StrictMode's double invoke, an abandoned concurrent pass, a suspended sibling — therefore\n * creates no models at all, so there is nothing to sweep, adopt or replace.\n */\nfunction ContributionMount({\n authority,\n contribution,\n fixtures,\n slotProps,\n}: ContributionMountProps): ReactElement | null {\n const owner = useMemo<MountOwner>(\n () => ({ authority, bundle: undefined, contribution, fixtures, retired: false }),\n [authority, contribution, fixtures],\n );\n const [committed, setCommitted] = useState<CommittedMount | undefined>(undefined);\n const bundle = committed?.owner === owner ? committed.bundle : undefined;\n const committedSlotProps = useRef(slotProps);\n\n useLayoutEffect(() => {\n committedSlotProps.current = slotProps;\n });\n\n // Suspense disconnects layout effects while hiding, but insertion cleanup only retires an actual identity.\n // Do not close Readables in insertion: their listeners may update React. A hidden tree has no layout cleanup\n // left to run on unmount, so the microtask also releases a mount that never becomes visible again.\n useInsertionEffect(\n () => () => {\n owner.retired = true;\n queueMicrotask(() => {\n try {\n closeOwnedMount(owner);\n } catch (error) {\n reportDetachedError(error);\n }\n });\n },\n [owner],\n );\n\n // The identity of the contribution owns the mount; a reveal and StrictMode reuse what its first commit created.\n useLayoutEffect(() => {\n const created = (owner.bundle ??= createMountBundle(\n owner.contribution,\n committedSlotProps.current,\n owner.fixtures ?? [],\n owner.authority,\n ));\n setCommitted(current => (current?.owner === owner ? current : { bundle: created, owner }));\n\n return () => {\n if (owner.retired) closeOwnedMount(owner);\n };\n }, [owner]);\n\n // Both the component and its models read the same committed snapshot, published before paint.\n useLayoutEffect(() => {\n if (bundle?.isOpen() === true) setStateFromOwner(bundle.props, slotProps);\n }, [bundle, slotProps]);\n\n if (bundle === undefined) return null;\n\n return <MountedContribution bundle={bundle} contribution={contribution} />;\n}\n\n/**\n * D256: every mount is a boundary. The isolation sits above `ContributionMount`, so it contains the render of the\n * component and the commit that builds the mount alike, and a retry rebuilds the mount instead of reusing the bundle\n * that failed. Containment is unconditional; the content a failure shows is the host policy of `ContributionBoundary`.\n */\nfunction IsolatedContributionMount(props: ContributionMountProps): ReactElement {\n const { authority, contribution, contributionId, fixtures, targetId } = props;\n const content = useContributionErrorContent();\n const identity = useMemo(() => ({ authority, contribution, fixtures }), [authority, contribution, fixtures]);\n const feature = authority?.feature;\n const source = useMemo(\n () => ({ contribution: contributionId, feature, target: targetId }),\n [contributionId, feature, targetId],\n );\n const reporter = authority?.reporter;\n const report = useCallback((error: unknown) => reportRuntimeFailure(error, reporter), [reporter]);\n\n return (\n <ContributionIsolation content={content} identity={identity} report={report} source={source}>\n <ContributionMount {...props} />\n </ContributionIsolation>\n );\n}\n\n/**\n * One mount, one memo: `Slot` recreates its children whenever `entries` changes, so publishing or withholding one\n * contribution used to re-render every other mount of that target. The props are the contribution, the slot props\n * and the fixtures, all of which are stable while nothing about that mount changed.\n */\nconst MemoizedContributionMount = memo(IsolatedContributionMount);\n\nexport { contributionAuthority, MemoizedContributionMount as ContributionMount };\nexport type { ContributionValue, ModelFixture };\n"],"names":["FrameContext","createContext","useFrame","frame","useContext","ContributionError","isResource","value","isResourceValue","paginationOf","resource","capability","createResourceInvokers","options","resourceInvokers","cache","existing","invokers","noLeases","releaseSubscriptions","subscriptions","failures","subscription","dispose","error","createAggregateError","ModelSelectionStore","sources","retained","notify","listener","source","held","removed","lease","release","ViewDemand","view","observed","resourceViewDemands","useSourceValue","instanceRef","useRef","instance","readable","target","isResourceView","demand","subscribe","useCallback","getSnapshot","selected","useSyncExternalStore","useLayoutEffect","_a","useReadable","bindingDefinitions","bindCommand","isCallTarget","getCommandBindingDefinition","binding","definition","isCommandBinding","modelBindingBrand","modelBindingIdentityBrand","modelBindingMarker","registry","modelFieldValue","declaration","model","field","descriptor","isObservedField","isReadable","snapshotModelFields","commands","fields","bindModel","isModel","getModelBindingMetadata","metadata","nextMountId","buildFrame","supplied","models","bound","candidate","command","MountFrameImpl","lifecycle","mountId","createLifetimeController","authority","record","onClose","ticket","calls","registration","subscribed","commandIndex","controller","createCallController","reportDetachedError","MountProvider","children","mount","renderable","useInsertionEffect","_jsx","closeOwnedMount","owner","bundle","bindFrameModel","passesThrough","key","contributionAuthority","entry","getContributionOwner","unownedContribution","cleanupFailedMount","close","cause","contributionModels","contribution","slotProps","fixtures","props","createState","granted","reporter","plans","requireContributionModelPlans","openContributionModels","closeStateFromOwner","createMountBundle","closed","createComponentElement","Component","MountedContribution","committedProps","componentProps","useMemo","ContributionMount","committed","setCommitted","useState","committedSlotProps","created","current","setStateFromOwner","IsolatedContributionMount","contributionId","targetId","content","useContributionErrorContent","identity","feature","report","reportRuntimeFailure","ContributionIsolation","MemoizedContributionMount","memo"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{jsx as s}from"react/jsx-runtime";import{useRef as l,createContext as p,useContext as h,Component as u}from"react";import{markCancellation as a,isCancellation as C}from"@opetope/core/internal";class c extends Error{code;cause;contribution;feature;name="ContributionError";target;constructor(t,r,e={}){super(r),this.code=t,this.cause=e.cause,this.contribution=e.contribution,this.feature=e.feature,this.target=e.target,t==="inactive"&&a(this)}}const b={current:null},d=p(b);function m({children:n,error:t}){const r=l(null);return r.current=t,s(d,{value:r,children:n})}function y(){return h(d)}function f(n,t,r){const e=n==="render-failed"?"Contribution":"Error content of contribution",i=t.feature===void 0?"":` of feature ${String(t.feature)}`,o=new c(n,`${e} ${String(t.contribution)}${i} failed in slot ${String(t.target)}.`,{cause:r,...t});return C(r)?a(o):o}function g({content:n,error:t,retry:r,source:e}){return n({...e,error:t,retry:r})}class E extends u{state={failed:!1};static getDerivedStateFromError(){return{failed:!0}}componentDidCatch(t){this.props.report(f("error-content-failed",this.props.source,t))}render(){if(this.state.failed)return null;const{content:t,error:r,retry:e,source:i}=this.props,o=t.current;return typeof o=="function"?s(g,{content:o,error:r,retry:e,source:i}):o}}class x extends u{state={error:void 0,failed:!1,identity:void 0};static getDerivedStateFromError(t){return{error:t,failed:!0}}static getDerivedStateFromProps(t,r){return r.identity===t.identity?null:{error:void 0,failed:!1,identity:t.identity}}componentDidCatch(t){this.props.report(f("render-failed",this.props.source,t))}retry=()=>{this.setState(t=>t.failed?{error:void 0,failed:!1,identity:t.identity}:null)};render(){const{error:t,failed:r}=this.state;return r?s(E,{content:this.props.content,error:t,report:this.props.report,retry:this.retry,source:this.props.source}):this.props.children}}export{c as C,m as a,x as b,y as u};
2
- //# sourceMappingURL=contribution-isolation-Bzfbt4iM.js.map
1
+ import{jsx as s}from"react/jsx-runtime";import{useRef as l,createContext as p,useContext as h,Component as u}from"react";import{markCancellation as a,isCancellation as C}from"@opetope/core/internal";class c extends Error{code;cause;contribution;feature;name="ContributionError";target;constructor(t,r,e={}){super(r),this.code=t,this.cause=e.cause,this.contribution=e.contribution,this.feature=e.feature,this.target=e.target,t==="inactive"&&a(this)}}const b={current:null},d=p(b);function m({children:n,error:t}){const r=l(null);return r.current=t,s(d,{value:r,children:n})}function y(){return h(d)}function f(n,t,r){const e=n==="render-failed"?"Contribution":"Error content of contribution",i=t.feature===void 0?"":` of feature ${String(t.feature)}`,o=new c(n,`${e} ${String(t.contribution)}${i} failed in slot ${String(t.target)}.`,{cause:r,...t});return C(r)?a(o):o}function g({content:n,error:t,retry:r,source:e}){return n({...e,error:t,retry:r})}class E extends u{state={failed:!1};static getDerivedStateFromError(){return{failed:!0}}componentDidCatch(t){this.props.report(f("error-content-failed",this.props.source,t))}render(){if(this.state.failed)return null;const{content:t,error:r,retry:e,source:i}=this.props,o=t.current;return typeof o=="function"?s(g,{content:o,error:r,retry:e,source:i}):o}}class x extends u{state={error:void 0,failed:!1,identity:void 0};static getDerivedStateFromError(t){return{error:t,failed:!0}}static getDerivedStateFromProps(t,r){return r.identity===t.identity?null:{error:void 0,failed:!1,identity:t.identity}}componentDidCatch(t){this.props.report(f("render-failed",this.props.source,t))}retry=()=>{this.setState(t=>t.failed?{error:void 0,failed:!1,identity:t.identity}:null)};render(){const{error:t,failed:r}=this.state;return r?s(E,{content:this.props.content,error:t,report:this.props.report,retry:this.retry,source:this.props.source}):this.props.children}}export{m as C,c as a,x as b,y as u};
2
+ //# sourceMappingURL=contribution-isolation-Ig70xFiC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contribution-isolation-Ig70xFiC.js","sources":["../src/errors.ts","../src/contribution-isolation.tsx"],"sourcesContent":["import type { DeclarationId } from '@opetope/core';\nimport { markCancellation } from '@opetope/core/internal';\n\ntype ContributionErrorCode =\n | 'binding-invalid'\n | 'duplicate'\n | 'error-content-failed'\n | 'inactive'\n | 'missing'\n | 'render-failed';\n\n/**\n * D257: what a contained failure says about itself. The mount is the only place that knows all three, and one\n * application reporter serves every feature, so a bare `TypeError` in it would name neither the surface nor the owner.\n */\ntype ContributionErrorDetails = {\n readonly cause?: unknown;\n readonly contribution?: DeclarationId | undefined;\n readonly feature?: DeclarationId | undefined;\n readonly target?: DeclarationId | undefined;\n};\n\n/*\n * One class per subject (D69): after D85 the subject is the contribution mount, which is what grants models and\n * fences commands, so model binding failures are its codes rather than a second class.\n */\n/**\n * Failure reported for a contribution mount, including its model bindings.\n */\nexport class ContributionError extends Error {\n readonly cause: unknown;\n\n /**\n * The published entry, `<feature>.<provides key>`; absent where a failure has no published mount behind it.\n */\n readonly contribution: DeclarationId | undefined;\n\n readonly feature: DeclarationId | undefined;\n\n override readonly name = 'ContributionError';\n\n readonly target: DeclarationId | undefined;\n\n constructor(\n readonly code: ContributionErrorCode,\n message: string,\n details: ContributionErrorDetails = {},\n ) {\n super(message);\n this.cause = details.cause;\n this.contribution = details.contribution;\n this.feature = details.feature;\n this.target = details.target;\n\n if (code === 'inactive') markCancellation(this);\n }\n}\n\nexport type { ContributionErrorCode };\n","import { Component, createContext, useContext, useRef } from 'react';\nimport type { ReactNode, RefObject } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport { isCancellation, markCancellation } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\n/**\n * Which contribution failed. The entry id is `<feature>.<provides key>` and the target is the slot it renders\n * in, so one application-wide branch can answer by surface without naming every contribution.\n */\ntype ContributionSource = {\n readonly contribution: DeclarationId;\n readonly feature: DeclarationId | undefined;\n readonly target: DeclarationId;\n};\n\n/**\n * What a failed contribution shows. The host error branch receives the failure itself plus the retry\n * that belongs to this contribution.\n */\ntype ContributionFailure = ContributionSource & {\n readonly error: unknown;\n readonly retry: () => void;\n};\n\ntype ContributionErrorContent = ((failure: ContributionFailure) => ReactNode) | ReactNode;\n\ntype ContributionBoundaryProps = {\n readonly children: ReactNode;\n readonly error: ContributionErrorContent;\n};\n\ntype ContributionErrorContentRef = RefObject<ContributionErrorContent>;\n\nconst noContributionErrorContent: ContributionErrorContentRef = { current: null };\n\n/**\n * One host policy for every mount below it: a contribution whose render or commit throws is replaced by this content\n * instead of reaching the host's own boundary. Containment does not depend on the provider — without it a failed\n * contribution renders nothing — so an application declares the boundary once, at the root, and never per slot.\n *\n * The context carries a stable ref, never the content itself: a host writes the error branch inline, and a context\n * value that changed with it would re-render every mount of the application and undo the per-mount memo (D130).\n * Writing the latest callback into a ref that only readers read is the shape `useModel` already uses for its selector\n * (D214); a mount reads it once, when it is already failing, so content replaced mid-failure lands on the next one.\n */\nconst ErrorContentContext = createContext<ContributionErrorContentRef>(noContributionErrorContent);\n\nfunction ContributionBoundary({ children, error }: ContributionBoundaryProps): ReactNode {\n const content = useRef<ContributionErrorContent>(null);\n content.current = error;\n\n return <ErrorContentContext value={content}>{children}</ErrorContentContext>;\n}\n\nfunction useContributionErrorContent(): ContributionErrorContentRef {\n return useContext(ErrorContentContext);\n}\n\n/**\n * A `RuntimeErrorReporter` takes one argument, so the identity of a contained failure travels inside the error.\n * Cancellation is a brand of the failure, not of a class (D69): a wrapper that dropped it would turn every teardown\n * race a host filters out into a product failure it has to read.\n */\nfunction contributionFailure(\n code: 'error-content-failed' | 'render-failed',\n source: ContributionSource,\n cause: unknown,\n): ContributionError {\n const subject = code === 'render-failed' ? 'Contribution' : 'Error content of contribution';\n const publisher = source.feature === undefined ? '' : ` of feature ${String(source.feature)}`;\n const failure = new ContributionError(\n code,\n `${subject} ${String(source.contribution)}${publisher} failed in slot ${String(source.target)}.`,\n { cause, ...source },\n );\n\n return isCancellation(cause) ? markCancellation(failure) : failure;\n}\n\ntype ContributionErrorProps = {\n readonly content: (failure: ContributionFailure) => ReactNode;\n readonly error: unknown;\n readonly retry: () => void;\n readonly source: ContributionSource;\n};\n\n/** The host callback runs in a child, never in the render of a boundary: a boundary cannot catch its own render. */\nfunction ContributionErrorContent({ content, error, retry, source }: ContributionErrorProps): ReactNode {\n return content({ ...source, error, retry });\n}\n\ntype ContributionFailureViewProps = {\n readonly content: ContributionErrorContentRef;\n readonly error: unknown;\n readonly report: (error: unknown) => void;\n readonly retry: () => void;\n readonly source: ContributionSource;\n};\n\n/**\n * The second stage of containment: error content written by the host is foreign code like any other, and a mount that\n * is already failing must not escalate because its replacement failed too. It exists only after a failure, so the\n * successful path pays for one boundary, not two.\n */\nclass ContributionFailureView extends Component<ContributionFailureViewProps, { readonly failed: boolean }> {\n override state: { readonly failed: boolean } = { failed: false };\n\n static getDerivedStateFromError(): { readonly failed: boolean } {\n return { failed: true };\n }\n\n override componentDidCatch(error: unknown): void {\n this.props.report(contributionFailure('error-content-failed', this.props.source, error));\n }\n\n override render(): ReactNode {\n if (this.state.failed) return null;\n\n const { content, error, retry, source } = this.props;\n const declared = content.current;\n\n return typeof declared === 'function' ? (\n <ContributionErrorContent content={declared} error={error} retry={retry} source={source} />\n ) : (\n declared\n );\n }\n}\n\ntype ContributionIsolationProps = {\n readonly children: ReactNode;\n readonly content: ContributionErrorContentRef;\n /** The mount this boundary belongs to: a replaced contribution starts clean instead of inheriting a dead failure. */\n readonly identity: object;\n readonly report: (error: unknown) => void;\n readonly source: ContributionSource;\n};\n\n/**\n * `failed` is a flag, not the presence of `error`: foreign code may throw `undefined`, and a sentinel would read that\n * as a healthy mount, render the children again and fail again until React gives up.\n */\ntype ContributionIsolationState = {\n readonly error: unknown;\n readonly failed: boolean;\n readonly identity: object | undefined;\n};\n\nclass ContributionIsolation extends Component<ContributionIsolationProps, ContributionIsolationState> {\n override state: ContributionIsolationState = { error: undefined, failed: false, identity: undefined };\n\n static getDerivedStateFromError(error: unknown): Pick<ContributionIsolationState, 'error' | 'failed'> {\n return { error, failed: true };\n }\n\n static getDerivedStateFromProps(\n props: ContributionIsolationProps,\n state: ContributionIsolationState,\n ): ContributionIsolationState | null {\n return state.identity === props.identity ? null : { error: undefined, failed: false, identity: props.identity };\n }\n\n override componentDidCatch(error: unknown): void {\n this.props.report(contributionFailure('render-failed', this.props.source, error));\n }\n\n /** Retry rebuilds the mount: the children remount, so their models are created again rather than reused. */\n private readonly retry = (): void => {\n this.setState(current => (current.failed ? { error: undefined, failed: false, identity: current.identity } : null));\n };\n\n override render(): ReactNode {\n const { error, failed } = this.state;\n\n if (!failed) return this.props.children;\n\n return (\n <ContributionFailureView\n content={this.props.content}\n error={error}\n report={this.props.report}\n retry={this.retry}\n source={this.props.source}\n />\n );\n }\n}\n\nexport { ContributionBoundary, ContributionIsolation, useContributionErrorContent };\nexport type { ContributionBoundaryProps, ContributionErrorContent, ContributionFailure };\n"],"names":["ContributionError","code","message","details","markCancellation","noContributionErrorContent","ErrorContentContext","createContext","ContributionBoundary","children","error","content","useRef","_jsx","useContributionErrorContent","useContext","contributionFailure","source","cause","subject","publisher","failure","isCancellation","ContributionErrorContent","retry","ContributionFailureView","Component","declared","ContributionIsolation","props","state","current","failed"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { Component } from 'react';
2
2
  import type { ReactNode, RefObject } from 'react';
3
3
  import type { DeclarationId } from '@opetope/core';
4
4
  /**
5
- * D257: which contribution failed. The entry id is `<feature>.<provides key>` and the target is the slot it renders
5
+ * Which contribution failed. The entry id is `<feature>.<provides key>` and the target is the slot it renders
6
6
  * in, so one application-wide branch can answer by surface without naming every contribution.
7
7
  */
8
8
  type ContributionSource = {
@@ -11,8 +11,8 @@ type ContributionSource = {
11
11
  readonly target: DeclarationId;
12
12
  };
13
13
  /**
14
- * D256: what a failed contribution shows. The shape is the one `FeatureBoundary` already uses for its failure branch,
15
- * so a host writes one kind of error content for both: the failure itself plus the retry that belongs to it.
14
+ * What a failed contribution shows. The host error branch receives the failure itself plus the retry
15
+ * that belongs to this contribution.
16
16
  */
17
17
  type ContributionFailure = ContributionSource & {
18
18
  readonly error: unknown;
package/dist/errors.d.ts CHANGED
@@ -11,13 +11,14 @@ type ContributionErrorDetails = {
11
11
  readonly target?: DeclarationId | undefined;
12
12
  };
13
13
  /**
14
- * One class per subject (D69): after D85 the subject is the contribution mount, which is what grants models and
15
- * fences commands, so model binding failures are its codes rather than a second class.
14
+ * Failure reported for a contribution mount, including its model bindings.
16
15
  */
17
16
  export declare class ContributionError extends Error {
18
17
  readonly code: ContributionErrorCode;
19
18
  readonly cause: unknown;
20
- /** The published entry, `<feature>.<provides key>`; absent where a failure has no published mount behind it. */
19
+ /**
20
+ * The published entry, `<feature>.<provides key>`; absent where a failure has no published mount behind it.
21
+ */
21
22
  readonly contribution: DeclarationId | undefined;
22
23
  readonly feature: DeclarationId | undefined;
23
24
  readonly name = "ContributionError";
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export { useCommand } from './command-hook.js';
3
3
  export type { CommandHook } from './command-hook.js';
4
4
  export { ContributionError } from './errors.js';
5
5
  export { useModel } from './model-hook.js';
6
- export { useReadable, useSelector } from './readable-hooks.js';
6
+ export type { SelectionContext } from './model-selection-snapshot.js';
7
+ export { useReadable } from './readable-hooks.js';
7
8
  export { requiresModels } from './requires-models.js';
8
9
  export { useResource } from './resource-hook.js';
9
10
  export type { PaginatedResourceHook, ResourceHook, ResourcePaginationHook } from './resource-command.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useState as O,useInsertionEffect as E,useLayoutEffect as y,useMemo as l,useSyncExternalStore as S,useRef as k,useCallback as P,useEffect as J,createElement as N,Fragment as Q}from"react";import{CommandError as X,declarationId as Y}from"@opetope/core";import{compatibleAbortReason as Z,isCancellation as _,reportDetachedError as I,isCallTarget as ee,isResourceView as A,resourceViewDemands as K,resourceViewReadable as b,isReadable as te,createAggregateError as ne,acquireResourceDemand as se,createContributionTarget as ie}from"@opetope/core/internal";import{C as f}from"./contribution-isolation-Bzfbt4iM.js";import{u as C,r as re,i as oe,p as ce,c as ae,a as H,M as ue,b as de}from"./contribution-frame-Bh104T5Z.js";import{d as ht}from"./contribution-frame-Bh104T5Z.js";function q(t,e){var n;if(_(e))return{reason:e,kind:"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(I)}catch(s){I(s)}return{error:e,kind:"failed"}}function fe(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 le(t,e){let n;const s=new Promise(i=>{n=i});return{input:t,options:e===void 0?void 0:{...e},promise:s,resolve:n}}function he(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 f("inactive","Command consumer is not mounted."),kind:"cancelled"});const s=this.cancelledInput(n);if(s!==void 0)return Promise.resolve(s);const i=le(e,n);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.",Z(e)),kind:"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,he(e,n),(s=this.notify)==null||s.call(this,this.inFlight>0,n))}invoke(e){let n;try{n=this.invoker.run(e.input,fe(e.options)),e.input=void 0}catch(s){this.finish(e,q(e.options,s));return}n.then(s=>this.finish(e,{kind:"ok",value:s}),s=>this.finish(e,q(e.options,s)))}start(e){var n;this.inFlight+=1,(n=this.notify)==null||n.call(this,!0),this.invoke(e)}}const be={inFlight:!1,outcome:void 0};function pe(t){return t===void 0||t.kind==="cancelled"?void 0:t}function ge(t,e,n,s){return s===void 0&&t.slot===e&&t.inFlight===n}function me(t,e,n,s){const i=pe(s);if(ge(t,e,n,i))return t;const r=t.slot===e?t.outcome:void 0;return{inFlight:n,outcome:i??r,slot:e}}function we(t){const[e]=O(()=>new WeakMap);let n=e.get(t);return n===void 0&&(n=new h(t),e.set(t,n)),n}function p(t){const[e,n]=O(()=>({inFlight:!1,outcome:void 0,slot:t}));E(()=>t.attach((o,c)=>n(a=>me(a,t,o,c))),[t]),y(()=>t.synchronize(),[t]);const{inFlight:s,outcome:i}=e.slot===t?e:be,r=t.run;return l(()=>({inFlight:s,outcome:i,run:r}),[s,i,r])}function ye(t){const n=C().commandRecords.get(t);if(n===void 0)throw new f("missing",`Command ${t.id} is not bound in this contribution.`);return p(we(n.invoker))}const Se=()=>{},$=()=>Se,ke=()=>0,ve=Object.freeze({});function g(t){return"kind"in t}function m(t){const e=new h(t);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:t,slot:e}}function Re(t,e){return t.inFlight===e.inFlight&&Object.is(t.outcome,e.outcome)}function Oe(t,e,n){const s=t.hook;if(n===void 0&&s.inFlight===e)return!1;const i=n===void 0||n.kind==="cancelled"?void 0:n,r={inFlight:e,outcome:i??s.outcome,run:s.run};return Re(s,r)?!1:(t.hook=r,!0)}function Ee(t,e){return t!==void 0&&!g(t)&&t.invoker===e?t:m(e)}function D(t,e){return{pagination:t.paginated?{loadNext:t.loadNext.hook,state:e.pagination}:void 0,refresh:t.refresh.hook,reset:t.reset.hook,retry:t.retry.hook,state:e.state}}function Ce(t,e){const n={kind:"resource",loadNext:m(t.loadNext),paginated:e.pagination!==void 0,refresh:m(t.refresh),reset:m(t.reset),retry:m(t.retry)};return{...n,hook:D(n,e)}}function Me(t){var n;const{hook:e}=t;return e.refresh===t.refresh.hook&&e.reset===t.reset.hook&&e.retry===t.retry.hook&&(!t.paginated||((n=e.pagination)==null?void 0:n.loadNext)===t.loadNext.hook)}function ze(t,e){var n;return Object.is(t.hook.state,e.state)&&Object.is((n=t.hook.pagination)==null?void 0:n.state,e.pagination)}function je(t,e){return Me(t)&&ze(t,e)}function xe(t,e,n){return t===void 0||!g(t)||t.refresh.invoker!==e.refresh?Ce(e,n):(je(t,n)||(t.hook=D(t,n)),t)}function Fe(t){return g(t)?[t.refresh,t.reset,t.retry,t.loadNext]:[t]}function T(t){var e;for(const n of Fe(t))(e=n.detach)==null||e.call(n),n.detach=void 0}function Pe(t){return t.hook}class Ne{entries=new Map;invokers=new WeakMap;listeners=new Set;revision=0;close(){for(const e of this.entries.values())T(e);this.entries.clear()}commit(e){for(const[n,s]of this.entries)e.get(n)!==s&&T(s);this.entries=e;for(const n of e.values())this.attachEntry(n)}attachEntry(e){if(g(e)){this.attachCommand(e.refresh),this.attachCommand(e.retry),this.attachCommand(e.reset),this.attachCommand(e.loadNext);return}this.attachCommand(e)}attachCommand(e){e.detach??(e.detach=e.slot.attach((n,s)=>{if(Oe(e,n,s)){this.revision+=1;for(const i of this.listeners)i()}}))}getSnapshot=()=>this.revision;prepare(e,n,s){const i=new Map;for(const r of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,r))continue;const o=e[r],c=o===void 0?void 0:s.commandRecords.get(o);if(c===void 0)throw new f("missing",`Command at ${String(r)} is not bound in this contribution.`);i.set(r,Ee(this.entries.get(r),c.invoker))}for(const[r,o]of n){const c=re(o.resource,this.invokers);i.set(r,xe(this.entries.get(r),c,o))}return i}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function Ie(t,e){const n=k(void 0);return e.size>0||Reflect.ownKeys(t).some(i=>Object.prototype.propertyIsEnumerable.call(t,i))?n.current??(n.current=new Ne):void 0}function Ae(t,e){const n=C(),s=Ie(t,e),i=(s==null?void 0:s.getSnapshot)??ke;S((s==null?void 0:s.subscribe)??$,i,i);const r=s==null?void 0:s.prepare(t,e,n);return E(()=>()=>s==null?void 0:s.close(),[s]),E(()=>{r!==void 0&&(s==null||s.commit(r))}),y(()=>{if(r!==void 0)for(const o of r.values())g(o)?(o.refresh.slot.synchronize(),o.retry.slot.synchronize(),o.reset.slot.synchronize(),o.loadNext.slot.synchronize()):o.slot.synchronize()}),r===void 0?ve:Object.fromEntries([...r].map(([o,c])=>[o,Pe(c)]))}const V=new Map,Ke=new Set;function He(t,e){if(!te(t))throw new f("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 L(t,e){if(e.has(t))return e.get(t);const n=t.getSnapshot();return e.set(t,n),n}function qe(t,e,n,s){if(!oe(e))return;const i=e,r=i.pagination;n.set(t,{pagination:r===void 0?void 0:L(b(r),s),resource:i,state:L(b(i),s)})}function $e(t){return t.size===0?V:t}function De(t,e){for(const{resource:n}of t.values())if(A(n))for(const s of K(n))e.add(s);else e.add(n)}function Te(t,e){const n=new Set,s=(a,d)=>{const u=He(a,e);return d===void 0?u:d(u)},r=t({acquire:(a,d)=>{if(!A(a))throw new f("binding-invalid","Model selection acquire requires a ResourceView.");for(const u of K(a))n.add(u);return s(a.observe(),d)},read:s});if(typeof r!="object"||r===null||Array.isArray(r))throw new f("binding-invalid","Model selection requires a record.");const o=[],c=new Map;for(const a of Reflect.ownKeys(r)){if(!Object.prototype.propertyIsEnumerable.call(r,a))continue;const d=r[a];o.push([a,d]),qe(a,d,c,e)}return De(c,n),{leases:n,resources:$e(c),values:Object.fromEntries(o)}}function Ve(t,e){if(t.size!==e.size)return!1;for(const[n,s]of e){const i=t.get(n);if((i==null?void 0:i.resource)!==s.resource||!Object.is(i.state,s.state)||!Object.is(i.pagination,s.pagination))return!1}return!0}function U(t,e){if(t.size!==e.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0}function Le(t,e,n,s,i){const r=Reflect.ownKeys(e),o=Reflect.ownKeys(t.values);return r.length!==o.length||!U(t.sources,n)||!U(t.leases,i)||!Ve(t.resources,s)?!1:r.every((c,a)=>o[a]===c&&Object.is(t.values[c],e[c]))}const v={commands:{},leases:Ke,resources:V,sources:new Set,values:{}};function Ue(t){return Object.fromEntries(Reflect.ownKeys(t).filter(e=>ee(t[e])).map(e=>[e,t[e]]))}function We(t){for(const[e,n]of t)if(!Object.is(e.getSnapshot(),n))return!1;return!0}function Ge(){return{model:void 0,observed:void 0,select:void 0,snapshot:v}}function Be(t,e,n){return n.model!==t&&(n.model=t,n.observed=void 0,n.select=void 0,n.snapshot=v),()=>{const s=e.current;if(s===void 0)return v;const i=n.observed;if(i!==void 0&&n.select===s&&We(i))return n.snapshot;const r=new Map,{leases:o,resources:c,values:a}=Te(u=>s(t,u),r),d=new Set(r.keys());return(i===void 0||!Le(n.snapshot,a,d,c,o))&&(n.snapshot={commands:Ue(a),leases:o,resources:c,sources:d,values:a}),n.observed=r,n.select=s,n.snapshot}}const M=new Set;function W(t,e=[]){for(const n of t){const s=n.dispose;n.dispose=void 0;try{s==null||s()}catch(i){e.push(i)}}if(e.length===1)throw e[0];if(e.length>1)throw ne(e,"Model selection sources failed to release.")}class Je{enabled=!1;leases=new Map;listeners=new Set;retained=M;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.retained=M,this.releaseAll()}commit(e,n=M){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(),W(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));W(e)}retain(e){const n={dispose:void 0};this.leases.set(e,n);const s=e.acquire();if(typeof s!="function")throw new TypeError("Model selection resource must answer a release 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 Qe=()=>v;function Xe(t){const e=k(void 0);return t?e.current??(e.current=new Je):void 0}function Ye(t,e){return Object.fromEntries(Reflect.ownKeys(t.values).map(n=>[n,Object.prototype.hasOwnProperty.call(e,n)?e[n]:t.values[n]]))}function Ze(t,e){const n=C();if(!n.models.has(t))throw new f("missing",`Model ${t.id} is not granted to this contribution.`);const s=n.models.get(t),i=Xe(e!==void 0),r=k(void 0);r.current=e;const o=e!==void 0,c=k(void 0),a=c.current??(c.current=Ge()),d=l(()=>o?Be(s,r,a):Qe,[a,s,o]),u=S((i==null?void 0:i.subscribe)??$,d,d),w=Ae(u.commands,u.resources);return y(()=>()=>i==null?void 0:i.close(),[i]),y(()=>i==null?void 0:i.commit(u.sources,u.leases),[u,i]),e===void 0?s:Ye(u,w)}function _e(t){const e=Object.freeze([...t]);return n=>{if(n.requires!==void 0)throw new f("duplicate","Component already declares the models it requires.");return Object.defineProperty(n,"requires",{enumerable:!1,value:e}),n}}function et(t){const[e]=O(()=>new WeakMap),n=e.get(t);if(n!==void 0)return n;const s=ae(t),i={loadNext:new h(s.loadNext),refresh:new h(s.refresh),reset:new h(s.reset),retry:new h(s.retry)};return e.set(t,i),i}const tt=Object.freeze({kind:"unavailable",reason:"resource-not-ready"}),nt=Object.freeze({getSnapshot:()=>tt,subscribe:()=>()=>{}});function st(t){const e=t,n=ce(e),s=P(R=>b(t).subscribe(R),[t]),i=l(()=>()=>b(t).getSnapshot(),[t]),r=S(s,i,i),o=n===void 0?nt:b(n),c=P(R=>o.subscribe(R),[o]),a=l(()=>()=>o.getSnapshot(),[o]),d=S(c,a,a);J(()=>se(t),[t]);const u=et(e),w=p(u.refresh),z=p(u.retry),j=p(u.reset),x=p(u.loadNext),F=l(()=>n===void 0?void 0:{loadNext:x,state:d},[n,x,d]);return l(()=>({pagination:F,refresh:w,reset:j,retry:z,state:r}),[F,w,j,z,r])}const G=Object.freeze({});function B(t){return ie(t,"slot")}function it(t){const e=Y(t),n=new Map,s=i=>{const r=n.get(i);if(r!==void 0)return r;const o=B(`${e}/${i}`);return n.set(i,o),o};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function rt(t){const e=H(t.target.entries),n="props"in t?t.props??G:G;return e.length===0?null:e.map(s=>N(Q,{key:s.id},N(ue,{authority:de(s),contribution:s.value,contributionId:s.id,slotProps:n,targetId:t.target.id})))}export{f as ContributionError,rt as Slot,B as defineSlot,it as defineSwitchSlot,_e as requiresModels,ye as useCommand,Ze as useModel,H as useReadable,st as useResource,ht as useSelector};
1
+ import{useState as m,useInsertionEffect as k,useLayoutEffect as g,useMemo as d,useSyncExternalStore as w,useRef as y,useCallback as K,createElement as N,Fragment as te}from"react";import{CommandError as ie,declarationId as re}from"@opetope/core";import{compatibleAbortReason as se,isCancellation as oe,reportDetachedError as $,enterSelectorEvaluation as A,leaveSelectorEvaluation as H,isCallTarget as ce,isResourceView as j,resourceViewDemands as C,resourceViewReadable as v,isReadable as T,createContributionTarget as ue}from"@opetope/core/internal";import{a as l}from"./contribution-isolation-Ig70xFiC.js";import{a as F,r as ae,i as fe,b as W,p as de,d as le,u as D,M as he,c as ge}from"./contribution-frame-D7_DN6Y2.js";function G(n,e){var t;if(oe(e))return{reason:e,kind:"cancelled"};try{const i=(t=n==null?void 0:n.onFailure)==null?void 0:t.call(n,e);i!==void 0&&Promise.resolve(i).catch($)}catch(i){$(i)}return{error:e,kind:"failed"}}function be(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 pe(n,e){let t;const i=new Promise(r=>{t=r});return{input:n,options:e===void 0?void 0:{...e},promise:i,resolve:t}}function me(n,e){const t=n.resolve;n.input=void 0,n.options=void 0,n.resolve=void 0,t==null||t(e)}class L{invoker;run=((e,t)=>{if(this.notify===void 0)return Promise.resolve({reason:new l("inactive","Command consumer is not mounted."),kind:"cancelled"});const i=this.cancelledInput(t);if(i!==void 0)return Promise.resolve(i);const r=pe(e,t);return this.start(r),r.promise});inFlight=0;notify;constructor(e){this.invoker=e}attach(e){return this.notify=e,()=>{this.notify===e&&(this.notify=void 0)}}synchronize(){var e;(e=this.notify)==null||e.call(this,this.inFlight>0)}aborted(e){return{reason:new ie("cancelled","Command input was cancelled.",se(e)),kind:"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 i;e.resolve!==void 0&&(this.inFlight-=1,me(e,t),(i=this.notify)==null||i.call(this,this.inFlight>0,t))}invoke(e){let t;try{t=this.invoker.run(e.input,be(e.options)),e.input=void 0}catch(i){this.finish(e,G(e.options,i));return}t.then(i=>this.finish(e,{kind:"ok",value:i}),i=>this.finish(e,G(e.options,i)))}start(e){var t;this.inFlight+=1,(t=this.notify)==null||t.call(this,!0),this.invoke(e)}}const ye={inFlight:!1,outcome:void 0};function Se(n){return n===void 0||n.kind==="cancelled"?void 0:n}function ke(n,e,t,i){return i===void 0&&n.slot===e&&n.inFlight===t}function we(n,e,t,i){const r=Se(i);if(ke(n,e,t,r))return n;const s=n.slot===e?n.outcome:void 0;return{inFlight:t,outcome:r??s,slot:e}}function ve(n){const[e]=m(()=>new WeakMap);let t=e.get(n);return t===void 0&&(t=new L(n),e.set(n,t)),t}function Re(n){const[e,t]=m(()=>({inFlight:!1,outcome:void 0,slot:n}));k(()=>n.attach((o,c)=>t(u=>we(u,n,o,c))),[n]),g(()=>n.synchronize(),[n]);const{inFlight:i,outcome:r}=e.slot===n?e:ye,s=n.run;return d(()=>({inFlight:i,outcome:r,run:s}),[i,r,s])}function Oe(n){const t=F().commandRecords.get(n);if(t===void 0)throw new l("missing",`Command ${n.id} is not bound in this contribution.`);return Re(ve(t.invoker))}const Ee=()=>{},q=()=>Ee,je=()=>0,U=Object.freeze({kind:"cancelled"});class R{start;inFlight=0;notify;pending;run=()=>{if(this.notify===void 0)return Promise.resolve(U);let e,t;const i=new Promise((o,c)=>{e=o,t=c}),r={active:!0,next:this.pending,previous:void 0,reject:t,resolve:e};if(this.pending!==void 0&&(this.pending.previous=r),this.pending=r,this.inFlight+=1,this.notify(!0),!r.active)return i;let s;try{s=Promise.resolve(this.start())}catch(o){return this.fail(r,o),i}return s.then(o=>this.finish(r,o),o=>this.fail(r,o)),i};constructor(e){this.start=e}attach(e){return this.notify=e,()=>{if(this.notify!==e)return;this.notify=void 0;let t=this.pending;for(this.pending=void 0,this.inFlight=0;t!==void 0;){const i=t.next;t.active=!1,t.next=void 0,t.previous=void 0;const r=t.resolve;t.resolve=void 0,t.reject=void 0,r==null||r(U),t=i}}}synchronize(){var e;(e=this.notify)==null||e.call(this,this.inFlight>0)}unlink(e){return e.active?(e.active=!1,e.previous===void 0?this.pending=e.next:e.previous.next=e.next,e.next!==void 0&&(e.next.previous=e.previous),e.next=void 0,e.previous=void 0,this.inFlight-=1,!0):!1}finish(e,t){var r;if(!this.unlink(e))return;const i=e.resolve;e.resolve=void 0,e.reject=void 0,i==null||i(t),(r=this.notify)==null||r.call(this,this.inFlight>0,t)}fail(e,t){var r;if(!this.unlink(e))return;const i=e.reject;e.resolve=void 0,e.reject=void 0,i==null||i(t),(r=this.notify)==null||r.call(this,this.inFlight>0)}}const Ce=Object.freeze({inFlight:!1,outcome:void 0});function Fe(n){return(n==null?void 0:n.kind)==="cancelled"?void 0:n}function ze(n,e,t,i){const r=n.slot===e?n.outcome:void 0,s=Fe(i);return n.slot===e&&n.inFlight===t&&s===void 0?n:{inFlight:t,outcome:s??r,run:e.run,slot:e}}function z(n){const[e,t]=m(()=>({inFlight:!1,outcome:void 0,run:n.run,slot:n}));k(()=>n.attach((o,c)=>t(u=>ze(u,n,o,c))),[n]),g(()=>n.synchronize(),[n]);const{inFlight:i,outcome:r}=e.slot===n?e:Ce,{run:s}=n;return d(()=>({inFlight:i,outcome:r,run:s}),[i,r,s])}const Me=Object.freeze({});function b(n){return"kind"in n}function xe(n){const e=new L(n);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:n,slot:e}}function M(n){const e=new R(n);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:n,slot:e}}function B(n,e){return n.inFlight===e.inFlight&&Object.is(n.outcome,e.outcome)}function Pe(n,e,t){const i=n.hook;if(t===void 0&&i.inFlight===e)return!1;const r=t===void 0||t.kind==="cancelled"?void 0:t,s={inFlight:e,outcome:r??i.outcome,run:i.run};return B(i,s)?!1:(n.hook=s,!0)}function Ve(n,e,t){const i=n.hook,r=(t==null?void 0:t.kind)==="cancelled"?void 0:t;if(r===void 0&&i.inFlight===e)return!1;const s={inFlight:e,outcome:r??i.outcome,run:i.run};return B(i,s)?!1:(n.hook=s,!0)}function Ie(n,e){return n!==void 0&&!b(n)&&n.invoker===e?n:xe(e)}function J(n,e){return{pagination:e.pagination===void 0?void 0:{loadNext:n.loadNext,state:e.pagination},refresh:n.refresh.hook,reset:n.reset.hook,retry:n.retry.hook,state:e.state}}function Ke(n,e){const t={kind:"resource",loadNext:n.loadNext,refresh:M(n.refresh),reset:M(n.reset),retry:M(n.retry)};return{...t,hook:J(t,e)}}function Ne(n){const{hook:e}=n;return e.refresh===n.refresh.hook&&e.reset===n.reset.hook&&e.retry===n.retry.hook}function $e(n,e){var t;return Object.is(n.hook.state,e.state)&&Object.is((t=n.hook.pagination)==null?void 0:t.state,e.pagination)}function Ae(n,e){return Ne(n)&&$e(n,e)}function He(n,e,t){return n===void 0||!b(n)||n.refresh.invoker!==e.refresh?Ke(e,t):Ae(n,t)?n:{...n,hook:J(n,t)}}function Q(n){var e,t;if(b(n)){for(const i of[n.refresh,n.reset,n.retry])(e=i.detach)==null||e.call(i),i.detach=void 0;return}(t=n.detach)==null||t.call(n),n.detach=void 0}function Te(n,e){return n.refresh===e.refresh&&n.reset===e.reset&&n.retry===e.retry}function We(n,e){return n===e?!0:e===void 0||!b(n)||!b(e)?!1:Te(n,e)}function De(n){return n.hook}class Ge{entries=new Map;invokers=new WeakMap;listeners=new Set;revision=0;close(){for(const e of this.entries.values())Q(e);this.entries.clear()}commit(e){for(const[t,i]of this.entries)We(i,e.get(t))||Q(i);this.entries=e;for(const t of e.values())this.attachEntry(t)}attachEntry(e){if(b(e)){this.attachVerb(e.refresh),this.attachVerb(e.retry),this.attachVerb(e.reset);return}this.attachCommand(e)}attachCommand(e){e.detach??(e.detach=e.slot.attach((t,i)=>{if(Pe(e,t,i)){this.revision+=1;for(const r of this.listeners)r()}}))}attachVerb(e){e.detach??(e.detach=e.slot.attach((t,i)=>{if(Ve(e,t,i)){this.revision+=1;for(const r of this.listeners)r()}}))}getSnapshot=()=>this.revision;prepare(e,t,i){const r=new Map;for(const s of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,s))continue;const o=e[s],c=o===void 0?void 0:i.commandRecords.get(o);if(c===void 0)throw new l("missing",`Command at ${String(s)} is not bound in this contribution.`);r.set(s,Ie(this.entries.get(s),c.invoker))}for(const[s,o]of t){const c=ae(o.resource,this.invokers);r.set(s,He(this.entries.get(s),c,o))}return r}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function Le(n,e){const t=y(void 0);return e.size>0||Reflect.ownKeys(n).some(r=>Object.prototype.propertyIsEnumerable.call(n,r))?t.current??(t.current=new Ge):void 0}function qe(n,e){const t=F(),i=Le(n,e),r=(i==null?void 0:i.getSnapshot)??je;w((i==null?void 0:i.subscribe)??q,r,r);const s=i==null?void 0:i.prepare(n,e,t);return k(()=>()=>i==null?void 0:i.close(),[i]),k(()=>{s!==void 0&&(i==null||i.commit(s))}),g(()=>{if(s!==void 0)for(const o of s.values())b(o)?(o.refresh.slot.synchronize(),o.retry.slot.synchronize(),o.reset.slot.synchronize()):o.slot.synchronize()}),s===void 0?Me:Object.fromEntries([...s].map(([o,c])=>[o,De(c)]))}const X=new Map,Ue=new Set;function Be(n,e){if(!T(n))throw new l("binding-invalid","Model selection read requires a Readable or ResourceView.");if(e.has(n))return e.get(n);const t=n.getSnapshot();return e.set(n,t),t}function Y(n,e){if(e.has(n))return e.get(n);const t=n.getSnapshot();return e.set(n,t),t}const Je=[Date,Map,Set,RegExp,WeakMap,WeakSet];function Qe(n){if(Array.isArray(n)||typeof n.then=="function")return!0;for(const e of Je)if(n instanceof e)return!0;return!1}function Xe(n,e){if(T(e))throw new TypeError(`A selector returned a Readable in field "${String(n)}": read(source) inside the selector.`);if(j(e))throw new TypeError(`A selector returned a ResourceView in field "${String(n)}": read(view) inside the selector, or select the Resource.`)}function Ye(n,e,t,i){if(!fe(e)){Xe(n,e);return}const r=e,s=r.pagination;t.set(n,{pagination:s===void 0?void 0:Y(v(s),i),resource:r,state:Y(v(r),i)})}function Ze(n){return n.size===0?X:n}function _e(n,e){for(const{resource:t}of n.values())if(j(t))for(const i of C(t))e.add(i);else e.add(t)}function en(n,e){const t=new Set;let i=!0;const r=(s,o)=>{if(!i)throw new TypeError("A selector's read was called after the selector returned: read inside the selector.");let c;if(j(s)){for(const a of C(s))t.add(a);c=s.observe()}else c=s;const u=Be(c,e);return o===void 0?u:o(u)};A();try{const s=n({read:r});if(typeof s!="object"||s===null||Qe(s))throw new l("binding-invalid","Model selection requires a record of named fields, not an array, a promise or another container.");const o=[],c=new Map;for(const u of Reflect.ownKeys(s)){if(!Object.prototype.propertyIsEnumerable.call(s,u))continue;const a=s[u];o.push([u,a]),Ye(u,a,c,e)}return _e(c,t),{leases:t,resources:Ze(c),values:Object.fromEntries(o)}}finally{i=!1,H()}}function nn(n,e){if(n.size!==e.size)return!1;for(const[t,i]of e){const r=n.get(t);if((r==null?void 0:r.resource)!==i.resource||!Object.is(r.state,i.state)||!Object.is(r.pagination,i.pagination))return!1}return!0}function Z(n,e){if(n.size!==e.size)return!1;for(const t of e)if(!n.has(t))return!1;return!0}function tn(n,e,t,i,r){const s=Reflect.ownKeys(e),o=Reflect.ownKeys(n.values);return s.length!==o.length||!Z(n.sources,t)||!Z(n.leases,r)||!nn(n.resources,i)?!1:s.every((c,u)=>o[u]===c&&Object.is(n.values[c],e[c]))}const O={commands:{},leases:Ue,resources:X,sources:new Set,values:{}};function rn(n){return Object.fromEntries(Reflect.ownKeys(n).filter(e=>ce(n[e])).map(e=>[e,n[e]]))}function sn(n){A();try{for(const[e,t]of n)if(!Object.is(e.getSnapshot(),t))return!1;return!0}finally{H()}}function on(){return{model:void 0,observed:void 0,select:void 0,snapshot:O}}function cn(n,e,t){return t.model!==n&&(t.model=n,t.observed=void 0,t.select=void 0,t.snapshot=O),()=>{const i=e.current;if(i===void 0)return O;const r=t.observed;if(r!==void 0&&t.select===i&&sn(r))return t.snapshot;const s=new Map,{leases:o,resources:c,values:u}=en(f=>i(n,f),s),a=new Set(s.keys());return(r===void 0||!tn(t.snapshot,u,a,c,o))&&(t.snapshot={commands:rn(u),leases:o,resources:c,sources:a,values:u}),t.observed=s,t.select=i,t.snapshot}}const un=()=>O;function an(n){const e=y(void 0);return n?e.current??(e.current=new W):void 0}function _(n,e,t){return Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.values[t]}function fn(n,e,t){const i=Reflect.ownKeys(n.values);if(t!==void 0){const r=Reflect.ownKeys(t);if(i.length===r.length){let s=!0;for(let o=0;o<i.length;o+=1){const c=i[o];if(c!==r[o]||!Object.is(t[c],_(n,e,c))){s=!1;break}}if(s)return t}}return Object.fromEntries(i.map(r=>[r,_(n,e,r)]))}function dn(n,e){const t=F();if(!t.models.has(n))throw new l("missing",`Model ${n.id} is not granted to this contribution.`);const i=t.models.get(n),r=an(e!==void 0),s=y(void 0);s.current=e;const o=e!==void 0,c=y(void 0),u=c.current??(c.current=on()),a=d(()=>o?cn(i,s,u):un,[u,i,o]),f=w((r==null?void 0:r.subscribe)??q,a,a),S=qe(f.commands,f.resources);g(()=>()=>r==null?void 0:r.close(),[r]),g(()=>r==null?void 0:r.commit(f.sources,f.leases),[f,r]);const h=y(void 0),p=o?fn(f,S,h.current):void 0;return g(()=>{h.current=p},[p]),e===void 0?i:p}function ln(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 hn(n){const[e]=m(()=>new WeakMap),t=e.get(n);if(t!==void 0)return t;const i=le(n),r={loadNext:i.loadNext,refresh:new R(i.refresh),reset:new R(i.reset),retry:new R(i.retry)};return e.set(n,r),r}const gn=Object.freeze({kind:"unavailable",reason:"resource-not-ready"}),bn=Object.freeze({getSnapshot:()=>gn,subscribe:()=>()=>{}});function pn(n){const e=n,t=de(e),[i]=m(()=>new W),r=d(()=>v(n),[n]),s=K(E=>i.subscribe(E),[i]),o=d(()=>()=>r.getSnapshot(),[r]),c=w(s,o,o),u=t===void 0?bn:v(t),a=K(E=>u.subscribe(E),[u]),f=d(()=>()=>u.getSnapshot(),[u]),S=w(a,f,f);g(()=>{i.commit(new Set([r]),new Set(C(n)))},[n,r,i]);const h=hn(e),p=z(h.refresh),x=z(h.retry),P=z(h.reset),{loadNext:V}=h,I=d(()=>t===void 0?void 0:{loadNext:V,state:S},[t,V,S]);return d(()=>({pagination:I,refresh:p,reset:P,retry:x,state:c}),[I,p,P,x,c])}const ee=Object.freeze({});function ne(n){return ue(n,"slot")}function mn(n){const e=re(n),t=new Map,i=r=>{const s=t.get(r);if(s!==void 0)return s;const o=ne(`${e}/${r}`);return t.set(r,o),o};return Object.defineProperty(i,"id",{enumerable:!0,value:e}),Object.freeze(i)}function yn(n){const e=D(n.target.entries),t="props"in n?n.props??ee:ee;return e.length===0?null:e.map(i=>N(te,{key:i.id},N(he,{authority:ge(i),contribution:i.value,contributionId:i.id,slotProps:t,targetId:n.target.id})))}export{l as ContributionError,yn as Slot,ne as defineSlot,mn as defineSwitchSlot,ln as requiresModels,Oe as useCommand,dn as useModel,D as useReadable,pn as useResource};
2
2
  //# sourceMappingURL=index.js.map