@opetope/runtime 0.2.0 → 0.4.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,47 @@
1
1
  # @opetope/runtime
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 23ca271: Let a contained failure name itself (D257).
8
+
9
+ A `ContributionFailure` now carries `contribution`, the published entry `<feature>.<provides key>`, `target`, the slot
10
+ it rendered in, and `feature`, the publishing feature or `undefined` for a test fixture. The error content of
11
+ `ContributionBoundary` receives the three next to the raw error and its retry, so one branch declared above every slot
12
+ can answer by surface without naming every contribution.
13
+
14
+ One application reporter serves every feature, so the diagnostic carries the same identity: the mount reports a
15
+ `ContributionError` whose `cause` is the original error, with the new codes `render-failed` for the contribution and
16
+ `error-content-failed` for error content that failed in its place. The authority of a generation carries the id of its
17
+ feature to make that possible.
18
+
19
+ A contained failure that was a cancellation stays one: the wrapper carries the same brand, so `isCancellation` answers
20
+ as it did before containment.
21
+
22
+ ### Patch Changes
23
+
24
+ - @opetope/core@0.4.0
25
+
26
+ ## 0.3.0
27
+
28
+ ### Minor Changes
29
+
30
+ - b15f15d: Contain a failed contribution in its own mount (D256).
31
+
32
+ A contribution whose render or commit throws no longer reaches the host's error boundary: the mount catches it,
33
+ reports it to the feature that published the contribution, and keeps the other mounts of the target rendering.
34
+ `ContributionBoundary` from `@opetope/react/integration` declares once, above every slot, what a failed mount shows;
35
+ its `error` takes a node or a `({ error, retry }) => ReactNode` callback, and `retry` remounts the contribution so its
36
+ models are created again. Error content that throws is contained by a second boundary instead of escalating.
37
+
38
+ The authority of a generation now carries the reporter of its feature, which is also the reporter the UI models of a
39
+ mount open with, and `renderSlot` accepts a `reporter` so a component test can assert a contained failure.
40
+
41
+ ### Patch Changes
42
+
43
+ - @opetope/core@0.3.0
44
+
3
45
  ## 0.2.0
4
46
 
5
47
  ### Minor Changes
