@opetope/runtime 0.1.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 +5 -0
- package/LICENSE +21 -0
- package/README.md +345 -0
- package/README.ru.md +344 -0
- package/dist/application-compiler-edges.d.ts +3 -0
- package/dist/application-compiler-edges.js +2 -0
- package/dist/application-compiler-edges.js.map +1 -0
- package/dist/application-compiler-graph.d.ts +8 -0
- package/dist/application-compiler-graph.js +2 -0
- package/dist/application-compiler-graph.js.map +1 -0
- package/dist/application-compiler.d.ts +116 -0
- package/dist/application-compiler.js +2 -0
- package/dist/application-compiler.js.map +1 -0
- package/dist/application-conditions.d.ts +18 -0
- package/dist/application-conditions.js +2 -0
- package/dist/application-conditions.js.map +1 -0
- package/dist/application-definition.d.ts +30 -0
- package/dist/application-definition.js +2 -0
- package/dist/application-definition.js.map +1 -0
- package/dist/application-error.d.ts +10 -0
- package/dist/application-error.js +2 -0
- package/dist/application-error.js.map +1 -0
- package/dist/application-execution.d.ts +43 -0
- package/dist/application-execution.js +2 -0
- package/dist/application-execution.js.map +1 -0
- package/dist/application-feature-bindings.d.ts +11 -0
- package/dist/application-feature-bindings.js +2 -0
- package/dist/application-feature-bindings.js.map +1 -0
- package/dist/application-feature-instance.d.ts +5 -0
- package/dist/application-feature-instance.js +2 -0
- package/dist/application-feature-instance.js.map +1 -0
- package/dist/application-group-order.d.ts +29 -0
- package/dist/application-group-order.js +2 -0
- package/dist/application-group-order.js.map +1 -0
- package/dist/application-instance-retirement.d.ts +36 -0
- package/dist/application-instance-retirement.js +2 -0
- package/dist/application-instance-retirement.js.map +1 -0
- package/dist/application-open-options.d.ts +29 -0
- package/dist/application-open-options.js +2 -0
- package/dist/application-open-options.js.map +1 -0
- package/dist/application-port-compiler.d.ts +28 -0
- package/dist/application-port-compiler.js +2 -0
- package/dist/application-port-compiler.js.map +1 -0
- package/dist/attachment-call-declaration.d.ts +43 -0
- package/dist/attachment-call-declaration.js +2 -0
- package/dist/attachment-call-declaration.js.map +1 -0
- package/dist/attachment-declaration.d.ts +68 -0
- package/dist/attachment-declaration.js +2 -0
- package/dist/attachment-declaration.js.map +1 -0
- package/dist/attachment-execution.d.ts +9 -0
- package/dist/attachment-execution.js +2 -0
- package/dist/attachment-execution.js.map +1 -0
- package/dist/attachment-retirement-scheduler.d.ts +14 -0
- package/dist/attachment-retirement-scheduler.js +2 -0
- package/dist/attachment-retirement-scheduler.js.map +1 -0
- package/dist/call-option-snapshot.d.ts +20 -0
- package/dist/call-option-snapshot.js +2 -0
- package/dist/call-option-snapshot.js.map +1 -0
- package/dist/compile-call-target-bindings.d.ts +14 -0
- package/dist/compile-call-target-bindings.js +2 -0
- package/dist/compile-call-target-bindings.js.map +1 -0
- package/dist/compile-module-template.d.ts +38 -0
- package/dist/compile-module-template.js +2 -0
- package/dist/compile-module-template.js.map +1 -0
- package/dist/condition-group-execution.d.ts +44 -0
- package/dist/condition-group-execution.js +2 -0
- package/dist/condition-group-execution.js.map +1 -0
- package/dist/condition-override.d.ts +28 -0
- package/dist/condition-override.js +2 -0
- package/dist/condition-override.js.map +1 -0
- package/dist/condition-source.d.ts +10 -0
- package/dist/condition-source.js +2 -0
- package/dist/condition-source.js.map +1 -0
- package/dist/condition-types.d.ts +14 -0
- package/dist/condition.d.ts +28 -0
- package/dist/condition.js +2 -0
- package/dist/condition.js.map +1 -0
- package/dist/control-registry.d.ts +56 -0
- package/dist/control-registry.js +2 -0
- package/dist/control-registry.js.map +1 -0
- package/dist/dynamic-scope-child.d.ts +23 -0
- package/dist/dynamic-scope-child.js +2 -0
- package/dist/dynamic-scope-child.js.map +1 -0
- package/dist/dynamic-scope-controller.d.ts +16 -0
- package/dist/dynamic-scope-controller.js +2 -0
- package/dist/dynamic-scope-controller.js.map +1 -0
- package/dist/feature-attachment-authoring-types.d.ts +51 -0
- package/dist/feature-attachment-lowering.d.ts +18 -0
- package/dist/feature-attachment-lowering.js +2 -0
- package/dist/feature-attachment-lowering.js.map +1 -0
- package/dist/feature-attachment.d.ts +45 -0
- package/dist/feature-attachment.js +2 -0
- package/dist/feature-attachment.js.map +1 -0
- package/dist/feature-authoring-types.d.ts +196 -0
- package/dist/feature-authoring.d.ts +15 -0
- package/dist/feature-authoring.js +2 -0
- package/dist/feature-authoring.js.map +1 -0
- package/dist/feature-body.d.ts +53 -0
- package/dist/feature-body.js +2 -0
- package/dist/feature-body.js.map +1 -0
- package/dist/feature-call-authority.d.ts +8 -0
- package/dist/feature-call-authority.js +2 -0
- package/dist/feature-call-authority.js.map +1 -0
- package/dist/feature-call-types.d.ts +39 -0
- package/dist/feature-call.d.ts +18 -0
- package/dist/feature-call.js +2 -0
- package/dist/feature-call.js.map +1 -0
- package/dist/feature-calls.d.ts +6 -0
- package/dist/feature-calls.js +2 -0
- package/dist/feature-calls.js.map +1 -0
- package/dist/feature-contract.d.ts +70 -0
- package/dist/feature-contract.js +2 -0
- package/dist/feature-contract.js.map +1 -0
- package/dist/feature-contribution-model.d.ts +38 -0
- package/dist/feature-contribution-model.js +2 -0
- package/dist/feature-contribution-model.js.map +1 -0
- package/dist/feature-contribution.d.ts +126 -0
- package/dist/feature-contribution.js +2 -0
- package/dist/feature-contribution.js.map +1 -0
- package/dist/feature-definition-api.d.ts +53 -0
- package/dist/feature-definition-support.d.ts +21 -0
- package/dist/feature-definition-support.js +2 -0
- package/dist/feature-definition-support.js.map +1 -0
- package/dist/feature-effect.d.ts +26 -0
- package/dist/feature-effect.js +2 -0
- package/dist/feature-effect.js.map +1 -0
- package/dist/feature-event.d.ts +32 -0
- package/dist/feature-event.js +2 -0
- package/dist/feature-event.js.map +1 -0
- package/dist/feature-generation.d.ts +34 -0
- package/dist/feature-generation.js +2 -0
- package/dist/feature-generation.js.map +1 -0
- package/dist/feature-lazy-generation.d.ts +6 -0
- package/dist/feature-lazy-generation.js +2 -0
- package/dist/feature-lazy-generation.js.map +1 -0
- package/dist/feature-lazy.d.ts +44 -0
- package/dist/feature-lazy.js +2 -0
- package/dist/feature-lazy.js.map +1 -0
- package/dist/feature-materialization-binding.d.ts +31 -0
- package/dist/feature-materialization-binding.js +2 -0
- package/dist/feature-materialization-binding.js.map +1 -0
- package/dist/feature-materialization-types.d.ts +45 -0
- package/dist/feature-model-dependencies.d.ts +23 -0
- package/dist/feature-model-dependencies.js +2 -0
- package/dist/feature-model-dependencies.js.map +1 -0
- package/dist/feature-model.d.ts +60 -0
- package/dist/feature-model.js +2 -0
- package/dist/feature-model.js.map +1 -0
- package/dist/feature-optional.d.ts +15 -0
- package/dist/feature-optional.js +2 -0
- package/dist/feature-optional.js.map +1 -0
- package/dist/feature-own-lowering.d.ts +22 -0
- package/dist/feature-own-lowering.js +2 -0
- package/dist/feature-own-lowering.js.map +1 -0
- package/dist/feature-port-binding.d.ts +6 -0
- package/dist/feature-port-binding.js +2 -0
- package/dist/feature-port-binding.js.map +1 -0
- package/dist/feature-port.d.ts +66 -0
- package/dist/feature-port.js +2 -0
- package/dist/feature-port.js.map +1 -0
- package/dist/feature-record.d.ts +6 -0
- package/dist/feature-record.js +2 -0
- package/dist/feature-record.js.map +1 -0
- package/dist/feature-resource.d.ts +29 -0
- package/dist/feature-resource.js +2 -0
- package/dist/feature-resource.js.map +1 -0
- package/dist/feature-scope-types.d.ts +35 -0
- package/dist/feature-scope.d.ts +29 -0
- package/dist/feature-scope.js +2 -0
- package/dist/feature-scope.js.map +1 -0
- package/dist/feature-stream.d.ts +37 -0
- package/dist/feature-stream.js +2 -0
- package/dist/feature-stream.js.map +1 -0
- package/dist/feature-timers.d.ts +14 -0
- package/dist/feature-timers.js +2 -0
- package/dist/feature-timers.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection-activity-protocol.d.ts +63 -0
- package/dist/inspection-activity.d.ts +54 -0
- package/dist/inspection-activity.js +2 -0
- package/dist/inspection-activity.js.map +1 -0
- package/dist/inspection-diff.d.ts +10 -0
- package/dist/inspection-diff.js +2 -0
- package/dist/inspection-diff.js.map +1 -0
- package/dist/inspection-module-activity.d.ts +4 -0
- package/dist/inspection-module-activity.js +2 -0
- package/dist/inspection-module-activity.js.map +1 -0
- package/dist/inspection-observer.d.ts +38 -0
- package/dist/inspection-observer.js +2 -0
- package/dist/inspection-observer.js.map +1 -0
- package/dist/inspection-plan.d.ts +33 -0
- package/dist/inspection-plan.js +2 -0
- package/dist/inspection-plan.js.map +1 -0
- package/dist/inspection-protocol.d.ts +333 -0
- package/dist/inspection-protocol.js +2 -0
- package/dist/inspection-protocol.js.map +1 -0
- package/dist/inspection-registry.d.ts +41 -0
- package/dist/inspection-registry.js +2 -0
- package/dist/inspection-registry.js.map +1 -0
- package/dist/inspection-session.d.ts +92 -0
- package/dist/inspection-session.js +2 -0
- package/dist/inspection-session.js.map +1 -0
- package/dist/inspection-snapshot.d.ts +4 -0
- package/dist/inspection-snapshot.js +2 -0
- package/dist/inspection-snapshot.js.map +1 -0
- package/dist/inspection-state.d.ts +91 -0
- package/dist/inspection-state.js +2 -0
- package/dist/inspection-state.js.map +1 -0
- package/dist/instance-demand.d.ts +31 -0
- package/dist/instance-demand.js +2 -0
- package/dist/instance-demand.js.map +1 -0
- package/dist/internal.d.ts +41 -0
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -0
- package/dist/keyed-scope-controller.d.ts +11 -0
- package/dist/keyed-scope-controller.js +2 -0
- package/dist/keyed-scope-controller.js.map +1 -0
- package/dist/model-kernel.d.ts +18 -0
- package/dist/model-kernel.js +2 -0
- package/dist/model-kernel.js.map +1 -0
- package/dist/module-call-context.d.ts +7 -0
- package/dist/module-call-context.js +2 -0
- package/dist/module-call-context.js.map +1 -0
- package/dist/module-call-runtime.d.ts +21 -0
- package/dist/module-call-runtime.js +2 -0
- package/dist/module-call-runtime.js.map +1 -0
- package/dist/module-generation.d.ts +57 -0
- package/dist/module-generation.js +2 -0
- package/dist/module-generation.js.map +1 -0
- package/dist/module-instance-types.d.ts +156 -0
- package/dist/module-instance.d.ts +19 -0
- package/dist/module-instance.js +2 -0
- package/dist/module-instance.js.map +1 -0
- package/dist/module-runtime-identity.d.ts +4 -0
- package/dist/module-runtime-identity.js +2 -0
- package/dist/module-runtime-identity.js.map +1 -0
- package/dist/module-scope-open.d.ts +4 -0
- package/dist/module-scope-open.js +2 -0
- package/dist/module-scope-open.js.map +1 -0
- package/dist/module-scope-retirement.d.ts +3 -0
- package/dist/module-scope-retirement.js +2 -0
- package/dist/module-scope-retirement.js.map +1 -0
- package/dist/module-template-ir.d.ts +59 -0
- package/dist/owner-generation-retirement.d.ts +6 -0
- package/dist/owner-generation-retirement.js +2 -0
- package/dist/owner-generation-retirement.js.map +1 -0
- package/dist/owner-generation-state.d.ts +123 -0
- package/dist/owner-generation-state.js +2 -0
- package/dist/owner-generation-state.js.map +1 -0
- package/dist/owner-generation.d.ts +16 -0
- package/dist/owner-generation.js +2 -0
- package/dist/owner-generation.js.map +1 -0
- package/dist/public-module-definition.d.ts +9 -0
- package/dist/public-module-definition.js +2 -0
- package/dist/public-module-definition.js.map +1 -0
- package/dist/public-module-instance.d.ts +6 -0
- package/dist/public-module-instance.js +2 -0
- package/dist/public-module-instance.js.map +1 -0
- package/dist/public-module-retirement-diagnostics.d.ts +5 -0
- package/dist/public-module-retirement-diagnostics.js +2 -0
- package/dist/public-module-retirement-diagnostics.js.map +1 -0
- package/dist/public-module-retirement.d.ts +3 -0
- package/dist/public-module-retirement.js +2 -0
- package/dist/public-module-retirement.js.map +1 -0
- package/dist/public-module-scope.d.ts +5 -0
- package/dist/public-module-scope.js +2 -0
- package/dist/public-module-scope.js.map +1 -0
- package/dist/public-module-state.d.ts +28 -0
- package/dist/public-module-state.js +2 -0
- package/dist/public-module-state.js.map +1 -0
- package/dist/public-module-types.d.ts +295 -0
- package/dist/public-module.d.ts +4 -0
- package/dist/resource-cache.d.ts +12 -0
- package/dist/resource-cache.js +2 -0
- package/dist/resource-cache.js.map +1 -0
- package/dist/resource-controller.d.ts +29 -0
- package/dist/resource-controller.js +2 -0
- package/dist/resource-controller.js.map +1 -0
- package/dist/resource-policy.d.ts +16 -0
- package/dist/resource-policy.js +2 -0
- package/dist/resource-policy.js.map +1 -0
- package/dist/resource-snapshot.d.ts +12 -0
- package/dist/resource-snapshot.js +2 -0
- package/dist/resource-snapshot.js.map +1 -0
- package/dist/resource-types.d.ts +3 -0
- package/dist/runtime-error-reporting.d.ts +4 -0
- package/dist/runtime-error-reporting.js +2 -0
- package/dist/runtime-error-reporting.js.map +1 -0
- package/dist/stream-backpressure.d.ts +18 -0
- package/dist/stream-backpressure.js +2 -0
- package/dist/stream-backpressure.js.map +1 -0
- package/dist/stream-cleanup.d.ts +17 -0
- package/dist/stream-cleanup.js +2 -0
- package/dist/stream-cleanup.js.map +1 -0
- package/dist/stream-controller-types.d.ts +51 -0
- package/dist/stream-controller.d.ts +5 -0
- package/dist/stream-controller.js +2 -0
- package/dist/stream-controller.js.map +1 -0
- package/docs/agent-guide.md +214 -0
- package/docs/agent-guide.ru.md +208 -0
- package/docs/cookbook.md +734 -0
- package/docs/cookbook.ru.md +729 -0
- package/docs/decisions.md +1437 -0
- package/docs/devtools.md +423 -0
- package/docs/devtools.ru.md +419 -0
- package/docs/how-it-works.md +521 -0
- package/docs/how-it-works.ru.md +495 -0
- package/docs/releases.md +78 -0
- package/docs/releases.ru.md +78 -0
- package/docs/spec.md +874 -0
- package/docs/spec.ru.md +884 -0
- package/package.json +72 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ResourceRequestKey } from './resource-types.js';
|
|
2
|
+
import type { StreamExecution } from './stream-controller-types.js';
|
|
3
|
+
/** One physical disposer at a time; a failed cleanup keeps its disposer available for an exact retry. */
|
|
4
|
+
declare function disposeStreamExecution<Target, Key extends ResourceRequestKey, Data>(current: StreamExecution<Target, Key, Data>): Promise<void>;
|
|
5
|
+
declare function collectCleanupSettlements(operations: readonly Promise<void>[], failures: Error[]): Promise<void>;
|
|
6
|
+
declare function throwCleanupFailures(failures: readonly Error[]): void;
|
|
7
|
+
/**
|
|
8
|
+
* A retirement handle taken before the work it stands for begins. Aborting a connection runs foreign code that may
|
|
9
|
+
* start a successor, and the successor has to wait for the drain of what it replaces, so the promise exists before
|
|
10
|
+
* the abort and is filled with the real drain right after it (D192).
|
|
11
|
+
*/
|
|
12
|
+
declare function reserveCleanupHandle(): {
|
|
13
|
+
readonly promise: Promise<void>;
|
|
14
|
+
readonly settle: (drain: Promise<void>) => void;
|
|
15
|
+
};
|
|
16
|
+
declare function settleCleanupOperations(operations: readonly Promise<void>[]): Promise<void>;
|
|
17
|
+
export { collectCleanupSettlements, disposeStreamExecution, reserveCleanupHandle, settleCleanupOperations, throwCleanupFailures, };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createErrorWithCause as a}from"@opetope/core/internal";function u(e){if(e.cleanup!==void 0)return e.cleanup;const n=e.disposer;if(n===void 0)return Promise.resolve();let s;try{s=n()}catch(o){return Promise.reject(o)}const t=Promise.resolve(s).then(()=>{e.disposer===n&&(e.disposer=void 0),e.cleanup=void 0});return e.cleanup=t,t.catch(()=>{e.cleanup===t&&(e.cleanup=void 0)}),t}function l(e){return e instanceof Error?e:a("Stream cleanup failed.",e)}async function r(e,n){const s=await Promise.allSettled(e);for(const t of s)t.status==="rejected"&&n.push(l(t.reason))}function i(e){const[n]=e;if(n!==void 0)throw n}function c(){let e;return{promise:new Promise((s,t)=>{e=o=>{o.then(s,t)}}),settle:e}}async function f(e){const n=[];await r(e,n),i(n)}export{r as collectCleanupSettlements,u as disposeStreamExecution,c as reserveCleanupHandle,f as settleCleanupOperations,i as throwCleanupFailures};
|
|
2
|
+
//# sourceMappingURL=stream-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-cleanup.js","sources":["../src/stream-cleanup.ts"],"sourcesContent":["import { createErrorWithCause } from '@opetope/core/internal';\nimport type { Awaitable } from '@opetope/core/internal';\n\nimport type { ResourceRequestKey } from './resource-types';\nimport type { StreamExecution } from './stream-controller-types';\n\n/** One physical disposer at a time; a failed cleanup keeps its disposer available for an exact retry. */\nfunction disposeStreamExecution<Target, Key extends ResourceRequestKey, Data>(\n current: StreamExecution<Target, Key, Data>,\n): Promise<void> {\n if (current.cleanup !== undefined) return current.cleanup;\n\n const disposer = current.disposer;\n\n if (disposer === undefined) return Promise.resolve();\n\n let result: Awaitable<void>;\n\n try {\n result = disposer();\n } catch (error) {\n return Promise.reject(error);\n }\n\n const cleanup = Promise.resolve(result).then(() => {\n if (current.disposer === disposer) current.disposer = undefined;\n\n current.cleanup = undefined;\n });\n current.cleanup = cleanup;\n void cleanup.catch(() => {\n if (current.cleanup === cleanup) current.cleanup = undefined;\n });\n\n return cleanup;\n}\n\nfunction cleanupError(value: unknown): Error {\n return value instanceof Error ? value : createErrorWithCause('Stream cleanup failed.', value);\n}\n\nasync function collectCleanupSettlements(operations: readonly Promise<void>[], failures: Error[]): Promise<void> {\n const results = await Promise.allSettled(operations);\n\n for (const result of results) {\n if (result.status === 'rejected') failures.push(cleanupError(result.reason));\n }\n}\n\nfunction throwCleanupFailures(failures: readonly Error[]): void {\n const [first] = failures;\n\n if (first === undefined) return;\n\n throw first;\n}\n\n/**\n * A retirement handle taken before the work it stands for begins. Aborting a connection runs foreign code that may\n * start a successor, and the successor has to wait for the drain of what it replaces, so the promise exists before\n * the abort and is filled with the real drain right after it (D192).\n */\nfunction reserveCleanupHandle(): {\n readonly promise: Promise<void>;\n readonly settle: (drain: Promise<void>) => void;\n} {\n let settle!: (drain: Promise<void>) => void;\n const promise = new Promise<void>((resolve, reject) => {\n settle = (drain: Promise<void>): void => void drain.then(resolve, reject);\n });\n\n return { promise, settle };\n}\n\nasync function settleCleanupOperations(operations: readonly Promise<void>[]): Promise<void> {\n const failures: Error[] = [];\n await collectCleanupSettlements(operations, failures);\n throwCleanupFailures(failures);\n}\n\nexport {\n collectCleanupSettlements,\n disposeStreamExecution,\n reserveCleanupHandle,\n settleCleanupOperations,\n throwCleanupFailures,\n};\n"],"names":["disposeStreamExecution","current","disposer","result","error","cleanup","cleanupError","value","createErrorWithCause","collectCleanupSettlements","operations","failures","results","throwCleanupFailures","first","reserveCleanupHandle","settle","resolve","reject","drain","settleCleanupOperations"],"mappings":"8DAOA,SAASA,EACPC,EAA2C,CAE3C,GAAIA,EAAQ,UAAY,OAAW,OAAOA,EAAQ,QAElD,MAAMC,EAAWD,EAAQ,SAEzB,GAAIC,IAAa,OAAW,OAAO,QAAQ,QAAO,EAElD,IAAIC,EAEJ,GAAI,CACFA,EAASD,EAAQ,CACnB,OAASE,EAAO,CACd,OAAO,QAAQ,OAAOA,CAAK,CAC7B,CAEA,MAAMC,EAAU,QAAQ,QAAQF,CAAM,EAAE,KAAK,IAAK,CAC5CF,EAAQ,WAAaC,IAAUD,EAAQ,SAAW,QAEtDA,EAAQ,QAAU,MACpB,CAAC,EACD,OAAAA,EAAQ,QAAUI,EACbA,EAAQ,MAAM,IAAK,CAClBJ,EAAQ,UAAYI,IAASJ,EAAQ,QAAU,OACrD,CAAC,EAEMI,CACT,CAEA,SAASC,EAAaC,EAAc,CAClC,OAAOA,aAAiB,MAAQA,EAAQC,EAAqB,yBAA0BD,CAAK,CAC9F,CAEA,eAAeE,EAA0BC,EAAsCC,EAAiB,CAC9F,MAAMC,EAAU,MAAM,QAAQ,WAAWF,CAAU,EAEnD,UAAWP,KAAUS,EACfT,EAAO,SAAW,YAAYQ,EAAS,KAAKL,EAAaH,EAAO,MAAM,CAAC,CAE/E,CAEA,SAASU,EAAqBF,EAA0B,CACtD,KAAM,CAACG,CAAK,EAAIH,EAEhB,GAAIG,IAAU,OAEd,MAAMA,CACR,CAOA,SAASC,GAAoB,CAI3B,IAAIC,EAKJ,MAAO,CAAE,QAJO,IAAI,QAAc,CAACC,EAASC,IAAU,CACpDF,EAAUG,GAAoB,CAAgBA,EAAM,KAAKF,EAASC,CAAM,EAC1E,CAAC,EAEiB,OAAAF,CAAM,CAC1B,CAEA,eAAeI,EAAwBV,EAAoC,CACzE,MAAMC,EAAoB,CAAA,EAC1B,MAAMF,EAA0BC,EAAYC,CAAQ,EACpDE,EAAqBF,CAAQ,CAC/B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one stream controller is made of: the options an author gives it, the two contexts it hands back and the
|
|
3
|
+
* record of one live execution. Kept apart from the machine so the machine stays under one screen of law per file.
|
|
4
|
+
*/
|
|
5
|
+
import type { Readable } from '@opetope/core';
|
|
6
|
+
import type { Awaitable } from '@opetope/core/internal';
|
|
7
|
+
import type { Resource, ResourceRequestKey, ResourceRetention, ResourceRetry, StreamBackpressure } from './resource-types.js';
|
|
8
|
+
import type { RuntimeErrorReporter } from './runtime-error-reporting.js';
|
|
9
|
+
import type { StreamAdmission } from './stream-backpressure.js';
|
|
10
|
+
type StreamDisposer = () => Awaitable<void>;
|
|
11
|
+
type StreamConnectContext<Source, Data> = {
|
|
12
|
+
readonly emit: (data: Data) => void;
|
|
13
|
+
readonly signal: AbortSignal;
|
|
14
|
+
readonly source: Source;
|
|
15
|
+
};
|
|
16
|
+
type StreamConsumeContext<Source, Target, Data> = {
|
|
17
|
+
readonly data: Data;
|
|
18
|
+
readonly signal: AbortSignal;
|
|
19
|
+
readonly source: Source;
|
|
20
|
+
readonly target: Target;
|
|
21
|
+
};
|
|
22
|
+
type StreamControllerOptions<Source, Target, Data, Key extends ResourceRequestKey> = {
|
|
23
|
+
readonly allocateGeneration?: () => number;
|
|
24
|
+
readonly backpressure: StreamBackpressure;
|
|
25
|
+
readonly connect: (target: Target, context: StreamConnectContext<Source, Data>) => Awaitable<StreamDisposer | void>;
|
|
26
|
+
readonly consume?: (context: StreamConsumeContext<Source, Target, Data>) => Awaitable<void>;
|
|
27
|
+
readonly key: (target: Target) => Key;
|
|
28
|
+
readonly reporter?: RuntimeErrorReporter;
|
|
29
|
+
readonly retention: ResourceRetention;
|
|
30
|
+
readonly retry?: ResourceRetry;
|
|
31
|
+
readonly source: Source;
|
|
32
|
+
/** The public selector may answer `T | null | undefined`; both nullish answers mean «no target» (D194). */
|
|
33
|
+
readonly target: Readable<Target | null | undefined>;
|
|
34
|
+
};
|
|
35
|
+
interface ManagedStreamRef<Data, Key extends ResourceRequestKey> extends Resource<Data, Key> {
|
|
36
|
+
readonly close: () => Promise<void>;
|
|
37
|
+
readonly fence: () => void;
|
|
38
|
+
}
|
|
39
|
+
type StreamExecution<Target, Key extends ResourceRequestKey, Data> = {
|
|
40
|
+
readonly abort: AbortController;
|
|
41
|
+
admission: StreamAdmission<Data> | undefined;
|
|
42
|
+
readonly attempt: number;
|
|
43
|
+
cleanup: Promise<void> | undefined;
|
|
44
|
+
disposer: StreamDisposer | undefined;
|
|
45
|
+
readonly generation: number;
|
|
46
|
+
readonly key: Key;
|
|
47
|
+
retirement: Promise<void> | undefined;
|
|
48
|
+
settlement: Promise<void> | undefined;
|
|
49
|
+
readonly target: Target;
|
|
50
|
+
};
|
|
51
|
+
export type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer, StreamExecution, };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ResourceRequestKey } from './resource-types.js';
|
|
2
|
+
import type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer } from './stream-controller-types.js';
|
|
3
|
+
declare function createStreamController<Source, Target, Data, Key extends ResourceRequestKey>(options: StreamControllerOptions<Source, Target, Data, Key>, register?: (controller: ManagedStreamRef<Data, Key>) => void): ManagedStreamRef<Data, Key>;
|
|
4
|
+
export { createStreamController };
|
|
5
|
+
export type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{abortWithReason as me}from"@opetope/core/internal";import{createRetainedResourceCache as ye}from"./resource-cache.js";import{idleSnapshot as M,pendingSnapshot as ee,readResourceData as te}from"./resource-snapshot.js";import{reportRuntimeFailure as pe}from"./runtime-error-reporting.js";import{createStreamAdmission as ge}from"./stream-backpressure.js";import{collectCleanupSettlements as ne,throwCleanupFailures as he,reserveCleanupHandle as ve,disposeStreamExecution as Se,settleCleanupOperations as B}from"./stream-cleanup.js";function we(i,b){var $,A;const q=new Set;let u;const c=ye(i.retention),re=(($=i.retry)==null?void 0:$.attempts)??1,ie=((A=i.retry)==null?void 0:A.delayMs)??0,k=new Set,R=new Set;let s=!1,E,y=!1,S=!1,z=0,T=0,o,g=0,P=0,j,d,l,w=0,h=M,O;const D=()=>{var e;return w+=1,g=((e=i.allocateGeneration)==null?void 0:e.call(i))??g+1,g},f=e=>pe(e,i.reporter),H=e=>s&&!y&&!S&&e===g,x=e=>o===e&&!e.abort.signal.aborted&&s&&e.generation===g,J=e=>s&&!y&&!S&&w===e,se=()=>{var e;(e=u==null?void 0:u.changed)==null||e.call(u);for(const t of[...q])try{t()}catch(n){f(n)}},v=e=>{h=Object.freeze(e),se()},L=()=>{j!==void 0&&(clearTimeout(j),j=void 0)},N=e=>{const t=e.admission;if(e.admission=void 0,t===void 0)return Promise.resolve();const n=t.close();return F(n),n},F=e=>{k.add(e),e.then(()=>k.delete(e),()=>k.delete(e))},I=(e,t,n)=>{if(e.retirement!==void 0)return e.retirement;const r=ve();e.retirement=r.promise,R.add(e);try{n==null||n(),me(e.abort,t)}finally{const p=Promise.resolve(e.settlement).then(()=>Se(e));r.settle(B([p,N(e)]))}return r.promise.then(()=>{e.retirement===r.promise&&(e.retirement=void 0),R.delete(e)},()=>{e.retirement===r.promise&&(e.retirement=void 0)}),r.promise},Q=e=>I(e),G=e=>{L();const t=o;if(o=void 0,t===void 0)return e==null||e(),Promise.resolve();const n=I(t,void 0,e);return F(n),n},U=(e,t,n)=>{if(x(e)){if(o=void 0,F(I(e,t)),H(e.generation)&&o===void 0){const r=te(h);v({attempt:e.attempt,data:r.present?r.data:void 0,error:t,generation:e.generation,key:e.key,retryable:!0,stale:r.present,status:"error"})}f(t)}},ae=(e,t)=>{var n;if(x(e)&&(c==null||c.set(e.key,t),v({attempt:e.attempt,data:t,error:void 0,generation:e.generation,key:e.key,stale:!1,status:"ready"}),!!x(e)))return(n=i.consume)==null?void 0:n.call(i,Object.freeze({data:t,signal:e.abort.signal,source:i.source,target:e.target}))},V=e=>{if(!x(e))return;const t=ge({consume:a=>ae(e,a),onConsumeError:f});e.admission=t;let n=()=>{},r=()=>{};const p=new Promise((a,m)=>{n=m,r=a});e.settlement=p,F(p);let C;try{C=i.connect(e.target,Object.freeze({emit:t.emit,signal:e.abort.signal,source:i.source}))}catch(a){C=Promise.reject(a)}Promise.resolve(C).then(a=>{try{if(a!==void 0&&typeof a!="function"){U(e,new TypeError("Stream connect must return void or a disposer function."),!0),r();return}e.disposer=a??void 0,r()}catch(m){n(m)}},a=>{try{if(!x(e)){r();return}if(e.attempt<re){const m=N(e);j=setTimeout(()=>{j=void 0,m.then(()=>{K(e.attempt+1,e.generation,e.key,e.target)})},ie),r();return}U(e,a,!0),r()}catch(m){n(m)}})},K=(e,t,n,r)=>{if(!H(t))return;const p=o!==void 0||R.size!==0,C=[...R].map(Q),a=G();if(!H(t)){B([a,...C]).catch(f);return}const m={abort:new AbortController,admission:void 0,attempt:e,cleanup:void 0,disposer:void 0,generation:t,key:n,retirement:void 0,settlement:void 0,target:r};if(o=m,v(ee(e,t,n,te(h))),!p){V(m);return}B([a,...C]).catch(f).then(()=>V(m))},W=(e,t)=>{const n=c==null?void 0:c.get(t),r=n===void 0?{present:!1}:{data:n.data,present:!0},p=D();v(ee(1,p,t,r)),K(1,p,t,e)},oe=()=>{if(d===void 0&&l===void 0&&h.status==="idle")return;d=void 0,l=void 0;const e=D();G().catch(f),g===e&&v({...M,generation:e})},de=(e,t)=>{let n;try{n=i.key(e)}catch(r){f(r);return}if(J(t)){if(d!==void 0&&Object.is(d,n)){l=e;return}d=n,l=e,W(e,n)}},X=()=>{if(!s||y)return;const e=++w;let t;try{t=i.target.getSnapshot()}catch(n){f(n);return}if(J(e)){if(t==null){oe();return}de(t,e)}},ce=e=>{try{e()}catch(t){f(t)}},le=e=>!y&&!S&&s&&z===e,Y=()=>{if(s||y||S)return;s=!0;const e=z+=1;try{const t=i.target.subscribe(X);if(!le(e)){ce(t);return}O=t,X()}catch(t){s=!1,O=void 0,f(t)}},Z=()=>{const e=O;O=void 0;try{e==null||e()}catch(t){f(t)}},fe=()=>{if(!s||i.retention.kind==="scope"||P!==0)return;w+=1,s=!1,z+=1,T+=1,d=void 0,l=void 0;const e=D();G(Z).catch(f),!(s||y||g!==e)&&v({...M,generation:e})},ue=()=>{const e=++T;queueMicrotask(()=>{e===T&&fe()})},_=Object.freeze({close:()=>{if(E!==void 0)return E;w+=1;const e=[...R].map(Q);let t=Promise.resolve();if(!y){y=!0,s=!1,z+=1,T+=1,P=0,t=G(),Z(),d=void 0,l=void 0,c==null||c.clear();const r=D();v({...M,generation:r})}const n=(async()=>{const r=[];for(await ne([t,...e],r);k.size!==0;)await ne([...k],r);he(r)})();return E=n,n.catch(()=>{E===n&&(E=void 0)}),n},fence(){var e,t;w+=1,S=!0,s=!1,L(),o==null||o.abort.abort(),(e=o==null?void 0:o.admission)==null||e.close(),(t=u==null?void 0:u.changed)==null||t.call(u)},getSnapshot:()=>h,inspectionState:()=>S||y?"fenced":h.status,invalidate:()=>{c==null||c.clear(),!(!s||d===void 0||l===void 0)&&W(l,d)},observe:e=>{u=e},refresh:()=>{!s||d===void 0||l===void 0||W(l,d)},retain:()=>{if(y)return()=>{};P+=1,T+=1,Y();let e=!0;return()=>{e&&(e=!1,P-=1,P===0&&i.retention.kind==="observer"&&ue())}},retry:()=>{!s||h.status!=="error"||d===void 0||l===void 0||K(h.attempt+1,g,d,l)},subscribe(e){if(typeof e!="function")throw new TypeError("Stream listener must be a function.");q.add(e);let t=!0;return()=>{t&&(t=!1,q.delete(e))}}});return b==null||b(_),i.retention.kind==="scope"&&Y(),_}export{we as createStreamController};
|
|
2
|
+
//# sourceMappingURL=stream-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-controller.js","sources":["../src/stream-controller.ts"],"sourcesContent":["import type { Awaitable, CallObservationSlot } from '@opetope/core/internal';\nimport { abortWithReason } from '@opetope/core/internal';\n\nimport { createRetainedResourceCache } from './resource-cache';\nimport type { ResourceCache } from './resource-cache';\nimport { idleSnapshot, pendingSnapshot, readResourceData } from './resource-snapshot';\nimport type { ResourceData } from './resource-snapshot';\nimport type { ResourceRequestKey, ResourceSnapshot } from './resource-types';\nimport { reportRuntimeFailure } from './runtime-error-reporting';\nimport { createStreamAdmission } from './stream-backpressure';\nimport {\n collectCleanupSettlements,\n disposeStreamExecution,\n reserveCleanupHandle,\n settleCleanupOperations,\n throwCleanupFailures,\n} from './stream-cleanup';\nimport type {\n ManagedStreamRef,\n StreamConnectContext,\n StreamConsumeContext,\n StreamControllerOptions,\n StreamDisposer,\n StreamExecution,\n} from './stream-controller-types';\n\nfunction createStreamController<Source, Target, Data, Key extends ResourceRequestKey>(\n options: StreamControllerOptions<Source, Target, Data, Key>,\n register?: (controller: ManagedStreamRef<Data, Key>) => void,\n): ManagedStreamRef<Data, Key> {\n const listeners = new Set<() => void>();\n let observation: CallObservationSlot | undefined;\n const cache: ResourceCache<Data, Key> | undefined = createRetainedResourceCache(options.retention);\n const maximumAttempts = options.retry?.attempts ?? 1;\n const retryDelayMs = options.retry?.delayMs ?? 0;\n const draining = new Set<Promise<void>>();\n const retiredExecutions = new Set<StreamExecution<Target, Key, Data>>();\n let active = false;\n let closePromise: Promise<void> | undefined;\n let closed = false;\n let fenced = false;\n /** Which activation owns the target subscription; a retain that only cancels a grace period never changes it. */\n let activation = 0;\n /** The pending deactivation of an observer retention: any retain cancels it without touching the activation. */\n let graceRevision = 0;\n let execution: StreamExecution<Target, Key, Data> | undefined;\n let generation = 0;\n let retainers = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let selectedKey: Key | undefined;\n let selectedTarget: Target | undefined;\n let selectionRevision = 0;\n let snapshot = idleSnapshot as ResourceSnapshot<Data, Key>;\n let unsubscribeTarget: (() => void) | undefined;\n\n const allocateGeneration = (): number => {\n selectionRevision += 1;\n generation = options.allocateGeneration?.() ?? generation + 1;\n\n return generation;\n };\n const report = (error: unknown): void => reportRuntimeFailure(error, options.reporter);\n const admitsExecution = (expectedGeneration: number): boolean =>\n active && !closed && !fenced && expectedGeneration === generation;\n const isCurrentExecution = (current: StreamExecution<Target, Key, Data>): boolean =>\n execution === current && !current.abort.signal.aborted && active && current.generation === generation;\n const isCurrentRead = (revision: number): boolean => active && !closed && !fenced && selectionRevision === revision;\n const notify = (): void => {\n observation?.changed?.();\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch (error) {\n report(error);\n }\n }\n };\n const publish = (next: ResourceSnapshot<Data, Key>): void => {\n snapshot = Object.freeze(next);\n notify();\n };\n const clearRetry = (): void => {\n if (retryTimer === undefined) return;\n\n clearTimeout(retryTimer);\n retryTimer = undefined;\n };\n /**\n * Closing admission returns the drain of work it already admitted, and that promise is never dropped: `close()`\n * waits for it, so an imported source is released only after the last `consume` has finished (D192).\n */\n const closeAdmission = (current: StreamExecution<Target, Key, Data>): Promise<void> => {\n const admission = current.admission;\n current.admission = undefined;\n\n if (admission === undefined) return Promise.resolve();\n\n const consumed = admission.close();\n // eslint-disable-next-line no-use-before-define -- the drain set is initialized above this closure.\n trackDrain(consumed);\n\n return consumed;\n };\n const trackDrain = (operation: Promise<void>): void => {\n draining.add(operation);\n void operation.then(\n () => draining.delete(operation),\n () => draining.delete(operation),\n );\n };\n const retireExecution = (\n current: StreamExecution<Target, Key, Data>,\n reason?: unknown,\n beforeAbort?: () => void,\n ): Promise<void> => {\n if (current.retirement !== undefined) return current.retirement;\n\n // D192: the abort may select another target, and that successor must find this execution already retiring.\n const reserved = reserveCleanupHandle();\n current.retirement = reserved.promise;\n retiredExecutions.add(current);\n\n try {\n beforeAbort?.();\n abortWithReason(current.abort, reason);\n } finally {\n // Disconnect may unblock consume; join both before releasing the imported source or opening a successor.\n const disposal = Promise.resolve(current.settlement).then(() => disposeStreamExecution(current));\n reserved.settle(settleCleanupOperations([disposal, closeAdmission(current)]));\n }\n\n void reserved.promise.then(\n () => {\n if (current.retirement === reserved.promise) current.retirement = undefined;\n\n retiredExecutions.delete(current);\n },\n () => {\n if (current.retirement === reserved.promise) current.retirement = undefined;\n },\n );\n\n return reserved.promise;\n };\n const retryRetirement = (current: StreamExecution<Target, Key, Data>): Promise<void> => retireExecution(current);\n const fenceExecution = (beforeAbort?: () => void): Promise<void> => {\n clearRetry();\n const current = execution;\n execution = undefined;\n\n if (current === undefined) {\n beforeAbort?.();\n\n return Promise.resolve();\n }\n\n const disposal = retireExecution(current, undefined, beforeAbort);\n trackDrain(disposal);\n\n return disposal;\n };\n const workspaceFailure = (\n current: StreamExecution<Target, Key, Data>,\n error: unknown,\n reportFailure: boolean,\n ): void => {\n if (!isCurrentExecution(current)) return;\n\n execution = undefined;\n // D192: workspace failure retires the same physical connection as replacement, including its pending consume.\n trackDrain(retireExecution(current, error));\n\n // Abort can select a successor or close the owner; neither gives this failure its snapshot back.\n if (admitsExecution(current.generation) && execution === undefined) {\n const data = readResourceData(snapshot);\n publish({\n attempt: current.attempt,\n data: data.present ? data.data : undefined,\n error,\n generation: current.generation,\n key: current.key,\n retryable: true,\n stale: data.present,\n status: 'error',\n });\n }\n\n if (reportFailure) report(error);\n };\n const consume = (current: StreamExecution<Target, Key, Data>, data: Data): Awaitable<void> => {\n if (!isCurrentExecution(current)) return;\n\n cache?.set(current.key, data);\n publish({\n attempt: current.attempt,\n data,\n error: undefined,\n generation: current.generation,\n key: current.key,\n stale: false,\n status: 'ready',\n });\n\n // D194: publishing `ready` runs foreign subscribers, and one of them may fence, close or retarget the stream.\n // The consumer of a stream nobody waits for any more never starts; work already inside it still drains.\n if (!isCurrentExecution(current)) return;\n\n return options.consume?.(\n Object.freeze({ data, signal: current.abort.signal, source: options.source, target: current.target }),\n );\n };\n const startConnect = (current: StreamExecution<Target, Key, Data>): void => {\n if (!isCurrentExecution(current)) return;\n\n const admission = createStreamAdmission<Data>({\n consume: data => consume(current, data),\n onConsumeError: report,\n });\n current.admission = admission;\n let rejectSettlement: (error: unknown) => void = () => undefined;\n let resolveSettlement: () => void = () => undefined;\n const settlement = new Promise<void>((resolve, reject) => {\n rejectSettlement = reject;\n resolveSettlement = resolve;\n });\n current.settlement = settlement;\n trackDrain(settlement);\n let connection: Awaitable<StreamDisposer | void>;\n\n try {\n connection = options.connect(\n current.target,\n Object.freeze({ emit: admission.emit, signal: current.abort.signal, source: options.source }),\n );\n } catch (error) {\n connection = Promise.reject(error);\n }\n\n void Promise.resolve(connection).then(\n disposer => {\n try {\n if (disposer !== undefined && typeof disposer !== 'function') {\n workspaceFailure(current, new TypeError('Stream connect must return void or a disposer function.'), true);\n resolveSettlement();\n\n return;\n }\n\n current.disposer = disposer ?? undefined;\n resolveSettlement();\n } catch (error) {\n rejectSettlement(error);\n }\n },\n (error: unknown) => {\n try {\n if (!isCurrentExecution(current)) {\n resolveSettlement();\n\n return;\n }\n\n if (current.attempt < maximumAttempts) {\n const consumed = closeAdmission(current);\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n // A successor never overlaps the consume of its predecessor: the admitted work drains first (D192).\n void consumed.then(() => {\n // eslint-disable-next-line no-use-before-define -- the timer fires after controller construction completes.\n execute(current.attempt + 1, current.generation, current.key, current.target);\n });\n }, retryDelayMs);\n resolveSettlement();\n\n return;\n }\n\n workspaceFailure(current, error, true);\n resolveSettlement();\n } catch (settlementError) {\n rejectSettlement(settlementError);\n }\n },\n );\n };\n const execute = (attempt: number, expectedGeneration: number, key: Key, target: Target): void => {\n if (!admitsExecution(expectedGeneration)) return;\n\n const hasPredecessor = execution !== undefined || retiredExecutions.size !== 0;\n const retired = [...retiredExecutions].map(retryRetirement);\n const previous = fenceExecution();\n\n // D194: retiring the predecessor aborts a foreign connection, and that cleanup may select another target. The\n // newer selection owns the controller from that moment, so this attempt drains what it fenced and walks away.\n if (!admitsExecution(expectedGeneration)) {\n void settleCleanupOperations([previous, ...retired]).catch(report);\n\n return;\n }\n\n const current: StreamExecution<Target, Key, Data> = {\n abort: new AbortController(),\n admission: undefined,\n attempt,\n cleanup: undefined,\n disposer: undefined,\n generation: expectedGeneration,\n key,\n retirement: undefined,\n settlement: undefined,\n target,\n };\n execution = current;\n publish(pendingSnapshot(attempt, expectedGeneration, key, readResourceData(snapshot)));\n\n if (!hasPredecessor) {\n startConnect(current);\n\n return;\n }\n\n void settleCleanupOperations([previous, ...retired])\n .catch(report)\n .then(() => startConnect(current));\n };\n const openSelection = (target: Target, key: Key): void => {\n const cached = cache?.get(key);\n const data: ResourceData<Data> = cached === undefined ? { present: false } : { data: cached.data, present: true };\n const nextGeneration = allocateGeneration();\n publish(pendingSnapshot(1, nextGeneration, key, data));\n execute(1, nextGeneration, key, target);\n };\n const closeSelection = (): void => {\n if (selectedKey === undefined && selectedTarget === undefined && snapshot.status === 'idle') return;\n\n selectedKey = undefined;\n selectedTarget = undefined;\n // D194: the fence aborts a connection whose cleanup may select a target of its own, so this transition\n // reserves its generation first and gives the controller up to whatever the cleanup selected.\n const nextGeneration = allocateGeneration();\n void fenceExecution().catch(report);\n\n if (generation !== nextGeneration) return;\n\n publish({ ...idleSnapshot, generation: nextGeneration });\n };\n const selectTarget = (target: Target, revision: number): void => {\n let key: Key;\n\n try {\n key = options.key(target);\n } catch (error) {\n report(error);\n\n return;\n }\n\n if (!isCurrentRead(revision)) return;\n\n if (selectedKey !== undefined && Object.is(selectedKey, key)) {\n selectedTarget = target;\n\n return;\n }\n\n selectedKey = key;\n selectedTarget = target;\n openSelection(target, key);\n };\n const readTarget = (): void => {\n if (!active || closed) return;\n\n const revision = ++selectionRevision;\n let target: Target | null | undefined;\n\n try {\n target = options.target.getSnapshot();\n } catch (error) {\n report(error);\n\n return;\n }\n\n if (!isCurrentRead(revision)) return;\n\n if (target === null || target === undefined) {\n closeSelection();\n\n return;\n }\n\n selectTarget(target, revision);\n };\n /** The subscription of a stale activation is foreign code too: it is released once and its failure reported. */\n const releaseStaleSubscription = (release: () => void): void => {\n try {\n release();\n } catch (error) {\n report(error);\n }\n };\n /** Whether the activation that took this subscription is still the one the controller waits for (D195). */\n const activationIsCurrent = (current: number): boolean => !closed && !fenced && active && activation === current;\n const activate = (): void => {\n if (active || closed || fenced) return;\n\n active = true;\n const current = (activation += 1);\n\n try {\n // D195: `subscribe` runs foreign code that may close or fence this controller before it returns. The disposer\n // of an activation nobody waits for any more is released here, exactly once, and never recorded.\n const release = options.target.subscribe(readTarget);\n\n if (!activationIsCurrent(current)) {\n releaseStaleSubscription(release);\n\n return;\n }\n\n unsubscribeTarget = release;\n readTarget();\n } catch (error) {\n active = false;\n unsubscribeTarget = undefined;\n report(error);\n }\n };\n const releaseTarget = (): void => {\n const unsubscribe = unsubscribeTarget;\n unsubscribeTarget = undefined;\n\n try {\n unsubscribe?.();\n } catch (error) {\n report(error);\n }\n };\n const deactivate = (): void => {\n if (!active || options.retention.kind === 'scope' || retainers !== 0) return;\n\n selectionRevision += 1;\n active = false;\n activation += 1;\n graceRevision += 1;\n // D195: both the subscription disposer and abort may retain again. Reserve selection and retirement first,\n // then release the old subscription before abort can install a new subscription with the same listener.\n selectedKey = undefined;\n selectedTarget = undefined;\n const nextGeneration = allocateGeneration();\n void fenceExecution(releaseTarget).catch(report);\n\n if (active || closed || generation !== nextGeneration) return;\n\n publish({ ...idleSnapshot, generation: nextGeneration });\n };\n const requestDeactivation = (): void => {\n const revision = ++graceRevision;\n\n queueMicrotask(() => {\n if (revision === graceRevision) deactivate();\n });\n };\n const refresh = (): void => {\n if (!active || selectedKey === undefined || selectedTarget === undefined) return;\n\n openSelection(selectedTarget, selectedKey);\n };\n const retry = (): void => {\n if (!active || snapshot.status !== 'error' || selectedKey === undefined || selectedTarget === undefined) {\n return;\n }\n\n execute(snapshot.attempt + 1, generation, selectedKey, selectedTarget);\n };\n const invalidate = (): void => {\n cache?.clear();\n\n if (!active || selectedKey === undefined || selectedTarget === undefined) return;\n\n openSelection(selectedTarget, selectedKey);\n };\n const retain = (): (() => void) => {\n if (closed) return () => undefined;\n\n retainers += 1;\n // A second lease taken while the first one activates cancels the grace period and keeps that activation alive.\n graceRevision += 1;\n activate();\n let retained = true;\n\n return (): void => {\n if (!retained) return;\n\n retained = false;\n retainers -= 1;\n\n if (retainers === 0 && options.retention.kind === 'observer') requestDeactivation();\n };\n };\n const close = (): Promise<void> => {\n if (closePromise !== undefined) return closePromise;\n\n selectionRevision += 1;\n const retired = [...retiredExecutions].map(retryRetirement);\n let initialRetirement = Promise.resolve();\n\n if (!closed) {\n closed = true;\n active = false;\n activation += 1;\n graceRevision += 1;\n retainers = 0;\n initialRetirement = fenceExecution();\n releaseTarget();\n selectedKey = undefined;\n selectedTarget = undefined;\n cache?.clear();\n const nextGeneration = allocateGeneration();\n publish({ ...idleSnapshot, generation: nextGeneration });\n }\n\n const operation = (async () => {\n const failures: Error[] = [];\n await collectCleanupSettlements([initialRetirement, ...retired], failures);\n\n while (draining.size !== 0) {\n await collectCleanupSettlements([...draining], failures);\n }\n\n throwCleanupFailures(failures);\n })();\n closePromise = operation;\n void operation.catch(() => {\n if (closePromise === operation) closePromise = undefined;\n });\n\n return operation;\n };\n\n const ref: ManagedStreamRef<Data, Key> = Object.freeze({\n close,\n fence(): void {\n selectionRevision += 1;\n fenced = true;\n active = false;\n clearRetry();\n execution?.abort.abort();\n // Preserve the admission until retireExecution captures its physical drain.\n void execution?.admission?.close();\n observation?.changed?.();\n },\n getSnapshot: () => snapshot,\n inspectionState: (): ResourceSnapshot<Data, Key>['status'] | 'fenced' =>\n fenced || closed ? 'fenced' : snapshot.status,\n invalidate,\n observe: (slot: CallObservationSlot): void => {\n observation = slot;\n },\n refresh,\n retain,\n retry,\n subscribe(listener: () => void): () => void {\n if (typeof listener !== 'function') throw new TypeError('Stream listener must be a function.');\n\n listeners.add(listener);\n let subscribed = true;\n\n return (): void => {\n if (!subscribed) return;\n\n subscribed = false;\n listeners.delete(listener);\n };\n },\n });\n\n register?.(ref);\n\n if (options.retention.kind === 'scope') activate();\n\n return ref;\n}\n\nexport { createStreamController };\nexport type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer };\n"],"names":["createStreamController","options","register","listeners","observation","cache","createRetainedResourceCache","maximumAttempts","_a","retryDelayMs","_b","draining","retiredExecutions","active","closePromise","closed","fenced","activation","graceRevision","execution","generation","retainers","retryTimer","selectedKey","selectedTarget","selectionRevision","snapshot","idleSnapshot","unsubscribeTarget","allocateGeneration","report","error","reportRuntimeFailure","admitsExecution","expectedGeneration","isCurrentExecution","current","isCurrentRead","revision","notify","listener","publish","next","clearRetry","closeAdmission","admission","consumed","trackDrain","operation","retireExecution","reason","beforeAbort","reserved","reserveCleanupHandle","abortWithReason","disposal","disposeStreamExecution","settleCleanupOperations","retryRetirement","fenceExecution","workspaceFailure","reportFailure","data","readResourceData","consume","startConnect","createStreamAdmission","rejectSettlement","resolveSettlement","settlement","resolve","reject","connection","disposer","execute","settlementError","attempt","key","target","hasPredecessor","retired","previous","pendingSnapshot","openSelection","cached","nextGeneration","closeSelection","selectTarget","readTarget","releaseStaleSubscription","release","activationIsCurrent","activate","releaseTarget","unsubscribe","deactivate","requestDeactivation","ref","initialRetirement","failures","collectCleanupSettlements","throwCleanupFailures","slot","retained","subscribed"],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Opetope: guide for agents and humans writing code on the framework
|
|
2
|
+
|
|
3
|
+
A short, checkable document: how to decide the shape of your code, what is forbidden and which error you will see,
|
|
4
|
+
which checks to run. Details are in [cookbook.md](cookbook.md), the machinery in
|
|
5
|
+
[how-it-works.md](how-it-works.md), the laws in [spec.md](spec.md).
|
|
6
|
+
|
|
7
|
+
## 1. Where things live
|
|
8
|
+
|
|
9
|
+
Keep feature composition, model implementations and UI in separate layers. The paths below illustrate a
|
|
10
|
+
`features/<f>/{integration,models,ui}` layout; the host owns the application declaration and the bindings supplied
|
|
11
|
+
when it opens that application. The dependency boundaries apply regardless of the directory names.
|
|
12
|
+
|
|
13
|
+
| What you write | Where | Imports |
|
|
14
|
+
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| contracts (`defineHostContract`, `definePort`, `defineSlot`, `defineModel`, `defineCondition`) | `features/<f>/integration/platform/*.contract.ts`, `ui/**/contracts.ts`, `slots.ts`; the consumer owns the target contract, the feature owns the contribution value | `@opetope/core`, `@opetope/runtime`, types; `@opetope/react` for UI slot targets |
|
|
16
|
+
| the feature (`defineFeature`) | `features/<f>/integration/platform/feature.ts(x)` | contracts, its own models/UI, `@opetope/runtime` |
|
|
17
|
+
| models (`defineModel` + factories) | `features/<f>/models/**` | `@opetope/core` |
|
|
18
|
+
| contribution UI (`requiresModels`, hooks) | `features/<f>/ui/**` | `@opetope/core`, `@opetope/react` |
|
|
19
|
+
| application composition and host integration | the application's bootstrap and integration layers | `@opetope/runtime`, `@opetope/react/integration`; internal entries only for low-level integration |
|
|
20
|
+
|
|
21
|
+
Keep the `internal` entries and `@opetope/react/integration` in runtime implementation, host integration and tests.
|
|
22
|
+
Feature UI and model implementations must not import `@opetope/runtime`, another feature's implementation or their
|
|
23
|
+
own integration layer. Enforce these dependency boundaries with the host project's static checks.
|
|
24
|
+
|
|
25
|
+
## 2. How to choose the shape (a checklist before `defineFeature`)
|
|
26
|
+
|
|
27
|
+
1. What am I given from outside? The host → `defineHostContract` + `imports`. Another feature →
|
|
28
|
+
`imports: { x: feature }` (hard) or `optional(feature)` (may be absent or switch off). A capability by name →
|
|
29
|
+
`requires: { p: port }`.
|
|
30
|
+
2. When do I live? Always → no `when`. Under a condition → `when: [condition]`; the condition is declared either by
|
|
31
|
+
the host (`defineCondition({ id })`) or by another feature (`defineCondition({ from, id, select })`).
|
|
32
|
+
3. What do I do? Host calls → `calls(imports.x, [...])`; my own process → `call({ run, lane?, once?, singleFlight?,
|
|
33
|
+
policy?, within })`; data by key → `resource`; a stream → `stream` with `latest()`; external events → `event`;
|
|
34
|
+
a reaction to a change → `effect`; a subprocess under a condition → `scope.while`; a model with state →
|
|
35
|
+
`model(Decl, { source: imports.source }, (ctx, { source }) => …)`.
|
|
36
|
+
4. What do I hand out? To other features → `exports` (`Call | Readable | Resource`, model fields, not the model).
|
|
37
|
+
To the application → `provides.port`. To the interface → `provides.slot/pipe/register`.
|
|
38
|
+
5. UI? Only as a slot contribution. The component declares per-mount models through `requiresModels([...])`; the
|
|
39
|
+
`own` models are available on their own.
|
|
40
|
+
|
|
41
|
+
A feature whose implementation is heavy is written as a header and a body: the header keeps `id`, `imports`,
|
|
42
|
+
`requires`, a metadata `provides`, `when` and `body: () => import(...)`, and the body file writes
|
|
43
|
+
`defineFeature.body(header, { own, exports, provides })`. The application and consumers import the header only, and
|
|
44
|
+
the body loads when an instance opens (D186, D207). `defineFeature.preload(feature)` fetches that code early without
|
|
45
|
+
opening anything; on a feature with no body it is a successful no-op (D208).
|
|
46
|
+
|
|
47
|
+
Declaration stages (`own` and outer `provides`) see refs. Instance stages (`exports`, nested contribution
|
|
48
|
+
factories and model factories) receive materialized values. Compose model dependencies with a readonly map of
|
|
49
|
+
current-feature imports and call refs, including `requires.x`; optional imports keep their lookup projection.
|
|
50
|
+
Use `port(Port, { from: own.model, select: value => value.call })` for a model's call. `calls(imports.x, keys)`
|
|
51
|
+
accepts host methods, exported calls of a hard feature import, and exported calls over `optional(feature)`, where
|
|
52
|
+
they answer `CallError` `unavailable` while nothing provides them. Read the data of a weak edge with
|
|
53
|
+
`fromOptional(source, select, { missing })` instead of unfolding `lookup.kind` by hand (D187). For resources and streams, `target` selects a
|
|
54
|
+
`Readable<T | null | undefined>` and `retention` is optional; `scoped({ capacity })` fixes LRU. Effect filtering
|
|
55
|
+
uses `when(current, previous)`. These are the canonical shapes without compatibility aliases (D168, D169).
|
|
56
|
+
|
|
57
|
+
Declare a command with `ctx.call(options)` in a model or `call(options)` in `own`. Execute an existing command only
|
|
58
|
+
through `context.invoke(target, input)` in its execution context, or destructure it:
|
|
59
|
+
`run: (input, { invoke }) => invoke(deps.submit, input)`. When migrating, rename execution-context accesses,
|
|
60
|
+
destructuring and custom context types together; factory `call`, `calls` and the `Call` noun remain (D243).
|
|
61
|
+
The nested invocation inherits cancellation, authority and the lane stack; it does not choose a new policy.
|
|
62
|
+
|
|
63
|
+
A contribution's `when` is either a `Readable<boolean>` or a pure, synchronous predicate of the instance —
|
|
64
|
+
`({ exports, imports, own, read }) => boolean`. Its `read` only records what the answer depends on: do not write,
|
|
65
|
+
call or await inside it, and expect it to run again whenever a source it read changes. In that context `own` is
|
|
66
|
+
materialized, so a model field is a `Readable` and not the declaration ref (D220). A `pipe` declares its handler as
|
|
67
|
+
a descriptor with one key — `pipe(target, { fold: (value, meta, context) => next })` — whose `context` is that same
|
|
68
|
+
evaluation context. The `fold` runs only when the target folds: neither the declaration, nor
|
|
69
|
+
`defineFeature.preload`, nor the opening of the instance calls it, so keep the work that must happen on open in
|
|
70
|
+
`own` (D223).
|
|
71
|
+
|
|
72
|
+
## 3. Forbidden — and what you will see
|
|
73
|
+
|
|
74
|
+
| Shape | Why not | Signal |
|
|
75
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `exports: ({ own }) => ({ m: own.model })` | a model is a record of values | typetest/`prepare`: `Feature export m must be a Call, a Readable or a Resource.` |
|
|
77
|
+
| `ctx.update(foreignReadable, v)` | the writer stays with the owner | a type error; at runtime `TypeError: Model update accepts only state created by this model context.` |
|
|
78
|
+
| `onDemand(feature)` | no such word since D105 | a type error and a `TypeError` in `feature-contract` |
|
|
79
|
+
| `optional(optional(x))` | one wrapper only | a type error and a `TypeError` |
|
|
80
|
+
| `when: 'authorized'` | only a `Condition` | a type error and a `TypeError` |
|
|
81
|
+
| a hard import where `provider.when ⊄ consumer.when` | the provider must live at least as long | the compiler: `… cannot depend on shorter-lived provider …; use optional for a weak edge.` |
|
|
82
|
+
| two providers of one port | one provider per application | the application compiler |
|
|
83
|
+
| `useModel(X)` of a per-mount contribution model in a component or hook without `requiresModels([X])` (nested ones included) | the type does not see the requirement; `own` models need no declaration (D158) | declaration review; runtime `missing` only if the model is absent from the frame |
|
|
84
|
+
| `throw` for a product outcome of a command | outcomes are values (D86) | `useCommand` shows `lastError` only for `failed`; `cancelled` is not an error |
|
|
85
|
+
| kernel words in public names (`Module`, `Attachment`, `Executor`, `Authority`, `Owner`, `Blueprint`, `IR`) and retired words (`Task`, `Signal`, `Domain`, `View`, `Setup`) | the author's vocabulary | the `ci:public-surface` gate |
|
|
86
|
+
| `createState`/`isCancellation` from `@opetope/core`, `useFeatureError` | off the safe entries (D142) | no such export; use `ctx.state`, and read cancellation through `useCommand` |
|
|
87
|
+
| wrapping every host method in `context.call` by hand | `context.calls(source, keys, { lane })` (D143) | — |
|
|
88
|
+
| `when: () => readable`, `when: async () => …`, `when: ({ read }) => read(counter)` | a predicate answers the fact, not a source, a promise or a truthy value (D220) | a type error; at runtime `TypeError: Feature contribution when predicate must return a boolean.` |
|
|
89
|
+
| `pipe(target, handler)` or `pipe(target, () => handler)` | a pipe takes a `{ fold }` descriptor (D223) | a type error; at runtime `TypeError: Feature pipe expects a descriptor: pipe(target, { fold: … }).` |
|
|
90
|
+
|
|
91
|
+
## 4. Errors: read the `code`, not the text
|
|
92
|
+
|
|
93
|
+
`FeatureError` (`not-ready | retired | quarantined | cleanup-failed`), `CallError` (`cancelled | closed |
|
|
94
|
+
publication-rejected | unavailable`), `ReadableError` (`closed`), `ContributionError` (`missing | inactive | duplicate |
|
|
95
|
+
binding-invalid`), `ApplicationError` (`closed`), `DeclarationError` (`invalid-id`). Cancellation is a brand, not a class: `CallError` only with the codes `cancelled`/`closed`, `FeatureError` `retired`, `ContributionError` `inactive` (D138); `unavailable` and `publication-rejected` are product answers, and `useCommand` reports them as `failed` with `lastError`. The predicate `isCancellation` lives on `@opetope/core/internal` (D142) — a feature author does not need it. Closed state propagates through derived nodes: `derive` over a provider that is gone throws `closed` (D146).
|
|
96
|
+
|
|
97
|
+
Close a feature through `await instance.close()`. `FeatureError.retryCleanup` is optional and appears only for a
|
|
98
|
+
retryable quarantined frontier; check it before invoking. Other errors do not expose a no-op retry. Demand retry
|
|
99
|
+
belongs to its current source, and `Resource.retry()` is a separate resource operation (D168, D170).
|
|
100
|
+
|
|
101
|
+
A model's state belongs to its creating context, even when another model holds an `OwnedState` of the same type.
|
|
102
|
+
Register nothing after the lifetime fence. A failed factory must release its partial kernel, and cancellation
|
|
103
|
+
must not release the source before admitted work physically drains. For React effects, depend on destructured
|
|
104
|
+
`useCommand(...).run`, not the status-bearing result object. Mount props update as one snapshot in layout before
|
|
105
|
+
paint; the adapter and direct props follow the same rule (D170).
|
|
106
|
+
|
|
107
|
+
Declare `policy: 'latest'` in the model's `context.call` for an absolute value setter that must deliver the newest
|
|
108
|
+
input while a previous one waits; the consumer names no policy of its own (D185, D203). The default `queue` runs
|
|
109
|
+
every input in order and keeps button behaviour. A replaced waiting input gets `cancelled`, not a success or failure
|
|
110
|
+
callback; never declare `latest` for a sequence whose intermediate operations matter. A command's lane and physical
|
|
111
|
+
lifetime remain model responsibilities.
|
|
112
|
+
|
|
113
|
+
## 5. Checks before commit
|
|
114
|
+
|
|
115
|
+
From the package: `npx tsc --noEmit`, `npm run ci:test`, `npm run ci:eslint`, `npm run ci:size-limit`.
|
|
116
|
+
In a consuming application, type-check the integration, run the affected feature tests and verify the production
|
|
117
|
+
build, chunk placement and application size budgets using that project's commands.
|
|
118
|
+
From the root: `npm run ci:eslint`, `npm run ci:oxlint`, `npm run ci:format`, `npm run ci:unused`,
|
|
119
|
+
`npm run ci:docs`.
|
|
120
|
+
From `tooling/stress`: `npm run ci:public-surface` on any change to a public name, `ci:type-stress`,
|
|
121
|
+
`ci:perf-memory`, `ci:inspection`.
|
|
122
|
+
|
|
123
|
+
### Turn on `@opetope/lint`
|
|
124
|
+
|
|
125
|
+
The package ships the checkable part of the laws above, so a review does not have to spell them out. Register its
|
|
126
|
+
flat config on the files where features, models and their UI live:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
// eslint.config.mjs
|
|
130
|
+
import opetope from '@opetope/lint';
|
|
131
|
+
|
|
132
|
+
export default [
|
|
133
|
+
{
|
|
134
|
+
files: ['src/**/*.{ts,tsx}'],
|
|
135
|
+
...opetope.configs.recommended,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`recommended` holds the rules that need no knowledge of a host's directories. The rules, their fixes and the
|
|
141
|
+
boundary of each one are in [`../lint/README.md`](../../lint/README.md).
|
|
142
|
+
|
|
143
|
+
## 6. Changing the framework, not a feature
|
|
144
|
+
|
|
145
|
+
- A public name or shape — only with a compile-checked pilot in a consuming application and a line in the decision log; do not
|
|
146
|
+
raise size or perf limits to hide a regression. An authorized vocabulary addition stays within the public-surface
|
|
147
|
+
target and updates its recorded budget with the decision (§5 of the spec).
|
|
148
|
+
- Files in `core/runtime/react/src` are ≤ 600 lines; split them by state machine, compiler, binding, or host
|
|
149
|
+
boundary.
|
|
150
|
+
- Hot path: no `Reflect.apply`/bind/argument arrays per call, no `Object.freeze` inside dispatcher loops, no `Error`
|
|
151
|
+
on a success path; a new allocation per operation only with a measurement.
|
|
152
|
+
- Internal names = the public words of the same concept; `module` is the kernel word for the unit of lowering, and
|
|
153
|
+
it stays in `internal`.
|
|
154
|
+
|
|
155
|
+
## 7. Reviewing your own code (the seven lenses, briefly)
|
|
156
|
+
|
|
157
|
+
A law from §4 → where is its test; authority → nothing extra on a public entry; races → close during open, retire
|
|
158
|
+
during a call, a React commit later than a microtask; performance → allocations per call, O(N) per event; DX → the
|
|
159
|
+
words of §3 and typetests on the negatives, error texts that name the feature/instance/contribution and suggest the
|
|
160
|
+
fix; over-engineering → does the mechanism have a production caller; the human → the person who closes a gate is
|
|
161
|
+
not its author.
|
|
162
|
+
|
|
163
|
+
## Data and commands in a component
|
|
164
|
+
|
|
165
|
+
`useModel(Declaration, (model, { read }) => ({ ... }))` selects data and command consumers in one hook (D205, D214).
|
|
166
|
+
|
|
167
|
+
A model selection can use a named interface without an index signature (D217). Its result is a flat data record;
|
|
168
|
+
arrays, functions, constructors and built-in collection/date/promise objects are not selection records.
|
|
169
|
+
The readonly inferred record maps authentic Call fields to `CommandHook`; other selected fields preserve their types.
|
|
170
|
+
`read(readable, project?)` reads explicitly, deduplicates source subscriptions and applies no automatic Resource retain.
|
|
171
|
+
Returned fields use `Object.is` equality; fresh nested objects are changed fields. The pure callback cannot call hooks,
|
|
172
|
+
commands or side effects. Subscription replacement and command admission happen at commit, not in an abandoned
|
|
173
|
+
render. Keys, aliases, local statuses and `.run` follow `useCommands`. No model creation, feature acquire or new
|
|
174
|
+
scheduler is implied. `useModel(Declaration)` keeps returning the granted model, with a constant hook layout when
|
|
175
|
+
selection changes. Sharing one hook is a DX choice, not a performance guarantee.
|
|
176
|
+
|
|
177
|
+
## Scenario tests and physical activity
|
|
178
|
+
|
|
179
|
+
`createScenario(application, options)` from `@opetope/react/testing` opens the real application and its existing
|
|
180
|
+
inspection session (D206, D215). Supply the normal `imports`/`conditions` and a test-owned
|
|
181
|
+
`host.mount(Component)` adapter returning an `unmount()` handle. The package adds no DOM renderer or test-runner
|
|
182
|
+
dependency. `scenario.mount(target, { props })` uses the published Slot contributions and returns
|
|
183
|
+
`{ host, updateProps, unmount }`; `host` is the renderer's original result. Typed targets require `options.props`,
|
|
184
|
+
while targets without props omit it, exactly as with `Slot` (D217). Fixture commands do not bypass authority.
|
|
185
|
+
|
|
186
|
+
The synchronous constructor exposes `ready`, so a test can inspect a pending lazy body before readiness.
|
|
187
|
+
`waitFor(snapshot => predicate, { label, timeoutMs, pollIntervalMs })` wakes on inspection changes and also polls
|
|
188
|
+
external UI predicates; `notify()` wakes it after a controlled fixture update. The default deadline is 1000ms,
|
|
189
|
+
with a 10ms predicate poll. A `ScenarioTimeoutError` carries the data-only snapshot, bounded history and observed
|
|
190
|
+
conditions, feature phases, body loads, lane blockers and resource retention facts. It does not infer repository
|
|
191
|
+
or network causes. `getSnapshot()` and `history()` use that same observation model; history defaults to 64 snapshots,
|
|
192
|
+
activity to 256 records. Capacities accept integers from 1 to 10000. Do not replace predicates with a fixed number of ticks.
|
|
193
|
+
|
|
194
|
+
`close()` fences application admission synchronously, unmounts all registered screens and joins their cleanup with
|
|
195
|
+
physical application drain. Its deadline does not cancel cleanup: a later `close()` can await the same drain.
|
|
196
|
+
A readiness deadline likewise leaves the application available for inspection and explicit cleanup.
|
|
197
|
+
`ownership()` reports only registered runtime ownership, with `unknown` for missing, stale or truncated evidence;
|
|
198
|
+
a workspace stale snapshot is complete only after the scenario witnessed successful physical cleanup. This permits
|
|
199
|
+
a scoped zero-count assertion, without proving absence of arbitrary host, UI or GC leaks. Successful cleanup clears
|
|
200
|
+
application imports and internal renderer references. A failed cleanup promise can retain original errors and retry
|
|
201
|
+
capabilities; a caller that keeps `mounted.host` also keeps its own renderer result.
|
|
202
|
+
|
|
203
|
+
The inspection graph/frame schema is `/3`, with optional `opetope.runtime-activity/1` snapshots. Within one session,
|
|
204
|
+
a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
|
|
205
|
+
(D216). Activity-bearing frames replace the previous activity in full.
|
|
206
|
+
Use matching runtime/devtools versions: `/2` readers reject the new revision. Activity identifies the execution,
|
|
207
|
+
actual feature generation, physical Calls, exact current lane blockers, registered resource leases and load attempts.
|
|
208
|
+
Host demand and UI models are unknown; stream observation covers state, not physical load identities. `freshness`
|
|
209
|
+
and `truncated` distinguish a complete live view from a partial or detached one. A closed session is stale;
|
|
210
|
+
`closed: true` requires successful physical application drain. No control authority or product payload is added.
|
|
211
|
+
Activity output is bounded by record capacity. Snapshot collection still visits registered owners, executors and
|
|
212
|
+
resources, so capacity does not bound traversal cost. Collection stops once truncation is proven;
|
|
213
|
+
idle executors may still require traversal to establish completeness. Normal call dispatch allocates no diagnostic record with
|
|
214
|
+
observation disabled. Graph frames remain bounded by the existing ring capacity.
|