@opetope/react 0.10.1 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # @opetope/react
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6f23026: **A retirement stops a source, it never breaks it (D364).** `Readable.subscribe` promises «I call you, you read
8
+ me», and `State.close()` was the only operation in the framework that notified and then refused the read. Every
9
+ foreign listener that followed the protocol literally therefore threw, the framework collected that throw into an
10
+ `AggregateError`, and reported it on behalf of a consumer that had done nothing wrong — with a Resource in the
11
+ picture, with a plain `ctx.state` cell and no Resource at all, and across an `optional` weak edge where a consumer
12
+ legitimately outlives its provider. D362 and D363 taught the framework's own nodes to read a close as a lifetime
13
+ fact; neither could reach a listener that is not a framework node.
14
+
15
+ - **The law.** A Resource says `idle('retired')`, a cell keeps its last value, and a read never fails because a
16
+ lifetime ended. `createState.close()` stops accepting writes, lets its listeners go and answers its last value
17
+ for ever; it notifies nobody, because nothing changed, and a later `subscribe` is inert. The controller of a
18
+ Resource publishes one terminal record at its fence and **seals** its publisher behind it, so a retired Resource
19
+ answers that record for as long as a consumer holds it and its controller's publisher retains none of the listener
20
+ closures it handed out. The terminal word belongs to the object whose life ended: a Resource a model owns says
21
+ `retired`, while a feature's `own` or exported Resource is a reference that outlives the instance, so the
22
+ retirement of its controller reaches it as a detachment and it answers `unleased` — one frame, and its verbs
23
+ answer the word its state answers.
24
+ - **One new type name, `ResourceIdleReason`, exported from `@opetope/core` and re-exported by `@opetope/runtime`.**
25
+ The compiler prints `'retired'` to a consumer whose exhaustive branch on `state.reason` breaks, and a union that
26
+ has no name to import is hand-copied instead — which is the one artefact that keeps compiling when a member is
27
+ added. The pagination reasons became one internal type and stay off the public surface.
28
+ - **Two new words, both inhabitants of types that already existed.** `ResourceIdleReason` gains `'retired'` — the
29
+ word the verbs of a retired Resource already answer, which made `state.getSnapshot()` the only member of the
30
+ object that refused the question the rest of it answered. `PaginationState` gains `unavailable('retired')` and
31
+ `PaginationOutcome` `skipped('retired')`: `resource-not-ready` reads as «not yet» and invites a retry loop, and
32
+ `end` would render «everything is loaded» over a Resource that is gone.
33
+ - **`ReadableError` is removed**, with `isClosedReadable`, the followed/unfollowed classification of a committed
34
+ failure, the close branches of `effect`, `scope` and the Resource inputs, the `unreadable` selection decision, both
35
+ catches of the feature `own` facade, and `keepLastOnClose`, the selection's closed-source memory and
36
+ `useResource`'s `lastGoodSnapshot` in `@opetope/react`. A write after the stop raises a `TypeError`; model writes
37
+ are already dropped in silence, so the only reachable caller is a test holding `testReadable`. An exhaustive
38
+ `switch` or a record over `state.reason` breaks at compile time, which is the migration this release asks for.
39
+ - **Inspection.** The frame schema is `opetope.devtools-frame/5`: the `readable` failure subject leaves the union
40
+ with the class that produced it, and the decoder accepts only `/5`. The subscriber count of a Resource is frozen
41
+ when its publisher seals instead of being zeroed, so the row of a fenced materialization reads «fenced, and N
42
+ consumers were still following it».
43
+ - **What did not change.** A producer failure is not a lifetime fact: `resource.state` still refuses a read after
44
+ `publisher.fail` and after a broken `nextCursor`, a `derive` still throws whatever its calculation threw, and
45
+ every such refusal still reaches the reader and is reported once. The weak-edge `Maybe` still says `none` before
46
+ the provider's cells stop, and that is the channel to read.
47
+ - **What it costs.** Stale is quiet where it was loud, and a React reader of `resource.state` renders one more —
48
+ idle — frame while a tree is torn down, where the close used to be swallowed and the last frame stood. A consumer
49
+ that keeps a handle on a stopped cell keeps its last value: measured at 7.99 MB over an 8 MB source, released in
50
+ full when the handle goes, and bounded by the consumer's own reference. A Resource is the opposite: the fence
51
+ releases its `Data`, its cache and its pagination anchor before the terminal record is published, so holding
52
+ `resource.state` for ever costs nothing. The disposer `subscribe` answers is built outside the cell now, so a
53
+ consumer that kept only the disposer — and had therefore let the `Readable` go — no longer pins the last value
54
+ through it.
55
+
56
+ ### Patch Changes
57
+
58
+ - 0eb82ba: **A close is recognized as this source's own, and its memory belongs to the mount (D363).** D362 shipped in 0.10.1
59
+ applied in fewer places than it claimed, and the predicate behind it could not tell three different things apart.
60
+
61
+ - **The scope input of a Resource.** An independent `identity.scope` was read inside the same `try` as
62
+ `resourceKeyOf` and `scopeOf`, so its close became a broken selector: one `ReadableError` in the reporter, and —
63
+ with no materialization current — a published `idle('missing-request')`, which moved the state a close must never
64
+ move. The source is read in its own `try` now; a throwing selector is still `broken`, with one record per
65
+ transition.
66
+ - **`useSelector` with an inline selector.** The memory of «what this reader last read» lived inside a `useMemo`
67
+ whose deps include the selector, so `useReadable` was safe and `useSelector(readable, inline)` — the documented
68
+ form — rebuilt the reader with an empty memory and failed mid-render after a close. The memory of `useSelector`
69
+ and of a `useModel` selection now lives in the ref the mount already holds, so a memo React discarded changes
70
+ nothing.
71
+ - **A source a node never read.** An `effect`, a `scope` or a Resource whose source was already closed when it
72
+ opened swallowed that close: the run never started, no child and no materialization ever opened, and nothing was
73
+ said anywhere. A close with nothing to keep is now reported, as React already did — the law is about not
74
+ reporting the end of work the runtime itself is ending.
75
+ - **A close raised inside a calculation.** A `derive` whose computation reads an unrelated closed `Readable` raised
76
+ a bare close that every observer swallowed, turning a programming fault into silence. Such a failure travels on
77
+ as `A calculation read a closed readable state.` with the close in its `cause`; a close that came out of a source the
78
+ node follows is unchanged.
79
+ - **Two copies of Core.** The predicate was `instanceof`, so a vendored copy beside an installed one raised a close
80
+ that was reported as a foreign failure. It now asks the shape the `Readable` contract already promises — an
81
+ `Error` named `ReadableError` with code `closed` — so the close of either copy is the same fact.
82
+ - **Two React readers that swallowed everything.** `useResource` and the passive reads of a selection caught every
83
+ refusal with a bare `catch`, including a failed publisher cell and a broken `nextCursor`. They apply the same
84
+ predicate now and rethrow anything else.
85
+ - **The selection fast path** records a closed source once instead of re-reading it on every render, so a mount
86
+ whose source closed stops rebuilding its selection — and stops allocating a failure per read — for the rest of
87
+ its life.
88
+
89
+ - Updated dependencies [6f23026]
90
+ - Updated dependencies [0eb82ba]
91
+ - @opetope/core@0.11.0
92
+ - @opetope/runtime@0.11.0
93
+
3
94
  ## 0.10.1
4
95
 
5
96
  ### Patch Changes
package/README.md CHANGED
@@ -229,9 +229,12 @@ beside it subscribe twice (D349). The compile-checked example of specification
229
229
  it: `inFlight` is true until the outcome of the operation settles, `outcome` carries that `ResourceOperationOutcome`
230
230
  as the `value` of an `ok` status, and `run(undefined, { signal })` cancels the wait of this consumer and not the work
231
231
  the model owns. Two consumers of one Resource keep separate statuses, and `run` keeps its identity while the Resource
232
- does. A render that caught up with retirement answers the last state it saw — what `useReadable` does with a source
233
- its owner closed — and a selection answers a closed record instead of throwing mid-render, so the tree unmounts and
234
- releases its lease rather than breaking the render (D359).
232
+ does. A render that caught up with retirement renders the terminal record — `idle` with reason `retired` for a Resource
233
+ a model owns, `unleased` for a feature's facade, which is a reference that outlives the instance — and unmounts,
234
+ releasing its lease (D359, D364). Nothing here refuses a read because a lifetime ended: a source whose owner retired
235
+ stops and keeps answering its last value, so `useReadable`, `useSelector` with an inline selector and a selection's
236
+ `read` each read on and need no memory of their own. A refusal that remains is work that failed — a state that
237
+ failed, a broken pagination cursor — and it still reaches the render.
235
238
 
236
239
  Direct component props, a contribution's `props` adapter and its model's props `Readable` share the same
237
240
  mount-owned snapshot. Incoming slot props publish in layout before paint, so direct and adapted renders cannot
@@ -292,7 +295,7 @@ a scoped zero-count assertion, without proving absence of arbitrary host, UI or
292
295
  application imports and internal renderer references. A failed cleanup promise can retain original errors and retry
293
296
  capabilities; a caller that keeps `mounted.host` also keeps its own renderer result.
294
297
 
295
- The inspection schemas are `opetope.devtools-graph/5` and `opetope.devtools-frame/4`, with optional
298
+ The inspection schemas are `opetope.devtools-graph/5` and `opetope.devtools-frame/5`, with optional
296
299
  `opetope.runtime-activity/4` snapshots (D266, D275, D344, D358). Within one session,
297
300
  a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
298
301
  (D216). Activity-bearing frames replace the previous activity in full.
package/README.ru.md CHANGED
@@ -228,9 +228,12 @@ render не открывает ничего, а при смене ссылки
228
228
  `inFlight` истинен, пока не осел исход операции, `outcome` несёт этот `ResourceOperationOutcome` значением `value`
229
229
  при статусе `ok`, а `run(undefined, { signal })` отменяет ожидание этого потребителя, а не работу, которой владеет
230
230
  модель. Два потребителя одного Resource держат раздельные статусы, а `run` сохраняет ссылку, пока та же
231
- идентичность Resource. Render, догнавший retirement, отвечает последним виденным состоянием — тем же, что делает
232
- `useReadable` с источником, закрытым его владельцем, — а выборка отвечает закрытой записью вместо падения посреди
233
- render-а, поэтому дерево размонтируется и отпустит аренду, а не сломает рендер (D359).
231
+ идентичность Resource. Render, догнавший retirement, рисует терминальную запись — `idle` с причиной `retired` у Resource, которым
232
+ владеет модель, и `unleased` у фасада фичи, потому что эта ссылка переживает экземпляр, — и размонтируется, отпустив
233
+ аренду (D359, D364). Ничто здесь не отказывает в чтении из-за того, что кончилась чья-то жизнь: источник, у которого
234
+ ушёл владелец, останавливается и продолжает отвечать последним значением, поэтому `useReadable`, `useSelector` с
235
+ inline-селектором и `read` выборки просто читают дальше, и собственная память им не нужна. Оставшийся отказ — это
236
+ упавшая работа: упавшее состояние, сломанный курсор пагинации, — и он по-прежнему доходит до рендера.
234
237
 
235
238
  Прямые пропсы компонента, `props`-адаптер вклада и props-`Readable` его модели используют один снимок
236
239
  монтирования. Входящие пропсы слота публикуются в layout до paint, поэтому прямой и адаптированный рендеры
@@ -292,7 +295,7 @@ Deadline готовности также оставляет приложение
292
295
  host/UI/GC-утечек. Успешный cleanup очищает imports приложения и внутренние ссылки на renderer. Promise отказавшего
293
296
  cleanup может удерживать исходные ошибки и retry capabilities; сохранённый пользователем `mounted.host` удерживает его renderer result.
294
297
 
295
- Inspection использует схемы `opetope.devtools-graph/5` и `opetope.devtools-frame/4`, а также optional snapshots
298
+ Inspection использует схемы `opetope.devtools-graph/5` и `opetope.devtools-frame/5`, а также optional snapshots
296
299
  `opetope.runtime-activity/4` (D266, D275, D344, D358). В пределах одной session
297
300
  frame без `activity` сохраняет предыдущую activity; полный snapshot/reset без этого поля очищает наблюдение (D216).
298
301
  Frame с `activity` заменяет предыдущую activity целиком.