@@ -1,2 +1,2 @@
1
- import{FeatureError as l}from"@opetope/core";import{hasOwn as E,withdrawContributions as K,isCallTarget as k}from"@opetope/core/internal";import{registerFeatureInstanceImports as D,registerFeatureInstanceCallTargets as $}from"./feature-call-authority.js";import{createFeatureContributionPublication as M}from"./feature-contribution.js";import{requireFeatureRecord as V,registerFeatureExportFacade as A}from"./feature-definition-support.js";import{getLazyFeature as L}from"./feature-lazy.js";import{openLazyFeatureInstance as Q}from"./feature-lazy-generation.js";import{materializeFeatureData as U,readInstanceModels as W,registerFeatureDataScope as B}from"./feature-model.js";import{materializeFeatureOwn as H}from"./feature-own-values.js";import{assertRequiredPortValue as J,isOptionalPortRequirement as N,getRequiredPort as X}from"./feature-port.js";import{bindFeatureReadyPorts as Y}from"./feature-port-binding.js";import{assertPlainRecord as F,dataEntries as b}from"./feature-record.js";import{registerModuleActivity as Z}from"./inspection-module-activity.js";import{carryFeatureGenerationFence as _,openFeature as v,notReadyGeneration as ee,quarantinedGeneration as ne}from"./module-generation.js";import{registerModuleRetirementParticipant as R}from"./public-module-instance.js";const re=new Set(["cleanupFailure","imports","reporter","requirements"]);function te(e,n){return{authority:void 0,context:void 0,contributions:n,evaluation:void 0,featureId:e,inactive:!1,inactiveReason:void 0,publication:void 0,retirement:void 0,retirementCause:void 0}}function j(e){return e.inactiveReason??(e.inactiveReason=new l("retired",e.featureId))}function f(e){e.context=void 0,e.evaluation=void 0}function z(e){if(e.inactive=!0,f(e),e.authority=void 0,e.publication===void 0)return;const n=e.publication;K(n),e.publication=void 0}function oe(e,n,t,r){e.context=n,e.evaluation=r,R(t,{fence:()=>z(e)})}function w(e){return e instanceof l&&e.code==="quarantined"&&e.retryCleanup!==void 0}function h(e,n){let t;return ne(e.declarationId,e.failures,n,()=>(t!==void 0||(t=(async()=>{try{await e.retryCleanup()}catch(r){throw w(r)?h(r,n):r}})()),t))}function I(e,n,t){if(e.retirementCause??(e.retirementCause=t),e.retirement!==void 0)return e.retirement;let r=()=>{},o=()=>{};const u=new Promise((i,a)=>{r=a,o=i});e.retirement=u;try{n.close().then(o,i=>{e.retirement===u&&(e.retirement=void 0),r(e.retirementCause!==void 0&&w(i)?h(i,e.retirementCause):i)})}catch(i){e.retirement=void 0,r(e.retirementCause!==void 0&&w(i)?h(i,e.retirementCause):i)}return u}function ie(e){const n=e.context;if(n===void 0)throw new TypeError("Feature contribution context is unavailable.");return{context:n}}function ue(e,n){e.publication=n,e.inactive&&z(e)}async function ce(e,n,t){try{if(e.inactive)throw j(e);const{context:r}=ie(e);if(M(e.contributions,r,o=>{ue(e,o)},e.authority,e.evaluation),f(e),e.inactive)throw j(e);return t}catch(r){f(e);const o=r instanceof l&&r.code==="retired"?r:ee(e.featureId,Object.freeze({cause:r,phase:"open",stage:"prepare"}));throw await I(e,n,o),o}}function ae(e,n){const t=n.ready.then(r=>ce(e,n,r),r=>{throw f(e),r});return{close:()=>I(e,n),instance:n.instance,ready:t}}function S(e,n,t){F(e,t);const r=b(e,t);if(r.length!==n.length||n.some(o=>!E(e,o)))throw new TypeError(`${t} must contain every exact field once.`);return Object.freeze(Object.fromEntries(r))}function pe(e){if(e!==void 0&&e!=="report"&&e!=="quarantine")throw new TypeError('openFeature cleanupFailure must be "report" or "quarantine".')}function fe(e,n,t){var r;for(const o of n.requirementKeys){const u=(r=e.requires)==null?void 0:r[o];J(X(u),t[o],o,N(u))}}function de(e,n,t){F(t,"openFeature options");const r=b(t,"openFeature options");for(const[i]of r)if(!re.has(i))throw new TypeError(`Unknown openFeature option ${i}.`);for(const i of["imports","reporter"])if(!E(t,i))throw new TypeError(`openFeature requires ${i}.`);if(typeof t.reporter!="function")throw new TypeError("openFeature reporter must be a function.");pe(t.cleanupFailure);const o=S(t.imports,n.importKeys,"openFeature imports"),u=S(t.requirements??{},n.requirementKeys,"openFeature requirements");return fe(e,n,u),Object.freeze({...Object.fromEntries(r),imports:o,requirements:u})}function se(e,n){if(k(n))return n;const t=n;if(typeof(t==null?void 0:t.getSnapshot)=="function"&&typeof t.subscribe=="function")return n;throw new TypeError(`Feature export ${e} must be a Call, a Readable or a Resource.`)}function me(e,n,t){const r=t({own:n}),o=r===n?Object.fromEntries(Object.keys(n).map(i=>[i,n[i]])):r;F(o,"defineFeature exports result");const u=Object.freeze(Object.fromEntries(b(o,"defineFeature exports result").map(([i,a])=>[i,se(i,a)])));return A(e,u),u}function le(e,n,t){const r=t.imports;D(e.imports,r);const o=t.requirements;$(o);const u={};return n.importScopeKey!==void 0&&(u[n.importScopeKey]=r),n.requirementScopeKey!==void 0&&(u[n.requirementScopeKey]=o),Object.freeze({imports:r,lowScopes:Object.freeze(u)})}function P(e,n,t,r,o){var q;(q=r==null?void 0:r.changed)==null||q.call(r);const{imports:u,lowScopes:i}=le(e,n,t),a=t.reporter,p=n.contributions.length===0?void 0:te(e.id,n.contributions),d=v(n.module,{...t.cleanupFailure===void 0?{}:{cleanupFailure:t.cleanupFailure},prepare:c=>{r!==void 0&&Z(c,r),n.dataDescriptors.length>0&&U(e.id,n.dataDescriptors,u,c,a,m=>{B(c,m),R(c,{fence:()=>{m.fence();const g=m.drain();return{drain:()=>g,retry:()=>g}}})},r);const O=n.providers.length===0?void 0:{available:new Set,local:new Set},s=H(c,e.own,O),G=me(e.exports,s,n.exports);o==null||o();const x=Object.freeze({imports:u,own:s});p!==void 0&&(oe(p,x,c,x),p.authority=Object.freeze({models:W(c,n.dataDescriptors)}));const C=Object.freeze({exports:G});return Y(C,n.providers,c,s,O,o),C},reporter:a,scopes:i});if(p===void 0)return d;const y=ae(p,d);return _(d,y),y}function T(e,n,t){const r=V(e),o=de(e,r,n);return L(e)===void 0?P(e,r,o,t):Q(e,(u,i)=>P(e,u,o,t,i))}function Fe(e,n){return T(e,n)}const be=Fe;export{be as openFeature,T as openFeatureWithActivity};
1
+ import{FeatureError as l}from"@opetope/core";import{hasOwn as E,withdrawContributions as K,isCallTarget as k}from"@opetope/core/internal";import{registerFeatureInstanceImports as D,registerFeatureInstanceCallTargets as $}from"./feature-call-authority.js";import{createFeatureContributionPublication as M}from"./feature-contribution.js";import{requireFeatureRecord as V,registerFeatureExportFacade as A}from"./feature-definition-support.js";import{getLazyFeature as L}from"./feature-lazy.js";import{openLazyFeatureInstance as Q}from"./feature-lazy-generation.js";import{materializeFeatureData as U,readInstanceModels as W,registerFeatureDataScope as B}from"./feature-model.js";import{materializeFeatureOwn as H}from"./feature-own-values.js";import{assertRequiredPortValue as J,isOptionalPortRequirement as N,getRequiredPort as X}from"./feature-port.js";import{bindFeatureReadyPorts as Y}from"./feature-port-binding.js";import{assertPlainRecord as F,dataEntries as b}from"./feature-record.js";import{registerModuleActivity as Z}from"./inspection-module-activity.js";import{carryFeatureGenerationFence as _,openFeature as v,notReadyGeneration as ee,quarantinedGeneration as ne}from"./module-generation.js";import{registerModuleRetirementParticipant as R}from"./public-module-instance.js";const re=new Set(["cleanupFailure","imports","reporter","requirements"]);function te(e,n){return{authority:void 0,context:void 0,contributions:n,evaluation:void 0,featureId:e,inactive:!1,inactiveReason:void 0,publication:void 0,retirement:void 0,retirementCause:void 0}}function j(e){return e.inactiveReason??(e.inactiveReason=new l("retired",e.featureId))}function f(e){e.context=void 0,e.evaluation=void 0}function z(e){if(e.inactive=!0,f(e),e.authority=void 0,e.publication===void 0)return;const n=e.publication;K(n),e.publication=void 0}function oe(e,n,t,r){e.context=n,e.evaluation=r,R(t,{fence:()=>z(e)})}function w(e){return e instanceof l&&e.code==="quarantined"&&e.retryCleanup!==void 0}function h(e,n){let t;return ne(e.declarationId,e.failures,n,()=>(t!==void 0||(t=(async()=>{try{await e.retryCleanup()}catch(r){throw w(r)?h(r,n):r}})()),t))}function I(e,n,t){if(e.retirementCause??(e.retirementCause=t),e.retirement!==void 0)return e.retirement;let r=()=>{},o=()=>{};const u=new Promise((i,a)=>{r=a,o=i});e.retirement=u;try{n.close().then(o,i=>{e.retirement===u&&(e.retirement=void 0),r(e.retirementCause!==void 0&&w(i)?h(i,e.retirementCause):i)})}catch(i){e.retirement=void 0,r(e.retirementCause!==void 0&&w(i)?h(i,e.retirementCause):i)}return u}function ie(e){const n=e.context;if(n===void 0)throw new TypeError("Feature contribution context is unavailable.");return{context:n}}function ue(e,n){e.publication=n,e.inactive&&z(e)}async function ce(e,n,t){try{if(e.inactive)throw j(e);const{context:r}=ie(e);if(M(e.contributions,r,o=>{ue(e,o)},e.authority,e.evaluation),f(e),e.inactive)throw j(e);return t}catch(r){f(e);const o=r instanceof l&&r.code==="retired"?r:ee(e.featureId,Object.freeze({cause:r,phase:"open",stage:"prepare"}));throw await I(e,n,o),o}}function ae(e,n){const t=n.ready.then(r=>ce(e,n,r),r=>{throw f(e),r});return{close:()=>I(e,n),instance:n.instance,ready:t}}function S(e,n,t){F(e,t);const r=b(e,t);if(r.length!==n.length||n.some(o=>!E(e,o)))throw new TypeError(`${t} must contain every exact field once.`);return Object.freeze(Object.fromEntries(r))}function pe(e){if(e!==void 0&&e!=="report"&&e!=="quarantine")throw new TypeError('openFeature cleanupFailure must be "report" or "quarantine".')}function fe(e,n,t){var r;for(const o of n.requirementKeys){const u=(r=e.requires)==null?void 0:r[o];J(X(u),t[o],o,N(u))}}function de(e,n,t){F(t,"openFeature options");const r=b(t,"openFeature options");for(const[i]of r)if(!re.has(i))throw new TypeError(`Unknown openFeature option ${i}.`);for(const i of["imports","reporter"])if(!E(t,i))throw new TypeError(`openFeature requires ${i}.`);if(typeof t.reporter!="function")throw new TypeError("openFeature reporter must be a function.");pe(t.cleanupFailure);const o=S(t.imports,n.importKeys,"openFeature imports"),u=S(t.requirements??{},n.requirementKeys,"openFeature requirements");return fe(e,n,u),Object.freeze({...Object.fromEntries(r),imports:o,requirements:u})}function se(e,n){if(k(n))return n;const t=n;if(typeof(t==null?void 0:t.getSnapshot)=="function"&&typeof t.subscribe=="function")return n;throw new TypeError(`Feature export ${e} must be a Call, a Readable or a Resource.`)}function me(e,n,t){const r=t({own:n}),o=r===n?Object.fromEntries(Object.keys(n).map(i=>[i,n[i]])):r;F(o,"defineFeature exports result");const u=Object.freeze(Object.fromEntries(b(o,"defineFeature exports result").map(([i,a])=>[i,se(i,a)])));return A(e,u),u}function le(e,n,t){const r=t.imports;D(e.imports,r);const o=t.requirements;$(o);const u={};return n.importScopeKey!==void 0&&(u[n.importScopeKey]=r),n.requirementScopeKey!==void 0&&(u[n.requirementScopeKey]=o),Object.freeze({imports:r,lowScopes:Object.freeze(u)})}function P(e,n,t,r,o){var q;(q=r==null?void 0:r.changed)==null||q.call(r);const{imports:u,lowScopes:i}=le(e,n,t),a=t.reporter,p=n.contributions.length===0?void 0:te(e.id,n.contributions),d=v(n.module,{...t.cleanupFailure===void 0?{}:{cleanupFailure:t.cleanupFailure},prepare:c=>{r!==void 0&&Z(c,r),n.dataDescriptors.length>0&&U(e.id,n.dataDescriptors,u,c,a,m=>{B(c,m),R(c,{fence:()=>{m.fence();const g=m.drain();return{drain:()=>g,retry:()=>g}}})},r);const O=n.providers.length===0?void 0:{available:new Set,local:new Set},s=H(c,e.own,O),G=me(e.exports,s,n.exports);o==null||o();const x=Object.freeze({imports:u,own:s});p!==void 0&&(oe(p,x,c,x),p.authority=Object.freeze({feature:e.id,models:W(c,n.dataDescriptors),reporter:a}));const C=Object.freeze({exports:G});return Y(C,n.providers,c,s,O,o),C},reporter:a,scopes:i});if(p===void 0)return d;const y=ae(p,d);return _(d,y),y}function T(e,n,t){const r=V(e),o=de(e,r,n);return L(e)===void 0?P(e,r,o,t):Q(e,(u,i)=>P(e,u,o,t,i))}function Fe(e,n){return T(e,n)}const be=Fe;export{be as openFeature,T as openFeatureWithActivity};
2
2
  //# sourceMappingURL=feature-generation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-generation.js","sources":["../src/feature-generation.ts"],"sourcesContent":["import { FeatureError } from '@opetope/core';\nimport type { DeclarationId, Readable } from '@opetope/core';\nimport { hasOwn, isCallTarget, withdrawContributions } from '@opetope/core/internal';\nimport type { ContributionPublication } from '@opetope/core/internal';\n\nimport type {\n AnyFeature,\n Feature,\n FeatureExportRecord,\n FeatureExportsOf,\n FeatureImportRecord,\n FeatureImportValues,\n FeatureOwnRecord,\n} from './feature-authoring';\nimport { registerFeatureInstanceCallTargets, registerFeatureInstanceImports } from './feature-call-authority';\nimport type { FeatureContractIdentity } from './feature-contract';\nimport { createFeatureContributionPublication } from './feature-contribution';\nimport type { FeatureContributionDescriptor, FeatureEvaluationValues } from './feature-contribution';\nimport { registerFeatureExportFacade, requireFeatureRecord } from './feature-definition-support';\nimport { getLazyFeature } from './feature-lazy';\nimport { openLazyFeatureInstance } from './feature-lazy-generation';\nimport { materializeFeatureData, readInstanceModels, registerFeatureDataScope } from './feature-model';\nimport { materializeFeatureOwn } from './feature-own-values';\nimport { assertRequiredPortValue, getRequiredPort, isOptionalPortRequirement } from './feature-port';\nimport type { PortRequirementRecord, RequiredPortValues } from './feature-port';\nimport { bindFeatureReadyPorts } from './feature-port-binding';\nimport { assertPlainRecord, dataEntries } from './feature-record';\nimport type { ExactKeys } from './feature-record';\nimport type { FeatureActivityOwner } from './inspection-activity';\nimport { registerModuleActivity } from './inspection-module-activity';\nimport {\n carryFeatureGenerationFence,\n notReadyGeneration,\n openFeature as openLoweredModuleInstance,\n quarantinedGeneration,\n} from './module-generation';\nimport type { FeatureInstance } from './module-generation';\nimport { registerModuleRetirementParticipant } from './public-module';\nimport type { CleanupFailurePolicy, ErrorReporter, ModuleInstanceRef } from './public-module';\n\ninterface FeatureReady<Exports> {\n readonly exports: Exports;\n}\n\ntype FeatureInstanceOf<Id extends string, Exports> = FeatureInstance<Id, FeatureReady<Exports>>;\n\ntype FeatureReadyOf<Definition> = FeatureReady<FeatureExportsOf<Definition>>;\n\ntype FeatureContributionInstanceState = {\n authority: unknown;\n context: unknown | undefined;\n readonly contributions: readonly FeatureContributionDescriptor[];\n evaluation: FeatureEvaluationValues | undefined;\n readonly featureId: DeclarationId;\n inactive: boolean;\n inactiveReason: FeatureError | undefined;\n publication: ContributionPublication | undefined;\n retirement: Promise<void> | undefined;\n retirementCause: FeatureError | undefined;\n};\n\ntype FeatureOpenBaseOptions<Imports> = {\n readonly cleanupFailure?: CleanupFailurePolicy;\n readonly imports: Imports;\n readonly reporter: ErrorReporter;\n};\n\ntype FeatureRequirementOpenOptions<Requirements, Keys extends PropertyKey> = keyof Requirements extends never\n ? { readonly requirements?: ExactKeys<Keys, Requirements> & Requirements }\n : { readonly requirements: ExactKeys<Keys, Requirements> & Requirements };\n\ntype ExactExpectedObjectMember<Actual, Expected> = Expected extends unknown\n ? Actual extends Expected\n ? keyof Actual extends keyof Expected\n ? true\n : never\n : never\n : never;\ntype ExactObjectUnionMember<Actual, Expected> = Actual extends unknown\n ? Actual extends readonly unknown[]\n ? true\n : Actual extends object\n ? true extends ExactExpectedObjectMember<Actual, Expected>\n ? true\n : false\n : true\n : never;\ntype NoExtraObjectKeys<Actual, Expected> = false extends ExactObjectUnionMember<Actual, Expected> ? never : unknown;\ntype ExactImportValueFields<Actual, Expected> = {\n readonly [Key in keyof Expected]: Key extends keyof Actual ? NoExtraObjectKeys<Actual[Key], Expected[Key]> : never;\n};\n\nconst featureOpenOptionKeys = new Set(['cleanupFailure', 'imports', 'reporter', 'requirements']);\n\nfunction createFeatureContributionInstanceState(\n featureId: DeclarationId,\n contributions: readonly FeatureContributionDescriptor[],\n): FeatureContributionInstanceState {\n return {\n authority: undefined,\n context: undefined,\n contributions,\n evaluation: undefined,\n featureId,\n inactive: false,\n inactiveReason: undefined,\n publication: undefined,\n retirement: undefined,\n retirementCause: undefined,\n };\n}\n\nfunction featureContributionInactiveReason(state: FeatureContributionInstanceState): FeatureError {\n return (state.inactiveReason ??= new FeatureError('retired', state.featureId));\n}\n\nfunction releaseFeatureContributionPreparation(state: FeatureContributionInstanceState): void {\n state.context = undefined;\n state.evaluation = undefined;\n}\n\nfunction fenceFeatureContributions(state: FeatureContributionInstanceState): void {\n state.inactive = true;\n releaseFeatureContributionPreparation(state);\n // The authority a mount was granted dies with the fence: keeping it alive would outlive the models it names.\n state.authority = undefined;\n\n if (state.publication === undefined) return;\n\n const publication = state.publication;\n withdrawContributions(publication);\n state.publication = undefined;\n}\n\nfunction prepareFeatureContributions(\n state: FeatureContributionInstanceState,\n context: unknown,\n instance: ModuleInstanceRef,\n evaluation: FeatureEvaluationValues,\n): void {\n state.context = context;\n state.evaluation = evaluation;\n registerModuleRetirementParticipant(instance, { fence: () => fenceFeatureContributions(state) });\n}\n\nfunction isQuarantinedGeneration(\n error: unknown,\n): error is FeatureError & { readonly retryCleanup: () => Promise<void> } {\n return error instanceof FeatureError && error.code === 'quarantined' && error.retryCleanup !== undefined;\n}\n\nfunction preserveFeatureContributionQuarantine(\n error: FeatureError & { readonly retryCleanup: () => Promise<void> },\n cause: FeatureError,\n): FeatureError {\n let recovery: Promise<void> | undefined;\n\n return quarantinedGeneration(error.declarationId, error.failures, cause, () => {\n if (recovery !== undefined) return recovery;\n\n recovery = (async () => {\n try {\n await error.retryCleanup();\n } catch (nextError) {\n if (isQuarantinedGeneration(nextError)) {\n throw preserveFeatureContributionQuarantine(nextError, cause);\n }\n\n throw nextError;\n }\n })();\n\n return recovery;\n });\n}\n\nfunction retireFeatureContributionGeneration(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, unknown>,\n cause?: FeatureError,\n): Promise<void> {\n state.retirementCause ??= cause;\n\n if (state.retirement !== undefined) return state.retirement;\n\n let rejectRetirement: (cause: unknown) => void = () => undefined;\n let resolveRetirement: () => void = () => undefined;\n const pending = new Promise<void>((resolve, reject) => {\n rejectRetirement = reject;\n resolveRetirement = resolve;\n });\n state.retirement = pending;\n\n try {\n void generation.close().then(resolveRetirement, (retirementCause: unknown) => {\n if (state.retirement === pending) state.retirement = undefined;\n\n rejectRetirement(\n state.retirementCause !== undefined && isQuarantinedGeneration(retirementCause)\n ? preserveFeatureContributionQuarantine(retirementCause, state.retirementCause)\n : retirementCause,\n );\n });\n } catch (retirementCause) {\n state.retirement = undefined;\n rejectRetirement(\n state.retirementCause !== undefined && isQuarantinedGeneration(retirementCause)\n ? preserveFeatureContributionQuarantine(retirementCause, state.retirementCause)\n : retirementCause,\n );\n }\n\n return pending;\n}\n\nfunction requireFeatureContributionPreparation(state: FeatureContributionInstanceState): {\n readonly context: unknown;\n} {\n const context = state.context;\n\n if (context === undefined) throw new TypeError('Feature contribution context is unavailable.');\n\n return { context };\n}\n\nfunction captureFeatureContributionPublication(\n state: FeatureContributionInstanceState,\n publication: ContributionPublication,\n): void {\n state.publication = publication;\n\n if (state.inactive) fenceFeatureContributions(state);\n}\n\nasync function publishFeatureContributions<Prepared>(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, unknown>,\n value: Prepared,\n): Promise<Prepared> {\n try {\n if (state.inactive) throw featureContributionInactiveReason(state);\n\n const { context } = requireFeatureContributionPreparation(state);\n createFeatureContributionPublication(\n state.contributions,\n context,\n publication => {\n captureFeatureContributionPublication(state, publication);\n },\n state.authority,\n state.evaluation,\n );\n releaseFeatureContributionPreparation(state);\n\n if (state.inactive) throw featureContributionInactiveReason(state);\n\n return value;\n } catch (cause) {\n releaseFeatureContributionPreparation(state);\n const failure =\n cause instanceof FeatureError && cause.code === 'retired'\n ? cause\n : notReadyGeneration(\n state.featureId,\n Object.freeze({ cause, phase: 'open' as const, stage: 'prepare' as const }),\n );\n await retireFeatureContributionGeneration(state, generation, failure);\n\n throw failure;\n }\n}\n\nfunction wrapFeatureContributionInstance<Prepared>(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, Prepared>,\n): FeatureInstance<string, Prepared> {\n const ready = generation.ready.then(\n value => publishFeatureContributions(state, generation, value),\n (cause: unknown) => {\n releaseFeatureContributionPreparation(state);\n\n throw cause;\n },\n );\n\n return {\n close: () => retireFeatureContributionGeneration(state, generation),\n instance: generation.instance,\n ready,\n };\n}\n\nfunction snapshotExactValues(\n value: unknown,\n expectedKeys: readonly string[],\n label: string,\n): Readonly<Record<string, unknown>> {\n assertPlainRecord(value, label);\n const entries = dataEntries(value, label);\n\n if (entries.length !== expectedKeys.length || expectedKeys.some(key => !hasOwn(value, key))) {\n throw new TypeError(`${label} must contain every exact field once.`);\n }\n\n return Object.freeze(Object.fromEntries(entries));\n}\n\nfunction checkFeatureCleanupPolicy(value: unknown): void {\n if (value !== undefined && value !== 'report' && value !== 'quarantine') {\n throw new TypeError('openFeature cleanupFailure must be \"report\" or \"quarantine\".');\n }\n}\n\n/** Validates every declared requirement binding; an `optional` port may be left unbound (D105). */\nfunction checkFeatureRequirements(\n feature: AnyFeature,\n record: ReturnType<typeof requireFeatureRecord>,\n requirements: Readonly<Record<string, unknown>>,\n): void {\n for (const key of record.requirementKeys) {\n const requirement = feature.requires?.[key];\n assertRequiredPortValue(\n getRequiredPort(requirement),\n requirements[key],\n key,\n isOptionalPortRequirement(requirement),\n );\n }\n}\n\nfunction snapshotFeatureOpenOptions(\n feature: Feature,\n record: ReturnType<typeof requireFeatureRecord>,\n value: unknown,\n): Readonly<Record<string, unknown>> {\n assertPlainRecord(value, 'openFeature options');\n const entries = dataEntries(value, 'openFeature options');\n\n for (const [key] of entries) {\n if (!featureOpenOptionKeys.has(key)) throw new TypeError(`Unknown openFeature option ${key}.`);\n }\n\n for (const key of ['imports', 'reporter'] as const) {\n if (!hasOwn(value, key)) throw new TypeError(`openFeature requires ${key}.`);\n }\n\n if (typeof value['reporter'] !== 'function') {\n throw new TypeError('openFeature reporter must be a function.');\n }\n\n checkFeatureCleanupPolicy(value['cleanupFailure']);\n const imports = snapshotExactValues(value['imports'], record.importKeys, 'openFeature imports');\n const requirements = snapshotExactValues(\n value['requirements'] ?? {},\n record.requirementKeys,\n 'openFeature requirements',\n );\n\n checkFeatureRequirements(feature, record, requirements);\n\n return Object.freeze({ ...Object.fromEntries(entries), imports, requirements });\n}\n\n/** The export record carries live values, so a ref, a model record or a plain object never reaches an importer. */\nfunction requireFeatureExportValue(key: string, value: unknown): unknown {\n if (isCallTarget(value)) return value;\n\n const readable = value as Partial<Readable<unknown>>;\n\n if (typeof readable?.getSnapshot === 'function' && typeof readable.subscribe === 'function') return value;\n\n throw new TypeError(`Feature export ${key} must be a Call, a Readable or a Resource.`);\n}\n\nfunction buildFeatureExports(\n contract: FeatureContractIdentity,\n values: Readonly<Record<string, unknown>>,\n select: (context: { readonly own: Readonly<Record<string, unknown>> }) => unknown,\n): Readonly<Record<string, unknown>> {\n const returned = select({ own: values });\n // `({ own }) => own` means «export every value this instance owns» (D127). The record the runtime handed to the\n // author reads its members lazily, so returning it materializes every one of them here, before the shared check\n // that a result carries data fields. A member with no value form still fails on its Own key, where D88 belongs.\n const selected =\n returned === values ? Object.fromEntries(Object.keys(values).map(key => [key, values[key]])) : returned;\n\n assertPlainRecord(selected, 'defineFeature exports result');\n const facade = Object.freeze(\n Object.fromEntries(\n dataEntries(selected, 'defineFeature exports result').map(([key, value]) => [\n key,\n requireFeatureExportValue(key, value),\n ]),\n ),\n );\n\n registerFeatureExportFacade(contract, facade);\n\n return facade;\n}\n\nfunction prepareFeatureInstanceInputs(\n feature: AnyFeature,\n record: ReturnType<typeof requireFeatureRecord>,\n snapshot: Readonly<Record<string, unknown>>,\n): Readonly<{ imports: Readonly<Record<string, unknown>>; lowScopes: Readonly<Record<string, unknown>> }> {\n const imports = snapshot['imports'] as Readonly<Record<string, unknown>>;\n registerFeatureInstanceImports(feature.imports, imports);\n const requirementCalls = snapshot['requirements'] as Readonly<Record<string, unknown>>;\n registerFeatureInstanceCallTargets(requirementCalls);\n const lowScopes: Record<string, unknown> = {};\n\n if (record.importScopeKey !== undefined) lowScopes[record.importScopeKey] = imports;\n\n if (record.requirementScopeKey !== undefined) lowScopes[record.requirementScopeKey] = requirementCalls;\n\n return Object.freeze({ imports, lowScopes: Object.freeze(lowScopes) });\n}\n\nfunction openMaterializedFeature(\n feature: Feature,\n record: ReturnType<typeof requireFeatureRecord>,\n snapshot: Readonly<Record<string, unknown>>,\n activity?: FeatureActivityOwner,\n assertCurrent?: () => void,\n): FeatureInstanceOf<string, unknown> {\n activity?.changed?.();\n const { imports, lowScopes } = prepareFeatureInstanceInputs(feature, record, snapshot);\n const reporter = snapshot['reporter'] as ErrorReporter;\n const contributionState =\n record.contributions.length === 0\n ? undefined\n : createFeatureContributionInstanceState(feature.id, record.contributions);\n\n const openGeneration = openLoweredModuleInstance as unknown as (\n definition: object,\n generationOptions: unknown,\n ) => FeatureInstance<string, FeatureReady<unknown>>;\n const generation = openGeneration(record.module, {\n ...(snapshot['cleanupFailure'] === undefined\n ? {}\n : { cleanupFailure: snapshot['cleanupFailure'] as CleanupFailurePolicy }),\n prepare: (instance: ModuleInstanceRef) => {\n if (activity !== undefined) registerModuleActivity(instance, activity);\n\n if (record.dataDescriptors.length > 0) {\n materializeFeatureData(\n feature.id,\n record.dataDescriptors,\n imports,\n instance,\n reporter,\n data => {\n registerFeatureDataScope(instance, data);\n registerModuleRetirementParticipant(instance, {\n fence: () => {\n data.fence();\n const drain = data.drain();\n\n return { drain: () => drain, retry: () => drain };\n },\n });\n },\n activity,\n );\n }\n\n // D255: exports, contribution factories and reactive calculations share this instance's live values.\n const portTargets =\n record.providers.length === 0 ? undefined : { available: new Set<object>(), local: new Set<object>() };\n const ownValues = materializeFeatureOwn(instance, feature.own, portTargets);\n const bound = buildFeatureExports(feature.exports, ownValues, record.exports);\n assertCurrent?.();\n const context = Object.freeze({\n imports,\n own: ownValues,\n });\n\n if (contributionState !== undefined) {\n prepareFeatureContributions(contributionState, context, instance, context);\n contributionState.authority = Object.freeze({\n models: readInstanceModels(instance, record.dataDescriptors),\n });\n }\n\n const ready = Object.freeze({ exports: bound });\n bindFeatureReadyPorts(ready, record.providers, instance, ownValues, portTargets, assertCurrent);\n\n return ready;\n },\n reporter,\n scopes: lowScopes,\n });\n\n if (contributionState === undefined) return generation;\n\n const wrapped = wrapFeatureContributionInstance(contributionState, generation);\n carryFeatureGenerationFence(generation, wrapped);\n\n return wrapped;\n}\n\nfunction openFeatureWithActivity(\n feature: Feature,\n options: unknown,\n activity?: FeatureActivityOwner,\n): FeatureInstanceOf<string, unknown> {\n const record = requireFeatureRecord(feature);\n // D211: exact bindings and base options belong to the call, never to the eventual body-load completion.\n const snapshot = snapshotFeatureOpenOptions(feature, record, options);\n\n return getLazyFeature(feature) === undefined\n ? openMaterializedFeature(feature, record, snapshot, activity)\n : openLazyFeatureInstance(feature, (body, assertCurrent) =>\n openMaterializedFeature(feature, body, snapshot, activity, assertCurrent),\n );\n}\n\nfunction openFeatureInstance<\n const Id extends string,\n const Imports extends FeatureImportRecord,\n const Runtime extends FeatureOwnRecord<Id>,\n const Exports extends FeatureExportRecord,\n const Requires extends PortRequirementRecord,\n const ImportValues extends FeatureImportValues<Imports>,\n const OptionKeys extends PropertyKey,\n const ImportKeys extends PropertyKey,\n const RequirementKeys extends PropertyKey,\n>(\n feature: Feature<Id, Imports, Runtime, Exports, Requires>,\n options: ExactKeys<\n OptionKeys,\n FeatureOpenBaseOptions<FeatureImportValues<Imports>> &\n FeatureRequirementOpenOptions<RequiredPortValues<Requires>, keyof RequiredPortValues<Requires>>\n > &\n FeatureOpenBaseOptions<ImportValues> &\n FeatureRequirementOpenOptions<RequiredPortValues<Requires>, RequirementKeys> & {\n readonly imports: ExactImportValueFields<ImportValues, FeatureImportValues<Imports>> &\n ExactKeys<ImportKeys, FeatureImportValues<Imports>>;\n },\n): FeatureInstanceOf<Id, Exports>;\nfunction openFeatureInstance(feature: Feature, options: unknown): FeatureInstanceOf<string, unknown> {\n return openFeatureWithActivity(feature, options);\n}\n\nconst openFeature = openFeatureInstance;\n\nexport { openFeature, openFeatureWithActivity };\nexport type { FeatureReady, FeatureReadyOf };\n"],"names":["featureOpenOptionKeys","createFeatureContributionInstanceState","featureId","contributions","featureContributionInactiveReason","state","FeatureError","releaseFeatureContributionPreparation","fenceFeatureContributions","publication","withdrawContributions","prepareFeatureContributions","context","instance","evaluation","registerModuleRetirementParticipant","isQuarantinedGeneration","error","preserveFeatureContributionQuarantine","cause","recovery","quarantinedGeneration","nextError","retireFeatureContributionGeneration","generation","rejectRetirement","resolveRetirement","pending","resolve","reject","retirementCause","requireFeatureContributionPreparation","captureFeatureContributionPublication","publishFeatureContributions","value","createFeatureContributionPublication","failure","notReadyGeneration","wrapFeatureContributionInstance","ready","snapshotExactValues","expectedKeys","label","assertPlainRecord","entries","dataEntries","key","hasOwn","checkFeatureCleanupPolicy","checkFeatureRequirements","feature","record","requirements","requirement","_a","assertRequiredPortValue","getRequiredPort","isOptionalPortRequirement","snapshotFeatureOpenOptions","imports","requireFeatureExportValue","isCallTarget","readable","buildFeatureExports","contract","values","select","returned","selected","facade","registerFeatureExportFacade","prepareFeatureInstanceInputs","snapshot","registerFeatureInstanceImports","requirementCalls","registerFeatureInstanceCallTargets","lowScopes","openMaterializedFeature","activity","assertCurrent","reporter","contributionState","openLoweredModuleInstance","registerModuleActivity","materializeFeatureData","data","registerFeatureDataScope","drain","portTargets","ownValues","materializeFeatureOwn","bound","readInstanceModels","bindFeatureReadyPorts","wrapped","carryFeatureGenerationFence","openFeatureWithActivity","options","requireFeatureRecord","getLazyFeature","openLazyFeatureInstance","body","openFeatureInstance","openFeature"],"mappings":"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"}
1
+ {"version":3,"file":"feature-generation.js","sources":["../src/feature-generation.ts"],"sourcesContent":["import { FeatureError } from '@opetope/core';\nimport type { DeclarationId, Readable } from '@opetope/core';\nimport { hasOwn, isCallTarget, withdrawContributions } from '@opetope/core/internal';\nimport type { ContributionPublication } from '@opetope/core/internal';\n\nimport type {\n AnyFeature,\n Feature,\n FeatureExportRecord,\n FeatureExportsOf,\n FeatureImportRecord,\n FeatureImportValues,\n FeatureOwnRecord,\n} from './feature-authoring';\nimport { registerFeatureInstanceCallTargets, registerFeatureInstanceImports } from './feature-call-authority';\nimport type { FeatureContractIdentity } from './feature-contract';\nimport { createFeatureContributionPublication } from './feature-contribution';\nimport type { FeatureContributionDescriptor, FeatureEvaluationValues } from './feature-contribution';\nimport { registerFeatureExportFacade, requireFeatureRecord } from './feature-definition-support';\nimport { getLazyFeature } from './feature-lazy';\nimport { openLazyFeatureInstance } from './feature-lazy-generation';\nimport { materializeFeatureData, readInstanceModels, registerFeatureDataScope } from './feature-model';\nimport { materializeFeatureOwn } from './feature-own-values';\nimport { assertRequiredPortValue, getRequiredPort, isOptionalPortRequirement } from './feature-port';\nimport type { PortRequirementRecord, RequiredPortValues } from './feature-port';\nimport { bindFeatureReadyPorts } from './feature-port-binding';\nimport { assertPlainRecord, dataEntries } from './feature-record';\nimport type { ExactKeys } from './feature-record';\nimport type { FeatureActivityOwner } from './inspection-activity';\nimport { registerModuleActivity } from './inspection-module-activity';\nimport {\n carryFeatureGenerationFence,\n notReadyGeneration,\n openFeature as openLoweredModuleInstance,\n quarantinedGeneration,\n} from './module-generation';\nimport type { FeatureInstance } from './module-generation';\nimport { registerModuleRetirementParticipant } from './public-module';\nimport type { CleanupFailurePolicy, ErrorReporter, ModuleInstanceRef } from './public-module';\n\ninterface FeatureReady<Exports> {\n readonly exports: Exports;\n}\n\ntype FeatureInstanceOf<Id extends string, Exports> = FeatureInstance<Id, FeatureReady<Exports>>;\n\ntype FeatureReadyOf<Definition> = FeatureReady<FeatureExportsOf<Definition>>;\n\ntype FeatureContributionInstanceState = {\n authority: unknown;\n context: unknown | undefined;\n readonly contributions: readonly FeatureContributionDescriptor[];\n evaluation: FeatureEvaluationValues | undefined;\n readonly featureId: DeclarationId;\n inactive: boolean;\n inactiveReason: FeatureError | undefined;\n publication: ContributionPublication | undefined;\n retirement: Promise<void> | undefined;\n retirementCause: FeatureError | undefined;\n};\n\ntype FeatureOpenBaseOptions<Imports> = {\n readonly cleanupFailure?: CleanupFailurePolicy;\n readonly imports: Imports;\n readonly reporter: ErrorReporter;\n};\n\ntype FeatureRequirementOpenOptions<Requirements, Keys extends PropertyKey> = keyof Requirements extends never\n ? { readonly requirements?: ExactKeys<Keys, Requirements> & Requirements }\n : { readonly requirements: ExactKeys<Keys, Requirements> & Requirements };\n\ntype ExactExpectedObjectMember<Actual, Expected> = Expected extends unknown\n ? Actual extends Expected\n ? keyof Actual extends keyof Expected\n ? true\n : never\n : never\n : never;\ntype ExactObjectUnionMember<Actual, Expected> = Actual extends unknown\n ? Actual extends readonly unknown[]\n ? true\n : Actual extends object\n ? true extends ExactExpectedObjectMember<Actual, Expected>\n ? true\n : false\n : true\n : never;\ntype NoExtraObjectKeys<Actual, Expected> = false extends ExactObjectUnionMember<Actual, Expected> ? never : unknown;\ntype ExactImportValueFields<Actual, Expected> = {\n readonly [Key in keyof Expected]: Key extends keyof Actual ? NoExtraObjectKeys<Actual[Key], Expected[Key]> : never;\n};\n\nconst featureOpenOptionKeys = new Set(['cleanupFailure', 'imports', 'reporter', 'requirements']);\n\nfunction createFeatureContributionInstanceState(\n featureId: DeclarationId,\n contributions: readonly FeatureContributionDescriptor[],\n): FeatureContributionInstanceState {\n return {\n authority: undefined,\n context: undefined,\n contributions,\n evaluation: undefined,\n featureId,\n inactive: false,\n inactiveReason: undefined,\n publication: undefined,\n retirement: undefined,\n retirementCause: undefined,\n };\n}\n\nfunction featureContributionInactiveReason(state: FeatureContributionInstanceState): FeatureError {\n return (state.inactiveReason ??= new FeatureError('retired', state.featureId));\n}\n\nfunction releaseFeatureContributionPreparation(state: FeatureContributionInstanceState): void {\n state.context = undefined;\n state.evaluation = undefined;\n}\n\nfunction fenceFeatureContributions(state: FeatureContributionInstanceState): void {\n state.inactive = true;\n releaseFeatureContributionPreparation(state);\n // The authority a mount was granted dies with the fence: keeping it alive would outlive the models it names.\n state.authority = undefined;\n\n if (state.publication === undefined) return;\n\n const publication = state.publication;\n withdrawContributions(publication);\n state.publication = undefined;\n}\n\nfunction prepareFeatureContributions(\n state: FeatureContributionInstanceState,\n context: unknown,\n instance: ModuleInstanceRef,\n evaluation: FeatureEvaluationValues,\n): void {\n state.context = context;\n state.evaluation = evaluation;\n registerModuleRetirementParticipant(instance, { fence: () => fenceFeatureContributions(state) });\n}\n\nfunction isQuarantinedGeneration(\n error: unknown,\n): error is FeatureError & { readonly retryCleanup: () => Promise<void> } {\n return error instanceof FeatureError && error.code === 'quarantined' && error.retryCleanup !== undefined;\n}\n\nfunction preserveFeatureContributionQuarantine(\n error: FeatureError & { readonly retryCleanup: () => Promise<void> },\n cause: FeatureError,\n): FeatureError {\n let recovery: Promise<void> | undefined;\n\n return quarantinedGeneration(error.declarationId, error.failures, cause, () => {\n if (recovery !== undefined) return recovery;\n\n recovery = (async () => {\n try {\n await error.retryCleanup();\n } catch (nextError) {\n if (isQuarantinedGeneration(nextError)) {\n throw preserveFeatureContributionQuarantine(nextError, cause);\n }\n\n throw nextError;\n }\n })();\n\n return recovery;\n });\n}\n\nfunction retireFeatureContributionGeneration(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, unknown>,\n cause?: FeatureError,\n): Promise<void> {\n state.retirementCause ??= cause;\n\n if (state.retirement !== undefined) return state.retirement;\n\n let rejectRetirement: (cause: unknown) => void = () => undefined;\n let resolveRetirement: () => void = () => undefined;\n const pending = new Promise<void>((resolve, reject) => {\n rejectRetirement = reject;\n resolveRetirement = resolve;\n });\n state.retirement = pending;\n\n try {\n void generation.close().then(resolveRetirement, (retirementCause: unknown) => {\n if (state.retirement === pending) state.retirement = undefined;\n\n rejectRetirement(\n state.retirementCause !== undefined && isQuarantinedGeneration(retirementCause)\n ? preserveFeatureContributionQuarantine(retirementCause, state.retirementCause)\n : retirementCause,\n );\n });\n } catch (retirementCause) {\n state.retirement = undefined;\n rejectRetirement(\n state.retirementCause !== undefined && isQuarantinedGeneration(retirementCause)\n ? preserveFeatureContributionQuarantine(retirementCause, state.retirementCause)\n : retirementCause,\n );\n }\n\n return pending;\n}\n\nfunction requireFeatureContributionPreparation(state: FeatureContributionInstanceState): {\n readonly context: unknown;\n} {\n const context = state.context;\n\n if (context === undefined) throw new TypeError('Feature contribution context is unavailable.');\n\n return { context };\n}\n\nfunction captureFeatureContributionPublication(\n state: FeatureContributionInstanceState,\n publication: ContributionPublication,\n): void {\n state.publication = publication;\n\n if (state.inactive) fenceFeatureContributions(state);\n}\n\nasync function publishFeatureContributions<Prepared>(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, unknown>,\n value: Prepared,\n): Promise<Prepared> {\n try {\n if (state.inactive) throw featureContributionInactiveReason(state);\n\n const { context } = requireFeatureContributionPreparation(state);\n createFeatureContributionPublication(\n state.contributions,\n context,\n publication => {\n captureFeatureContributionPublication(state, publication);\n },\n state.authority,\n state.evaluation,\n );\n releaseFeatureContributionPreparation(state);\n\n if (state.inactive) throw featureContributionInactiveReason(state);\n\n return value;\n } catch (cause) {\n releaseFeatureContributionPreparation(state);\n const failure =\n cause instanceof FeatureError && cause.code === 'retired'\n ? cause\n : notReadyGeneration(\n state.featureId,\n Object.freeze({ cause, phase: 'open' as const, stage: 'prepare' as const }),\n );\n await retireFeatureContributionGeneration(state, generation, failure);\n\n throw failure;\n }\n}\n\nfunction wrapFeatureContributionInstance<Prepared>(\n state: FeatureContributionInstanceState,\n generation: FeatureInstance<string, Prepared>,\n): FeatureInstance<string, Prepared> {\n const ready = generation.ready.then(\n value => publishFeatureContributions(state, generation, value),\n (cause: unknown) => {\n releaseFeatureContributionPreparation(state);\n\n throw cause;\n },\n );\n\n return {\n close: () => retireFeatureContributionGeneration(state, generation),\n instance: generation.instance,\n ready,\n };\n}\n\nfunction snapshotExactValues(\n value: unknown,\n expectedKeys: readonly string[],\n label: string,\n): Readonly<Record<string, unknown>> {\n assertPlainRecord(value, label);\n const entries = dataEntries(value, label);\n\n if (entries.length !== expectedKeys.length || expectedKeys.some(key => !hasOwn(value, key))) {\n throw new TypeError(`${label} must contain every exact field once.`);\n }\n\n return Object.freeze(Object.fromEntries(entries));\n}\n\nfunction checkFeatureCleanupPolicy(value: unknown): void {\n if (value !== undefined && value !== 'report' && value !== 'quarantine') {\n throw new TypeError('openFeature cleanupFailure must be \"report\" or \"quarantine\".');\n }\n}\n\n/** Validates every declared requirement binding; an `optional` port may be left unbound (D105). */\nfunction checkFeatureRequirements(\n feature: AnyFeature,\n record: ReturnType<typeof requireFeatureRecord>,\n requirements: Readonly<Record<string, unknown>>,\n): void {\n for (const key of record.requirementKeys) {\n const requirement = feature.requires?.[key];\n assertRequiredPortValue(\n getRequiredPort(requirement),\n requirements[key],\n key,\n isOptionalPortRequirement(requirement),\n );\n }\n}\n\nfunction snapshotFeatureOpenOptions(\n feature: Feature,\n record: ReturnType<typeof requireFeatureRecord>,\n value: unknown,\n): Readonly<Record<string, unknown>> {\n assertPlainRecord(value, 'openFeature options');\n const entries = dataEntries(value, 'openFeature options');\n\n for (const [key] of entries) {\n if (!featureOpenOptionKeys.has(key)) throw new TypeError(`Unknown openFeature option ${key}.`);\n }\n\n for (const key of ['imports', 'reporter'] as const) {\n if (!hasOwn(value, key)) throw new TypeError(`openFeature requires ${key}.`);\n }\n\n if (typeof value['reporter'] !== 'function') {\n throw new TypeError('openFeature reporter must be a function.');\n }\n\n checkFeatureCleanupPolicy(value['cleanupFailure']);\n const imports = snapshotExactValues(value['imports'], record.importKeys, 'openFeature imports');\n const requirements = snapshotExactValues(\n value['requirements'] ?? {},\n record.requirementKeys,\n 'openFeature requirements',\n );\n\n checkFeatureRequirements(feature, record, requirements);\n\n return Object.freeze({ ...Object.fromEntries(entries), imports, requirements });\n}\n\n/** The export record carries live values, so a ref, a model record or a plain object never reaches an importer. */\nfunction requireFeatureExportValue(key: string, value: unknown): unknown {\n if (isCallTarget(value)) return value;\n\n const readable = value as Partial<Readable<unknown>>;\n\n if (typeof readable?.getSnapshot === 'function' && typeof readable.subscribe === 'function') return value;\n\n throw new TypeError(`Feature export ${key} must be a Call, a Readable or a Resource.`);\n}\n\nfunction buildFeatureExports(\n contract: FeatureContractIdentity,\n values: Readonly<Record<string, unknown>>,\n select: (context: { readonly own: Readonly<Record<string, unknown>> }) => unknown,\n): Readonly<Record<string, unknown>> {\n const returned = select({ own: values });\n // `({ own }) => own` means «export every value this instance owns» (D127). The record the runtime handed to the\n // author reads its members lazily, so returning it materializes every one of them here, before the shared check\n // that a result carries data fields. A member with no value form still fails on its Own key, where D88 belongs.\n const selected =\n returned === values ? Object.fromEntries(Object.keys(values).map(key => [key, values[key]])) : returned;\n\n assertPlainRecord(selected, 'defineFeature exports result');\n const facade = Object.freeze(\n Object.fromEntries(\n dataEntries(selected, 'defineFeature exports result').map(([key, value]) => [\n key,\n requireFeatureExportValue(key, value),\n ]),\n ),\n );\n\n registerFeatureExportFacade(contract, facade);\n\n return facade;\n}\n\nfunction prepareFeatureInstanceInputs(\n feature: AnyFeature,\n record: ReturnType<typeof requireFeatureRecord>,\n snapshot: Readonly<Record<string, unknown>>,\n): Readonly<{ imports: Readonly<Record<string, unknown>>; lowScopes: Readonly<Record<string, unknown>> }> {\n const imports = snapshot['imports'] as Readonly<Record<string, unknown>>;\n registerFeatureInstanceImports(feature.imports, imports);\n const requirementCalls = snapshot['requirements'] as Readonly<Record<string, unknown>>;\n registerFeatureInstanceCallTargets(requirementCalls);\n const lowScopes: Record<string, unknown> = {};\n\n if (record.importScopeKey !== undefined) lowScopes[record.importScopeKey] = imports;\n\n if (record.requirementScopeKey !== undefined) lowScopes[record.requirementScopeKey] = requirementCalls;\n\n return Object.freeze({ imports, lowScopes: Object.freeze(lowScopes) });\n}\n\nfunction openMaterializedFeature(\n feature: Feature,\n record: ReturnType<typeof requireFeatureRecord>,\n snapshot: Readonly<Record<string, unknown>>,\n activity?: FeatureActivityOwner,\n assertCurrent?: () => void,\n): FeatureInstanceOf<string, unknown> {\n activity?.changed?.();\n const { imports, lowScopes } = prepareFeatureInstanceInputs(feature, record, snapshot);\n const reporter = snapshot['reporter'] as ErrorReporter;\n const contributionState =\n record.contributions.length === 0\n ? undefined\n : createFeatureContributionInstanceState(feature.id, record.contributions);\n\n const openGeneration = openLoweredModuleInstance as unknown as (\n definition: object,\n generationOptions: unknown,\n ) => FeatureInstance<string, FeatureReady<unknown>>;\n const generation = openGeneration(record.module, {\n ...(snapshot['cleanupFailure'] === undefined\n ? {}\n : { cleanupFailure: snapshot['cleanupFailure'] as CleanupFailurePolicy }),\n prepare: (instance: ModuleInstanceRef) => {\n if (activity !== undefined) registerModuleActivity(instance, activity);\n\n if (record.dataDescriptors.length > 0) {\n materializeFeatureData(\n feature.id,\n record.dataDescriptors,\n imports,\n instance,\n reporter,\n data => {\n registerFeatureDataScope(instance, data);\n registerModuleRetirementParticipant(instance, {\n fence: () => {\n data.fence();\n const drain = data.drain();\n\n return { drain: () => drain, retry: () => drain };\n },\n });\n },\n activity,\n );\n }\n\n // D255: exports, contribution factories and reactive calculations share this instance's live values.\n const portTargets =\n record.providers.length === 0 ? undefined : { available: new Set<object>(), local: new Set<object>() };\n const ownValues = materializeFeatureOwn(instance, feature.own, portTargets);\n const bound = buildFeatureExports(feature.exports, ownValues, record.exports);\n assertCurrent?.();\n const context = Object.freeze({\n imports,\n own: ownValues,\n });\n\n if (contributionState !== undefined) {\n prepareFeatureContributions(contributionState, context, instance, context);\n contributionState.authority = Object.freeze({\n feature: feature.id,\n models: readInstanceModels(instance, record.dataDescriptors),\n reporter,\n });\n }\n\n const ready = Object.freeze({ exports: bound });\n bindFeatureReadyPorts(ready, record.providers, instance, ownValues, portTargets, assertCurrent);\n\n return ready;\n },\n reporter,\n scopes: lowScopes,\n });\n\n if (contributionState === undefined) return generation;\n\n const wrapped = wrapFeatureContributionInstance(contributionState, generation);\n carryFeatureGenerationFence(generation, wrapped);\n\n return wrapped;\n}\n\nfunction openFeatureWithActivity(\n feature: Feature,\n options: unknown,\n activity?: FeatureActivityOwner,\n): FeatureInstanceOf<string, unknown> {\n const record = requireFeatureRecord(feature);\n // D211: exact bindings and base options belong to the call, never to the eventual body-load completion.\n const snapshot = snapshotFeatureOpenOptions(feature, record, options);\n\n return getLazyFeature(feature) === undefined\n ? openMaterializedFeature(feature, record, snapshot, activity)\n : openLazyFeatureInstance(feature, (body, assertCurrent) =>\n openMaterializedFeature(feature, body, snapshot, activity, assertCurrent),\n );\n}\n\nfunction openFeatureInstance<\n const Id extends string,\n const Imports extends FeatureImportRecord,\n const Runtime extends FeatureOwnRecord<Id>,\n const Exports extends FeatureExportRecord,\n const Requires extends PortRequirementRecord,\n const ImportValues extends FeatureImportValues<Imports>,\n const OptionKeys extends PropertyKey,\n const ImportKeys extends PropertyKey,\n const RequirementKeys extends PropertyKey,\n>(\n feature: Feature<Id, Imports, Runtime, Exports, Requires>,\n options: ExactKeys<\n OptionKeys,\n FeatureOpenBaseOptions<FeatureImportValues<Imports>> &\n FeatureRequirementOpenOptions<RequiredPortValues<Requires>, keyof RequiredPortValues<Requires>>\n > &\n FeatureOpenBaseOptions<ImportValues> &\n FeatureRequirementOpenOptions<RequiredPortValues<Requires>, RequirementKeys> & {\n readonly imports: ExactImportValueFields<ImportValues, FeatureImportValues<Imports>> &\n ExactKeys<ImportKeys, FeatureImportValues<Imports>>;\n },\n): FeatureInstanceOf<Id, Exports>;\nfunction openFeatureInstance(feature: Feature, options: unknown): FeatureInstanceOf<string, unknown> {\n return openFeatureWithActivity(feature, options);\n}\n\nconst openFeature = openFeatureInstance;\n\nexport { openFeature, openFeatureWithActivity };\nexport type { FeatureReady, FeatureReadyOf };\n"],"names":["featureOpenOptionKeys","createFeatureContributionInstanceState","featureId","contributions","featureContributionInactiveReason","state","FeatureError","releaseFeatureContributionPreparation","fenceFeatureContributions","publication","withdrawContributions","prepareFeatureContributions","context","instance","evaluation","registerModuleRetirementParticipant","isQuarantinedGeneration","error","preserveFeatureContributionQuarantine","cause","recovery","quarantinedGeneration","nextError","retireFeatureContributionGeneration","generation","rejectRetirement","resolveRetirement","pending","resolve","reject","retirementCause","requireFeatureContributionPreparation","captureFeatureContributionPublication","publishFeatureContributions","value","createFeatureContributionPublication","failure","notReadyGeneration","wrapFeatureContributionInstance","ready","snapshotExactValues","expectedKeys","label","assertPlainRecord","entries","dataEntries","key","hasOwn","checkFeatureCleanupPolicy","checkFeatureRequirements","feature","record","requirements","requirement","_a","assertRequiredPortValue","getRequiredPort","isOptionalPortRequirement","snapshotFeatureOpenOptions","imports","requireFeatureExportValue","isCallTarget","readable","buildFeatureExports","contract","values","select","returned","selected","facade","registerFeatureExportFacade","prepareFeatureInstanceInputs","snapshot","registerFeatureInstanceImports","requirementCalls","registerFeatureInstanceCallTargets","lowScopes","openMaterializedFeature","activity","assertCurrent","reporter","contributionState","openLoweredModuleInstance","registerModuleActivity","materializeFeatureData","data","registerFeatureDataScope","drain","portTargets","ownValues","materializeFeatureOwn","bound","readInstanceModels","bindFeatureReadyPorts","wrapped","carryFeatureGenerationFence","openFeatureWithActivity","options","requireFeatureRecord","getLazyFeature","openLazyFeatureInstance","body","openFeatureInstance","openFeature"],"mappings":"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"}
@@ -17,6 +17,8 @@ export type { FeatureContract } from './feature-contract.js';
17
17
  export type { ImportDemand, OnDemandHostContract, OptionalHostContract } from './feature-contract.js';