@@ -0,0 +1,2 @@
1
+ import{jsx as m}from"react/jsx-runtime";import{useContext as T,createContext as V,useCallback as S,useRef as O,useMemo as p,useSyncExternalStore as k,useEffect as D,useInsertionEffect as A,useLayoutEffect as w,memo as N,useState as W}from"react";import{reportDetachedError as C,compatibleAbortReason as K,isCallTarget as _,isModel as G,isReadable as j,createLifetimeController as H,createCallController as J,getContributionOwner as Q,createState as U,createAggregateError as X}from"@opetope/core/internal";import{isResourceValue as Y,reportRuntimeFailure as Z,requireContributionModelPlans as ee,openContributionModels as te}from"@opetope/runtime/internal";import{C as u,u as ne,b as re}from"./contribution-isolation-Bzfbt4iM.js";import{CallError as ie}from"@opetope/core";const x=V(void 0);function oe(){const t=T(x);if(t===void 0)throw new u("missing","Opetope hook was called outside a contribution mount.");return t}function y(t){return Y(t)}function se(t,e){return new Promise((n,r)=>{const i=()=>{r(new ie("cancelled","Resource operation wait was cancelled.",K(e)))};if(e.aborted){i();return}e.addEventListener("abort",i,{once:!0}),t.then(o=>{e.removeEventListener("abort",i),n(o)},o=>{e.removeEventListener("abort",i),r(o)})})}function ae(t,e){var r;const n=(r=e==null?void 0:e.onSuccess)==null?void 0:r.call(e,t);return n!==void 0&&Promise.resolve(n).catch(C),t}function v(t){return{run:(e,n)=>{let r;try{r=Promise.resolve(t())}catch(o){return Promise.reject(o)}const i=n==null?void 0:n.signal;return(i===void 0?r:se(r,i)).then(o=>ae(o,n))}}}const ce=Object.freeze({kind:"skipped",reason:"resource-not-ready"});function de(t){const e=t.pagination;if(!(typeof e!="object"||e===null))return e}function E(t){return{invalidate:v(()=>t.invalidate()),loadNext:v(()=>{const e=t.pagination;return e===void 0?Promise.resolve(ce):e.loadNext()}),refresh:v(()=>t.refresh()),retry:v(()=>t.retry())}}function ue(t,e){const n=e.get(t);if(n!==void 0)return n;const r=E(t);return e.set(t,r),r}const le=t=>t;function F(t,e,n){const r=e,i=(n==null?void 0:n.equals)??Object.is,o=S(d=>t.subscribe(d),[t]),c=O(void 0);let s=c.current;s===void 0&&(s={hasValue:!1,value:void 0},c.current=s);const a=p(()=>{let d=!1,h,f;return()=>{const b=t.getSnapshot();if(d&&Object.is(h,b))return f;const g=r(b);if(!d){let R=g;return s.hasValue&&i(s.value,g)&&(R=s.value),d=!0,h=b,f=R,f}return i(f,g)?(h=b,f):(h=b,f=g,f)}},[s,i,t,r]),l=k(o,a,a);return D(()=>{s.hasValue=!0,s.value=l},[s,l]),l}function z(t){return F(t,le)}const M=new WeakMap;function I(t){if(arguments.length>1)throw new u("binding-invalid","Command follows the target it binds; binding options are forbidden.");if(!_(t))throw new u("binding-invalid","Command target is not authentic.");return M.set(t,{target:t}),t}function fe(t){const e=M.get(t);if(e===void 0)throw new u("binding-invalid","Command binding is not authentic.");return e}function $(t){return typeof t=="object"&&t!==null&&M.has(t)}const he=Symbol("opetope.model-binding"),me=Symbol("opetope.model-binding-identity"),be=Object.freeze({}),L=new WeakMap;function pe(t,e,n){if(typeof n!="string")throw new u("binding-invalid",`Model ${t.id} accepts only string fields.`);const r=Object.getOwnPropertyDescriptor(e,n);if((r==null?void 0:r.enumerable)!==!0||!("value"in r))throw new u("binding-invalid",`Model ${t.id}.${n} requires an enumerable data binding.`);return r.value}function ge(t,e){const n=[],r=Reflect.ownKeys(e);if(r.length===0)throw new u("binding-invalid",`Model ${t.id} requires at least one field.`);for(const i of r){const o=pe(t,e,i);if(!(j(o)||y(o)||typeof o=="function")){if(!$(o))throw new u("binding-invalid",`Model ${t.id}.${String(i)} requires a Readable, a Resource or a bound command.`);n.push(o)}}return Object.freeze(n)}function q(t,e){if(!G(t))throw new u("binding-invalid","Model declaration is not authentic.");if(typeof e!="object"||e===null)throw new u("binding-invalid",`Model ${t.id} requires a record binding.`);const n=ge(t,e),r=Object.freeze({[he]:be,[me]:!0});return L.set(r,{commands:n,declaration:t,model:e}),r}function we(t){const e=L.get(t);if(e===void 0)throw new u("binding-invalid","Model binding is not authentic.");return e}let Ce=0;function ve(t){const e=new Map,n=[],r=new Set;for(const i of t){const o=we(i);if(e.has(o.declaration))throw new u("duplicate",`Model ${o.declaration.id} is bound twice in one mount.`);e.set(o.declaration,o.model);for(const c of o.commands)r.has(c)||(r.add(c),n.push(c))}return{commands:Object.freeze(n),frame:{commandRecords:new Map,models:e}}}class ye{frame;authority;boundCommands;closeTicket=0;isActive;lifetime;onClose;released=!1;rendererAttached=!1;renderListeners=new Set;state="detached";constructor(e,n){const r=++Ce;this.lifetime=H({id:`contribution.${String(r)}`}),this.isActive=n==null?void 0:n.isActive,this.onClose=n==null?void 0:n.onClose;const{commands:i,frame:o}=ve(e);this.frame=o,this.boundCommands=i,this.buildCommandRecords(r)}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 u("duplicate","One contribution mount cannot mount twice.");const e=this.createAuthority(this.lifetime.open());try{for(const n of this.frame.commandRecords.values())n.controller.activate()}catch(n){throw this.authority=e,this.state="active",this.close(),n}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 n of this.frame.commandRecords.values())n.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(n=>n.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 n={listener:e};this.renderListeners.add(n);let r=!0;return()=>{r&&(r=!1,this.renderListeners.delete(n))}};buildCommandRecords(e){let n=0;for(const r of this.boundCommands){fe(r);const i=J(r,{captureAuthority:this.captureAuthority,id:`contributionCommand.${String(e)}.${String(++n)}`,scheduling:{policy:"parallel"}});this.frame.commandRecords.set(r,{controller:i,invoker:Object.freeze({run:i.run})})}}captureAuthority=()=>{const e=this.authority;if(this.state!=="active"||e===void 0)throw new u("inactive","Contribution mount is not active.");return e.assertCurrent(),e};createAuthority(e){return Object.freeze({assertCurrent:()=>{if(this.state!=="active")throw new u("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(n){C(n)}}releaseFrame(){this.released||(this.released=!0,this.frame.commandRecords.clear(),this.frame.models.clear(),this.boundCommands=[],this.renderListeners.clear())}}function Me({children:t,mount:e}){const n=k(e.subscribeRenderable,e.getRenderableSnapshot,e.getRenderableSnapshot);return A(()=>{if(e.attach())return()=>e.detach()},[e]),w(()=>{n||e.releaseClosedFrame()},[e,n]),n?m(x,{value:e.frame,children:t}):null}function B(t){const e=t.bundle;t.bundle=void 0,e==null||e.frame.close()}function Re(t,e){if(typeof e!="object"||e===null)return q(t,e);const n=i=>j(i)||y(i)||typeof i=="function"||$(i),r=Object.entries(e).map(([i,o])=>[i,n(o)?o:I(o)]);return q(t,Object.fromEntries(r))}function Se(t){return Q(t)}const Oe={models:[]};function P(t,e){try{t()}catch(n){throw X([e,n],"Contribution mount setup and cleanup failed.",{cause:e})}throw e}function ke(t,e,n,r){const i=U(e),{models:o,reporter:c}=r??Oe;try{const s=ee(t.models),a=s.length===0?void 0:te(s,i,c);return{bundle:a,models:[...o,...(a==null?void 0:a.models)??[],...n],props:i}}catch(s){return P(i.close,s)}}function Ae(t,e,n,r){const{bundle:i,models:o,props:c}=ke(t,e,n,r);let s=!1;const a=()=>{if(!s){s=!0;try{i==null||i.close().catch(C)}finally{c.close()}}};try{return{frame:new ye(o.map(([d,h])=>Re(d,h)),{isActive:()=>!s,onClose:a}),isOpen:()=>!s,props:c}}catch(l){return P(a,l)}}function je(t,e){return m(t,{...e})}function xe({bundle:t,contribution:e}){const n=z(t.props),r=p(()=>e.props===void 0?n:e.props(n),[n,e]);return m(Me,{mount:t.frame,children:je(e.Component,r)})}function Ee({authority:t,contribution:e,fixtures:n,slotProps:r}){const i=p(()=>({authority:t,bundle:void 0,contribution:e,fixtures:n,retired:!1}),[t,e,n]),[o,c]=W(void 0),s=(o==null?void 0:o.owner)===i?o.bundle:void 0,a=O(r);return w(()=>{a.current=r}),A(()=>()=>{i.retired=!0,queueMicrotask(()=>{try{B(i)}catch(l){C(l)}})},[i]),w(()=>{const l=i.bundle??(i.bundle=Ae(i.contribution,a.current,i.fixtures??[],i.authority));return c(d=>(d==null?void 0:d.owner)===i?d:{bundle:l,owner:i}),()=>{i.retired&&B(i)}},[i]),w(()=>{(s==null?void 0:s.isOpen())===!0&&s.props.set(r)},[s,r]),s===void 0?null:m(xe,{bundle:s,contribution:e})}function Fe(t){const{authority:e,contribution:n,contributionId:r,fixtures:i,targetId:o}=t,c=ne(),s=p(()=>({authority:e,contribution:n,fixtures:i}),[e,n,i]),a=e==null?void 0:e.feature,l=p(()=>({contribution:r,feature:a,target:o}),[r,a,o]),d=e==null?void 0:e.reporter,h=S(f=>Z(f,d),[d]);return m(re,{content:c,identity:s,report:h,source:l,children:m(Ee,{...t})})}const ze=N(Fe);export{ze as M,z as a,Se as b,E as c,F as d,I as e,y as i,de as p,ue as r,oe as u};
2
+ //# sourceMappingURL=contribution-frame-DkNmjChZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contribution-frame-DkNmjChZ.js","sources":["../src/mount-context.tsx","../src/resource-command.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, dressed as the commands a button already knows, 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 { CallError } from '@opetope/core';\nimport type {\n PaginationOutcome,\n PaginationState,\n Resource,\n ResourceOperationOutcome,\n ResourceState,\n} from '@opetope/core';\nimport { compatibleAbortReason, reportDetachedError } from '@opetope/core/internal';\nimport type { CallRunOptions, ResourceKey } from '@opetope/core/internal';\nimport { isResourceValue } from '@opetope/runtime/internal';\n\nimport type { CommandHook } from './command-hook';\nimport type { CommandInvoker } from './command-hook-controller';\n\ntype AnyResource = Resource<never, ResourceKey>;\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/** D356: the nested capability, with `loadNext` as a command and the projected pagination state beside it. */\ntype ResourcePaginationHook<Cursor extends ResourceKey> = Readonly<{\n loadNext: CommandHook<void, 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 * The verbs never reject (a Resource answers an outcome), so the ordinary path of `outcome` is\n * `{ status: 'ok', value }`; `failed` stays in the type because the controller turns a synchronous throw of an\n * invoker into it, and the wrapper below carries its own test that it never throws synchronously.\n */\ntype ResourceHook<Data, Key extends ResourceKey = never> = Readonly<{\n invalidate: CommandHook<void, ResourceOperationOutcome>;\n /** Present when the declaration carried `pagination`; `undefined` for every other Resource. */\n pagination: ResourcePaginationHook<ResourceKey> | undefined;\n refresh: CommandHook<void, ResourceOperationOutcome>;\n retry: CommandHook<void, ResourceOperationOutcome>;\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 invalidate: CommandInvoker;\n loadNext: CommandInvoker;\n refresh: CommandInvoker;\n retry: CommandInvoker;\n}>;\n\n/** The consumer's own cancellation: it ends this wait, and the work the model owns keeps running. */\nfunction awaitCancellable(operation: Promise<unknown>, signal: AbortSignal): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n const cancel = (): void => {\n reject(new CallError('cancelled', 'Resource operation wait was cancelled.', compatibleAbortReason(signal)));\n };\n\n if (signal.aborted) {\n cancel();\n\n return;\n }\n\n signal.addEventListener('abort', cancel, { once: true });\n operation.then(\n value => {\n signal.removeEventListener('abort', cancel);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', cancel);\n reject(error);\n },\n );\n });\n}\n\nfunction notifySuccess(value: unknown, options: CallRunOptions<unknown> | undefined): unknown {\n const notification = options?.onSuccess?.(value);\n\n // The failure of a consumer's own callback is detached from the operation it watched, as it is for a command.\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n\n return value;\n}\n\n/**\n * The invariant of this wrapper: it never throws synchronously, so the `failed` outcome a consumer can see is the one\n * the verb itself produced and never one this layer invented in the middle of a render commit.\n */\nfunction operationInvoker(start: () => Promise<unknown>): CommandInvoker {\n return {\n run: (_input: never, options?: CallRunOptions<unknown>): Promise<unknown> => {\n let operation: Promise<unknown>;\n\n try {\n operation = Promise.resolve(start());\n } catch (error) {\n return Promise.reject(error);\n }\n\n const signal = options?.signal;\n\n return (signal === undefined ? operation : awaitCancellable(operation, signal)).then(value =>\n notifySuccess(value, options),\n );\n },\n };\n}\n\n/** A Resource without the capability still answers a `loadNext` invoker, so the hook count never moves. */\nconst unavailablePage: PaginationOutcome<ResourceKey> = Object.freeze({\n kind: 'skipped',\n reason: 'resource-not-ready',\n});\n\nfunction paginationOf(resource: AnyResource): ResourcePaginationHook<ResourceKey> | 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 unknown as ResourcePaginationHook<ResourceKey>;\n}\n\n/**\n * One set per Resource identity. `selectEntry` and `useCommandSlot` 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 invalidate: operationInvoker(() => resource.invalidate()),\n loadNext: operationInvoker(() => {\n const capability = (resource as { readonly pagination?: { loadNext(): Promise<unknown> } }).pagination;\n\n return capability === undefined ? Promise.resolve(unavailablePage) : capability.loadNext();\n }),\n refresh: operationInvoker(() => resource.refresh()),\n retry: operationInvoker(() => 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 { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Readable } from '@opetope/core';\nimport type { EqualityOption } from '@opetope/core/internal';\n\nconst identity = <Value>(value: Value): Value => value;\n\n/**\n * D364: what this mount answered last, and nothing about closes. A source whose owner retired stops instead of\n * refusing — it keeps answering its last value — so a render that races a retirement shows its last frame and\n * unmounts (G13) without the reader having to remember anything for it.\n */\ninterface SelectionInstance<Selected> {\n hasValue: boolean;\n value: Selected | undefined;\n}\n\n/**\n * D279: the source and the selection are positional; `equals` is the one modifier, spelled as in Core — including\n * D348's rule for where the comparator is read, so a named non-generic one compiles here as it does in `derive`.\n */\nexport function useSelector<Value, Selected>(\n readable: Readable<Value>,\n select: (value: NoInfer<Value>) => Selected,\n options?: { readonly equals?: EqualityOption<NoInfer<Selected>> },\n): Selected {\n const selector = select;\n const isEqual = options?.equals ?? Object.is;\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const instanceRef = useRef<SelectionInstance<Selected> | undefined>(undefined);\n let instance = instanceRef.current;\n\n if (instance === undefined) {\n instance = { hasValue: false, value: undefined };\n instanceRef.current = instance;\n }\n\n const getSelectedSnapshot = useMemo(() => {\n let initialized = false;\n let sourceSnapshot: Value;\n let selectedSnapshot: Selected;\n\n return (): Selected => {\n const nextSource = readable.getSnapshot();\n\n if (initialized && Object.is(sourceSnapshot, nextSource)) return selectedSnapshot;\n\n const nextSelected = selector(nextSource);\n\n if (!initialized) {\n let nextSelectedSnapshot = nextSelected;\n\n if (instance.hasValue && isEqual(instance.value, nextSelected)) {\n nextSelectedSnapshot = instance.value as Selected;\n }\n\n initialized = true;\n sourceSnapshot = nextSource;\n selectedSnapshot = nextSelectedSnapshot;\n\n return selectedSnapshot;\n }\n\n if (isEqual(selectedSnapshot, nextSelected)) {\n sourceSnapshot = nextSource;\n\n return selectedSnapshot;\n }\n\n sourceSnapshot = nextSource;\n selectedSnapshot = nextSelected;\n\n return selectedSnapshot;\n };\n }, [instance, isEqual, readable, selector]);\n\n const selected = useSyncExternalStore(subscribe, getSelectedSnapshot, getSelectedSnapshot);\n\n useEffect(() => {\n instance.hasValue = true;\n instance.value = selected;\n }, [instance, selected]);\n\n return selected;\n}\n\nexport function useReadable<Value>(readable: Readable<Value>): Value {\n return useSelector(readable, identity);\n}\n","import type { Call } from '@opetope/core';\nimport { isCallTarget } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\n/**\n * A bound command is the authentic `Call` 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 = Call<never, unknown>;\ntype CommandBinding<Input, Output> = Call<Input, Output>;\n\ntype CommandOutcome<Output> =\n | { readonly error: unknown; readonly status: 'failed' }\n | { readonly reason: unknown; readonly status: 'cancelled' }\n | { readonly status: 'ok'; readonly value: Output };\n\ntype AnyCommandBinding = CommandBinding<never, unknown>;\n\ntype CommandBindingDefinition<Input, Output> = {\n readonly target: Call<Input, Output>;\n};\n\nconst bindingDefinitions = new WeakMap<CommandBindingIdentity, CommandBindingDefinition<never, unknown>>();\n\nfunction bindCommand<Input, Output>(target: Call<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: Call<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 { Call, ModelOf } from '@opetope/core';\nimport { isModel, isReadable } from '@opetope/core/internal';\nimport type { ModelIdentity, NoExtraKeys } 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 = Call<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 * A model field is either a stable Readable or a bound command. The declaration carries the shape in its type, so\n * binding authority is the runtime check that every published field is one of those two authentic kinds.\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 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 (isReadable(value) || isResource(value) || typeof value === 'function') continue;\n\n if (!isCommandBinding(value)) {\n throw new ContributionError(\n 'binding-invalid',\n `Model ${declaration.id}.${String(field)} requires a Readable, a Resource 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 Actual extends ModelOf<Declaration>>(\n declaration: Declaration,\n model: Actual & NoExtraKeys<Actual, 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 Call 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 Calls, 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, State } from '@opetope/core';\nimport {\n createAggregateError,\n createState,\n getContributionOwner,\n isReadable,\n reportDetachedError,\n} from '@opetope/core/internal';\nimport type { ModelIdentity } 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) || isResource(value) || typeof value === 'function' || 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(props.close, 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 props.close();\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) bundle.props.set(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","awaitCancellable","operation","signal","resolve","reject","cancel","CallError","compatibleAbortReason","error","notifySuccess","options","notification","_a","reportDetachedError","operationInvoker","start","_input","unavailablePage","paginationOf","resource","capability","createResourceInvokers","resourceInvokers","cache","existing","invokers","identity","useSelector","readable","select","selector","isEqual","subscribe","useCallback","listener","instanceRef","useRef","instance","getSelectedSnapshot","useMemo","initialized","sourceSnapshot","selectedSnapshot","nextSource","nextSelected","nextSelectedSnapshot","selected","useSyncExternalStore","useEffect","useReadable","bindingDefinitions","bindCommand","target","isCallTarget","getCommandBindingDefinition","binding","definition","isCommandBinding","modelBindingBrand","modelBindingIdentityBrand","modelBindingMarker","registry","modelFieldValue","declaration","model","field","descriptor","snapshotModelFields","commands","fields","isReadable","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","lease","MountProvider","children","mount","renderable","useInsertionEffect","useLayoutEffect","_jsx","closeOwnedMount","owner","bundle","bindFrameModel","passesThrough","key","contributionAuthority","entry","getContributionOwner","unownedContribution","cleanupFailedMount","close","cause","createAggregateError","contributionModels","contribution","slotProps","fixtures","props","createState","granted","reporter","plans","requireContributionModelPlans","openContributionModels","createMountBundle","closed","createComponentElement","Component","MountedContribution","committedProps","componentProps","ContributionMount","committed","setCommitted","useState","committedSlotProps","created","current","IsolatedContributionMount","contributionId","targetId","content","useContributionErrorContent","feature","source","report","reportRuntimeFailure","ContributionIsolation","MemoizedContributionMount","memo"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useState as v,useInsertionEffect as k,useLayoutEffect as y,useMemo as f,useSyncExternalStore as w,useRef as C,useCallback as F,useEffect as V,createElement as P,Fragment as X}from"react";import{CallError as Y,declarationId as Z}from"@opetope/core";import{compatibleAbortReason as _,isCancellation as ee,reportDetachedError as N,isCallTarget as te,isReadable as ne,isClosedReadable as se,createAggregateError as ie,createContributionTarget as oe}from"@opetope/core/internal";import{C as l}from"./contribution-isolation-Bzfbt4iM.js";import{u as O,r as re,i as ae,p as ce,c as ue,a as I,M as de,b as le}from"./contribution-frame-QRJSP2W3.js";import{d as ft}from"./contribution-frame-QRJSP2W3.js";function A(t,e){var n;if(ee(e))return{reason:e,status:"cancelled"};try{const s=(n=t==null?void 0:t.onFailure)==null?void 0:n.call(t,e);s!==void 0&&Promise.resolve(s).catch(N)}catch(s){N(s)}return{error:e,status:"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 he(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 me(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 l("inactive","Command consumer is not mounted."),status:"cancelled"});const s=this.cancelledInput(n);if(s!==void 0)return Promise.resolve(s);const i=he(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 Y("cancelled","Command input was cancelled.",_(e)),status:"cancelled"}}cancelledInput(e){var n;return((n=e==null?void 0:e.signal)==null?void 0:n.aborted)===!0?this.aborted(e.signal):void 0}finish(e,n){var s;e.resolve!==void 0&&(this.inFlight-=1,me(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,A(e.options,s));return}n.then(s=>this.finish(e,{status:"ok",value:s}),s=>this.finish(e,A(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.status==="cancelled"?void 0:t}function ge(t,e,n,s){return s===void 0&&t.slot===e&&t.inFlight===n}function ye(t,e,n,s){const i=pe(s);if(ge(t,e,n,i))return t;const o=t.slot===e?t.outcome:void 0;return{inFlight:n,outcome:i??o,slot:e}}function we(t){const[e]=v(()=>new WeakMap);let n=e.get(t);return n===void 0&&(n=new h(t),e.set(t,n)),n}function m(t){const[e,n]=v(()=>({inFlight:!1,outcome:void 0,slot:t}));k(()=>t.attach((r,a)=>n(c=>ye(c,t,r,a))),[t]),y(()=>t.synchronize(),[t]);const{inFlight:s,outcome:i}=e.slot===t?e:be,o=t.run;return f(()=>({inFlight:s,outcome:i,run:o}),[s,i,o])}function Se(t){const n=O().commandRecords.get(t);if(n===void 0)throw new l("missing",`Command ${t.id} is not bound in this contribution.`);return m(we(n.invoker))}const ve=()=>{},K=()=>ve,ke=()=>0,Ce=Object.freeze({}),Oe=new Map;function b(t){return"kind"in t}function p(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 Ee(t,e,n){const s=t.hook;if(n===void 0&&s.inFlight===e)return!1;const i=n===void 0||n.status==="cancelled"?void 0:n,o={inFlight:e,outcome:i??s.outcome,run:s.run};return Re(s,o)?!1:(t.hook=o,!0)}function ze(t,e){return t!==void 0&&!b(t)&&t.invoker===e?t:p(e)}function H(t,e){return{invalidate:t.invalidate.hook,pagination:t.paginated?{loadNext:t.loadNext.hook,state:e.pagination}:void 0,refresh:t.refresh.hook,retry:t.retry.hook,state:e.state}}function Me(t,e){const n={invalidate:p(t.invalidate),kind:"resource",loadNext:p(t.loadNext),paginated:e.pagination!==void 0,refresh:p(t.refresh),retry:p(t.retry)};return{...n,hook:H(n,e)}}function je(t){var n;const{hook:e}=t;return e.refresh===t.refresh.hook&&e.retry===t.retry.hook&&e.invalidate===t.invalidate.hook&&(!t.paginated||((n=e.pagination)==null?void 0:n.loadNext)===t.loadNext.hook)}function xe(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 Fe(t,e){return je(t)&&xe(t,e)}function Pe(t,e,n){return t===void 0||!b(t)||t.refresh.invoker!==e.refresh?Me(e,n):(Fe(t,n)||(t.hook=H(t,n)),t)}function Ne(t){return b(t)?[t.refresh,t.retry,t.invalidate,t.loadNext]:[t]}function $(t){var e;for(const n of Ne(t))(e=n.detach)==null||e.call(n),n.detach=void 0}function Ie(t){return t.hook}class Ae{entries=new Map;invokers=new WeakMap;listeners=new Set;revision=0;close(){for(const e of this.entries.values())$(e);this.entries.clear()}commit(e){for(const[n,s]of this.entries)e.get(n)!==s&&$(s);this.entries=e;for(const n of e.values())this.attachEntry(n)}attachEntry(e){if(b(e)){this.attachCommand(e.refresh),this.attachCommand(e.retry),this.attachCommand(e.invalidate),this.attachCommand(e.loadNext);return}this.attachCommand(e)}attachCommand(e){e.detach??(e.detach=e.slot.attach((n,s)=>{if(Ee(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 o of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,o))continue;const r=e[o],a=r===void 0?void 0:s.commandRecords.get(r);if(a===void 0)throw new l("missing",`Command at ${String(o)} is not bound in this contribution.`);i.set(o,ze(this.entries.get(o),a.invoker))}for(const[o,r]of n){const a=re(r.resource,this.invokers);i.set(o,Pe(this.entries.get(o),a,r))}return i}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function Ke(t,e){const n=C(void 0);return e.size>0||Reflect.ownKeys(t).some(i=>Object.prototype.propertyIsEnumerable.call(t,i))?n.current??(n.current=new Ae):void 0}function L(t,e){const n=O(),s=Ke(t,e),i=(s==null?void 0:s.getSnapshot)??ke;w((s==null?void 0:s.subscribe)??K,i,i);const o=s==null?void 0:s.prepare(t,e,n);return k(()=>()=>s==null?void 0:s.close(),[s]),k(()=>{o!==void 0&&(s==null||s.commit(o))}),y(()=>{if(o!==void 0)for(const r of o.values())b(r)?(r.refresh.slot.synchronize(),r.retry.slot.synchronize(),r.invalidate.slot.synchronize(),r.loadNext.slot.synchronize()):r.slot.synchronize()}),o===void 0?Ce:Object.fromEntries([...o].map(([r,a])=>[r,Ie(a)]))}function He(t){return L(t,Oe)}const T=new Map,G=Symbol("opetope.selection-closed-state");function $e(t){try{return t.getSnapshot()}catch{return G}}const W=new Set;function Le(t,e,n){if(!ne(t))throw new l("binding-invalid","Model selection read requires a Readable.");if(e.has(t))return e.get(t);let s;try{s=t.getSnapshot()}catch(i){if(!se(i)||n===void 0||!n.has(t))throw i;s=n.get(t)}return e.set(t,s),s}function U(t,e){if(e.has(t))return e.get(t);let n;try{n=t.getSnapshot()}catch{n=G}return e.set(t,n),n}function Te(t,e,n,s){if(!ae(e))return;const i=e,o=i.pagination;n.set(t,{pagination:o===void 0?void 0:U(o.state,s),resource:i,state:U(i.state,s)})}function Ge(t){return t.size===0?T:t}function We(t,e,n){const i=t({read:(a,c)=>{const u=Le(a,e,n);return c===void 0?u:c(u)}});if(typeof i!="object"||i===null||Array.isArray(i))throw new l("binding-invalid","Model selection requires a record.");const o=[],r=new Map;for(const a of Reflect.ownKeys(i)){if(!Object.prototype.propertyIsEnumerable.call(i,a))continue;const c=i[a];o.push([a,c]),Te(a,c,r,e)}return{resources:Ge(r),values:Object.fromEntries(o)}}function Ue(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 qe(t,e,n,s){const i=Reflect.ownKeys(e),o=Reflect.ownKeys(t.values);if(i.length!==o.length||n.size!==t.sources.size)return!1;for(const r of n)if(!t.sources.has(r))return!1;return Ue(t.resources,s)?i.every((r,a)=>o[a]===r&&Object.is(t.values[r],e[r])):!1}const R={commands:{},leases:W,resources:T,sources:new Set,values:{}};function De(t){return Object.fromEntries(Reflect.ownKeys(t).filter(e=>te(t[e])).map(e=>[e,t[e]]))}function Be(t){if(t.size===0)return W;const e=new Set;for(const n of t.values())e.add(n.resource);return e}function Je(t,e){let n,s,i=R;return()=>{const o=e.current;if(o===void 0)return R;if(n!==void 0&&s===o&&[...n].every(([d,g])=>Object.is($e(d),g)))return i;const r=new Map,{resources:a,values:c}=We(d=>o(t,d),r,n),u=new Set(r.keys());return(n===void 0||!qe(i,c,u,a))&&(i={commands:De(c),leases:Be(a),resources:a,sources:u,values:c}),n=r,s=o,i}}const E=new Set;function q(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 ie(e,"Model selection sources failed to release.")}class Qe{enabled=!1;leases=new Map;listeners=new Set;retained=E;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.retained=E,this.releaseAll()}commit(e,n=E){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(),q(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));q(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 Ve=()=>R;function Xe(t){const e=C(void 0);return t?e.current??(e.current=new Qe):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=O();if(!n.models.has(t))throw new l("missing",`Model ${t.id} is not granted to this contribution.`);const s=n.models.get(t),i=Xe(e!==void 0),o=C(void 0);o.current=e;const r=e!==void 0,a=f(()=>r?Je(s,o):Ve,[s,r]),c=w((i==null?void 0:i.subscribe)??K,a,a),u=L(c.commands,c.resources);return y(()=>()=>i==null?void 0:i.close(),[i]),y(()=>i==null?void 0:i.commit(c.sources,c.leases),[c,i]),e===void 0?s:Ye(c,u)}function _e(t){const e=Object.freeze([...t]);return n=>{if(n.requires!==void 0)throw new l("duplicate","Component already declares the models it requires.");return Object.defineProperty(n,"requires",{enumerable:!1,value:e}),n}}function et(t){const[e]=v(()=>new WeakMap),n=e.get(t);if(n!==void 0)return n;const s=ue(t),i={invalidate:new h(s.invalidate),loadNext:new h(s.loadNext),refresh:new h(s.refresh),retry:new h(s.retry)};return e.set(t,i),i}function D(t,e){let n=e;return()=>{try{n=t.getSnapshot()}catch{}return n}}const B=Object.freeze({kind:"unavailable",reason:"resource-not-ready"}),tt=Object.freeze({getSnapshot:()=>B,subscribe:()=>()=>{}});function nt(t){const e=t,n=ce(e),s=F(S=>t.state.subscribe(S),[t]),i=f(()=>D(t.state,{kind:"idle",reason:"unleased"}),[t]),o=w(s,i,i),r=(n==null?void 0:n.state)??tt,a=F(S=>r.subscribe(S),[r]),c=f(()=>D(r,B),[r]),u=w(a,c,c);V(()=>t.acquire(),[t]);const d=et(e),g=m(d.refresh),z=m(d.retry),M=m(d.invalidate),j=m(d.loadNext),x=f(()=>n===void 0?void 0:{loadNext:j,state:u},[n,j,u]);return f(()=>({invalidate:M,pagination:x,refresh:g,retry:z,state:o}),[M,x,g,z,o])}const J=Object.freeze({});function Q(t){return oe(t,"slot")}function st(t){const e=Z(t),n=new Map,s=i=>{const o=n.get(i);if(o!==void 0)return o;const r=Q(`${e}/${i}`);return n.set(i,r),r};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function it(t){const e=I(t.target.entries),n="props"in t?t.props??J:J;return e.length===0?null:e.map(s=>P(X,{key:s.id},P(de,{authority:le(s),contribution:s.value,contributionId:s.id,slotProps:n,targetId:t.target.id})))}export{l as ContributionError,it as Slot,Q as defineSlot,st as defineSwitchSlot,_e as requiresModels,Se as useCommand,He as useCommands,Ze as useModel,I as useReadable,nt as useResource,ft as useSelector};
1
+ import{useState as O,useInsertionEffect as E,useLayoutEffect as w,useMemo as l,useSyncExternalStore as y,useRef as S,useCallback as F,useEffect as B,createElement as P,Fragment as J}from"react";import{CallError as Q,declarationId as V}from"@opetope/core";import{compatibleAbortReason as X,isCancellation as Y,reportDetachedError as N,isCallTarget as Z,isReadable as _,createAggregateError as ee,createContributionTarget as te}from"@opetope/core/internal";import{C as d}from"./contribution-isolation-Bzfbt4iM.js";import{u as C,r as ne,i as se,p as ie,c as oe,a as I,M as re,b as ae}from"./contribution-frame-DkNmjChZ.js";import{d as dt}from"./contribution-frame-DkNmjChZ.js";function A(t,e){var n;if(Y(e))return{reason:e,status:"cancelled"};try{const s=(n=t==null?void 0:t.onFailure)==null?void 0:n.call(t,e);s!==void 0&&Promise.resolve(s).catch(N)}catch(s){N(s)}return{error:e,status:"failed"}}function ce(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 ue(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 de(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 d("inactive","Command consumer is not mounted."),status:"cancelled"});const s=this.cancelledInput(n);if(s!==void 0)return Promise.resolve(s);const i=ue(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 Q("cancelled","Command input was cancelled.",X(e)),status:"cancelled"}}cancelledInput(e){var n;return((n=e==null?void 0:e.signal)==null?void 0:n.aborted)===!0?this.aborted(e.signal):void 0}finish(e,n){var s;e.resolve!==void 0&&(this.inFlight-=1,de(e,n),(s=this.notify)==null||s.call(this,this.inFlight>0,n))}invoke(e){let n;try{n=this.invoker.run(e.input,ce(e.options)),e.input=void 0}catch(s){this.finish(e,A(e.options,s));return}n.then(s=>this.finish(e,{status:"ok",value:s}),s=>this.finish(e,A(e.options,s)))}start(e){var n;this.inFlight+=1,(n=this.notify)==null||n.call(this,!0),this.invoke(e)}}const fe={inFlight:!1,outcome:void 0};function le(t){return t===void 0||t.status==="cancelled"?void 0:t}function he(t,e,n,s){return s===void 0&&t.slot===e&&t.inFlight===n}function be(t,e,n,s){const i=le(s);if(he(t,e,n,i))return t;const o=t.slot===e?t.outcome:void 0;return{inFlight:n,outcome:i??o,slot:e}}function pe(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 b(t){const[e,n]=O(()=>({inFlight:!1,outcome:void 0,slot:t}));E(()=>t.attach((r,a)=>n(c=>be(c,t,r,a))),[t]),w(()=>t.synchronize(),[t]);const{inFlight:s,outcome:i}=e.slot===t?e:fe,o=t.run;return l(()=>({inFlight:s,outcome:i,run:o}),[s,i,o])}function ge(t){const n=C().commandRecords.get(t);if(n===void 0)throw new d("missing",`Command ${t.id} is not bound in this contribution.`);return b(pe(n.invoker))}const me=()=>{},K=()=>me,we=()=>0,ye=Object.freeze({}),Se=new Map;function p(t){return"kind"in t}function g(t){const e=new h(t);return{detach:void 0,hook:{inFlight:!1,outcome:void 0,run:e.run},invoker:t,slot:e}}function ve(t,e){return t.inFlight===e.inFlight&&Object.is(t.outcome,e.outcome)}function ke(t,e,n){const s=t.hook;if(n===void 0&&s.inFlight===e)return!1;const i=n===void 0||n.status==="cancelled"?void 0:n,o={inFlight:e,outcome:i??s.outcome,run:s.run};return ve(s,o)?!1:(t.hook=o,!0)}function Oe(t,e){return t!==void 0&&!p(t)&&t.invoker===e?t:g(e)}function H(t,e){return{invalidate:t.invalidate.hook,pagination:t.paginated?{loadNext:t.loadNext.hook,state:e.pagination}:void 0,refresh:t.refresh.hook,retry:t.retry.hook,state:e.state}}function Ee(t,e){const n={invalidate:g(t.invalidate),kind:"resource",loadNext:g(t.loadNext),paginated:e.pagination!==void 0,refresh:g(t.refresh),retry:g(t.retry)};return{...n,hook:H(n,e)}}function Ce(t){var n;const{hook:e}=t;return e.refresh===t.refresh.hook&&e.retry===t.retry.hook&&e.invalidate===t.invalidate.hook&&(!t.paginated||((n=e.pagination)==null?void 0:n.loadNext)===t.loadNext.hook)}function Re(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 ze(t,e){return Ce(t)&&Re(t,e)}function Me(t,e,n){return t===void 0||!p(t)||t.refresh.invoker!==e.refresh?Ee(e,n):(ze(t,n)||(t.hook=H(t,n)),t)}function je(t){return p(t)?[t.refresh,t.retry,t.invalidate,t.loadNext]:[t]}function $(t){var e;for(const n of je(t))(e=n.detach)==null||e.call(n),n.detach=void 0}function xe(t){return t.hook}class Fe{entries=new Map;invokers=new WeakMap;listeners=new Set;revision=0;close(){for(const e of this.entries.values())$(e);this.entries.clear()}commit(e){for(const[n,s]of this.entries)e.get(n)!==s&&$(s);this.entries=e;for(const n of e.values())this.attachEntry(n)}attachEntry(e){if(p(e)){this.attachCommand(e.refresh),this.attachCommand(e.retry),this.attachCommand(e.invalidate),this.attachCommand(e.loadNext);return}this.attachCommand(e)}attachCommand(e){e.detach??(e.detach=e.slot.attach((n,s)=>{if(ke(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 o of Reflect.ownKeys(e)){if(!Object.prototype.propertyIsEnumerable.call(e,o))continue;const r=e[o],a=r===void 0?void 0:s.commandRecords.get(r);if(a===void 0)throw new d("missing",`Command at ${String(o)} is not bound in this contribution.`);i.set(o,Oe(this.entries.get(o),a.invoker))}for(const[o,r]of n){const a=ne(r.resource,this.invokers);i.set(o,Me(this.entries.get(o),a,r))}return i}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})}function Pe(t,e){const n=S(void 0);return e.size>0||Reflect.ownKeys(t).some(i=>Object.prototype.propertyIsEnumerable.call(t,i))?n.current??(n.current=new Fe):void 0}function L(t,e){const n=C(),s=Pe(t,e),i=(s==null?void 0:s.getSnapshot)??we;y((s==null?void 0:s.subscribe)??K,i,i);const o=s==null?void 0:s.prepare(t,e,n);return E(()=>()=>s==null?void 0:s.close(),[s]),E(()=>{o!==void 0&&(s==null||s.commit(o))}),w(()=>{if(o!==void 0)for(const r of o.values())p(r)?(r.refresh.slot.synchronize(),r.retry.slot.synchronize(),r.invalidate.slot.synchronize(),r.loadNext.slot.synchronize()):r.slot.synchronize()}),o===void 0?ye:Object.fromEntries([...o].map(([r,a])=>[r,xe(a)]))}function Ne(t){return L(t,Se)}const T=new Map,U=new Set;function Ie(t,e){if(!_(t))throw new d("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 W(t,e){if(e.has(t))return e.get(t);const n=t.getSnapshot();return e.set(t,n),n}function Ae(t,e,n,s){if(!se(e))return;const i=e,o=i.pagination;n.set(t,{pagination:o===void 0?void 0:W(o.state,s),resource:i,state:W(i.state,s)})}function Ke(t){return t.size===0?T:t}function He(t,e){const s=t({read:(r,a)=>{const c=Ie(r,e);return a===void 0?c:a(c)}});if(typeof s!="object"||s===null||Array.isArray(s))throw new d("binding-invalid","Model selection requires a record.");const i=[],o=new Map;for(const r of Reflect.ownKeys(s)){if(!Object.prototype.propertyIsEnumerable.call(s,r))continue;const a=s[r];i.push([r,a]),Ae(r,a,o,e)}return{resources:Ke(o),values:Object.fromEntries(i)}}function $e(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 Le(t,e,n,s){const i=Reflect.ownKeys(e),o=Reflect.ownKeys(t.values);if(i.length!==o.length||n.size!==t.sources.size)return!1;for(const r of n)if(!t.sources.has(r))return!1;return $e(t.resources,s)?i.every((r,a)=>o[a]===r&&Object.is(t.values[r],e[r])):!1}const v={commands:{},leases:U,resources:T,sources:new Set,values:{}};function Te(t){return Object.fromEntries(Reflect.ownKeys(t).filter(e=>Z(t[e])).map(e=>[e,t[e]]))}function Ue(t){if(t.size===0)return U;const e=new Set;for(const n of t.values())e.add(n.resource);return 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 qe(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 o=new Map,{resources:r,values:a}=He(f=>s(t,f),o),c=new Set(o.keys());return(i===void 0||!Le(n.snapshot,a,c,r))&&(n.snapshot={commands:Te(a),leases:Ue(r),resources:r,sources:c,values:a}),n.observed=o,n.select=s,n.snapshot}}const R=new Set;function G(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 ee(e,"Model selection sources failed to release.")}class De{enabled=!1;leases=new Map;listeners=new Set;retained=R;sources=new Set;subscriptions=new Map;close(){this.enabled=!1,this.sources=new Set,this.retained=R,this.releaseAll()}commit(e,n=R){this.sources=e,this.retained=n,this.enabled=!0;try{this.synchronize()}catch(s){this.releaseAll([s])}}subscribe=e=>{const n={notify:e};this.listeners.add(n);try{this.synchronize()}catch(s){this.listeners.delete(n),this.releaseAll([s])}return()=>{this.listeners.delete(n),this.listeners.size===0&&this.releaseAll()}};add(e){const n={dispose:void 0};this.subscriptions.set(e,n);const s=e.subscribe(this.notify);if(typeof s!="function")throw new TypeError("Model selection source must return a disposer.");this.subscriptions.get(e)===n?n.dispose=s:s()}notify=()=>{for(const e of[...this.listeners])this.listeners.has(e)&&e.notify()};releaseAll(e){const n=[...this.subscriptions.values(),...this.leases.values()];this.subscriptions.clear(),this.leases.clear(),G(n,e)}removeUnselected(){const e=[];for(const[n,s]of this.subscriptions)this.sources.has(n)||(this.subscriptions.delete(n),e.push(s));for(const[n,s]of this.leases)this.retained.has(n)||(this.leases.delete(n),e.push(s));G(e)}retain(e){const n={dispose:void 0};this.leases.set(e,n);const s=e.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 Be=()=>v;function Je(t){const e=S(void 0);return t?e.current??(e.current=new De):void 0}function Qe(t,e){return Object.fromEntries(Reflect.ownKeys(t.values).map(n=>[n,Object.prototype.hasOwnProperty.call(e,n)?e[n]:t.values[n]]))}function Ve(t,e){const n=C();if(!n.models.has(t))throw new d("missing",`Model ${t.id} is not granted to this contribution.`);const s=n.models.get(t),i=Je(e!==void 0),o=S(void 0);o.current=e;const r=e!==void 0,a=S(void 0),c=a.current??(a.current=Ge()),f=l(()=>r?qe(s,o,c):Be,[c,s,r]),u=y((i==null?void 0:i.subscribe)??K,f,f),m=L(u.commands,u.resources);return w(()=>()=>i==null?void 0:i.close(),[i]),w(()=>i==null?void 0:i.commit(u.sources,u.leases),[u,i]),e===void 0?s:Qe(u,m)}function Xe(t){const e=Object.freeze([...t]);return n=>{if(n.requires!==void 0)throw new d("duplicate","Component already declares the models it requires.");return Object.defineProperty(n,"requires",{enumerable:!1,value:e}),n}}function Ye(t){const[e]=O(()=>new WeakMap),n=e.get(t);if(n!==void 0)return n;const s=oe(t),i={invalidate:new h(s.invalidate),loadNext:new h(s.loadNext),refresh:new h(s.refresh),retry:new h(s.retry)};return e.set(t,i),i}const Ze=Object.freeze({kind:"unavailable",reason:"resource-not-ready"}),_e=Object.freeze({getSnapshot:()=>Ze,subscribe:()=>()=>{}});function et(t){const e=t,n=ie(e),s=F(k=>t.state.subscribe(k),[t]),i=l(()=>()=>t.state.getSnapshot(),[t]),o=y(s,i,i),r=(n==null?void 0:n.state)??_e,a=F(k=>r.subscribe(k),[r]),c=l(()=>()=>r.getSnapshot(),[r]),f=y(a,c,c);B(()=>t.acquire(),[t]);const u=Ye(e),m=b(u.refresh),z=b(u.retry),M=b(u.invalidate),j=b(u.loadNext),x=l(()=>n===void 0?void 0:{loadNext:j,state:f},[n,j,f]);return l(()=>({invalidate:M,pagination:x,refresh:m,retry:z,state:o}),[M,x,m,z,o])}const q=Object.freeze({});function D(t){return te(t,"slot")}function tt(t){const e=V(t),n=new Map,s=i=>{const o=n.get(i);if(o!==void 0)return o;const r=D(`${e}/${i}`);return n.set(i,r),r};return Object.defineProperty(s,"id",{enumerable:!0,value:e}),Object.freeze(s)}function nt(t){const e=I(t.target.entries),n="props"in t?t.props??q:q;return e.length===0?null:e.map(s=>P(J,{key:s.id},P(re,{authority:ae(s),contribution:s.value,contributionId:s.id,slotProps:n,targetId:t.target.id})))}export{d as ContributionError,nt as Slot,D as defineSlot,tt as defineSwitchSlot,Xe as requiresModels,ge as useCommand,Ne as useCommands,Ve as useModel,I as useReadable,et as useResource,dt as useSelector};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/command-hook-controller.ts","../src/command-hook.ts","../src/idle-subscription.ts","../src/commands-hook.ts","../src/model-selection-snapshot.ts","../src/model-selection-store.ts","../src/model-hook.ts","../src/requires-models.ts","../src/resource-hook.ts","../src/slot.tsx"],"sourcesContent":["import { CallError } from '@opetope/core';\nimport { compatibleAbortReason, isCancellation, reportDetachedError } from '@opetope/core/internal';\nimport type { CallInputArgs, CallRunOptions } from '@opetope/core/internal';\n\nimport type { CommandOutcome } from './command';\nimport { ContributionError } from './errors';\n\ntype CommandRunOptions<Output> = Readonly<{\n onFailure?: (error: unknown) => PromiseLike<void> | void;\n onSuccess?: (value: Output) => PromiseLike<void> | void;\n signal?: AbortSignal;\n}>;\n\ntype CommandRun<Input, Output> = {\n (input: Input, options?: CommandRunOptions<Output>): Promise<CommandOutcome<Output>>;\n (...args: [...CallInputArgs<Input>, options?: CommandRunOptions<Output>]): Promise<CommandOutcome<Output>>;\n};\n\ntype CommandInvoker = Readonly<{\n run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;\n}>;\n\ntype CommandNotification<Output> = (inFlight: boolean, outcome?: CommandOutcome<Output>) => void;\n\ntype CommandRequest<Input, Output> = {\n input: Input | undefined;\n options: CommandRunOptions<Output> | undefined;\n readonly promise: Promise<CommandOutcome<Output>>;\n resolve: ((outcome: CommandOutcome<Output>) => void) | undefined;\n};\n\nfunction failureOutcome<Output>(options: CommandRunOptions<Output> | undefined, error: unknown): CommandOutcome<never> {\n if (isCancellation(error)) return { reason: error, status: 'cancelled' };\n\n try {\n const notification = options?.onFailure?.(error);\n\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n } catch (callbackError) {\n reportDetachedError(callbackError);\n }\n\n return { error, status: 'failed' };\n}\n\nfunction runOptions<Output>(options: CommandRunOptions<Output> | undefined): CallRunOptions<Output> | undefined {\n const onSuccess = options?.onSuccess;\n const signal = options?.signal;\n\n if (onSuccess === undefined) return signal === undefined ? undefined : { signal };\n\n return signal === undefined ? { onSuccess } : { onSuccess, signal };\n}\n\nfunction requestOf<Input, Output>(\n input: Input,\n options: CommandRunOptions<Output> | undefined,\n): CommandRequest<Input, Output> {\n let resolve!: (outcome: CommandOutcome<Output>) => void;\n const promise = new Promise<CommandOutcome<Output>>(settle => {\n resolve = settle;\n });\n\n return { input, options: options === undefined ? undefined : { ...options }, promise, resolve };\n}\n\nfunction settle<Input, Output>(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n const resolve = request.resolve;\n request.input = undefined;\n request.options = undefined;\n request.resolve = undefined;\n resolve?.(outcome);\n}\n\n/**\n * D203: the consumer keeps a status, not a schedule. Every `run` reaches the call, and the policy the call was\n * created with — `queue`, `latest`, `parallel`, `once`, `dedupe` — decides what happens to it. What stays\n * local is what only this consumer knows: whether it is mounted, its own input signal, and the outcome it shows.\n */\nclass CommandHookController<Input, Output> {\n readonly run: CommandRun<Input, Output> = ((input: Input, options?: CommandRunOptions<Output>) => {\n if (this.notify === undefined) {\n return Promise.resolve({\n reason: new ContributionError('inactive', 'Command consumer is not mounted.'),\n status: 'cancelled' as const,\n });\n }\n\n const aborted = this.cancelledInput(options);\n\n if (aborted !== undefined) return Promise.resolve(aborted);\n\n const request = requestOf(input, options);\n this.start(request);\n\n return request.promise;\n }) as CommandRun<Input, Output>;\n private inFlight = 0;\n private notify: CommandNotification<Output> | undefined;\n\n constructor(private readonly invoker: CommandInvoker) {}\n\n attach(notify: CommandNotification<Output>): () => void {\n this.notify = notify;\n\n return () => {\n if (this.notify !== notify) return;\n\n this.notify = undefined;\n };\n }\n\n synchronize(): void {\n this.notify?.(this.inFlight > 0);\n }\n\n private aborted(signal: AbortSignal): CommandOutcome<never> {\n return {\n reason: new CallError('cancelled', 'Command input was cancelled.', compatibleAbortReason(signal)),\n status: 'cancelled',\n };\n }\n\n private cancelledInput(options: CommandRunOptions<Output> | undefined): CommandOutcome<never> | undefined {\n return options?.signal?.aborted === true ? this.aborted(options.signal) : undefined;\n }\n\n private finish(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n if (request.resolve === undefined) return;\n\n this.inFlight -= 1;\n settle(request, outcome);\n this.notify?.(this.inFlight > 0, outcome);\n }\n\n private invoke(request: CommandRequest<Input, Output>): void {\n let invocation: Promise<Output>;\n\n try {\n invocation = this.invoker.run(request.input as never, runOptions(request.options) as never) as Promise<Output>;\n request.input = undefined;\n } catch (error) {\n this.finish(request, failureOutcome(request.options, error));\n\n return;\n }\n\n void invocation.then(\n value => this.finish(request, { status: 'ok', value }),\n (error: unknown) => this.finish(request, failureOutcome(request.options, error)),\n );\n }\n\n private start(request: CommandRequest<Input, Output>): void {\n this.inFlight += 1;\n // Reserve the flight before publishing: a reentrant run must already see this consumer as busy.\n this.notify?.(true);\n this.invoke(request);\n }\n}\n\nexport { CommandHookController };\nexport type { CommandInvoker, CommandRun };\n","import { useInsertionEffect, useLayoutEffect, useMemo, useState } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker, CommandRun } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { useFrame } from './mount-context';\n\n/** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */\ntype SettledOutcome<Output> = Exclude<CommandOutcome<Output>, { readonly status: 'cancelled' }>;\n\ninterface CommandHookStatus<Output> {\n readonly inFlight: boolean;\n readonly outcome: SettledOutcome<Output> | undefined;\n}\n\nconst emptyStatus: CommandHookStatus<never> = { inFlight: false, outcome: undefined };\n\ntype CommandHook<Input, Output> = CommandHookStatus<Output> & {\n readonly run: CommandRun<Input, Output>;\n};\n\ntype CommandHookState<Input, Output> = CommandHookStatus<Output> & {\n readonly slot: CommandHookController<Input, Output>;\n};\n\n/** D292: a cancellation moves nothing, so it is the same non-event as a notification without an outcome at all. */\nfunction settledOutcome<Output>(outcome: CommandOutcome<Output> | undefined): SettledOutcome<Output> | undefined {\n return outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n}\n\nfunction unchangedState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n settled: SettledOutcome<Output> | undefined,\n): boolean {\n return settled === undefined && current.slot === slot && current.inFlight === inFlight;\n}\n\nfunction nextState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n outcome?: CommandOutcome<Output>,\n): CommandHookState<Input, Output> {\n const settled = settledOutcome(outcome);\n\n if (unchangedState(current, slot, inFlight, settled)) return current;\n\n // A new run clears nothing: the previous answer stays readable while its retry is in flight.\n const previous = current.slot === slot ? current.outcome : undefined;\n\n return { inFlight, outcome: settled ?? previous, slot };\n}\n\nfunction useCommandSlot<Input, Output>(invoker: CommandInvoker): CommandHookController<Input, Output> {\n // Slots are inert until commit. An interrupted render cannot fence the committed source or admit its own work.\n const [slots] = useState(() => new WeakMap<object, CommandHookController<Input, Output>>());\n let slot = slots.get(invoker);\n\n if (slot === undefined) {\n slot = new CommandHookController(invoker);\n slots.set(invoker, slot);\n }\n\n return slot;\n}\n\n/**\n * The status half of a consumer, apart from what it is a consumer of. D321 gives a resource the same two verbs a\n * command has, so the same three React hooks answer for them: the state of this consumer, the attachment that moves\n * it, and the synchronisation of a slot that was already busy when this mount arrived.\n */\nfunction useCommandStatus<Input, Output>(\n currentSlot: CommandHookController<Input, Output>,\n): CommandHook<Input, Output> {\n const [hookState, setHookState] = useState<CommandHookState<Input, Output>>(() => ({\n inFlight: false,\n outcome: undefined,\n slot: currentSlot,\n }));\n useInsertionEffect(\n () =>\n currentSlot.attach((inFlight, outcome) =>\n setHookState(current => nextState(current, currentSlot, inFlight, outcome)),\n ),\n [currentSlot],\n );\n useLayoutEffect(() => currentSlot.synchronize(), [currentSlot]);\n const { inFlight, outcome } = hookState.slot === currentSlot ? hookState : emptyStatus;\n const run = currentSlot.run;\n\n return useMemo(() => ({ inFlight, outcome, run }), [inFlight, outcome, run]);\n}\n\nfunction useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output> {\n const frame = useFrame();\n const record = frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command ${command.id} is not bound in this contribution.`);\n }\n\n return useCommandStatus(useCommandSlot<Input, Output>(record.invoker));\n}\n\nexport { useCommand, useCommandStatus };\nexport type { CommandHook };\n","const idleRelease = (): void => undefined;\nconst subscribeIdle = (): (() => void) => idleRelease;\nconst idleRevision = (): number => 0;\n\nexport { idleRevision, subscribeIdle };\n","import { useInsertionEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport type { CommandHook } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { idleRevision, subscribeIdle } from './idle-subscription';\nimport type { SelectedResource } from './model-selection-snapshot';\nimport { useFrame } from './mount-context';\nimport type { Frame } from './mount-frame';\nimport { resourceInvokers } from './resource-command';\nimport type { ResourceInvokers } from './resource-command';\n\ntype CommandSelection = Readonly<Record<string, Call<never, unknown>>>;\ntype ResourceSelection = ReadonlyMap<PropertyKey, SelectedResource>;\ntype HookOf<Command> = Command extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : never;\ntype CommandsHook<Commands> = {\n readonly [Key in keyof Commands]: HookOf<Commands[Key]>;\n};\n/** D359: what one selected Resource answers — its three verbs, its state, and the pagination capability if it has one. */\ntype SelectedResourceHook = Readonly<{\n invalidate: CommandHook<never, unknown>;\n pagination: Readonly<{ loadNext: CommandHook<never, unknown>; state: unknown }> | undefined;\n refresh: CommandHook<never, unknown>;\n retry: CommandHook<never, unknown>;\n state: unknown;\n}>;\ntype SelectionHooks = Readonly<Record<PropertyKey, CommandHook<never, unknown> | SelectedResourceHook>>;\n\nconst emptyHooks: SelectionHooks = Object.freeze({});\nconst noResources: ResourceSelection = new Map();\n\ntype CommandEntry = {\n detach: (() => void) | undefined;\n hook: CommandHook<never, unknown>;\n readonly invoker: CommandInvoker;\n readonly slot: CommandHookController<never, unknown>;\n};\n\n/** D359: one key of a selection, four consumers. The record is the value; `entries` still holds one entry per key. */\ntype ResourceEntry = {\n /** D359: the value this key hands the render, kept on the record so a still frame is the same object again. */\n hook: SelectedResourceHook;\n readonly invalidate: CommandEntry;\n readonly kind: 'resource';\n readonly loadNext: CommandEntry;\n /** Whether the declaration carried the pagination capability at all. */\n readonly paginated: boolean;\n readonly refresh: CommandEntry;\n readonly retry: CommandEntry;\n};\n\ntype SelectionEntry = CommandEntry | ResourceEntry;\n\nfunction isResourceEntry(entry: SelectionEntry): entry is ResourceEntry {\n return 'kind' in entry;\n}\n\nfunction createEntry(invoker: CommandInvoker): CommandEntry {\n const slot = new CommandHookController<never, unknown>(invoker);\n\n return { detach: undefined, hook: { inFlight: false, outcome: undefined, run: slot.run }, invoker, slot };\n}\n\nfunction unchangedHook(previous: CommandHook<never, unknown>, next: CommandHook<never, unknown>): boolean {\n return previous.inFlight === next.inFlight && Object.is(previous.outcome, next.outcome);\n}\n\nfunction updateEntry(entry: CommandEntry, inFlight: boolean, outcome?: CommandOutcome<unknown>): boolean {\n const previous = entry.hook;\n\n if (outcome === undefined && previous.inFlight === inFlight) return false;\n\n // D292: only a settled non-cancelled outcome replaces the one this key shows; a retry keeps the previous answer.\n const settled = outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n const next = { inFlight, outcome: settled ?? previous.outcome, run: previous.run };\n\n if (unchangedHook(previous, next)) return false;\n\n entry.hook = next;\n\n return true;\n}\n\nfunction selectEntry(previous: SelectionEntry | undefined, invoker: CommandInvoker): CommandEntry {\n return previous !== undefined && !isResourceEntry(previous) && previous.invoker === invoker\n ? previous\n : createEntry(invoker);\n}\n\nfunction resourceHook(entry: Omit<ResourceEntry, 'hook'>, selected: SelectedResource): SelectedResourceHook {\n return {\n invalidate: entry.invalidate.hook,\n pagination: entry.paginated ? { loadNext: entry.loadNext.hook, state: selected.pagination } : undefined,\n refresh: entry.refresh.hook,\n retry: entry.retry.hook,\n state: selected.state,\n };\n}\n\nfunction createResourceEntry(invokers: ResourceInvokers, selected: SelectedResource): ResourceEntry {\n const parts = {\n invalidate: createEntry(invokers.invalidate),\n kind: 'resource' as const,\n loadNext: createEntry(invokers.loadNext),\n paginated: selected.pagination !== undefined,\n refresh: createEntry(invokers.refresh),\n retry: createEntry(invokers.retry),\n };\n\n return { ...parts, hook: resourceHook(parts, selected) };\n}\n\n/** The four commands of a Resource: the three verbs every one has, and `loadNext` only where one was declared. */\nfunction unchangedResourceCommands(entry: ResourceEntry): boolean {\n const { hook } = entry;\n\n return (\n hook.refresh === entry.refresh.hook &&\n hook.retry === entry.retry.hook &&\n hook.invalidate === entry.invalidate.hook &&\n (!entry.paginated || hook.pagination?.loadNext === entry.loadNext.hook)\n );\n}\n\n/** What this selection read: the state of the Resource, and the state of its capability where it has one. */\nfunction unchangedResourceState(entry: ResourceEntry, selected: SelectedResource): boolean {\n return Object.is(entry.hook.state, selected.state) && Object.is(entry.hook.pagination?.state, selected.pagination);\n}\n\nfunction unchangedResourceHook(entry: ResourceEntry, selected: SelectedResource): boolean {\n return unchangedResourceCommands(entry) && unchangedResourceState(entry, selected);\n}\n\n/**\n * S6/D359: the same Resource that did not move answers the same object, so a child under `memo` is not re-rendered by\n * the mere act of reading it. Every part is a render input, so this is a memo and not a piece of state.\n */\nfunction selectResourceEntry(\n previous: SelectionEntry | undefined,\n invokers: ResourceInvokers,\n selected: SelectedResource,\n): ResourceEntry {\n if (previous === undefined || !isResourceEntry(previous) || previous.refresh.invoker !== invokers.refresh) {\n return createResourceEntry(invokers, selected);\n }\n\n if (!unchangedResourceHook(previous, selected)) previous.hook = resourceHook(previous, selected);\n\n return previous;\n}\n\nfunction commandEntries(entry: SelectionEntry): readonly CommandEntry[] {\n return isResourceEntry(entry) ? [entry.refresh, entry.retry, entry.invalidate, entry.loadNext] : [entry];\n}\n\nfunction detachEntry(entry: SelectionEntry): void {\n for (const command of commandEntries(entry)) {\n command.detach?.();\n command.detach = undefined;\n }\n}\n\nfunction hookOf(entry: SelectionEntry): CommandHook<never, unknown> | SelectedResourceHook {\n return entry.hook;\n}\n\n/** Selection is inert during render; only a committed selection can admit work or discard an old queue. */\nclass CommandGroup {\n private entries = new Map<PropertyKey, SelectionEntry>();\n /** One set of invokers per Resource identity, so `run` keeps its reference across renders (D359). */\n private readonly invokers = new WeakMap<object, ResourceInvokers>();\n private readonly listeners = new Set<() => void>();\n private revision = 0;\n\n close(): void {\n for (const entry of this.entries.values()) detachEntry(entry);\n\n this.entries.clear();\n }\n\n commit(selected: Map<PropertyKey, SelectionEntry>): void {\n for (const [key, entry] of this.entries) {\n if (selected.get(key) !== entry) detachEntry(entry);\n }\n\n this.entries = selected;\n\n for (const entry of selected.values()) this.attachEntry(entry);\n }\n\n /** S7: the attach runs on every commit, so the verbs are named rather than gathered into an array. */\n private attachEntry(entry: SelectionEntry): void {\n if (isResourceEntry(entry)) {\n this.attachCommand(entry.refresh);\n this.attachCommand(entry.retry);\n this.attachCommand(entry.invalidate);\n this.attachCommand(entry.loadNext);\n\n return;\n }\n\n this.attachCommand(entry);\n }\n\n private attachCommand(command: CommandEntry): void {\n command.detach ??= command.slot.attach((inFlight, outcome) => {\n if (!updateEntry(command, inFlight, outcome)) return;\n\n this.revision += 1;\n\n for (const listener of this.listeners) listener();\n });\n }\n\n getSnapshot = (): number => this.revision;\n\n /**\n * D321: two outcomes no longer, but two kinds. A command that is not bound is still a `ContributionError`; a\n * resource never appears in `frame.commandRecords`, so it is prepared from its own identity instead.\n */\n prepare(commands: CommandSelection, resources: ResourceSelection, frame: Frame): Map<PropertyKey, SelectionEntry> {\n const selected = new Map<PropertyKey, SelectionEntry>();\n\n for (const key of Reflect.ownKeys(commands)) {\n if (!Object.prototype.propertyIsEnumerable.call(commands, key)) continue;\n\n const command = (commands as Readonly<Record<PropertyKey, Call<never, unknown>>>)[key];\n const record = command === undefined ? undefined : frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command at ${String(key)} is not bound in this contribution.`);\n }\n\n selected.set(key, selectEntry(this.entries.get(key), record.invoker));\n }\n\n for (const [key, resource] of resources) {\n const invokers = resourceInvokers(resource.resource, this.invokers);\n\n selected.set(key, selectResourceEntry(this.entries.get(key), invokers, resource));\n }\n\n return selected;\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n };\n}\n\nfunction useCommandGroup(commands: CommandSelection, resources: ResourceSelection): CommandGroup | undefined {\n const groupRef = useRef<CommandGroup | undefined>(undefined);\n const populated =\n resources.size > 0 ||\n Reflect.ownKeys(commands).some(key => Object.prototype.propertyIsEnumerable.call(commands, key));\n\n return populated ? (groupRef.current ??= new CommandGroup()) : undefined;\n}\n\n/** The consumers of one selection: a command per key, and the two verbs of a resource per key (D321). */\nfunction useSelectionHooks(commands: CommandSelection, resources: ResourceSelection): SelectionHooks {\n const frame = useFrame();\n const group = useCommandGroup(commands, resources);\n const getSnapshot = group?.getSnapshot ?? idleRevision;\n useSyncExternalStore(group?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const selected = group?.prepare(commands, resources, frame);\n useInsertionEffect(() => () => group?.close(), [group]);\n useInsertionEffect(() => {\n if (selected !== undefined) group?.commit(selected);\n });\n useLayoutEffect(() => {\n if (selected === undefined) return;\n\n // S7: the same branch as the attach — this runs after every commit, and the arrays were pure allocation.\n for (const entry of selected.values()) {\n if (isResourceEntry(entry)) {\n entry.refresh.slot.synchronize();\n entry.retry.slot.synchronize();\n entry.invalidate.slot.synchronize();\n entry.loadNext.slot.synchronize();\n } else entry.slot.synchronize();\n }\n });\n\n return selected === undefined\n ? emptyHooks\n : Object.fromEntries([...selected].map(([key, entry]) => [key, hookOf(entry)]));\n}\n\nfunction useCommands<Commands extends { readonly [Key in keyof Commands]: Call<never, unknown> }>(\n commands: Commands,\n): CommandsHook<Commands> {\n return useSelectionHooks(commands, noResources) as CommandsHook<Commands>;\n}\n\nexport { useCommands, useSelectionHooks };\nexport type { SelectionHooks };\n","import type { Readable } from '@opetope/core';\nimport { isCallTarget, isClosedReadable, isReadable } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\nimport type { AnyCommand } from './model-binding';\nimport { isResource } from './resource-command';\nimport type { AnyResource } from './resource-command';\n\ninterface SelectionRead {\n <Value>(source: Readable<Value>): Value;\n <Value, Selected>(source: Readable<Value>, select: (value: Value) => Selected): Selected;\n}\n\ntype SelectionContext = Readonly<{ read: SelectionRead }>;\ntype NonSelectionRecord =\n | readonly unknown[]\n | ((...arguments_: never[]) => unknown)\n | (abstract new (...arguments_: never[]) => unknown)\n | Date\n | PromiseLike<unknown>\n | ReadonlyMap<unknown, unknown>\n | ReadonlySet<unknown>\n | RegExp\n | WeakMap<object, unknown>\n | WeakSet<object>;\n/** A named interface needs no open index signature; container and callable results are not field selections. */\ntype SelectionShape<Selected extends object> = Selected extends NonSelectionRecord ? never : Selected;\ntype SelectionRecord = Readonly<Record<PropertyKey, unknown>>;\n/**\n * D359: the Resource a field selected, and what this selection read from it — its state, and the state of its\n * pagination capability when it has one. Both are read passively: a selection takes no lease by reading.\n */\ntype SelectedResource = Readonly<{ pagination: unknown; resource: AnyResource; state: unknown }>;\ntype SelectionSnapshot = Readonly<{\n commands: Readonly<Record<PropertyKey, AnyCommand>>;\n /** The set the mount leases; two keys naming one resource are one lease. */\n leases: ReadonlySet<AnyResource>;\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n sources: ReadonlySet<Readable<unknown>>;\n values: SelectionRecord;\n}>;\n\nconst noResources: ReadonlyMap<PropertyKey, SelectedResource> = new Map();\n\n/**\n * G13: a render may race the retirement of the owner, and a retired materialization closes its state by the general\n * Readable contract. A selection answers the closed record rather than throwing, so the tree unmounts instead of\n * failing in the middle of a render.\n */\nconst closedSelection = Symbol('opetope.selection-closed-state');\n\n/** The fast path re-reads what it observed, and a source the owner closed answers the same closed record. */\nfunction readCurrent(source: Readable<unknown>): unknown {\n try {\n return source.getSnapshot();\n } catch {\n return closedSelection;\n }\n}\nconst noLeases: ReadonlySet<AnyResource> = new Set();\n\nfunction readSource<Value>(\n source: Readable<Value>,\n sources: Map<Readable<unknown>, unknown>,\n observed: ReadonlyMap<Readable<unknown>, unknown> | undefined,\n): Value {\n if (!isReadable(source)) {\n throw new ContributionError('binding-invalid', 'Model selection read requires a Readable.');\n }\n\n if (sources.has(source)) return sources.get(source) as Value;\n\n let value: unknown;\n\n try {\n value = source.getSnapshot();\n } catch (error) {\n /*\n * G13, D362: a close is the owner's decision, and the render it wakes is the one that races the retirement. An\n * explicit `read` answers what this selection last read from that source — the selector keeps seeing a value of\n * the type it was written for — and the tree unmounts instead of failing in the middle of a render. A source\n * this selection never read has no such answer, and every other refusal is the source's own.\n */\n if (!isClosedReadable(error) || observed === undefined || !observed.has(source)) throw error;\n\n value = observed.get(source);\n }\n\n sources.set(source, value);\n\n return value as Value;\n}\n\nfunction readPassively(source: Readable<unknown>, sources: Map<Readable<unknown>, unknown>): unknown {\n if (sources.has(source)) return sources.get(source);\n\n let value: unknown;\n\n try {\n value = source.getSnapshot();\n } catch {\n value = closedSelection;\n }\n\n sources.set(source, value);\n\n return value;\n}\n\n/** D359: the predicate that answers the question the type of the field asked, and the read that leases nothing. */\nfunction classifyResource(\n key: PropertyKey,\n value: unknown,\n resources: Map<PropertyKey, SelectedResource>,\n sources: Map<Readable<unknown>, unknown>,\n): void {\n if (!isResource(value)) return;\n\n const resource = value as AnyResource;\n const capability = (resource as { readonly pagination?: { readonly state: Readable<unknown> } }).pagination;\n\n resources.set(key, {\n pagination: capability === undefined ? undefined : readPassively(capability.state, sources),\n resource,\n state: readPassively(resource.state, sources),\n });\n}\n\nfunction selectedResources(resources: Map<PropertyKey, SelectedResource>): ReadonlyMap<PropertyKey, SelectedResource> {\n return resources.size === 0 ? noResources : resources;\n}\n\ntype CollectedSelection = Readonly<{\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n values: SelectionRecord;\n}>;\n\n/**\n * D359: recognition happens in the one pass that already walks the selection. `Resource` is a structural contract\n * (D199) and the type of a selected field promises `ResourceHook` for anything that satisfies it, so the predicate\n * this uses answers the same question the type asked. Reading the state here also puts it among `sources`, so its\n * publication invalidates the fast path of this reader exactly as a read source does.\n */\nfunction collectSelection(\n select: (context: SelectionContext) => object,\n sources: Map<Readable<unknown>, unknown>,\n observed: ReadonlyMap<Readable<unknown>, unknown> | undefined,\n): CollectedSelection {\n const read = <Value, Selected>(source: Readable<Value>, project?: (value: Value) => Selected) => {\n const value = readSource(source, sources, observed);\n\n return project === undefined ? value : project(value);\n };\n const selected = select({ read });\n\n if (typeof selected !== 'object' || selected === null || Array.isArray(selected)) {\n throw new ContributionError('binding-invalid', 'Model selection requires a record.');\n }\n\n const entries: (readonly [PropertyKey, unknown])[] = [];\n const resources = new Map<PropertyKey, SelectedResource>();\n\n for (const key of Reflect.ownKeys(selected)) {\n if (!Object.prototype.propertyIsEnumerable.call(selected, key)) continue;\n\n const value = (selected as SelectionRecord)[key];\n\n entries.push([key, value]);\n classifyResource(key, value, resources, sources);\n }\n\n // `__proto__` is a key like any other here, and only a defined property keeps it one (D214).\n return { resources: selectedResources(resources), values: Object.fromEntries(entries) };\n}\n\n/**\n * D359: `values` keeps the Resource itself — a stable reference — so a selection that holds one is compared by\n * `Object.is` exactly as before. What moves is the state behind it, and that is compared here: without this the\n * selection would be «equal», the previous state would be kept, and a published Resource would reach no render.\n */\nfunction equalResources(\n previous: ReadonlyMap<PropertyKey, SelectedResource>,\n next: ReadonlyMap<PropertyKey, SelectedResource>,\n): boolean {\n if (previous.size !== next.size) return false;\n\n for (const [key, selected] of next) {\n const before = previous.get(key);\n\n if (\n before?.resource !== selected.resource ||\n !Object.is(before.state, selected.state) ||\n !Object.is(before.pagination, selected.pagination)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction equalSelection(\n previous: SelectionSnapshot,\n values: SelectionRecord,\n sources: ReadonlySet<Readable<unknown>>,\n resources: ReadonlyMap<PropertyKey, SelectedResource>,\n) {\n const keys = Reflect.ownKeys(values);\n const previousKeys = Reflect.ownKeys(previous.values);\n\n if (keys.length !== previousKeys.length || sources.size !== previous.sources.size) return false;\n\n for (const source of sources) if (!previous.sources.has(source)) return false;\n\n if (!equalResources(previous.resources, resources)) return false;\n\n return keys.every((key, index) => previousKeys[index] === key && Object.is(previous.values[key], values[key]));\n}\n\ntype ModelSelector<Model> = (model: Model, context: SelectionContext) => object;\ntype SelectionReaderRef<Model> = { readonly current: ModelSelector<Model> | undefined };\n\nconst emptySelectionSnapshot: SelectionSnapshot = {\n commands: {},\n leases: noLeases,\n resources: noResources,\n sources: new Set(),\n values: {},\n};\n\nfunction selectionCommands(values: SelectionRecord): Readonly<Record<PropertyKey, AnyCommand>> {\n return Object.fromEntries(\n Reflect.ownKeys(values)\n .filter(key => isCallTarget(values[key]))\n .map(key => [key, values[key]]),\n ) as Readonly<Record<PropertyKey, AnyCommand>>;\n}\n\nfunction selectionLeases(resources: ReadonlyMap<PropertyKey, SelectedResource>): ReadonlySet<AnyResource> {\n if (resources.size === 0) return noLeases;\n\n const leased = new Set<AnyResource>();\n\n for (const selected of resources.values()) leased.add(selected.resource);\n\n return leased;\n}\n\n/**\n * D214: one reader per model, not per render. The author's selector arrives through a ref, so an inline arrow does not\n * rebuild the reader; the fast path — nothing observed has moved — belongs to the selector that computed the snapshot,\n * so a new selector is always called again, its result compared field by field, and an equal selection keeps the\n * previous snapshot. A rejected or abandoned render still changes nothing: only a committed selection subscribes.\n */\nfunction createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>): () => SelectionSnapshot {\n let observed: Map<Readable<unknown>, unknown> | undefined;\n let observedSelect: ModelSelector<Model> | undefined;\n let snapshot: SelectionSnapshot = emptySelectionSnapshot;\n\n return () => {\n const select = selectRef.current;\n\n if (select === undefined) return emptySelectionSnapshot;\n\n if (\n observed !== undefined &&\n observedSelect === select &&\n [...observed].every(([source, value]) => Object.is(readCurrent(source), value))\n ) {\n return snapshot;\n }\n\n const nextObserved = new Map<Readable<unknown>, unknown>();\n const { resources, values } = collectSelection(context => select(model, context), nextObserved, observed);\n const sources = new Set(nextObserved.keys());\n\n if (observed === undefined || !equalSelection(snapshot, values, sources, resources)) {\n snapshot = {\n commands: selectionCommands(values),\n leases: selectionLeases(resources),\n resources,\n sources,\n values,\n };\n }\n\n observed = nextObserved;\n observedSelect = select;\n\n return snapshot;\n };\n}\n\nexport { createSelectionSnapshot, emptySelectionSnapshot };\nexport type { ModelSelector, SelectedResource, SelectionContext, SelectionShape, SelectionSnapshot };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\nimport type { AnyResource } from './resource-command';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nconst noLeases: ReadonlySet<AnyResource> = new Set();\n\nfunction releaseSubscriptions(subscriptions: Iterable<Subscription>, failures: unknown[] = []): void {\n for (const subscription of subscriptions) {\n const dispose = subscription.dispose;\n subscription.dispose = undefined;\n\n try {\n dispose?.();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n\n if (failures.length > 1) throw createAggregateError(failures, 'Model selection sources failed to release.');\n}\n\n/** A subscription set, not a scheduler: source notifications use React's snapshot consistency check. */\nclass ModelSelectionStore {\n private enabled = false;\n /** D359: one lease per Resource identity, diffed like a subscription, so an unchanged selection takes none. */\n private readonly leases = new Map<AnyResource, Subscription>();\n private readonly listeners = new Set<Listener>();\n private retained: ReadonlySet<AnyResource> = noLeases;\n private sources: ReadonlySet<Readable<unknown>> = new Set();\n private readonly subscriptions = new Map<Readable<unknown>, Subscription>();\n\n close(): void {\n this.enabled = false;\n this.sources = new Set();\n this.retained = noLeases;\n this.releaseAll();\n }\n\n commit(sources: ReadonlySet<Readable<unknown>>, retained: ReadonlySet<AnyResource> = noLeases): void {\n this.sources = sources;\n this.retained = retained;\n this.enabled = true;\n\n try {\n this.synchronize();\n } catch (error) {\n this.releaseAll([error]);\n }\n }\n\n subscribe = (notify: () => void): (() => void) => {\n const listener = { notify };\n this.listeners.add(listener);\n\n try {\n this.synchronize();\n } catch (error) {\n this.listeners.delete(listener);\n this.releaseAll([error]);\n }\n\n return () => {\n this.listeners.delete(listener);\n\n if (this.listeners.size === 0) this.releaseAll();\n };\n };\n\n private add(source: Readable<unknown>): void {\n const subscription: Subscription = { dispose: undefined };\n // Reserve before foreign code: a synchronous notification may replace or close this selection.\n this.subscriptions.set(source, subscription);\n const dispose = source.subscribe(this.notify);\n\n if (typeof dispose !== 'function') throw new TypeError('Model selection source must return a disposer.');\n\n if (this.subscriptions.get(source) === subscription) subscription.dispose = dispose;\n else dispose();\n }\n\n private notify = (): void => {\n for (const listener of [...this.listeners]) {\n if (this.listeners.has(listener)) listener.notify();\n }\n };\n\n private releaseAll(failures?: unknown[]): void {\n const held = [...this.subscriptions.values(), ...this.leases.values()];\n this.subscriptions.clear();\n this.leases.clear();\n releaseSubscriptions(held, failures);\n }\n\n private removeUnselected(): void {\n const removed: Subscription[] = [];\n\n for (const [source, subscription] of this.subscriptions) {\n if (this.sources.has(source)) continue;\n\n this.subscriptions.delete(source);\n removed.push(subscription);\n }\n\n for (const [resource, lease] of this.leases) {\n if (this.retained.has(resource)) continue;\n\n this.leases.delete(resource);\n removed.push(lease);\n }\n\n releaseSubscriptions(removed);\n }\n\n /**\n * D321: `retain()` is reentrant — it reaches `activate()`, the target subscription, the first read and the\n * publication that follows, all synchronously — and a listener woken there may rebuild or close this selection.\n * The record is reserved first, the lease is taken second, and it is kept only if the record is still this one.\n */\n private retain(resource: AnyResource): void {\n const lease: Subscription = { dispose: undefined };\n this.leases.set(resource, lease);\n const release = resource.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 if (!this.ready()) return;\n\n if (this.retained.has(resource) && !this.leases.has(resource)) this.retain(resource);\n }\n }\n\n private subscribeSources(): void {\n for (const source of this.sources) {\n if (!this.ready()) return;\n\n if (this.sources.has(source) && !this.subscriptions.has(source)) this.add(source);\n }\n }\n\n private synchronize(): void {\n this.removeUnselected();\n this.subscribeSources();\n this.retainSelected();\n }\n}\n\nexport { ModelSelectionStore };\n","import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call, ModelOf, PaginatedResource, Resource } from '@opetope/core';\n\nimport type { CommandHook } from './command-hook';\nimport { useSelectionHooks } from './commands-hook';\nimport type { SelectionHooks } from './commands-hook';\nimport { ContributionError } from './errors';\nimport { subscribeIdle } from './idle-subscription';\nimport type { AnyModel } from './model-binding';\nimport { createSelectionSnapshot, emptySelectionSnapshot } from './model-selection-snapshot';\nimport type { ModelSelector, SelectionContext, SelectionShape, SelectionSnapshot } from './model-selection-snapshot';\nimport { ModelSelectionStore } from './model-selection-store';\nimport { useFrame } from './mount-context';\nimport type { PaginatedResourceHook, ResourceHook, ResourceKey } from './resource-command';\n\n/**\n * D359: a field whose value is a Resource answers the hook `useResource` answers, and the mount leases it exactly as\n * `useResource` does. `read(resource.state)` is unchanged — it hands out the state and leases nothing — so naming one\n * Resource both ways registers it twice: the subscription and the lease are independent.\n */\ntype SelectedValue<Value> =\n Value extends Call<infer Input, infer Output>\n ? CommandHook<Input, Output>\n : Value extends PaginatedResource<infer Data, infer Key extends ResourceKey, infer Cursor extends ResourceKey>\n ? PaginatedResourceHook<Data, Key, Cursor>\n : Value extends Resource<infer Data, infer Key extends ResourceKey>\n ? ResourceHook<Data, Key>\n : Value;\ntype ModelSelection<Selected> = { readonly [Key in keyof Selected]: SelectedValue<Selected[Key]> };\n\nconst readEmptySnapshot = () => emptySelectionSnapshot;\n\nfunction useSelectionStore(selected: boolean): ModelSelectionStore | undefined {\n const storeRef = useRef<ModelSelectionStore | undefined>(undefined);\n\n return selected ? (storeRef.current ??= new ModelSelectionStore()) : undefined;\n}\n\n/**\n * Two branches of one key: the consumer this selection prepared — a bound command, or the verbs, the state and the\n * capability of a selected Resource — and otherwise the value the selector wrote. S6/D359: the Resource branch is\n * assembled on the record and not here, so a key whose state did not move hands back the very same object.\n */\nfunction mergeSelection(snapshot: SelectionSnapshot, hooks: SelectionHooks): unknown {\n return Object.fromEntries(\n Reflect.ownKeys(snapshot.values).map(key => [\n key,\n Object.prototype.hasOwnProperty.call(hooks, key) ? hooks[key] : snapshot.values[key],\n ]),\n );\n}\n\n/** D205: resolve only a granted model, explicitly read data, and reuse the ordinary per-key command consumers. */\nfunction useModel<Declaration extends AnyModel>(declaration: Declaration): ModelOf<Declaration>;\nfunction useModel<Declaration extends AnyModel, const Selected extends object>(\n declaration: Declaration,\n select: (model: ModelOf<Declaration>, context: SelectionContext) => Selected & SelectionShape<Selected>,\n): ModelSelection<Selected>;\nfunction useModel<Declaration extends AnyModel>(\n declaration: Declaration,\n select?: (model: ModelOf<Declaration>, context: SelectionContext) => object,\n): unknown {\n const frame = useFrame();\n\n if (!frame.models.has(declaration)) {\n throw new ContributionError('missing', `Model ${declaration.id} is not granted to this contribution.`);\n }\n\n const model = frame.models.get(declaration) as ModelOf<Declaration>;\n const store = useSelectionStore(select !== undefined);\n // D214: the selector of this render, read by a reader that belongs to the model. Writing the latest callback into\n // a ref is what `useSyncExternalStoreWithSelector` does: the reader only reads it, and never writes to it.\n const selectRef = useRef<ModelSelector<ModelOf<Declaration>> | undefined>(undefined);\n selectRef.current = select;\n const selected = select !== undefined;\n const getSnapshot = useMemo(\n () => (selected ? createSelectionSnapshot(model, selectRef) : readEmptySnapshot),\n [model, selected],\n );\n const snapshot = useSyncExternalStore(store?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const hooks = useSelectionHooks(snapshot.commands, snapshot.resources);\n useLayoutEffect(() => () => store?.close(), [store]);\n // D359: a lease is taken in commit and never in a render, and the store diffs the set by identity.\n useLayoutEffect(() => store?.commit(snapshot.sources, snapshot.leases), [snapshot, store]);\n\n if (select === undefined) return model;\n\n return mergeSelection(snapshot, hooks);\n}\n\nexport { useModel };\n","import type { FunctionComponent } from 'react';\n\nimport type { ModelIdentity } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\ntype AnyModel = ModelIdentity;\n\n/**\n * The marker a component uses to state which UI models of its contribution it reads. `slot` checks by type that the\n * contribution grants every model of this list — it may grant more, because the mount serves its whole instance.\n *\n * The runtime authority is the mount frame, not this marker: `useModel` resolves against what the mount was given\n * and refuses anything else. The lint rule `opetope/require-declared-models` checks visible contribution sites and\n * each reader of a per-mount model within the same module. Imported implementations and dynamic declaration lists\n * remain unknown to this syntactic check (D85, D158, D250).\n */\ntype ComponentRequiringModels<Props, Models extends readonly AnyModel[]> = FunctionComponent<Props> & {\n readonly requires: Models;\n};\n\nfunction requiresModels<const Models extends readonly AnyModel[]>(\n models: Models,\n): <Props>(component: FunctionComponent<Props>) => ComponentRequiringModels<Props, Models> {\n const declared = Object.freeze([...models]) as unknown as Models;\n\n return <Props>(component: FunctionComponent<Props>): ComponentRequiringModels<Props, Models> => {\n const existing = (component as { readonly requires?: readonly AnyModel[] }).requires;\n\n if (existing !== undefined) {\n throw new ContributionError('duplicate', 'Component already declares the models it requires.');\n }\n\n Object.defineProperty(component, 'requires', { enumerable: false, value: declared });\n\n return component as ComponentRequiringModels<Props, Models>;\n };\n}\n\nexport { requiresModels };\n","import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport type { PaginatedResource, PaginationState, Readable, Resource } from '@opetope/core';\nimport type { ResourceKey } from '@opetope/core/internal';\n\nimport { useCommandStatus } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport { createResourceInvokers, paginationOf } from './resource-command';\nimport type { AnyResource, PaginatedResourceHook, ResourceHook, ResourcePaginationHook } from './resource-command';\n\ntype ResourceSlots = Readonly<{\n invalidate: CommandHookController<void, unknown>;\n loadNext: CommandHookController<void, unknown>;\n refresh: CommandHookController<void, unknown>;\n retry: CommandHookController<void, unknown>;\n}>;\n\n/**\n * D359: one set of controllers per Resource identity, exactly as `useCommandSlot` keeps one per invoker. The slots are\n * what the set of invokers is kept alive by, so one map answers for all four. Slots are inert until commit, so an\n * interrupted render neither admits work nor resets the status of the committed one.\n */\nfunction useResourceSlots(resource: AnyResource): ResourceSlots {\n const [cache] = useState(() => new WeakMap<object, ResourceSlots>());\n const existing = cache.get(resource);\n\n if (existing !== undefined) return existing;\n\n const invokers = createResourceInvokers(resource);\n const slots: ResourceSlots = {\n invalidate: new CommandHookController<void, unknown>(invokers.invalidate),\n loadNext: new CommandHookController<void, unknown>(invokers.loadNext),\n refresh: new CommandHookController<void, unknown>(invokers.refresh),\n retry: new CommandHookController<void, unknown>(invokers.retry),\n };\n cache.set(resource, slots);\n\n return slots;\n}\n\n/**\n * G13: a render may race the retirement of the model that owns the Resource, and a retired Resource closes its state\n * by the general Readable contract. Reading it then throws, so this render answers the last state it saw — which is\n * what `useReadable` does for a closed source — and the unmount that follows releases the lease.\n */\nfunction lastGoodSnapshot<Value>(source: Readable<Value>, fallback: Value): () => Value {\n let last = fallback;\n\n return (): Value => {\n try {\n last = source.getSnapshot();\n } catch {\n // The owner closed this source; the previous value is the honest answer until this tree unmounts.\n }\n\n return last;\n };\n}\n\nconst unavailablePagination: PaginationState<ResourceKey> = Object.freeze({\n kind: 'unavailable',\n reason: 'resource-not-ready',\n});\nconst noPaginationSource: Readable<PaginationState<ResourceKey>> = Object.freeze({\n getSnapshot: () => unavailablePagination,\n subscribe: () => () => undefined,\n});\n\n/**\n * D359: the state of a Resource, the lease that keeps it open for as long as this component is mounted, and its verbs\n * as commands. The number of React hooks here does not depend on the Resource: a declaration without `pagination`\n * subscribes to a constant source and keeps a `loadNext` that answers `skipped`.\n */\nfunction useResource<Data, Key extends ResourceKey, Cursor extends ResourceKey>(\n resource: PaginatedResource<Data, Key, Cursor>,\n): PaginatedResourceHook<Data, Key, Cursor>;\nfunction useResource<Data, Key extends ResourceKey>(resource: Resource<Data, Key>): ResourceHook<Data, Key>;\nfunction useResource<Data, Key extends ResourceKey>(resource: Resource<Data, Key>): ResourceHook<Data, Key> {\n const handle = resource as unknown as AnyResource;\n const capability = paginationOf(handle);\n const subscribe = useCallback((listener: () => void) => resource.state.subscribe(listener), [resource]);\n const getState = useMemo(() => lastGoodSnapshot(resource.state, { kind: 'idle', reason: 'unleased' }), [resource]);\n const state = useSyncExternalStore(subscribe, getState, getState);\n const paginationSource: Readable<PaginationState<ResourceKey>> =\n (capability?.state as Readable<PaginationState<ResourceKey>> | undefined) ?? noPaginationSource;\n const subscribePagination = useCallback(\n (listener: () => void) => paginationSource.subscribe(listener),\n [paginationSource],\n );\n const getPaginationState = useMemo(\n () => lastGoodSnapshot(paginationSource, unavailablePagination),\n [paginationSource],\n );\n const paginationState = useSyncExternalStore(subscribePagination, getPaginationState, getPaginationState);\n\n // D349: the lease is taken in a commit effect, so an interrupted render opens nothing.\n useEffect(() => resource.acquire(), [resource]);\n\n const slots = useResourceSlots(handle);\n const refresh = useCommandStatus(slots.refresh);\n const retry = useCommandStatus(slots.retry);\n const invalidate = useCommandStatus(slots.invalidate);\n const loadNext = useCommandStatus(slots.loadNext);\n const pagination = useMemo(\n () =>\n capability === undefined\n ? undefined\n : ({ loadNext, state: paginationState } as unknown as ResourcePaginationHook<ResourceKey>),\n [capability, loadNext, paginationState],\n );\n\n return useMemo(\n () => ({ invalidate, pagination, refresh, retry, state }) as ResourceHook<Data, Key>,\n [invalidate, pagination, refresh, retry, state],\n );\n}\n\nexport { useResource };\n","import type { ComponentType, ReactNode } from 'react';\nimport { createElement, Fragment } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport { declarationId } from '@opetope/core';\nimport type { ContributionTarget } from '@opetope/core/internal';\nimport { createContributionTarget } from '@opetope/core/internal';\nimport type { ContributionModel } from '@opetope/runtime/internal';\n\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\n\ntype SlotProperties = object | undefined;\ntype EmptySlotProperties = Readonly<Record<string, never>>;\ntype SlotComponentProperties<Props extends SlotProperties> = [Props] extends [undefined]\n ? EmptySlotProperties\n : Extract<Props, object>;\n\n/**\n * What a contribution publishes: the component, the optional props adapter and the optional UI models the mount\n * creates. Without an adapter the component takes the slot props; with one it takes exactly the adapter result (D85).\n */\ntype SlotContribution<Props extends SlotProperties = undefined> = {\n readonly Component: ComponentType<never>;\n readonly models?: readonly ContributionModel[];\n readonly props?: (slotProps: SlotComponentProperties<Props>) => object;\n};\n\ntype SlotTarget<Props extends SlotProperties = undefined> = ContributionTarget<SlotContribution<Props>>;\n\ntype SwitchSlotTarget<Route extends string, Props extends SlotProperties = undefined> = ((\n route: Route,\n) => SlotTarget<Props>) & {\n readonly id: DeclarationId;\n};\n\ntype SlotRenderProps<Props extends SlotProperties> = [Props] extends [undefined]\n ? {\n readonly props?: never;\n readonly target: SlotTarget<Props>;\n }\n : {\n readonly props: SlotComponentProperties<Props>;\n readonly target: SlotTarget<Props>;\n };\n\nconst emptySlotProperties: EmptySlotProperties = Object.freeze({});\n\n/** D280: a slot is a name, so it takes its id as the first argument. */\nfunction defineSlot<Props extends SlotProperties = undefined>(id: string): SlotTarget<Props> {\n return createContributionTarget<SlotContribution<Props>>(id, 'slot');\n}\n\nfunction defineSwitchSlot<Route extends string, Props extends SlotProperties = undefined>(\n id: string,\n): SwitchSlotTarget<Route, Props> {\n const declared = declarationId(id);\n const targets = new Map<Route, SlotTarget<Props>>();\n\n const switchSlot = (route: Route): SlotTarget<Props> => {\n const existing = targets.get(route);\n\n if (existing !== undefined) return existing;\n\n const target = defineSlot<Props>(`${declared}/${route}`);\n targets.set(route, target);\n\n return target;\n };\n\n Object.defineProperty(switchSlot, 'id', { enumerable: true, value: declared });\n\n return Object.freeze(switchSlot) as SwitchSlotTarget<Route, Props>;\n}\n\n/**\n * Every contribution renders inside its own mount: the models its generation owns plus the UI models it declared,\n * created on mount and closed on unmount (D70, D85).\n */\nfunction Slot<Props extends SlotProperties>(options: SlotRenderProps<Props>): ReactNode {\n const entries = useReadable(options.target.entries);\n const slotProps: object = 'props' in options ? (options.props ?? emptySlotProperties) : emptySlotProperties;\n\n if (entries.length === 0) return null;\n\n return entries.map(entry =>\n createElement(\n Fragment,\n { key: entry.id },\n createElement(ContributionMount, {\n authority: contributionAuthority(entry),\n contribution: entry.value,\n contributionId: entry.id,\n slotProps,\n targetId: options.target.id,\n }),\n ),\n );\n}\n\nexport { defineSlot, defineSwitchSlot, Slot };\nexport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget, SwitchSlotTarget };\n"],"names":["failureOutcome","options","error","isCancellation","notification","_a","reportDetachedError","callbackError","runOptions","onSuccess","signal","requestOf","input","resolve","promise","settle","request","outcome","CommandHookController","ContributionError","aborted","invoker","notify","CallError","compatibleAbortReason","invocation","value","emptyStatus","settledOutcome","unchangedState","current","slot","inFlight","settled","nextState","previous","useCommandSlot","slots","useState","useCommandStatus","currentSlot","hookState","setHookState","useInsertionEffect","useLayoutEffect","run","useMemo","useCommand","command","record","useFrame","idleRelease","subscribeIdle","idleRevision","emptyHooks","noResources","isResourceEntry","entry","createEntry","unchangedHook","next","updateEntry","selectEntry","resourceHook","selected","createResourceEntry","invokers","parts","unchangedResourceCommands","hook","unchangedResourceState","unchangedResourceHook","selectResourceEntry","commandEntries","detachEntry","hookOf","CommandGroup","key","listener","commands","resources","frame","resource","resourceInvokers","useCommandGroup","groupRef","useRef","useSelectionHooks","group","getSnapshot","useSyncExternalStore","useCommands","closedSelection","readCurrent","source","noLeases","readSource","sources","observed","isReadable","isClosedReadable","readPassively","classifyResource","isResource","capability","selectedResources","collectSelection","select","project","entries","equalResources","before","equalSelection","values","keys","previousKeys","index","emptySelectionSnapshot","selectionCommands","isCallTarget","selectionLeases","leased","createSelectionSnapshot","model","selectRef","observedSelect","snapshot","nextObserved","context","releaseSubscriptions","subscriptions","failures","subscription","dispose","createAggregateError","ModelSelectionStore","retained","held","removed","lease","release","readEmptySnapshot","useSelectionStore","storeRef","mergeSelection","hooks","useModel","declaration","store","requiresModels","models","declared","component","useResourceSlots","cache","existing","createResourceInvokers","lastGoodSnapshot","fallback","last","unavailablePagination","noPaginationSource","useResource","handle","paginationOf","subscribe","useCallback","getState","state","paginationSource","subscribePagination","getPaginationState","paginationState","useEffect","refresh","retry","invalidate","loadNext","pagination","emptySlotProperties","defineSlot","id","createContributionTarget","defineSwitchSlot","declarationId","targets","switchSlot","route","target","Slot","useReadable","slotProps","createElement","Fragment","ContributionMount","contributionAuthority"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/command-hook-controller.ts","../src/command-hook.ts","../src/idle-subscription.ts","../src/commands-hook.ts","../src/model-selection-snapshot.ts","../src/model-selection-store.ts","../src/model-hook.ts","../src/requires-models.ts","../src/resource-hook.ts","../src/slot.tsx"],"sourcesContent":["import { CallError } from '@opetope/core';\nimport { compatibleAbortReason, isCancellation, reportDetachedError } from '@opetope/core/internal';\nimport type { CallInputArgs, CallRunOptions } from '@opetope/core/internal';\n\nimport type { CommandOutcome } from './command';\nimport { ContributionError } from './errors';\n\ntype CommandRunOptions<Output> = Readonly<{\n onFailure?: (error: unknown) => PromiseLike<void> | void;\n onSuccess?: (value: Output) => PromiseLike<void> | void;\n signal?: AbortSignal;\n}>;\n\ntype CommandRun<Input, Output> = {\n (input: Input, options?: CommandRunOptions<Output>): Promise<CommandOutcome<Output>>;\n (...args: [...CallInputArgs<Input>, options?: CommandRunOptions<Output>]): Promise<CommandOutcome<Output>>;\n};\n\ntype CommandInvoker = Readonly<{\n run: (input: never, options?: CallRunOptions<unknown>) => Promise<unknown>;\n}>;\n\ntype CommandNotification<Output> = (inFlight: boolean, outcome?: CommandOutcome<Output>) => void;\n\ntype CommandRequest<Input, Output> = {\n input: Input | undefined;\n options: CommandRunOptions<Output> | undefined;\n readonly promise: Promise<CommandOutcome<Output>>;\n resolve: ((outcome: CommandOutcome<Output>) => void) | undefined;\n};\n\nfunction failureOutcome<Output>(options: CommandRunOptions<Output> | undefined, error: unknown): CommandOutcome<never> {\n if (isCancellation(error)) return { reason: error, status: 'cancelled' };\n\n try {\n const notification = options?.onFailure?.(error);\n\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n } catch (callbackError) {\n reportDetachedError(callbackError);\n }\n\n return { error, status: 'failed' };\n}\n\nfunction runOptions<Output>(options: CommandRunOptions<Output> | undefined): CallRunOptions<Output> | undefined {\n const onSuccess = options?.onSuccess;\n const signal = options?.signal;\n\n if (onSuccess === undefined) return signal === undefined ? undefined : { signal };\n\n return signal === undefined ? { onSuccess } : { onSuccess, signal };\n}\n\nfunction requestOf<Input, Output>(\n input: Input,\n options: CommandRunOptions<Output> | undefined,\n): CommandRequest<Input, Output> {\n let resolve!: (outcome: CommandOutcome<Output>) => void;\n const promise = new Promise<CommandOutcome<Output>>(settle => {\n resolve = settle;\n });\n\n return { input, options: options === undefined ? undefined : { ...options }, promise, resolve };\n}\n\nfunction settle<Input, Output>(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n const resolve = request.resolve;\n request.input = undefined;\n request.options = undefined;\n request.resolve = undefined;\n resolve?.(outcome);\n}\n\n/**\n * D203: the consumer keeps a status, not a schedule. Every `run` reaches the call, and the policy the call was\n * created with — `queue`, `latest`, `parallel`, `once`, `dedupe` — decides what happens to it. What stays\n * local is what only this consumer knows: whether it is mounted, its own input signal, and the outcome it shows.\n */\nclass CommandHookController<Input, Output> {\n readonly run: CommandRun<Input, Output> = ((input: Input, options?: CommandRunOptions<Output>) => {\n if (this.notify === undefined) {\n return Promise.resolve({\n reason: new ContributionError('inactive', 'Command consumer is not mounted.'),\n status: 'cancelled' as const,\n });\n }\n\n const aborted = this.cancelledInput(options);\n\n if (aborted !== undefined) return Promise.resolve(aborted);\n\n const request = requestOf(input, options);\n this.start(request);\n\n return request.promise;\n }) as CommandRun<Input, Output>;\n private inFlight = 0;\n private notify: CommandNotification<Output> | undefined;\n\n constructor(private readonly invoker: CommandInvoker) {}\n\n attach(notify: CommandNotification<Output>): () => void {\n this.notify = notify;\n\n return () => {\n if (this.notify !== notify) return;\n\n this.notify = undefined;\n };\n }\n\n synchronize(): void {\n this.notify?.(this.inFlight > 0);\n }\n\n private aborted(signal: AbortSignal): CommandOutcome<never> {\n return {\n reason: new CallError('cancelled', 'Command input was cancelled.', compatibleAbortReason(signal)),\n status: 'cancelled',\n };\n }\n\n private cancelledInput(options: CommandRunOptions<Output> | undefined): CommandOutcome<never> | undefined {\n return options?.signal?.aborted === true ? this.aborted(options.signal) : undefined;\n }\n\n private finish(request: CommandRequest<Input, Output>, outcome: CommandOutcome<Output>): void {\n if (request.resolve === undefined) return;\n\n this.inFlight -= 1;\n settle(request, outcome);\n this.notify?.(this.inFlight > 0, outcome);\n }\n\n private invoke(request: CommandRequest<Input, Output>): void {\n let invocation: Promise<Output>;\n\n try {\n invocation = this.invoker.run(request.input as never, runOptions(request.options) as never) as Promise<Output>;\n request.input = undefined;\n } catch (error) {\n this.finish(request, failureOutcome(request.options, error));\n\n return;\n }\n\n void invocation.then(\n value => this.finish(request, { status: 'ok', value }),\n (error: unknown) => this.finish(request, failureOutcome(request.options, error)),\n );\n }\n\n private start(request: CommandRequest<Input, Output>): void {\n this.inFlight += 1;\n // Reserve the flight before publishing: a reentrant run must already see this consumer as busy.\n this.notify?.(true);\n this.invoke(request);\n }\n}\n\nexport { CommandHookController };\nexport type { CommandInvoker, CommandRun };\n","import { useInsertionEffect, useLayoutEffect, useMemo, useState } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker, CommandRun } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { useFrame } from './mount-context';\n\n/** D292: a cancellation is not an answer of the product, so it never settles into the outcome this hook shows. */\ntype SettledOutcome<Output> = Exclude<CommandOutcome<Output>, { readonly status: 'cancelled' }>;\n\ninterface CommandHookStatus<Output> {\n readonly inFlight: boolean;\n readonly outcome: SettledOutcome<Output> | undefined;\n}\n\nconst emptyStatus: CommandHookStatus<never> = { inFlight: false, outcome: undefined };\n\ntype CommandHook<Input, Output> = CommandHookStatus<Output> & {\n readonly run: CommandRun<Input, Output>;\n};\n\ntype CommandHookState<Input, Output> = CommandHookStatus<Output> & {\n readonly slot: CommandHookController<Input, Output>;\n};\n\n/** D292: a cancellation moves nothing, so it is the same non-event as a notification without an outcome at all. */\nfunction settledOutcome<Output>(outcome: CommandOutcome<Output> | undefined): SettledOutcome<Output> | undefined {\n return outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n}\n\nfunction unchangedState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n settled: SettledOutcome<Output> | undefined,\n): boolean {\n return settled === undefined && current.slot === slot && current.inFlight === inFlight;\n}\n\nfunction nextState<Input, Output>(\n current: CommandHookState<Input, Output>,\n slot: CommandHookController<Input, Output>,\n inFlight: boolean,\n outcome?: CommandOutcome<Output>,\n): CommandHookState<Input, Output> {\n const settled = settledOutcome(outcome);\n\n if (unchangedState(current, slot, inFlight, settled)) return current;\n\n // A new run clears nothing: the previous answer stays readable while its retry is in flight.\n const previous = current.slot === slot ? current.outcome : undefined;\n\n return { inFlight, outcome: settled ?? previous, slot };\n}\n\nfunction useCommandSlot<Input, Output>(invoker: CommandInvoker): CommandHookController<Input, Output> {\n // Slots are inert until commit. An interrupted render cannot fence the committed source or admit its own work.\n const [slots] = useState(() => new WeakMap<object, CommandHookController<Input, Output>>());\n let slot = slots.get(invoker);\n\n if (slot === undefined) {\n slot = new CommandHookController(invoker);\n slots.set(invoker, slot);\n }\n\n return slot;\n}\n\n/**\n * The status half of a consumer, apart from what it is a consumer of. D321 gives a resource the same two verbs a\n * command has, so the same three React hooks answer for them: the state of this consumer, the attachment that moves\n * it, and the synchronisation of a slot that was already busy when this mount arrived.\n */\nfunction useCommandStatus<Input, Output>(\n currentSlot: CommandHookController<Input, Output>,\n): CommandHook<Input, Output> {\n const [hookState, setHookState] = useState<CommandHookState<Input, Output>>(() => ({\n inFlight: false,\n outcome: undefined,\n slot: currentSlot,\n }));\n useInsertionEffect(\n () =>\n currentSlot.attach((inFlight, outcome) =>\n setHookState(current => nextState(current, currentSlot, inFlight, outcome)),\n ),\n [currentSlot],\n );\n useLayoutEffect(() => currentSlot.synchronize(), [currentSlot]);\n const { inFlight, outcome } = hookState.slot === currentSlot ? hookState : emptyStatus;\n const run = currentSlot.run;\n\n return useMemo(() => ({ inFlight, outcome, run }), [inFlight, outcome, run]);\n}\n\nfunction useCommand<Input, Output>(command: Call<Input, Output>): CommandHook<Input, Output> {\n const frame = useFrame();\n const record = frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command ${command.id} is not bound in this contribution.`);\n }\n\n return useCommandStatus(useCommandSlot<Input, Output>(record.invoker));\n}\n\nexport { useCommand, useCommandStatus };\nexport type { CommandHook };\n","const idleRelease = (): void => undefined;\nconst subscribeIdle = (): (() => void) => idleRelease;\nconst idleRevision = (): number => 0;\n\nexport { idleRevision, subscribeIdle };\n","import { useInsertionEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call } from '@opetope/core';\n\nimport type { CommandOutcome } from './command';\nimport type { CommandHook } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport type { CommandInvoker } from './command-hook-controller';\nimport { ContributionError } from './errors';\nimport { idleRevision, subscribeIdle } from './idle-subscription';\nimport type { SelectedResource } from './model-selection-snapshot';\nimport { useFrame } from './mount-context';\nimport type { Frame } from './mount-frame';\nimport { resourceInvokers } from './resource-command';\nimport type { ResourceInvokers } from './resource-command';\n\ntype CommandSelection = Readonly<Record<string, Call<never, unknown>>>;\ntype ResourceSelection = ReadonlyMap<PropertyKey, SelectedResource>;\ntype HookOf<Command> = Command extends Call<infer Input, infer Output> ? CommandHook<Input, Output> : never;\ntype CommandsHook<Commands> = {\n readonly [Key in keyof Commands]: HookOf<Commands[Key]>;\n};\n/** D359: what one selected Resource answers — its three verbs, its state, and the pagination capability if it has one. */\ntype SelectedResourceHook = Readonly<{\n invalidate: CommandHook<never, unknown>;\n pagination: Readonly<{ loadNext: CommandHook<never, unknown>; state: unknown }> | undefined;\n refresh: CommandHook<never, unknown>;\n retry: CommandHook<never, unknown>;\n state: unknown;\n}>;\ntype SelectionHooks = Readonly<Record<PropertyKey, CommandHook<never, unknown> | SelectedResourceHook>>;\n\nconst emptyHooks: SelectionHooks = Object.freeze({});\nconst noResources: ResourceSelection = new Map();\n\ntype CommandEntry = {\n detach: (() => void) | undefined;\n hook: CommandHook<never, unknown>;\n readonly invoker: CommandInvoker;\n readonly slot: CommandHookController<never, unknown>;\n};\n\n/** D359: one key of a selection, four consumers. The record is the value; `entries` still holds one entry per key. */\ntype ResourceEntry = {\n /** D359: the value this key hands the render, kept on the record so a still frame is the same object again. */\n hook: SelectedResourceHook;\n readonly invalidate: CommandEntry;\n readonly kind: 'resource';\n readonly loadNext: CommandEntry;\n /** Whether the declaration carried the pagination capability at all. */\n readonly paginated: boolean;\n readonly refresh: CommandEntry;\n readonly retry: CommandEntry;\n};\n\ntype SelectionEntry = CommandEntry | ResourceEntry;\n\nfunction isResourceEntry(entry: SelectionEntry): entry is ResourceEntry {\n return 'kind' in entry;\n}\n\nfunction createEntry(invoker: CommandInvoker): CommandEntry {\n const slot = new CommandHookController<never, unknown>(invoker);\n\n return { detach: undefined, hook: { inFlight: false, outcome: undefined, run: slot.run }, invoker, slot };\n}\n\nfunction unchangedHook(previous: CommandHook<never, unknown>, next: CommandHook<never, unknown>): boolean {\n return previous.inFlight === next.inFlight && Object.is(previous.outcome, next.outcome);\n}\n\nfunction updateEntry(entry: CommandEntry, inFlight: boolean, outcome?: CommandOutcome<unknown>): boolean {\n const previous = entry.hook;\n\n if (outcome === undefined && previous.inFlight === inFlight) return false;\n\n // D292: only a settled non-cancelled outcome replaces the one this key shows; a retry keeps the previous answer.\n const settled = outcome === undefined || outcome.status === 'cancelled' ? undefined : outcome;\n const next = { inFlight, outcome: settled ?? previous.outcome, run: previous.run };\n\n if (unchangedHook(previous, next)) return false;\n\n entry.hook = next;\n\n return true;\n}\n\nfunction selectEntry(previous: SelectionEntry | undefined, invoker: CommandInvoker): CommandEntry {\n return previous !== undefined && !isResourceEntry(previous) && previous.invoker === invoker\n ? previous\n : createEntry(invoker);\n}\n\nfunction resourceHook(entry: Omit<ResourceEntry, 'hook'>, selected: SelectedResource): SelectedResourceHook {\n return {\n invalidate: entry.invalidate.hook,\n pagination: entry.paginated ? { loadNext: entry.loadNext.hook, state: selected.pagination } : undefined,\n refresh: entry.refresh.hook,\n retry: entry.retry.hook,\n state: selected.state,\n };\n}\n\nfunction createResourceEntry(invokers: ResourceInvokers, selected: SelectedResource): ResourceEntry {\n const parts = {\n invalidate: createEntry(invokers.invalidate),\n kind: 'resource' as const,\n loadNext: createEntry(invokers.loadNext),\n paginated: selected.pagination !== undefined,\n refresh: createEntry(invokers.refresh),\n retry: createEntry(invokers.retry),\n };\n\n return { ...parts, hook: resourceHook(parts, selected) };\n}\n\n/** The four commands of a Resource: the three verbs every one has, and `loadNext` only where one was declared. */\nfunction unchangedResourceCommands(entry: ResourceEntry): boolean {\n const { hook } = entry;\n\n return (\n hook.refresh === entry.refresh.hook &&\n hook.retry === entry.retry.hook &&\n hook.invalidate === entry.invalidate.hook &&\n (!entry.paginated || hook.pagination?.loadNext === entry.loadNext.hook)\n );\n}\n\n/** What this selection read: the state of the Resource, and the state of its capability where it has one. */\nfunction unchangedResourceState(entry: ResourceEntry, selected: SelectedResource): boolean {\n return Object.is(entry.hook.state, selected.state) && Object.is(entry.hook.pagination?.state, selected.pagination);\n}\n\nfunction unchangedResourceHook(entry: ResourceEntry, selected: SelectedResource): boolean {\n return unchangedResourceCommands(entry) && unchangedResourceState(entry, selected);\n}\n\n/**\n * S6/D359: the same Resource that did not move answers the same object, so a child under `memo` is not re-rendered by\n * the mere act of reading it. Every part is a render input, so this is a memo and not a piece of state.\n */\nfunction selectResourceEntry(\n previous: SelectionEntry | undefined,\n invokers: ResourceInvokers,\n selected: SelectedResource,\n): ResourceEntry {\n if (previous === undefined || !isResourceEntry(previous) || previous.refresh.invoker !== invokers.refresh) {\n return createResourceEntry(invokers, selected);\n }\n\n if (!unchangedResourceHook(previous, selected)) previous.hook = resourceHook(previous, selected);\n\n return previous;\n}\n\nfunction commandEntries(entry: SelectionEntry): readonly CommandEntry[] {\n return isResourceEntry(entry) ? [entry.refresh, entry.retry, entry.invalidate, entry.loadNext] : [entry];\n}\n\nfunction detachEntry(entry: SelectionEntry): void {\n for (const command of commandEntries(entry)) {\n command.detach?.();\n command.detach = undefined;\n }\n}\n\nfunction hookOf(entry: SelectionEntry): CommandHook<never, unknown> | SelectedResourceHook {\n return entry.hook;\n}\n\n/** Selection is inert during render; only a committed selection can admit work or discard an old queue. */\nclass CommandGroup {\n private entries = new Map<PropertyKey, SelectionEntry>();\n /** One set of invokers per Resource identity, so `run` keeps its reference across renders (D359). */\n private readonly invokers = new WeakMap<object, ResourceInvokers>();\n private readonly listeners = new Set<() => void>();\n private revision = 0;\n\n close(): void {\n for (const entry of this.entries.values()) detachEntry(entry);\n\n this.entries.clear();\n }\n\n commit(selected: Map<PropertyKey, SelectionEntry>): void {\n for (const [key, entry] of this.entries) {\n if (selected.get(key) !== entry) detachEntry(entry);\n }\n\n this.entries = selected;\n\n for (const entry of selected.values()) this.attachEntry(entry);\n }\n\n /** S7: the attach runs on every commit, so the verbs are named rather than gathered into an array. */\n private attachEntry(entry: SelectionEntry): void {\n if (isResourceEntry(entry)) {\n this.attachCommand(entry.refresh);\n this.attachCommand(entry.retry);\n this.attachCommand(entry.invalidate);\n this.attachCommand(entry.loadNext);\n\n return;\n }\n\n this.attachCommand(entry);\n }\n\n private attachCommand(command: CommandEntry): void {\n command.detach ??= command.slot.attach((inFlight, outcome) => {\n if (!updateEntry(command, inFlight, outcome)) return;\n\n this.revision += 1;\n\n for (const listener of this.listeners) listener();\n });\n }\n\n getSnapshot = (): number => this.revision;\n\n /**\n * D321: two outcomes no longer, but two kinds. A command that is not bound is still a `ContributionError`; a\n * resource never appears in `frame.commandRecords`, so it is prepared from its own identity instead.\n */\n prepare(commands: CommandSelection, resources: ResourceSelection, frame: Frame): Map<PropertyKey, SelectionEntry> {\n const selected = new Map<PropertyKey, SelectionEntry>();\n\n for (const key of Reflect.ownKeys(commands)) {\n if (!Object.prototype.propertyIsEnumerable.call(commands, key)) continue;\n\n const command = (commands as Readonly<Record<PropertyKey, Call<never, unknown>>>)[key];\n const record = command === undefined ? undefined : frame.commandRecords.get(command);\n\n if (record === undefined) {\n throw new ContributionError('missing', `Command at ${String(key)} is not bound in this contribution.`);\n }\n\n selected.set(key, selectEntry(this.entries.get(key), record.invoker));\n }\n\n for (const [key, resource] of resources) {\n const invokers = resourceInvokers(resource.resource, this.invokers);\n\n selected.set(key, selectResourceEntry(this.entries.get(key), invokers, resource));\n }\n\n return selected;\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n };\n}\n\nfunction useCommandGroup(commands: CommandSelection, resources: ResourceSelection): CommandGroup | undefined {\n const groupRef = useRef<CommandGroup | undefined>(undefined);\n const populated =\n resources.size > 0 ||\n Reflect.ownKeys(commands).some(key => Object.prototype.propertyIsEnumerable.call(commands, key));\n\n return populated ? (groupRef.current ??= new CommandGroup()) : undefined;\n}\n\n/** The consumers of one selection: a command per key, and the two verbs of a resource per key (D321). */\nfunction useSelectionHooks(commands: CommandSelection, resources: ResourceSelection): SelectionHooks {\n const frame = useFrame();\n const group = useCommandGroup(commands, resources);\n const getSnapshot = group?.getSnapshot ?? idleRevision;\n useSyncExternalStore(group?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const selected = group?.prepare(commands, resources, frame);\n useInsertionEffect(() => () => group?.close(), [group]);\n useInsertionEffect(() => {\n if (selected !== undefined) group?.commit(selected);\n });\n useLayoutEffect(() => {\n if (selected === undefined) return;\n\n // S7: the same branch as the attach — this runs after every commit, and the arrays were pure allocation.\n for (const entry of selected.values()) {\n if (isResourceEntry(entry)) {\n entry.refresh.slot.synchronize();\n entry.retry.slot.synchronize();\n entry.invalidate.slot.synchronize();\n entry.loadNext.slot.synchronize();\n } else entry.slot.synchronize();\n }\n });\n\n return selected === undefined\n ? emptyHooks\n : Object.fromEntries([...selected].map(([key, entry]) => [key, hookOf(entry)]));\n}\n\nfunction useCommands<Commands extends { readonly [Key in keyof Commands]: Call<never, unknown> }>(\n commands: Commands,\n): CommandsHook<Commands> {\n return useSelectionHooks(commands, noResources) as CommandsHook<Commands>;\n}\n\nexport { useCommands, useSelectionHooks };\nexport type { SelectionHooks };\n","import type { Readable } from '@opetope/core';\nimport { isCallTarget, isReadable } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\nimport type { AnyCommand } from './model-binding';\nimport { isResource } from './resource-command';\nimport type { AnyResource } from './resource-command';\n\ninterface SelectionRead {\n <Value>(source: Readable<Value>): Value;\n <Value, Selected>(source: Readable<Value>, select: (value: Value) => Selected): Selected;\n}\n\ntype SelectionContext = Readonly<{ read: SelectionRead }>;\ntype NonSelectionRecord =\n | readonly unknown[]\n | ((...arguments_: never[]) => unknown)\n | (abstract new (...arguments_: never[]) => unknown)\n | Date\n | PromiseLike<unknown>\n | ReadonlyMap<unknown, unknown>\n | ReadonlySet<unknown>\n | RegExp\n | WeakMap<object, unknown>\n | WeakSet<object>;\n/** A named interface needs no open index signature; container and callable results are not field selections. */\ntype SelectionShape<Selected extends object> = Selected extends NonSelectionRecord ? never : Selected;\ntype SelectionRecord = Readonly<Record<PropertyKey, unknown>>;\n/**\n * D359: the Resource a field selected, and what this selection read from it — its state, and the state of its\n * pagination capability when it has one. Both are read passively: a selection takes no lease by reading.\n */\ntype SelectedResource = Readonly<{ pagination: unknown; resource: AnyResource; state: unknown }>;\ntype SelectionSnapshot = Readonly<{\n commands: Readonly<Record<PropertyKey, AnyCommand>>;\n /** The set the mount leases; two keys naming one resource are one lease. */\n leases: ReadonlySet<AnyResource>;\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n sources: ReadonlySet<Readable<unknown>>;\n values: SelectionRecord;\n}>;\n\nconst noResources: ReadonlyMap<PropertyKey, SelectedResource> = new Map();\n\n/**\n * D363: what this mount read last and the snapshot it answered with. It belongs to the mount and not to the reader:\n * React may discard the `useMemo` that builds the reader, and a memory kept there would be empty exactly when it is\n * needed. D364: it no longer remembers which sources ended — a source whose owner retired stops instead of refusing,\n * so it answers its last value on every read and there is nothing to remember or to step around.\n */\ntype SelectionMemory = {\n model: unknown;\n observed: Map<Readable<unknown>, unknown> | undefined;\n select: unknown;\n snapshot: SelectionSnapshot;\n};\n\nconst noLeases: ReadonlySet<AnyResource> = new Set();\n\nfunction readSource<Value>(source: Readable<Value>, sources: Map<Readable<unknown>, unknown>): Value {\n if (!isReadable(source)) {\n throw new ContributionError('binding-invalid', 'Model selection read requires a Readable.');\n }\n\n if (sources.has(source)) return sources.get(source) as Value;\n\n // D364: a refusal here is the source's own failure and belongs to the render, because no lifetime ends in one.\n const value: unknown = source.getSnapshot();\n sources.set(source, value);\n\n return value as Value;\n}\n\nfunction readPassively(source: Readable<unknown>, sources: Map<Readable<unknown>, unknown>): unknown {\n if (sources.has(source)) return sources.get(source);\n\n const value: unknown = source.getSnapshot();\n sources.set(source, value);\n\n return value;\n}\n\n/** D359: the predicate that answers the question the type of the field asked, and the read that leases nothing. */\nfunction classifyResource(\n key: PropertyKey,\n value: unknown,\n resources: Map<PropertyKey, SelectedResource>,\n sources: Map<Readable<unknown>, unknown>,\n): void {\n if (!isResource(value)) return;\n\n const resource = value as AnyResource;\n const capability = (resource as { readonly pagination?: { readonly state: Readable<unknown> } }).pagination;\n\n resources.set(key, {\n pagination: capability === undefined ? undefined : readPassively(capability.state, sources),\n resource,\n state: readPassively(resource.state, sources),\n });\n}\n\nfunction selectedResources(resources: Map<PropertyKey, SelectedResource>): ReadonlyMap<PropertyKey, SelectedResource> {\n return resources.size === 0 ? noResources : resources;\n}\n\ntype CollectedSelection = Readonly<{\n resources: ReadonlyMap<PropertyKey, SelectedResource>;\n values: SelectionRecord;\n}>;\n\n/**\n * D359: recognition happens in the one pass that already walks the selection. `Resource` is a structural contract\n * (D199) and the type of a selected field promises `ResourceHook` for anything that satisfies it, so the predicate\n * this uses answers the same question the type asked. Reading the state here also puts it among `sources`, so its\n * publication invalidates the fast path of this reader exactly as a read source does.\n */\nfunction collectSelection(\n select: (context: SelectionContext) => object,\n sources: Map<Readable<unknown>, unknown>,\n): CollectedSelection {\n const read = <Value, Selected>(source: Readable<Value>, project?: (value: Value) => Selected) => {\n const value = readSource(source, sources);\n\n return project === undefined ? value : project(value);\n };\n const selected = select({ read });\n\n if (typeof selected !== 'object' || selected === null || Array.isArray(selected)) {\n throw new ContributionError('binding-invalid', 'Model selection requires a record.');\n }\n\n const entries: (readonly [PropertyKey, unknown])[] = [];\n const resources = new Map<PropertyKey, SelectedResource>();\n\n for (const key of Reflect.ownKeys(selected)) {\n if (!Object.prototype.propertyIsEnumerable.call(selected, key)) continue;\n\n const value = (selected as SelectionRecord)[key];\n\n entries.push([key, value]);\n classifyResource(key, value, resources, sources);\n }\n\n // `__proto__` is a key like any other here, and only a defined property keeps it one (D214).\n return { resources: selectedResources(resources), values: Object.fromEntries(entries) };\n}\n\n/**\n * D359: `values` keeps the Resource itself — a stable reference — so a selection that holds one is compared by\n * `Object.is` exactly as before. What moves is the state behind it, and that is compared here: without this the\n * selection would be «equal», the previous state would be kept, and a published Resource would reach no render.\n */\nfunction equalResources(\n previous: ReadonlyMap<PropertyKey, SelectedResource>,\n next: ReadonlyMap<PropertyKey, SelectedResource>,\n): boolean {\n if (previous.size !== next.size) return false;\n\n for (const [key, selected] of next) {\n const before = previous.get(key);\n\n if (\n before?.resource !== selected.resource ||\n !Object.is(before.state, selected.state) ||\n !Object.is(before.pagination, selected.pagination)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction equalSelection(\n previous: SelectionSnapshot,\n values: SelectionRecord,\n sources: ReadonlySet<Readable<unknown>>,\n resources: ReadonlyMap<PropertyKey, SelectedResource>,\n) {\n const keys = Reflect.ownKeys(values);\n const previousKeys = Reflect.ownKeys(previous.values);\n\n if (keys.length !== previousKeys.length || sources.size !== previous.sources.size) return false;\n\n for (const source of sources) if (!previous.sources.has(source)) return false;\n\n if (!equalResources(previous.resources, resources)) return false;\n\n return keys.every((key, index) => previousKeys[index] === key && Object.is(previous.values[key], values[key]));\n}\n\ntype ModelSelector<Model> = (model: Model, context: SelectionContext) => object;\ntype SelectionReaderRef<Model> = { readonly current: ModelSelector<Model> | undefined };\n\nconst emptySelectionSnapshot: SelectionSnapshot = {\n commands: {},\n leases: noLeases,\n resources: noResources,\n sources: new Set(),\n values: {},\n};\n\nfunction selectionCommands(values: SelectionRecord): Readonly<Record<PropertyKey, AnyCommand>> {\n return Object.fromEntries(\n Reflect.ownKeys(values)\n .filter(key => isCallTarget(values[key]))\n .map(key => [key, values[key]]),\n ) as Readonly<Record<PropertyKey, AnyCommand>>;\n}\n\nfunction selectionLeases(resources: ReadonlyMap<PropertyKey, SelectedResource>): ReadonlySet<AnyResource> {\n if (resources.size === 0) return noLeases;\n\n const leased = new Set<AnyResource>();\n\n for (const selected of resources.values()) leased.add(selected.resource);\n\n return leased;\n}\n\n/**\n * The fast path re-reads what it observed. D364: a source that stopped answers the same value it answered last, so\n * it compares equal and costs one read — there is no terminal source to record and step around any more.\n */\nfunction observedUnchanged(observed: Map<Readable<unknown>, unknown>): boolean {\n for (const [source, value] of observed) {\n if (!Object.is(source.getSnapshot(), value)) return false;\n }\n\n return true;\n}\n\n/** D363: the memory of one mount, empty until its first read and reset when the reader is built for another model. */\nfunction createSelectionMemory(): SelectionMemory {\n return { model: undefined, observed: undefined, select: undefined, snapshot: emptySelectionSnapshot };\n}\n\n/**\n * D214: one reader per model, not per render. The author's selector arrives through a ref, so an inline arrow does not\n * rebuild the reader; the fast path — nothing observed has moved — belongs to the selector that computed the snapshot,\n * so a new selector is always called again, its result compared field by field, and an equal selection keeps the\n * previous snapshot. A rejected or abandoned render still changes nothing: only a committed selection subscribes.\n *\n * D363: what the reader remembers lives in the mount's memory, so a reader React rebuilt — because it discarded the\n * memo — still knows what this selection read last.\n */\nfunction createSelectionSnapshot<Model>(\n model: Model,\n selectRef: SelectionReaderRef<Model>,\n memory: SelectionMemory,\n): () => SelectionSnapshot {\n if (memory.model !== model) {\n memory.model = model;\n memory.observed = undefined;\n memory.select = undefined;\n memory.snapshot = emptySelectionSnapshot;\n }\n\n return () => {\n const select = selectRef.current;\n\n if (select === undefined) return emptySelectionSnapshot;\n\n const observed = memory.observed;\n\n if (observed !== undefined && memory.select === select && observedUnchanged(observed)) {\n return memory.snapshot;\n }\n\n const nextObserved = new Map<Readable<unknown>, unknown>();\n const { resources, values } = collectSelection(context => select(model, context), nextObserved);\n const sources = new Set(nextObserved.keys());\n\n if (observed === undefined || !equalSelection(memory.snapshot, values, sources, resources)) {\n memory.snapshot = {\n commands: selectionCommands(values),\n leases: selectionLeases(resources),\n resources,\n sources,\n values,\n };\n }\n\n memory.observed = nextObserved;\n memory.select = select;\n\n return memory.snapshot;\n };\n}\n\nexport { createSelectionMemory, createSelectionSnapshot, emptySelectionSnapshot };\nexport type { ModelSelector, SelectedResource, SelectionContext, SelectionMemory, SelectionShape, SelectionSnapshot };\n","import type { Readable } from '@opetope/core';\nimport { createAggregateError } from '@opetope/core/internal';\n\nimport type { AnyResource } from './resource-command';\n\ntype Subscription = { dispose: (() => void) | undefined };\ntype Listener = Readonly<{ notify: () => void }>;\n\nconst noLeases: ReadonlySet<AnyResource> = new Set();\n\nfunction releaseSubscriptions(subscriptions: Iterable<Subscription>, failures: unknown[] = []): void {\n for (const subscription of subscriptions) {\n const dispose = subscription.dispose;\n subscription.dispose = undefined;\n\n try {\n dispose?.();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n\n if (failures.length > 1) throw createAggregateError(failures, 'Model selection sources failed to release.');\n}\n\n/** A subscription set, not a scheduler: source notifications use React's snapshot consistency check. */\nclass ModelSelectionStore {\n private enabled = false;\n /** D359: one lease per Resource identity, diffed like a subscription, so an unchanged selection takes none. */\n private readonly leases = new Map<AnyResource, Subscription>();\n private readonly listeners = new Set<Listener>();\n private retained: ReadonlySet<AnyResource> = noLeases;\n private sources: ReadonlySet<Readable<unknown>> = new Set();\n private readonly subscriptions = new Map<Readable<unknown>, Subscription>();\n\n close(): void {\n this.enabled = false;\n this.sources = new Set();\n this.retained = noLeases;\n this.releaseAll();\n }\n\n commit(sources: ReadonlySet<Readable<unknown>>, retained: ReadonlySet<AnyResource> = noLeases): void {\n this.sources = sources;\n this.retained = retained;\n this.enabled = true;\n\n try {\n this.synchronize();\n } catch (error) {\n this.releaseAll([error]);\n }\n }\n\n subscribe = (notify: () => void): (() => void) => {\n const listener = { notify };\n this.listeners.add(listener);\n\n try {\n this.synchronize();\n } catch (error) {\n this.listeners.delete(listener);\n this.releaseAll([error]);\n }\n\n return () => {\n this.listeners.delete(listener);\n\n if (this.listeners.size === 0) this.releaseAll();\n };\n };\n\n private add(source: Readable<unknown>): void {\n const subscription: Subscription = { dispose: undefined };\n // Reserve before foreign code: a synchronous notification may replace or close this selection.\n this.subscriptions.set(source, subscription);\n const dispose = source.subscribe(this.notify);\n\n if (typeof dispose !== 'function') throw new TypeError('Model selection source must return a disposer.');\n\n if (this.subscriptions.get(source) === subscription) subscription.dispose = dispose;\n else dispose();\n }\n\n private notify = (): void => {\n for (const listener of [...this.listeners]) {\n if (this.listeners.has(listener)) listener.notify();\n }\n };\n\n private releaseAll(failures?: unknown[]): void {\n const held = [...this.subscriptions.values(), ...this.leases.values()];\n this.subscriptions.clear();\n this.leases.clear();\n releaseSubscriptions(held, failures);\n }\n\n private removeUnselected(): void {\n const removed: Subscription[] = [];\n\n for (const [source, subscription] of this.subscriptions) {\n if (this.sources.has(source)) continue;\n\n this.subscriptions.delete(source);\n removed.push(subscription);\n }\n\n for (const [resource, lease] of this.leases) {\n if (this.retained.has(resource)) continue;\n\n this.leases.delete(resource);\n removed.push(lease);\n }\n\n releaseSubscriptions(removed);\n }\n\n /**\n * D321: `retain()` is reentrant — it reaches `activate()`, the target subscription, the first read and the\n * publication that follows, all synchronously — and a listener woken there may rebuild or close this selection.\n * The record is reserved first, the lease is taken second, and it is kept only if the record is still this one.\n */\n private retain(resource: AnyResource): void {\n const lease: Subscription = { dispose: undefined };\n this.leases.set(resource, lease);\n const release = resource.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 if (!this.ready()) return;\n\n if (this.retained.has(resource) && !this.leases.has(resource)) this.retain(resource);\n }\n }\n\n private subscribeSources(): void {\n for (const source of this.sources) {\n if (!this.ready()) return;\n\n if (this.sources.has(source) && !this.subscriptions.has(source)) this.add(source);\n }\n }\n\n private synchronize(): void {\n this.removeUnselected();\n this.subscribeSources();\n this.retainSelected();\n }\n}\n\nexport { ModelSelectionStore };\n","import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Call, ModelOf, PaginatedResource, Resource } from '@opetope/core';\n\nimport type { CommandHook } from './command-hook';\nimport { useSelectionHooks } from './commands-hook';\nimport type { SelectionHooks } from './commands-hook';\nimport { ContributionError } from './errors';\nimport { subscribeIdle } from './idle-subscription';\nimport type { AnyModel } from './model-binding';\nimport { createSelectionMemory, createSelectionSnapshot, emptySelectionSnapshot } from './model-selection-snapshot';\nimport type {\n ModelSelector,\n SelectionContext,\n SelectionMemory,\n SelectionShape,\n SelectionSnapshot,\n} from './model-selection-snapshot';\nimport { ModelSelectionStore } from './model-selection-store';\nimport { useFrame } from './mount-context';\nimport type { PaginatedResourceHook, ResourceHook, ResourceKey } from './resource-command';\n\n/**\n * D359: a field whose value is a Resource answers the hook `useResource` answers, and the mount leases it exactly as\n * `useResource` does. `read(resource.state)` is unchanged — it hands out the state and leases nothing — so naming one\n * Resource both ways registers it twice: the subscription and the lease are independent.\n */\ntype SelectedValue<Value> =\n Value extends Call<infer Input, infer Output>\n ? CommandHook<Input, Output>\n : Value extends PaginatedResource<infer Data, infer Key extends ResourceKey, infer Cursor extends ResourceKey>\n ? PaginatedResourceHook<Data, Key, Cursor>\n : Value extends Resource<infer Data, infer Key extends ResourceKey>\n ? ResourceHook<Data, Key>\n : Value;\ntype ModelSelection<Selected> = { readonly [Key in keyof Selected]: SelectedValue<Selected[Key]> };\n\nconst readEmptySnapshot = () => emptySelectionSnapshot;\n\nfunction useSelectionStore(selected: boolean): ModelSelectionStore | undefined {\n const storeRef = useRef<ModelSelectionStore | undefined>(undefined);\n\n return selected ? (storeRef.current ??= new ModelSelectionStore()) : undefined;\n}\n\n/**\n * Two branches of one key: the consumer this selection prepared — a bound command, or the verbs, the state and the\n * capability of a selected Resource — and otherwise the value the selector wrote. S6/D359: the Resource branch is\n * assembled on the record and not here, so a key whose state did not move hands back the very same object.\n */\nfunction mergeSelection(snapshot: SelectionSnapshot, hooks: SelectionHooks): unknown {\n return Object.fromEntries(\n Reflect.ownKeys(snapshot.values).map(key => [\n key,\n Object.prototype.hasOwnProperty.call(hooks, key) ? hooks[key] : snapshot.values[key],\n ]),\n );\n}\n\n/** D205: resolve only a granted model, explicitly read data, and reuse the ordinary per-key command consumers. */\nfunction useModel<Declaration extends AnyModel>(declaration: Declaration): ModelOf<Declaration>;\nfunction useModel<Declaration extends AnyModel, const Selected extends object>(\n declaration: Declaration,\n select: (model: ModelOf<Declaration>, context: SelectionContext) => Selected & SelectionShape<Selected>,\n): ModelSelection<Selected>;\nfunction useModel<Declaration extends AnyModel>(\n declaration: Declaration,\n select?: (model: ModelOf<Declaration>, context: SelectionContext) => object,\n): unknown {\n const frame = useFrame();\n\n if (!frame.models.has(declaration)) {\n throw new ContributionError('missing', `Model ${declaration.id} is not granted to this contribution.`);\n }\n\n const model = frame.models.get(declaration) as ModelOf<Declaration>;\n const store = useSelectionStore(select !== undefined);\n // D214: the selector of this render, read by a reader that belongs to the model. Writing the latest callback into\n // a ref is what `useSyncExternalStoreWithSelector` does: the reader only reads it, and never writes to it.\n const selectRef = useRef<ModelSelector<ModelOf<Declaration>> | undefined>(undefined);\n selectRef.current = select;\n const selected = select !== undefined;\n // D363: the memory of this selection belongs to the mount, so a reader React rebuilt keeps what it read last.\n const memoryRef = useRef<SelectionMemory | undefined>(undefined);\n const memory = (memoryRef.current ??= createSelectionMemory());\n const getSnapshot = useMemo(\n () => (selected ? createSelectionSnapshot(model, selectRef, memory) : readEmptySnapshot),\n [memory, model, selected],\n );\n const snapshot = useSyncExternalStore(store?.subscribe ?? subscribeIdle, getSnapshot, getSnapshot);\n const hooks = useSelectionHooks(snapshot.commands, snapshot.resources);\n useLayoutEffect(() => () => store?.close(), [store]);\n // D359: a lease is taken in commit and never in a render, and the store diffs the set by identity.\n useLayoutEffect(() => store?.commit(snapshot.sources, snapshot.leases), [snapshot, store]);\n\n if (select === undefined) return model;\n\n return mergeSelection(snapshot, hooks);\n}\n\nexport { useModel };\n","import type { FunctionComponent } from 'react';\n\nimport type { ModelIdentity } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\ntype AnyModel = ModelIdentity;\n\n/**\n * The marker a component uses to state which UI models of its contribution it reads. `slot` checks by type that the\n * contribution grants every model of this list — it may grant more, because the mount serves its whole instance.\n *\n * The runtime authority is the mount frame, not this marker: `useModel` resolves against what the mount was given\n * and refuses anything else. The lint rule `opetope/require-declared-models` checks visible contribution sites and\n * each reader of a per-mount model within the same module. Imported implementations and dynamic declaration lists\n * remain unknown to this syntactic check (D85, D158, D250).\n */\ntype ComponentRequiringModels<Props, Models extends readonly AnyModel[]> = FunctionComponent<Props> & {\n readonly requires: Models;\n};\n\nfunction requiresModels<const Models extends readonly AnyModel[]>(\n models: Models,\n): <Props>(component: FunctionComponent<Props>) => ComponentRequiringModels<Props, Models> {\n const declared = Object.freeze([...models]) as unknown as Models;\n\n return <Props>(component: FunctionComponent<Props>): ComponentRequiringModels<Props, Models> => {\n const existing = (component as { readonly requires?: readonly AnyModel[] }).requires;\n\n if (existing !== undefined) {\n throw new ContributionError('duplicate', 'Component already declares the models it requires.');\n }\n\n Object.defineProperty(component, 'requires', { enumerable: false, value: declared });\n\n return component as ComponentRequiringModels<Props, Models>;\n };\n}\n\nexport { requiresModels };\n","import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport type { PaginatedResource, PaginationState, Readable, Resource } from '@opetope/core';\nimport type { ResourceKey } from '@opetope/core/internal';\n\nimport { useCommandStatus } from './command-hook';\nimport { CommandHookController } from './command-hook-controller';\nimport { createResourceInvokers, paginationOf } from './resource-command';\nimport type { AnyResource, PaginatedResourceHook, ResourceHook, ResourcePaginationHook } from './resource-command';\n\ntype ResourceSlots = Readonly<{\n invalidate: CommandHookController<void, unknown>;\n loadNext: CommandHookController<void, unknown>;\n refresh: CommandHookController<void, unknown>;\n retry: CommandHookController<void, unknown>;\n}>;\n\n/**\n * D359: one set of controllers per Resource identity, exactly as `useCommandSlot` keeps one per invoker. The slots are\n * what the set of invokers is kept alive by, so one map answers for all four. Slots are inert until commit, so an\n * interrupted render neither admits work nor resets the status of the committed one.\n */\nfunction useResourceSlots(resource: AnyResource): ResourceSlots {\n const [cache] = useState(() => new WeakMap<object, ResourceSlots>());\n const existing = cache.get(resource);\n\n if (existing !== undefined) return existing;\n\n const invokers = createResourceInvokers(resource);\n const slots: ResourceSlots = {\n invalidate: new CommandHookController<void, unknown>(invokers.invalidate),\n loadNext: new CommandHookController<void, unknown>(invokers.loadNext),\n refresh: new CommandHookController<void, unknown>(invokers.refresh),\n retry: new CommandHookController<void, unknown>(invokers.retry),\n };\n cache.set(resource, slots);\n\n return slots;\n}\n\nconst unavailablePagination: PaginationState<ResourceKey> = Object.freeze({\n kind: 'unavailable',\n reason: 'resource-not-ready',\n});\nconst noPaginationSource: Readable<PaginationState<ResourceKey>> = Object.freeze({\n getSnapshot: () => unavailablePagination,\n subscribe: () => () => undefined,\n});\n\n/**\n * D359: the state of a Resource, the lease that keeps it open for as long as this component is mounted, and its verbs\n * as commands. The number of React hooks here does not depend on the Resource: a declaration without `pagination`\n * subscribes to a constant source and keeps a `loadNext` that answers `skipped`.\n */\nfunction useResource<Data, Key extends ResourceKey, Cursor extends ResourceKey>(\n resource: PaginatedResource<Data, Key, Cursor>,\n): PaginatedResourceHook<Data, Key, Cursor>;\nfunction useResource<Data, Key extends ResourceKey>(resource: Resource<Data, Key>): ResourceHook<Data, Key>;\nfunction useResource<Data, Key extends ResourceKey>(resource: Resource<Data, Key>): ResourceHook<Data, Key> {\n const handle = resource as unknown as AnyResource;\n const capability = paginationOf(handle);\n const subscribe = useCallback((listener: () => void) => resource.state.subscribe(listener), [resource]);\n /*\n * D364: `resource.state` is a total `Readable` for the life of the object this hook holds — a retired one answers\n * `idle('retired')` — so the read needs no memory of its own. What it can still refuse is a producer failure, a\n * broken `nextCursor` among them (D356), and that belongs to the render (G13).\n */\n const getState = useMemo(() => () => resource.state.getSnapshot(), [resource]);\n const state = useSyncExternalStore(subscribe, getState, getState);\n const paginationSource: Readable<PaginationState<ResourceKey>> =\n (capability?.state as Readable<PaginationState<ResourceKey>> | undefined) ?? noPaginationSource;\n const subscribePagination = useCallback(\n (listener: () => void) => paginationSource.subscribe(listener),\n [paginationSource],\n );\n const getPaginationState = useMemo(() => () => paginationSource.getSnapshot(), [paginationSource]);\n const paginationState = useSyncExternalStore(subscribePagination, getPaginationState, getPaginationState);\n\n // D349: the lease is taken in a commit effect, so an interrupted render opens nothing.\n useEffect(() => resource.acquire(), [resource]);\n\n const slots = useResourceSlots(handle);\n const refresh = useCommandStatus(slots.refresh);\n const retry = useCommandStatus(slots.retry);\n const invalidate = useCommandStatus(slots.invalidate);\n const loadNext = useCommandStatus(slots.loadNext);\n const pagination = useMemo(\n () =>\n capability === undefined\n ? undefined\n : ({ loadNext, state: paginationState } as unknown as ResourcePaginationHook<ResourceKey>),\n [capability, loadNext, paginationState],\n );\n\n return useMemo(\n () => ({ invalidate, pagination, refresh, retry, state }) as ResourceHook<Data, Key>,\n [invalidate, pagination, refresh, retry, state],\n );\n}\n\nexport { useResource };\n","import type { ComponentType, ReactNode } from 'react';\nimport { createElement, Fragment } from 'react';\n\nimport type { DeclarationId } from '@opetope/core';\nimport { declarationId } from '@opetope/core';\nimport type { ContributionTarget } from '@opetope/core/internal';\nimport { createContributionTarget } from '@opetope/core/internal';\nimport type { ContributionModel } from '@opetope/runtime/internal';\n\nimport { contributionAuthority, ContributionMount } from './contribution-frame';\nimport { useReadable } from './readable-hooks';\n\ntype SlotProperties = object | undefined;\ntype EmptySlotProperties = Readonly<Record<string, never>>;\ntype SlotComponentProperties<Props extends SlotProperties> = [Props] extends [undefined]\n ? EmptySlotProperties\n : Extract<Props, object>;\n\n/**\n * What a contribution publishes: the component, the optional props adapter and the optional UI models the mount\n * creates. Without an adapter the component takes the slot props; with one it takes exactly the adapter result (D85).\n */\ntype SlotContribution<Props extends SlotProperties = undefined> = {\n readonly Component: ComponentType<never>;\n readonly models?: readonly ContributionModel[];\n readonly props?: (slotProps: SlotComponentProperties<Props>) => object;\n};\n\ntype SlotTarget<Props extends SlotProperties = undefined> = ContributionTarget<SlotContribution<Props>>;\n\ntype SwitchSlotTarget<Route extends string, Props extends SlotProperties = undefined> = ((\n route: Route,\n) => SlotTarget<Props>) & {\n readonly id: DeclarationId;\n};\n\ntype SlotRenderProps<Props extends SlotProperties> = [Props] extends [undefined]\n ? {\n readonly props?: never;\n readonly target: SlotTarget<Props>;\n }\n : {\n readonly props: SlotComponentProperties<Props>;\n readonly target: SlotTarget<Props>;\n };\n\nconst emptySlotProperties: EmptySlotProperties = Object.freeze({});\n\n/** D280: a slot is a name, so it takes its id as the first argument. */\nfunction defineSlot<Props extends SlotProperties = undefined>(id: string): SlotTarget<Props> {\n return createContributionTarget<SlotContribution<Props>>(id, 'slot');\n}\n\nfunction defineSwitchSlot<Route extends string, Props extends SlotProperties = undefined>(\n id: string,\n): SwitchSlotTarget<Route, Props> {\n const declared = declarationId(id);\n const targets = new Map<Route, SlotTarget<Props>>();\n\n const switchSlot = (route: Route): SlotTarget<Props> => {\n const existing = targets.get(route);\n\n if (existing !== undefined) return existing;\n\n const target = defineSlot<Props>(`${declared}/${route}`);\n targets.set(route, target);\n\n return target;\n };\n\n Object.defineProperty(switchSlot, 'id', { enumerable: true, value: declared });\n\n return Object.freeze(switchSlot) as SwitchSlotTarget<Route, Props>;\n}\n\n/**\n * Every contribution renders inside its own mount: the models its generation owns plus the UI models it declared,\n * created on mount and closed on unmount (D70, D85).\n */\nfunction Slot<Props extends SlotProperties>(options: SlotRenderProps<Props>): ReactNode {\n const entries = useReadable(options.target.entries);\n const slotProps: object = 'props' in options ? (options.props ?? emptySlotProperties) : emptySlotProperties;\n\n if (entries.length === 0) return null;\n\n return entries.map(entry =>\n createElement(\n Fragment,\n { key: entry.id },\n createElement(ContributionMount, {\n authority: contributionAuthority(entry),\n contribution: entry.value,\n contributionId: entry.id,\n slotProps,\n targetId: options.target.id,\n }),\n ),\n );\n}\n\nexport { defineSlot, defineSwitchSlot, Slot };\nexport type { SlotComponentProperties, SlotContribution, SlotProperties, SlotTarget, SwitchSlotTarget };\n"],"names":["failureOutcome","options","error","isCancellation","notification","_a","reportDetachedError","callbackError","runOptions","onSuccess","signal","requestOf","input","resolve","promise","settle","request","outcome","CommandHookController","ContributionError","aborted","invoker","notify","CallError","compatibleAbortReason","invocation","value","emptyStatus","settledOutcome","unchangedState","current","slot","inFlight","settled","nextState","previous","useCommandSlot","slots","useState","useCommandStatus","currentSlot","hookState","setHookState","useInsertionEffect","useLayoutEffect","run","useMemo","useCommand","command","record","useFrame","idleRelease","subscribeIdle","idleRevision","emptyHooks","noResources","isResourceEntry","entry","createEntry","unchangedHook","next","updateEntry","selectEntry","resourceHook","selected","createResourceEntry","invokers","parts","unchangedResourceCommands","hook","unchangedResourceState","unchangedResourceHook","selectResourceEntry","commandEntries","detachEntry","hookOf","CommandGroup","key","listener","commands","resources","frame","resource","resourceInvokers","useCommandGroup","groupRef","useRef","useSelectionHooks","group","getSnapshot","useSyncExternalStore","useCommands","noLeases","readSource","source","sources","isReadable","readPassively","classifyResource","isResource","capability","selectedResources","collectSelection","select","project","entries","equalResources","before","equalSelection","values","keys","previousKeys","index","emptySelectionSnapshot","selectionCommands","isCallTarget","selectionLeases","leased","observedUnchanged","observed","createSelectionMemory","createSelectionSnapshot","model","selectRef","memory","nextObserved","context","releaseSubscriptions","subscriptions","failures","subscription","dispose","createAggregateError","ModelSelectionStore","retained","held","removed","lease","release","readEmptySnapshot","useSelectionStore","storeRef","mergeSelection","snapshot","hooks","useModel","declaration","store","memoryRef","requiresModels","models","declared","component","useResourceSlots","cache","existing","createResourceInvokers","unavailablePagination","noPaginationSource","useResource","handle","paginationOf","subscribe","useCallback","getState","state","paginationSource","subscribePagination","getPaginationState","paginationState","useEffect","refresh","retry","invalidate","loadNext","pagination","emptySlotProperties","defineSlot","id","createContributionTarget","defineSwitchSlot","declarationId","targets","switchSlot","route","target","Slot","useReadable","slotProps","createElement","Fragment","ContributionMount","contributionAuthority"],"mappings":"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"}
@@ -29,17 +29,34 @@ type SelectionSnapshot = Readonly<{
29
29
  sources: ReadonlySet<Readable<unknown>>;
30
30
  values: SelectionRecord;
31
31
  }>;
32
+ /**
33
+ * D363: what this mount read last and the snapshot it answered with. It belongs to the mount and not to the reader:
34
+ * React may discard the `useMemo` that builds the reader, and a memory kept there would be empty exactly when it is
35
+ * needed. D364: it no longer remembers which sources ended — a source whose owner retired stops instead of refusing,
36
+ * so it answers its last value on every read and there is nothing to remember or to step around.
37
+ */
38
+ type SelectionMemory = {
39
+ model: unknown;
40
+ observed: Map<Readable<unknown>, unknown> | undefined;
41
+ select: unknown;
42
+ snapshot: SelectionSnapshot;
43
+ };
32
44
  type ModelSelector<Model> = (model: Model, context: SelectionContext) => object;
33
45
  type SelectionReaderRef<Model> = {
34
46
  readonly current: ModelSelector<Model> | undefined;
35
47
  };
36
48
  declare const emptySelectionSnapshot: SelectionSnapshot;
49
+ /** D363: the memory of one mount, empty until its first read and reset when the reader is built for another model. */
50
+ declare function createSelectionMemory(): SelectionMemory;
37
51
  /**
38
52
  * D214: one reader per model, not per render. The author's selector arrives through a ref, so an inline arrow does not
39
53
  * rebuild the reader; the fast path — nothing observed has moved — belongs to the selector that computed the snapshot,
40
54
  * so a new selector is always called again, its result compared field by field, and an equal selection keeps the
41
55
  * previous snapshot. A rejected or abandoned render still changes nothing: only a committed selection subscribes.
56
+ *
57
+ * D363: what the reader remembers lives in the mount's memory, so a reader React rebuilt — because it discarded the
58
+ * memo — still knows what this selection read last.
42
59
  */
43
- declare function createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>): () => SelectionSnapshot;
44
- export { createSelectionSnapshot, emptySelectionSnapshot };
45
- export type { ModelSelector, SelectedResource, SelectionContext, SelectionShape, SelectionSnapshot };
60
+ declare function createSelectionSnapshot<Model>(model: Model, selectRef: SelectionReaderRef<Model>, memory: SelectionMemory): () => SelectionSnapshot;
61
+ export { createSelectionMemory, createSelectionSnapshot, emptySelectionSnapshot };
62
+ export type { ModelSelector, SelectedResource, SelectionContext, SelectionMemory, SelectionShape, SelectionSnapshot };
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import{CallTimeoutError as ye,WaitTimeoutError as Se,eventually as ge,openModel as ve,runCall as $e,settled as ke,testReadable as Me,testResourceEpoch as Ce,waitFor as je,yieldTurn as Ee}from"@opetope/runtime/testing";import{hasOwn as F,createAggregateError as x}from"@opetope/core/internal";import{openApplication as _}from"@opetope/runtime";import{createInspectionSession as N}from"@opetope/runtime/internal";import{createElement as B}from"react";import{declarationId as H}from"@opetope/core";import{command as W}from"@opetope/core/testing";import{e as q,a as P,b as D,M as L}from"./contribution-frame-QRJSP2W3.js";function U(e){if(e.state.kind==="ready")return[];const t=e.state.kind==="waiting"?` (${e.state.reason})`:"";return[`Feature ${e.label}: ${e.state.kind}${t}.`]}function G(e){if(e.lane===null)return`Call ${e.declaration}: ${e.state}; lane parallel; blockers none (parallel).`;const t=e.lane.blockedBy==="unknown"?"unknown":e.lane.blockedBy.join(", ");return`Call ${e.declaration}: ${e.state}; lane ${e.lane.declaration}; blockers ${t}.`}function J(e){if(e===void 0)return["Runtime activity: unknown."];const t=[];return(e.freshness==="stale"||e.truncated)&&t.push(`Activity: ${e.freshness}${e.truncated?", truncated":""}; completeness unknown.`),[...t,...e.features.map(n=>`Feature ${n.feature}: ${n.phase}; body ${n.body}; host demand unknown.`),...e.calls.map(G),...e.resources.map(n=>`Resource ${n.id}: ${n.state}; leases ${String(n.leases)}.`)]}function K(e){const t=[...e.runtime.conditions.flatMap(n=>n.state.kind==="true"?[]:[`Condition ${n.label}: ${n.state.kind}.`]),...e.runtime.instances.flatMap(U),...J(e.activity)];return t.length===0?"No registered runtime blocker observed; external UI and host work are unknown.":t.join(`
1
+ import{CallTimeoutError as ye,WaitTimeoutError as Se,eventually as ge,openModel as ve,runCall as $e,settled as ke,testReadable as Me,testResourceEpoch as Ce,waitFor as je,yieldTurn as Ee}from"@opetope/runtime/testing";import{hasOwn as F,createAggregateError as x}from"@opetope/core/internal";import{openApplication as _}from"@opetope/runtime";import{createInspectionSession as N}from"@opetope/runtime/internal";import{createElement as B}from"react";import{declarationId as H}from"@opetope/core";import{command as W}from"@opetope/core/testing";import{e as q,a as P,b as D,M as L}from"./contribution-frame-DkNmjChZ.js";function U(e){if(e.state.kind==="ready")return[];const t=e.state.kind==="waiting"?` (${e.state.reason})`:"";return[`Feature ${e.label}: ${e.state.kind}${t}.`]}function G(e){if(e.lane===null)return`Call ${e.declaration}: ${e.state}; lane parallel; blockers none (parallel).`;const t=e.lane.blockedBy==="unknown"?"unknown":e.lane.blockedBy.join(", ");return`Call ${e.declaration}: ${e.state}; lane ${e.lane.declaration}; blockers ${t}.`}function J(e){if(e===void 0)return["Runtime activity: unknown."];const t=[];return(e.freshness==="stale"||e.truncated)&&t.push(`Activity: ${e.freshness}${e.truncated?", truncated":""}; completeness unknown.`),[...t,...e.features.map(n=>`Feature ${n.feature}: ${n.phase}; body ${n.body}; host demand unknown.`),...e.calls.map(G),...e.resources.map(n=>`Resource ${n.id}: ${n.state}; leases ${String(n.leases)}.`)]}function K(e){const t=[...e.runtime.conditions.flatMap(n=>n.state.kind==="true"?[]:[`Condition ${n.label}: ${n.state.kind}.`]),...e.runtime.instances.flatMap(U),...J(e.activity)];return t.length===0?"No registered runtime blocker observed; external UI and host work are unknown.":t.join(`
2
2
  `)}class T extends Error{label;timeoutMs;snapshot;history;code="timeout";constructor(t,n,r,o){super(`Scenario timed out after ${String(n)}ms: ${t}.
3
3
  ${K(r)}`),this.label=t,this.timeoutMs=n,this.snapshot=r,this.history=o,this.name="ScenarioTimeoutError"}}function Q(e,t){return!t||e===void 0?!1:e.closed&&!e.truncated}function V(e,t,n){if(e===void 0)return["Runtime activity is unavailable."];const r=[];return e.truncated&&r.push("The activity snapshot is truncated."),e.freshness==="stale"&&!t&&r.push("The activity snapshot is stale."),n||r.push("Successful application and mount cleanup has not been observed."),Object.freeze(r)}function X(e,t){return e===void 0||e.truncated||e.freshness==="stale"&&!t?{calls:"unknown",features:"unknown",resources:"unknown"}:{calls:e.calls.length,features:e.features.length,resources:e.resources.length}}function Y(e,t){const n=e.activity,r=Q(n,t);return Object.freeze({...X(n,r),closed:t,reasons:V(n,r,t),scope:"registered-runtime",status:r?"complete":"unknown"})}class Z{released;get closed(){return this.completion!==void 0}available=!1;completion;handle;reject;resolve;constructor(t){this.released=t}close=()=>this.completion!==void 0?this.completion:(this.completion=new Promise((t,n)=>{this.resolve=t,this.reject=n}),this.available&&this.releaseAvailable(),this.completion);provide(t){this.available=!0,this.handle=t,this.closed&&this.releaseAvailable()}finish(t,n){const r=this.resolve,o=this.reject;this.resolve=void 0,this.reject=void 0,this.released(t,n),t?o==null||o(n):r==null||r()}releaseAvailable(){const t=this.handle;this.handle=void 0,this.available=!1;try{Promise.resolve(t==null?void 0:t.unmount()).then(()=>this.finish(!1),n=>this.finish(!0,n))}catch(n){this.finish(!0,n)}}}const ee=H("testing.contribution");function te(e){return q(W(e))}function O(e,t={}){const n=t.models??[];let r=t.props??{};const o=new Set,w={getSnapshot:()=>r,subscribe:i=>(o.add(i),()=>o.delete(i))},c=t.contribution,s=t.reporter,l=s===void 0?void 0:{models:[],reporter:s};return Object.freeze({close:()=>{r={},o.clear()},Slot:()=>{const i=P(e.entries),d=P(w);return(c===void 0?i.map(p=>[p.id,p.value,D(p)??l]):[[ee,c,l]]).map(([p,f,k])=>B(L,{authority:k,contribution:f,contributionId:p,fixtures:n,key:p,slotProps:d,targetId:e.id}))},updateProps:i=>{r=i;for(const d of[...o])d()}})}function ne(e,t={}){const n=O(e,t);return Object.freeze({Slot:n.Slot,updateProps:n.updateProps})}function C(e,t){if(!Number.isFinite(e)||e<=0||e>2147483647)throw new RangeError(`${t} must be a finite duration from 0 (exclusive) to 2147483647ms.`);return e}class re{source;listeners=new Set;pending=new Set;constructor(t){this.source=t}cancel(){for(const t of[...this.pending])t(new Error("Scenario is closing."))}deadline(t,n,r=this.source.timeoutMs){return C(r,"Scenario timeoutMs"),new Promise((o,w)=>{const c=setTimeout(()=>w(this.timeout(n,r)),r);Promise.resolve(t).then(s=>{clearTimeout(c),o(s)},s=>{clearTimeout(c),w(s)})})}notify=()=>{for(const t of[...this.listeners])t()};waitFor(t,n){const r=C(n.timeoutMs??this.source.timeoutMs,"Scenario timeoutMs"),o=C(n.pollIntervalMs??10,"Scenario pollIntervalMs");return new Promise((w,c)=>{let s=!1,l=()=>{};const y=(f,k=f!==void 0)=>{s||(s=!0,l(),k?c(f):w())},i=()=>{if(!s)try{t(this.source.getSnapshot())&&y()}catch(f){y(f,!0)}},d=f=>y(f),v=setTimeout(()=>y(this.timeout(n.label??"waitFor",r)),r),p=setInterval(i,o);l=()=>{clearTimeout(v),clearInterval(p),this.listeners.delete(i),this.pending.delete(d)},this.listeners.add(i),this.pending.add(d),i()})}timeout(t,n){return new T(t,n,this.source.getSnapshot(),this.source.history())}}function R(e,t){if(!Number.isSafeInteger(e)||e<1||e>1e4)throw new RangeError(`${t} must be an integer from 1 to 10000.`);return e}function oe(e){if(e===null||typeof e!="object"||!("mount"in e)||typeof e.mount!="function")throw new TypeError("Scenario requires a host.mount(Component) adapter.")}function se(e){return{...e.cleanupFailure===void 0?{}:{cleanupFailure:e.cleanupFailure},...F(e,"conditions")?{conditions:e.conditions}:{},...F(e,"imports")?{imports:e.imports}:{},reporter:e.reporter}}function ie(e,t){const n=C(t.timeoutMs??1e3,"Scenario timeoutMs"),r=R(t.historyCapacity??64,"Scenario historyCapacity"),o=R(t.activityCapacity??256,"Scenario activityCapacity");let w=t.host;oe(w);let c=_(e,se(t)),s;try{s=N(c,{activityCapacity:o,ringCapacity:r})}catch(u){throw c.ready.catch(()=>{}),c.close().catch(()=>{}),u}let l="open",y=!1,i=!1;const d=[],v=new Set,p=[],f=()=>s.getSnapshot(),k=()=>Object.freeze([...d]),$=new re({getSnapshot:f,history:k,timeoutMs:n}),j=()=>{if(!i){i=!0;try{const u=f(),m=d[d.length-1];((m==null?void 0:m.stateRevision)!==u.stateRevision||m.activity!==u.activity)&&(d.push(u),d.length>r&&d.shift())}finally{i=!1}$.notify()}},I=s.subscribe(j);j();const E=$.deadline(c.ready,"application ready");E.catch(()=>{});let M,S;const A=()=>{if(M!==void 0)return M;let u,m;M=new Promise((a,h)=>{u=a,m=h}),l="closing",$.cancel();const g=[];try{g.push(c.close())}catch(a){g.push(Promise.reject(a))}return g.push(...[...v].map(a=>a.close())),Promise.allSettled(g).then(a=>{const h=[...new Set([...p,...a.flatMap(b=>b.status==="rejected"?[b.reason]:[])])];p.length=0,l="closed";try{j()}catch(b){h.push(b)}finally{I(),s.close(),v.clear(),c=void 0,w=void 0}y=h.length===0,h.length===0?u():m(x(h,"Scenario cleanup failed."))}),M};return Object.freeze({close:()=>{if(S!==void 0)return S;const u=A();return S!==void 0||(S=$.deadline(u,"scenario close"),S.catch(()=>{l==="closing"&&(S=void 0)})),S},getSnapshot:f,history:k,mount:(u,m={})=>{if(l!=="open")throw new Error("Scenario is closing.");const g=O(u,m),a=new Z((b,z)=>{v.delete(a),g.close(),b&&p.push(z)});v.add(a);let h;try{if(h=w.mount(g.Slot),h===null||typeof h!="object"||typeof h.unmount!="function")throw new TypeError("Scenario host.mount must return an unmount() handle.");a.provide(h)}catch(b){throw a.provide(void 0),a.close().catch(()=>{}),b}return Object.freeze({host:h,unmount:a.close,updateProps:b=>{if(l!=="open"||a.closed)throw new Error("Scenario mount is closed.");g.updateProps(b),$.notify()}})},notify:()=>{l==="open"&&$.notify()},ownership:()=>Y(f(),y),ready:E,waitFor:(u,m={})=>l!=="open"?Promise.reject(new Error("Scenario is closing.")):$.waitFor(u,m)})}export{ye as CallTimeoutError,T as ScenarioTimeoutError,Se as WaitTimeoutError,te as command,ie as createScenario,ge as eventually,ve as openModel,ne as renderSlot,$e as runCall,ke as settled,Me as testReadable,Ce as testResourceEpoch,je as waitFor,Ee as yieldTurn};
4
4
  //# sourceMappingURL=testing.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/react",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },
@@ -50,16 +50,16 @@
50
50
  }
51
51
  ],
52
52
  "devDependencies": {
53
- "@opetope/core": "0.10.1",
54
- "@opetope/runtime": "0.10.1",
53
+ "@opetope/core": "0.11.0",
54
+ "@opetope/runtime": "0.11.0",
55
55
  "@testing-library/react": "16.3.3",
56
56
  "@types/react": "19.2.18",
57
57
  "react": "19.2.8",
58
58
  "react-dom": "19.2.8"
59
59
  },
60
60
  "peerDependencies": {
61
- "@opetope/core": "0.10.1",
62
- "@opetope/runtime": "0.10.1",
61
+ "@opetope/core": "0.11.0",
62
+ "@opetope/runtime": "0.11.0",
63
63
  "react": ">=19.0.0 <20"
64
64
  },
65
65
  "sideEffects": false,
@@ -1,2 +0,0 @@
1
- import{jsx as m}from"react/jsx-runtime";import{useContext as T,createContext as V,useCallback as S,useRef as O,useMemo as p,useSyncExternalStore as k,useEffect as D,useInsertionEffect as A,useLayoutEffect as C,memo as N,useState as W}from"react";import{reportDetachedError as w,compatibleAbortReason as K,isClosedReadable as _,isCallTarget as G,isModel as H,isReadable as j,createLifetimeController as J,createCallController as Q,getContributionOwner as U,createState as X,createAggregateError as Y}from"@opetope/core/internal";import{isResourceValue as Z,reportRuntimeFailure as ee,requireContributionModelPlans as te,openContributionModels as ne}from"@opetope/runtime/internal";import{C as u,u as re,b as ie}from"./contribution-isolation-Bzfbt4iM.js";import{CallError as oe}from"@opetope/core";const x=V(void 0);function se(){const t=T(x);if(t===void 0)throw new u("missing","Opetope hook was called outside a contribution mount.");return t}function y(t){return Z(t)}function ae(t,e){return new Promise((n,r)=>{const i=()=>{r(new oe("cancelled","Resource operation wait was cancelled.",K(e)))};if(e.aborted){i();return}e.addEventListener("abort",i,{once:!0}),t.then(o=>{e.removeEventListener("abort",i),n(o)},o=>{e.removeEventListener("abort",i),r(o)})})}function ce(t,e){var r;const n=(r=e==null?void 0:e.onSuccess)==null?void 0:r.call(e,t);return n!==void 0&&Promise.resolve(n).catch(w),t}function v(t){return{run:(e,n)=>{let r;try{r=Promise.resolve(t())}catch(o){return Promise.reject(o)}const i=n==null?void 0:n.signal;return(i===void 0?r:ae(r,i)).then(o=>ce(o,n))}}}const de=Object.freeze({kind:"skipped",reason:"resource-not-ready"});function ue(t){const e=t.pagination;if(!(typeof e!="object"||e===null))return e}function E(t){return{invalidate:v(()=>t.invalidate()),loadNext:v(()=>{const e=t.pagination;return e===void 0?Promise.resolve(de):e.loadNext()}),refresh:v(()=>t.refresh()),retry:v(()=>t.retry())}}function le(t,e){const n=e.get(t);if(n!==void 0)return n;const r=E(t);return e.set(t,r),r}const fe=t=>t;function he(t){let e,n=!1;return()=>{try{return e=t(),n=!0,e}catch(r){if(!n||!_(r))throw r;return e}}}function F(t,e,n){const r=e,i=(n==null?void 0:n.equals)??Object.is,o=S(d=>t.subscribe(d),[t]),c=O(void 0);let s=c.current;s===void 0&&(s={hasValue:!1,value:void 0},c.current=s);const a=p(()=>{let d=!1,h,f;return he(()=>{const b=t.getSnapshot();if(d&&Object.is(h,b))return f;const g=r(b);if(!d){let R=g;return s.hasValue&&i(s.value,g)&&(R=s.value),d=!0,h=b,f=R,f}return i(f,g)?(h=b,f):(h=b,f=g,f)})},[s,i,t,r]),l=k(o,a,a);return D(()=>{s.hasValue=!0,s.value=l},[s,l]),l}function z(t){return F(t,fe)}const M=new WeakMap;function I(t){if(arguments.length>1)throw new u("binding-invalid","Command follows the target it binds; binding options are forbidden.");if(!G(t))throw new u("binding-invalid","Command target is not authentic.");return M.set(t,{target:t}),t}function me(t){const e=M.get(t);if(e===void 0)throw new u("binding-invalid","Command binding is not authentic.");return e}function L(t){return typeof t=="object"&&t!==null&&M.has(t)}const be=Symbol("opetope.model-binding"),pe=Symbol("opetope.model-binding-identity"),ge=Object.freeze({}),$=new WeakMap;function Ce(t,e,n){if(typeof n!="string")throw new u("binding-invalid",`Model ${t.id} accepts only string fields.`);const r=Object.getOwnPropertyDescriptor(e,n);if((r==null?void 0:r.enumerable)!==!0||!("value"in r))throw new u("binding-invalid",`Model ${t.id}.${n} requires an enumerable data binding.`);return r.value}function we(t,e){const n=[],r=Reflect.ownKeys(e);if(r.length===0)throw new u("binding-invalid",`Model ${t.id} requires at least one field.`);for(const i of r){const o=Ce(t,e,i);if(!(j(o)||y(o)||typeof o=="function")){if(!L(o))throw new u("binding-invalid",`Model ${t.id}.${String(i)} requires a Readable, a Resource or a bound command.`);n.push(o)}}return Object.freeze(n)}function q(t,e){if(!H(t))throw new u("binding-invalid","Model declaration is not authentic.");if(typeof e!="object"||e===null)throw new u("binding-invalid",`Model ${t.id} requires a record binding.`);const n=we(t,e),r=Object.freeze({[be]:ge,[pe]:!0});return $.set(r,{commands:n,declaration:t,model:e}),r}function ve(t){const e=$.get(t);if(e===void 0)throw new u("binding-invalid","Model binding is not authentic.");return e}let ye=0;function Me(t){const e=new Map,n=[],r=new Set;for(const i of t){const o=ve(i);if(e.has(o.declaration))throw new u("duplicate",`Model ${o.declaration.id} is bound twice in one mount.`);e.set(o.declaration,o.model);for(const c of o.commands)r.has(c)||(r.add(c),n.push(c))}return{commands:Object.freeze(n),frame:{commandRecords:new Map,models:e}}}class Re{frame;authority;boundCommands;closeTicket=0;isActive;lifetime;onClose;released=!1;rendererAttached=!1;renderListeners=new Set;state="detached";constructor(e,n){const r=++ye;this.lifetime=J({id:`contribution.${String(r)}`}),this.isActive=n==null?void 0:n.isActive,this.onClose=n==null?void 0:n.onClose;const{commands:i,frame:o}=Me(e);this.frame=o,this.boundCommands=i,this.buildCommandRecords(r)}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 u("duplicate","One contribution mount cannot mount twice.");const e=this.createAuthority(this.lifetime.open());try{for(const n of this.frame.commandRecords.values())n.controller.activate()}catch(n){throw this.authority=e,this.state="active",this.close(),n}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 n of this.frame.commandRecords.values())n.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(n=>n.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 n={listener:e};this.renderListeners.add(n);let r=!0;return()=>{r&&(r=!1,this.renderListeners.delete(n))}};buildCommandRecords(e){let n=0;for(const r of this.boundCommands){me(r);const i=Q(r,{captureAuthority:this.captureAuthority,id:`contributionCommand.${String(e)}.${String(++n)}`,scheduling:{policy:"parallel"}});this.frame.commandRecords.set(r,{controller:i,invoker:Object.freeze({run:i.run})})}}captureAuthority=()=>{const e=this.authority;if(this.state!=="active"||e===void 0)throw new u("inactive","Contribution mount is not active.");return e.assertCurrent(),e};createAuthority(e){return Object.freeze({assertCurrent:()=>{if(this.state!=="active")throw new u("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(n){w(n)}}releaseFrame(){this.released||(this.released=!0,this.frame.commandRecords.clear(),this.frame.models.clear(),this.boundCommands=[],this.renderListeners.clear())}}function Se({children:t,mount:e}){const n=k(e.subscribeRenderable,e.getRenderableSnapshot,e.getRenderableSnapshot);return A(()=>{if(e.attach())return()=>e.detach()},[e]),C(()=>{n||e.releaseClosedFrame()},[e,n]),n?m(x,{value:e.frame,children:t}):null}function B(t){const e=t.bundle;t.bundle=void 0,e==null||e.frame.close()}function Oe(t,e){if(typeof e!="object"||e===null)return q(t,e);const n=i=>j(i)||y(i)||typeof i=="function"||L(i),r=Object.entries(e).map(([i,o])=>[i,n(o)?o:I(o)]);return q(t,Object.fromEntries(r))}function ke(t){return U(t)}const Ae={models:[]};function P(t,e){try{t()}catch(n){throw Y([e,n],"Contribution mount setup and cleanup failed.",{cause:e})}throw e}function je(t,e,n,r){const i=X(e),{models:o,reporter:c}=r??Ae;try{const s=te(t.models),a=s.length===0?void 0:ne(s,i,c);return{bundle:a,models:[...o,...(a==null?void 0:a.models)??[],...n],props:i}}catch(s){return P(i.close,s)}}function xe(t,e,n,r){const{bundle:i,models:o,props:c}=je(t,e,n,r);let s=!1;const a=()=>{if(!s){s=!0;try{i==null||i.close().catch(w)}finally{c.close()}}};try{return{frame:new Re(o.map(([d,h])=>Oe(d,h)),{isActive:()=>!s,onClose:a}),isOpen:()=>!s,props:c}}catch(l){return P(a,l)}}function Ee(t,e){return m(t,{...e})}function Fe({bundle:t,contribution:e}){const n=z(t.props),r=p(()=>e.props===void 0?n:e.props(n),[n,e]);return m(Se,{mount:t.frame,children:Ee(e.Component,r)})}function ze({authority:t,contribution:e,fixtures:n,slotProps:r}){const i=p(()=>({authority:t,bundle:void 0,contribution:e,fixtures:n,retired:!1}),[t,e,n]),[o,c]=W(void 0),s=(o==null?void 0:o.owner)===i?o.bundle:void 0,a=O(r);return C(()=>{a.current=r}),A(()=>()=>{i.retired=!0,queueMicrotask(()=>{try{B(i)}catch(l){w(l)}})},[i]),C(()=>{const l=i.bundle??(i.bundle=xe(i.contribution,a.current,i.fixtures??[],i.authority));return c(d=>(d==null?void 0:d.owner)===i?d:{bundle:l,owner:i}),()=>{i.retired&&B(i)}},[i]),C(()=>{(s==null?void 0:s.isOpen())===!0&&s.props.set(r)},[s,r]),s===void 0?null:m(Fe,{bundle:s,contribution:e})}function Ie(t){const{authority:e,contribution:n,contributionId:r,fixtures:i,targetId:o}=t,c=re(),s=p(()=>({authority:e,contribution:n,fixtures:i}),[e,n,i]),a=e==null?void 0:e.feature,l=p(()=>({contribution:r,feature:a,target:o}),[r,a,o]),d=e==null?void 0:e.reporter,h=S(f=>ee(f,d),[d]);return m(ie,{content:c,identity:s,report:h,source:l,children:m(ze,{...t})})}const Le=N(Ie);export{Le as M,z as a,ke as b,E as c,F as d,I as e,y as i,ue as p,le as r,se as u};
2
- //# sourceMappingURL=contribution-frame-QRJSP2W3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contribution-frame-QRJSP2W3.js","sources":["../src/mount-context.tsx","../src/resource-command.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, dressed as the commands a button already knows, 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 { CallError } from '@opetope/core';\nimport type {\n PaginationOutcome,\n PaginationState,\n Resource,\n ResourceOperationOutcome,\n ResourceState,\n} from '@opetope/core';\nimport { compatibleAbortReason, reportDetachedError } from '@opetope/core/internal';\nimport type { CallRunOptions, ResourceKey } from '@opetope/core/internal';\nimport { isResourceValue } from '@opetope/runtime/internal';\n\nimport type { CommandHook } from './command-hook';\nimport type { CommandInvoker } from './command-hook-controller';\n\ntype AnyResource = Resource<never, ResourceKey>;\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/** D356: the nested capability, with `loadNext` as a command and the projected pagination state beside it. */\ntype ResourcePaginationHook<Cursor extends ResourceKey> = Readonly<{\n loadNext: CommandHook<void, 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 * The verbs never reject (a Resource answers an outcome), so the ordinary path of `outcome` is\n * `{ status: 'ok', value }`; `failed` stays in the type because the controller turns a synchronous throw of an\n * invoker into it, and the wrapper below carries its own test that it never throws synchronously.\n */\ntype ResourceHook<Data, Key extends ResourceKey = never> = Readonly<{\n invalidate: CommandHook<void, ResourceOperationOutcome>;\n /** Present when the declaration carried `pagination`; `undefined` for every other Resource. */\n pagination: ResourcePaginationHook<ResourceKey> | undefined;\n refresh: CommandHook<void, ResourceOperationOutcome>;\n retry: CommandHook<void, ResourceOperationOutcome>;\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 invalidate: CommandInvoker;\n loadNext: CommandInvoker;\n refresh: CommandInvoker;\n retry: CommandInvoker;\n}>;\n\n/** The consumer's own cancellation: it ends this wait, and the work the model owns keeps running. */\nfunction awaitCancellable(operation: Promise<unknown>, signal: AbortSignal): Promise<unknown> {\n return new Promise<unknown>((resolve, reject) => {\n const cancel = (): void => {\n reject(new CallError('cancelled', 'Resource operation wait was cancelled.', compatibleAbortReason(signal)));\n };\n\n if (signal.aborted) {\n cancel();\n\n return;\n }\n\n signal.addEventListener('abort', cancel, { once: true });\n operation.then(\n value => {\n signal.removeEventListener('abort', cancel);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', cancel);\n reject(error);\n },\n );\n });\n}\n\nfunction notifySuccess(value: unknown, options: CallRunOptions<unknown> | undefined): unknown {\n const notification = options?.onSuccess?.(value);\n\n // The failure of a consumer's own callback is detached from the operation it watched, as it is for a command.\n if (notification !== undefined) void Promise.resolve(notification).catch(reportDetachedError);\n\n return value;\n}\n\n/**\n * The invariant of this wrapper: it never throws synchronously, so the `failed` outcome a consumer can see is the one\n * the verb itself produced and never one this layer invented in the middle of a render commit.\n */\nfunction operationInvoker(start: () => Promise<unknown>): CommandInvoker {\n return {\n run: (_input: never, options?: CallRunOptions<unknown>): Promise<unknown> => {\n let operation: Promise<unknown>;\n\n try {\n operation = Promise.resolve(start());\n } catch (error) {\n return Promise.reject(error);\n }\n\n const signal = options?.signal;\n\n return (signal === undefined ? operation : awaitCancellable(operation, signal)).then(value =>\n notifySuccess(value, options),\n );\n },\n };\n}\n\n/** A Resource without the capability still answers a `loadNext` invoker, so the hook count never moves. */\nconst unavailablePage: PaginationOutcome<ResourceKey> = Object.freeze({\n kind: 'skipped',\n reason: 'resource-not-ready',\n});\n\nfunction paginationOf(resource: AnyResource): ResourcePaginationHook<ResourceKey> | 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 unknown as ResourcePaginationHook<ResourceKey>;\n}\n\n/**\n * One set per Resource identity. `selectEntry` and `useCommandSlot` 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 invalidate: operationInvoker(() => resource.invalidate()),\n loadNext: operationInvoker(() => {\n const capability = (resource as { readonly pagination?: { loadNext(): Promise<unknown> } }).pagination;\n\n return capability === undefined ? Promise.resolve(unavailablePage) : capability.loadNext();\n }),\n refresh: operationInvoker(() => resource.refresh()),\n retry: operationInvoker(() => 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 { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport type { Readable } from '@opetope/core';\nimport { isClosedReadable } from '@opetope/core/internal';\nimport type { EqualityOption } from '@opetope/core/internal';\n\nconst identity = <Value>(value: Value): Value => value;\n\n/**\n * G13, D362: the owner closed this source — a retirement this render raced, and the consumer could not have\n * prevented it. The reader answers the last value it read, so the tree shows its last frame and unmounts instead of\n * failing in the middle of a render. A source never read has nothing to answer with, and every other refusal of the\n * source is the consumer's to see.\n */\nfunction keepLastOnClose<Selected>(read: () => Selected): () => Selected {\n let last: Selected | undefined;\n let hasLast = false;\n\n return (): Selected => {\n try {\n last = read();\n hasLast = true;\n\n return last;\n } catch (error) {\n if (!hasLast || !isClosedReadable(error)) throw error;\n\n return last as Selected;\n }\n };\n}\n\ninterface SelectionInstance<Selected> {\n hasValue: boolean;\n value: Selected | undefined;\n}\n\n/**\n * D279: the source and the selection are positional; `equals` is the one modifier, spelled as in Core — including\n * D348's rule for where the comparator is read, so a named non-generic one compiles here as it does in `derive`.\n */\nexport function useSelector<Value, Selected>(\n readable: Readable<Value>,\n select: (value: NoInfer<Value>) => Selected,\n options?: { readonly equals?: EqualityOption<NoInfer<Selected>> },\n): Selected {\n const selector = select;\n const isEqual = options?.equals ?? Object.is;\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const instanceRef = useRef<SelectionInstance<Selected> | undefined>(undefined);\n let instance = instanceRef.current;\n\n if (instance === undefined) {\n instance = { hasValue: false, value: undefined };\n instanceRef.current = instance;\n }\n\n const getSelectedSnapshot = useMemo(() => {\n let initialized = false;\n let sourceSnapshot: Value;\n let selectedSnapshot: Selected;\n\n return keepLastOnClose((): Selected => {\n const nextSource = readable.getSnapshot();\n\n if (initialized && Object.is(sourceSnapshot, nextSource)) return selectedSnapshot;\n\n const nextSelected = selector(nextSource);\n\n if (!initialized) {\n let nextSelectedSnapshot = nextSelected;\n\n if (instance.hasValue && isEqual(instance.value, nextSelected)) {\n nextSelectedSnapshot = instance.value as Selected;\n }\n\n initialized = true;\n sourceSnapshot = nextSource;\n selectedSnapshot = nextSelectedSnapshot;\n\n return selectedSnapshot;\n }\n\n if (isEqual(selectedSnapshot, nextSelected)) {\n sourceSnapshot = nextSource;\n\n return selectedSnapshot;\n }\n\n sourceSnapshot = nextSource;\n selectedSnapshot = nextSelected;\n\n return selectedSnapshot;\n });\n }, [instance, isEqual, readable, selector]);\n\n const selected = useSyncExternalStore(subscribe, getSelectedSnapshot, getSelectedSnapshot);\n\n useEffect(() => {\n instance.hasValue = true;\n instance.value = selected;\n }, [instance, selected]);\n\n return selected;\n}\n\nexport function useReadable<Value>(readable: Readable<Value>): Value {\n return useSelector(readable, identity);\n}\n","import type { Call } from '@opetope/core';\nimport { isCallTarget } from '@opetope/core/internal';\n\nimport { ContributionError } from './errors';\n\n/**\n * A bound command is the authentic `Call` 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 = Call<never, unknown>;\ntype CommandBinding<Input, Output> = Call<Input, Output>;\n\ntype CommandOutcome<Output> =\n | { readonly error: unknown; readonly status: 'failed' }\n | { readonly reason: unknown; readonly status: 'cancelled' }\n | { readonly status: 'ok'; readonly value: Output };\n\ntype AnyCommandBinding = CommandBinding<never, unknown>;\n\ntype CommandBindingDefinition<Input, Output> = {\n readonly target: Call<Input, Output>;\n};\n\nconst bindingDefinitions = new WeakMap<CommandBindingIdentity, CommandBindingDefinition<never, unknown>>();\n\nfunction bindCommand<Input, Output>(target: Call<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: Call<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 { Call, ModelOf } from '@opetope/core';\nimport { isModel, isReadable } from '@opetope/core/internal';\nimport type { ModelIdentity, NoExtraKeys } 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 = Call<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 * A model field is either a stable Readable or a bound command. The declaration carries the shape in its type, so\n * binding authority is the runtime check that every published field is one of those two authentic kinds.\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 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 (isReadable(value) || isResource(value) || typeof value === 'function') continue;\n\n if (!isCommandBinding(value)) {\n throw new ContributionError(\n 'binding-invalid',\n `Model ${declaration.id}.${String(field)} requires a Readable, a Resource 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 Actual extends ModelOf<Declaration>>(\n declaration: Declaration,\n model: Actual & NoExtraKeys<Actual, 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 Call 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 Calls, 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, State } from '@opetope/core';\nimport {\n createAggregateError,\n createState,\n getContributionOwner,\n isReadable,\n reportDetachedError,\n} from '@opetope/core/internal';\nimport type { ModelIdentity } 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) || isResource(value) || typeof value === 'function' || 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(props.close, 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 props.close();\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) bundle.props.set(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","awaitCancellable","operation","signal","resolve","reject","cancel","CallError","compatibleAbortReason","error","notifySuccess","options","notification","_a","reportDetachedError","operationInvoker","start","_input","unavailablePage","paginationOf","resource","capability","createResourceInvokers","resourceInvokers","cache","existing","invokers","identity","keepLastOnClose","read","last","hasLast","isClosedReadable","useSelector","readable","select","selector","isEqual","subscribe","useCallback","listener","instanceRef","useRef","instance","getSelectedSnapshot","useMemo","initialized","sourceSnapshot","selectedSnapshot","nextSource","nextSelected","nextSelectedSnapshot","selected","useSyncExternalStore","useEffect","useReadable","bindingDefinitions","bindCommand","target","isCallTarget","getCommandBindingDefinition","binding","definition","isCommandBinding","modelBindingBrand","modelBindingIdentityBrand","modelBindingMarker","registry","modelFieldValue","declaration","model","field","descriptor","snapshotModelFields","commands","fields","isReadable","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","lease","MountProvider","children","mount","renderable","useInsertionEffect","useLayoutEffect","_jsx","closeOwnedMount","owner","bundle","bindFrameModel","passesThrough","key","contributionAuthority","entry","getContributionOwner","unownedContribution","cleanupFailedMount","close","cause","createAggregateError","contributionModels","contribution","slotProps","fixtures","props","createState","granted","reporter","plans","requireContributionModelPlans","openContributionModels","createMountBundle","closed","createComponentElement","Component","MountedContribution","committedProps","componentProps","ContributionMount","committed","setCommitted","useState","committedSlotProps","created","current","IsolatedContributionMount","contributionId","targetId","content","useContributionErrorContent","feature","source","report","reportRuntimeFailure","ContributionIsolation","MemoizedContributionMount","memo"],"mappings":"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"}