18
18
  export type { FeatureContributionDescriptor } from './feature-contribution.js';
19
19
  export { openContributionModels, requireContributionModelPlans } from './feature-contribution-model.js';
20
+ export { reportRuntimeFailure } from './runtime-error-reporting.js';
21
+ export type { RuntimeErrorReporter } from './runtime-error-reporting.js';
20
22
  export type { ContributionModel, ContributionModelBundle, ContributionModelPlan } from './feature-contribution-model.js';
21
23
  export type { FeatureReady, FeatureReadyOf } from './feature-generation.js';
22
24
  export { bindFeatureResource } from './feature-materialization-binding.js';
package/dist/internal.js CHANGED
@@ -1,2 +1,2 @@
1
- import{getApplicationPlan as t}from"./application-definition.js";import{createAttachmentCall as n}from"./attachment-call-declaration.js";import{createScopeBlueprint as i}from"./attachment-declaration.js";import{compileModuleTemplate as p,createLocalAttachmentModuleDefinition as m}from"./compile-module-template.js";import{createControlSession as d}from"./control-registry.js";import{openContributionModels as u,requireContributionModelPlans as x}from"./feature-contribution-model.js";import{bindFeatureResource as M}from"./feature-materialization-binding.js";import{createInspectionSession as C}from"./inspection-registry.js";import{createInstanceDemandCoordinator as O}from"./instance-demand.js";import{instantiateModuleTemplate as b}from"./module-instance.js";import{bindAttachmentLifecycleToOwner as T,createOwnerGenerationSlot as g,openOwnerBoundAttachment as B,retryOwnerBoundAttachmentClose as D}from"./owner-generation.js";import{acknowledgeOwnerGenerationDrain as y,retireOwnedGeneration as I}from"./owner-generation-retirement.js";import{defineModule as P}from"./public-module-definition.js";import{instantiateModule as q,resolveModuleCallTarget as v}from"./public-module-instance.js";export{y as acknowledgeOwnerGenerationDrain,T as bindAttachmentLifecycleToOwner,M as bindFeatureResource,p as compileModuleTemplate,n as createAttachmentCall,d as createControlSession,C as createInspectionSession,O as createInstanceDemandCoordinator,m as createLocalAttachmentModuleDefinition,g as createOwnerGenerationSlot,i as createScopeBlueprint,P as defineModule,t as getApplicationPlan,q as instantiateModule,b as instantiateModuleTemplate,u as openContributionModels,B as openOwnerBoundAttachment,x as requireContributionModelPlans,v as resolveModuleCallTarget,I as retireOwnedGeneration,D as retryOwnerBoundAttachmentClose};
1
+ import{getApplicationPlan as t}from"./application-definition.js";import{createAttachmentCall as n}from"./attachment-call-declaration.js";import{createScopeBlueprint as i}from"./attachment-declaration.js";import{compileModuleTemplate as p,createLocalAttachmentModuleDefinition as m}from"./compile-module-template.js";import{createControlSession as f}from"./control-registry.js";import{openContributionModels as u,requireContributionModelPlans as x}from"./feature-contribution-model.js";import{reportRuntimeFailure as M}from"./runtime-error-reporting.js";import{bindFeatureResource as C}from"./feature-materialization-binding.js";import{createInspectionSession as O}from"./inspection-registry.js";import{createInstanceDemandCoordinator as b}from"./instance-demand.js";import{instantiateModuleTemplate as T}from"./module-instance.js";import{bindAttachmentLifecycleToOwner as B,createOwnerGenerationSlot as D,openOwnerBoundAttachment as G,retryOwnerBoundAttachmentClose as y}from"./owner-generation.js";import{acknowledgeOwnerGenerationDrain as I,retireOwnedGeneration as L}from"./owner-generation-retirement.js";import{defineModule as R}from"./public-module-definition.js";import{instantiateModule as q,resolveModuleCallTarget as v}from"./public-module-instance.js";export{I as acknowledgeOwnerGenerationDrain,B as bindAttachmentLifecycleToOwner,C as bindFeatureResource,p as compileModuleTemplate,n as createAttachmentCall,f as createControlSession,O as createInspectionSession,b as createInstanceDemandCoordinator,m as createLocalAttachmentModuleDefinition,D as createOwnerGenerationSlot,i as createScopeBlueprint,R as defineModule,t as getApplicationPlan,q as instantiateModule,T as instantiateModuleTemplate,u as openContributionModels,G as openOwnerBoundAttachment,M as reportRuntimeFailure,x as requireContributionModelPlans,v as resolveModuleCallTarget,L as retireOwnedGeneration,y as retryOwnerBoundAttachmentClose};
2
2
  //# sourceMappingURL=internal.js.map
package/docs/decisions.md CHANGED
@@ -1449,3 +1449,29 @@ Schema 11 заменяет смешанный метод [D252](#d252): для
1449
1449
  Все вложенные контексты получают тот же материализованный `own`, что секция `exports`: модель становится записью её полей, call ref — готовым `Call`, resource/stream ref — ресурсом. Регистрация вызова внутренней модели не требует публикации этого вызова в `exports`. Модели создаются до селекторов портов и фабрик вкладов, каждый экземпляр получает свои значения, запись раскрывает поля лениво, а lifetime вызовов и read-only проекция owned state сохраняются. Builder секции `own` по-прежнему использует refs импортов и требований для декларации зависимостей.
1450
1450
 
1451
1451
  Миграция меняет исходный код: внешний `own` убирается из `provides`, выбор вызова переносится во второй аргумент `port` как `({ own }) => own.call` или `({ own }) => own.model.call`. Вместо `context.exports.submit` выбирается исходный `context.own.order.submit`; только внутренние экспорты удаляются из секции `exports`. Вместо `context.instance` с внутренним bind/resolve выбирается готовое значение из `own`. План модели монтирования объявляется через `model` фабрики `slot`. Уточняет [D88](#d88), [D169](#d169), [D220](#d220), [D223](#d223).
1452
+
1453
+ <a id="d256"></a>
1454
+
1455
+ ## D256 — сбой вклада изолируется его монтированием
1456
+
1457
+ Рендер и коммит одного вклада больше не могут остановить приложение. `ContributionMount` сам является границей ошибок: сбой компонента, отказ `useModel` в невыданной модели, сбой чтения источника и сбой создания UI-моделей этого монтирования перехватываются на нём и не доходят до границы хоста. Соседние вклады того же слота и остальное дерево продолжают рендериться. Изоляция безусловна: она не зависит от того, объявил ли хост содержимое ошибки.
1458
+
1459
+ Содержимое сбоя объявляется один раз на всё приложение. `ContributionBoundary` из `@opetope/react/integration` принимает `error` — узел или колбэк `({ error, retry }) => ReactNode` той же формы, что ветвь `error` у `FeatureBoundary` ([D142](#d142), [D177](#d177), [D198](#d198)): хост пишет один вид содержимого ошибки для обоих. Без провайдера упавший вклад рендерит пустоту. `retry` пересобирает монтирование целиком — поддерево монтируется заново, поэтому модели вклада создаются заново, а не переиспользуют сборку, которая упала.
1460
+
1461
+ Содержимое живёт в стабильной ссылке, а не в значении контекста: хост пишет ветвь ошибки инлайном, и контекст, меняющийся вместе с ней, перерисовывал бы каждое монтирование приложения и отменял бы memo на монтирование ([D130](#d130)). Монтирование читает ссылку один раз, уже упав, поэтому переписанное во время сбоя содержимое попадает в следующий сбой ([D214](#d214)). Сбой опознаётся флагом, а не наличием `error`: чужой код вправе бросить `undefined`, и sentinel принял бы это за исправное монтирование.
1462
+
1463
+ Содержимое ошибки — тоже чужой код. Оно рендерится во второй, вложенной границе, которая существует только после сбоя: если само содержимое падает, монтирование сообщает и этот сбой, после чего рендерит пустоту. Поэтому провайдер, доступный в одном месте дерева и недоступный в другом, не превращает изоляцию обратно в эскалацию. Успешный путь платит одну границу на монтирование, а не две.
1464
+
1465
+ Диагностика принадлежит владельцу: authority поколения ([D70](#d70)) несёт reporter фичи, опубликовавшей вклад, туда уходит перехваченный сбой, и по тому же адресу впервые открываются UI-модели монтирования. Без authority сбой остаётся detached, как и любая другая ничья ошибка. Тестовый `renderSlot` принимает `reporter` ради тех же диагностик: у компонентного теста нет фичи, которой можно сообщить. Уточняет [D70](#d70), [D85](#d85), [D188](#d188).
1466
+
1467
+ <a id="d257"></a>
1468
+
1469
+ ## D257 — изолированный сбой называет себя
1470
+
1471
+ `ContributionFailure` несёт идентичность вместе с ошибкой: `contribution` — id опубликованной записи вида `<фича>.<ключ provides>`, `target` — слот, в котором вклад рендерился, `feature` — id опубликовавшей его фичи или `undefined` у тестовой фикстуры. Одна корневая ветвь ошибки отвечает по поверхности, не перечисляя вклады поимённо: слой уведомлений молчит, поверхность страницы показывает карточку. Все три известны только монтированию: id записи и цель знает `Slot`, фичу несёт authority поколения ([D70](#d70)).
1472
+
1473
+ Диагностика получает то же самое, но обёрткой. `RuntimeErrorReporter` принимает один аргумент, а один reporter приложения обслуживает все фичи, поэтому голый `TypeError` в нём не назвал бы ни поверхность, ни владельца. Монтирование сообщает `ContributionError` с теми же тремя полями и исходной ошибкой в `cause`; расширять сам reporter до `(error, context)` значило бы менять контракт всех отчётов core и runtime ради одного случая. Показу обёртка не нужна: содержимое ошибки по-прежнему получает `error` сырым.
1474
+
1475
+ Отмена остаётся отменой: если изолированный сбой помечен как cancellation ([D69](#d69)), обёртка несёт тот же бренд. Метка живёт на экземпляре, а обёртка создаёт новый объект, поэтому без явной перестановки `isCancellation` перестал бы быть одной проверкой для каждого субъекта. Саму проверку безопасный вход пока не отдаёт, так что свойство держит внутренние проверки фреймворка; фильтр отмен в reporter хоста потребовал бы отдельного решения о публикации `isCancellation`.
1476
+
1477
+ Кодов два, а не один: `render-failed` говорит, что сломан вклад, `error-content-failed` — что сломано содержимое ошибки самого хоста. Это разные сигналы, и второй заслуживает более громкой реакции, чем первый; различать их по тексту сообщения хост не должен. Уточняет [D256](#d256), [D69](#d69).
@@ -410,6 +410,16 @@ component's own props, `models` supplies the per-mount UI models, and a componen
410
410
  declares which of them it needs, which is checked by the type at the contribution boundary. A mount is memoized:
411
411
  publishing or withdrawing one contribution does not re-render the other mounts of the same target (D130).
412
412
 
413
+ Above every mount sits its own error boundary, so a contribution that throws stops there instead of reaching the host
414
+ (D256). It wraps `ContributionMount` rather than the component inside it, which is why it also contains the commit that
415
+ builds the mount — a model factory that throws is the same kind of failure as a component that throws — and why a retry
416
+ remounts the subtree and creates those models again. The failure goes to the reporter carried by the authority of the
417
+ publishing generation, the same reporter the mount's UI models now open with, wrapped in a `ContributionError` that
418
+ names the entry, its feature and its slot and keeps the original on `cause` (D257). What the mount shows in its place is the
419
+ `error` content of the nearest `ContributionBoundary`, and nothing when a host declared none. That content renders in a
420
+ second boundary, created only after a failure, so error content that throws is contained as well and the successful path
421
+ still costs one boundary per mount.
422
+
413
423
  ### 6.3 `fold` on a pipe
414
424
 
415
425
  `fold(value, meta, read?)` walks the handlers in order; with a reader passed inside a `computed`, the dependencies
@@ -398,6 +398,16 @@ disposer и слив очереди.
398
398
  `requiresModels([...])` объявляет, какие из них ему нужны, и это сверяется типом на границе вклада. Монтирование мемоизировано: публикация или
399
399
  снятие одного вклада не перерисовывает остальные монтирования той же цели (D130).
400
400
 
401
+ Над каждым монтированием стоит его собственная граница ошибок, поэтому упавший вклад останавливается на ней и не
402
+ доходит до хоста (D256). Она оборачивает `ContributionMount`, а не компонент внутри него: поэтому она держит и коммит,
403
+ который собирает монтирование — упавшая фабрика модели это тот же сбой, что упавший компонент, — и поэтому retry
404
+ перемонтирует поддерево и создаёт эти модели заново. Сбой уходит в reporter, который несёт authority публикующего
405
+ поколения; с этим же reporter теперь открываются UI-модели монтирования. В отчёт он едет `ContributionError`, который
406
+ называет запись, её фичу и её слот и держит исходную ошибку в `cause` (D257). Вместо вклада показывается содержимое `error`
407
+ ближайшего `ContributionBoundary`, а если хост его не объявил — ничего. Это содержимое рендерится во второй границе,
408
+ создаваемой только после сбоя, поэтому упавшее содержимое ошибки тоже изолировано, а успешный путь по-прежнему стоит
409
+ одну границу на монтирование.
410
+
401
411
  ### 6.3 `fold` у pipe
402
412
 
403
413
  `fold(value, meta, read?)` проходит обработчики по порядку; с переданным читателем внутри `computed` в зависимости
package/docs/spec.md CHANGED
@@ -295,7 +295,9 @@ takes them as they are, with a `props` adapter it takes exactly the adapter's re
295
295
 
296
296
  `opetope/require-declared-models` in `@opetope/lint` recommended checks visible `slot` contributions and model readers within one module. It resolves import aliases and local bindings, including named components. Imported implementations and dynamic declarations remain unverified; no diagnostic does not prove their requirements complete (D250).
297
297
 
298
- Hooks: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Components: `Slot`, and
298
+ Every mount is a boundary: a contribution whose render or commit throws is contained there, reported to the feature
299
+ that published it, and replaced by the content of `ContributionBoundary` — one host policy declared once, above every
300
+ slot, never per slot (D256). Hooks: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Components: `Slot`, and
299
301
  `FeatureBoundary` from `@opetope/react/integration`, which holds a feature open and renders `fallback` or `error`.
300
302
  Both of its branches take either a node or a render callback: `children` receives the ready instance typed by the
301
303
  demand, `error` receives `{ error, retry }`, only the branch that is shown runs, and a ready consumer needs no
@@ -391,6 +393,17 @@ the call site. The optional `cleanupFailure` carries the same two words as `open
391
393
  the host that chooses `quarantine` keeps a failed cleanup as an exact frontier on `FeatureError.retryCleanup`
392
394
  instead of a report, and the devtools control port can then retry it (D182).
393
395
 
396
+ The root of the application is where a host states what a failed contribution shows, once for every mount below it:
397
+
398
+ ```tsx
399
+ // bootstrap/root.tsx — the application root, above every slot
400
+ const Root = () => (
401
+ <ContributionBoundary error={({ error, retry }) => <FailedContribution error={error} onRetry={retry} />}>
402
+ <Slot target={applicationSurface} />
403
+ </ContributionBoundary>
404
+ );
405
+ ```
406
+
394
407
  ---
395
408
 
396
409
  ## 3. Public vocabulary
@@ -605,6 +618,21 @@ Each law is proved by package tests and survives renaming.
605
618
  thrown away — the second StrictMode copy, an abandoned concurrent pass, a subtree suspended by a lazy sibling —
606
619
  creates nothing at all, so nothing has to be swept, adopted or rebuilt, and a child layout effect never sees a
607
620
  closed mount.
621
+ - **Contained contributions.** A mount is the boundary of the contribution it renders (D256). A render or commit that
622
+ throws — the component, an ungranted model, a source read, the creation of the mount's own UI models — stops there,
623
+ is reported to the feature that published the contribution, and never reaches the boundary of the host. The other
624
+ mounts of the same target and the rest of the tree keep rendering. Containment is unconditional; what a failed mount
625
+ shows is the `error` content of `ContributionBoundary`, declared once above every slot, and nothing at all without
626
+ it; that content lives in a stable ref, so an inline error branch re-renders no mount and content replaced
627
+ mid-failure lands on the next one. Its `retry` remounts the subtree, so the models of the contribution are created again rather than reused. Error
628
+ content is foreign code too: a failure inside it is contained by a second boundary that exists only after the first
629
+ failure, reported the same way, and leaves the mount empty instead of escalating.
630
+ - **Named failures.** A contained failure names itself (D257): `contribution` is the published entry,
631
+ `<feature>.<provides key>`, `target` is the slot it rendered in, and `feature` is the publishing feature or
632
+ `undefined` for a test fixture. The error content receives the three next to the raw `error` and its `retry`; the
633
+ reporter receives them on a `ContributionError` whose `cause` is that same error, with code `render-failed` for the
634
+ contribution and `error-content-failed` for error content that failed in its place. A contained failure that was a
635
+ cancellation stays one: the wrapper carries the same brand, so `isCancellation` answers as it did before.
608
636
  - **Structural readers.** `Readable`, `Resource` and the demand source are structural contracts, so a host may
609
637
  implement one with an object whose members are methods. Every hook calls them through the object it was handed and
610
638
  never as a detached function, so an adapter may rely on its own `this` (D199).
package/docs/spec.ru.md CHANGED
@@ -295,7 +295,9 @@ per-mount UI-модель обязан объявить тот компонен
295
295
 
296
296
  `opetope/require-declared-models` из recommended `@opetope/lint` проверяет видимые `slot`-вклады и readers моделей в пределах одного модуля. Он учитывает aliases импортов и локальные bindings, включая именованные компоненты. Импортированные реализации и динамические объявления остаются непроверенными; отсутствие диагностики не доказывает полноту их требований (D250).
297
297
 
298
- Хуки: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Компоненты: `Slot` и `FeatureBoundary`
298
+ Каждое монтирование граница: сбой рендера или коммита вклада остаётся на нём, уходит в reporter опубликовавшей его
299
+ фичи и заменяется содержимым `ContributionBoundary` — одной политикой хоста, объявленной один раз над всеми слотами,
300
+ а не на каждый слот (D256). Хуки: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Компоненты: `Slot` и `FeatureBoundary`
299
301
  из `@opetope/react/integration`, который держит фичу открытой и показывает `fallback` или `error`. Обе ветви
300
302
  принимают либо узел, либо render-колбэк: `children` получает готовый экземпляр, типизированный по `demand`, `error`
301
303
  получает `{ error, retry }`, выполняется только показанная ветвь, и готовому потребителю не нужны второй
@@ -391,6 +393,17 @@ devtools-плане, отдельного объявления группы не
391
393
  провалившуюся уборку точным фронтиром на `FeatureError.retryCleanup` вместо отчёта, и порт управления devtools может
392
394
  её повторить (D182).
393
395
 
396
+ Корень приложения — место, где хост один раз говорит, что показывает упавший вклад, для всех монтирований ниже:
397
+
398
+ ```tsx
399
+ // bootstrap/root.tsx — корень приложения, выше любого слота
400
+ const Root = () => (
401
+ <ContributionBoundary error={({ error, retry }) => <FailedContribution error={error} onRetry={retry} />}>
402
+ <Slot target={applicationSurface} />
403
+ </ContributionBoundary>
404
+ );
405
+ ```
406
+
394
407
  ---
395
408
 
396
409
  ## 3. Публичный словарь
@@ -613,6 +626,21 @@ builder `own`. `fold` дескриптора `pipe` получает треть
613
626
  рендер — вторая копия StrictMode, прерванный concurrent-проход, поддерево, приостановленное ленивым соседом, — не
614
627
  создаёт ничего, поэтому подметать, усыновлять и пересобирать нечего, а layout-эффект ребёнка никогда не видит
615
628
  закрытое монтирование.
629
+ - **Изолированные вклады.** Монтирование — граница того вклада, который оно рендерит (D256). Сбой рендера или
630
+ коммита — компонента, невыданной модели, чтения источника, создания собственных UI-моделей монтирования —
631
+ останавливается на нём, уходит в reporter опубликовавшей вклад фичи и не доходит до границы хоста. Остальные
632
+ монтирования той же цели и остальное дерево продолжают рендериться. Изоляция безусловна; что показывает упавшее
633
+ монтирование, задаёт содержимое `error` у `ContributionBoundary`, объявленного один раз над всеми слотами, и без
634
+ него — ничего; содержимое живёт в стабильной ссылке, поэтому инлайновая ветвь ошибки не перерисовывает
635
+ монтирования, а переписанное во время сбоя содержимое попадает в следующий сбой. Его `retry` перемонтирует поддерево, поэтому модели вклада создаются заново, а не переиспользуются.
636
+ Содержимое ошибки — тоже чужой код: его сбой держит вторая граница, существующая только после первого сбоя, он
637
+ сообщается тем же путём и оставляет монтирование пустым вместо эскалации.
638
+ - **Названный сбой.** Изолированный сбой называет себя (D257): `contribution` — опубликованная запись вида
639
+ `<фича>.<ключ provides>`, `target` — слот, в котором она рендерилась, `feature` — опубликовавшая фича или
640
+ `undefined` у тестовой фикстуры. Содержимое ошибки получает эти три рядом с сырым `error` и его `retry`, а
641
+ reporter — на `ContributionError`, у которого в `cause` та же ошибка, с кодом `render-failed` для вклада и
642
+ `error-content-failed` для содержимого ошибки, упавшего вместо него. Изолированный сбой, бывший отменой, остаётся
643
+ отменой: обёртка несёт тот же бренд, поэтому `isCancellation` отвечает как прежде.
616
644
  - **Структурные читатели.** `Readable`, `Resource` и источник спроса это структурные контракты, поэтому хост вправе
617
645
  реализовать их объектом, члены которого — методы. Любой хук зовёт их через тот объект, который ему передали, и
618
646
  никогда как оторванную функцию, поэтому адаптер вправе опираться на свой `this` (D199).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/runtime",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },
@@ -52,10 +52,10 @@
52
52
  }
53
53
  ],
54
54
  "devDependencies": {
55
- "@opetope/core": "0.2.0"
55
+ "@opetope/core": "0.4.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@opetope/core": "0.2.0"
58
+ "@opetope/core": "0.4.0"
59
59
  },
60
60
  "sideEffects": false,
61
61
  "description": "Feature composition and owned lifecycles for Opetope.",