@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
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksei Berezin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# `@opetope/runtime`
|
|
2
|
+
|
|
3
|
+
The runtime with no renderer binding: the feature declaration, the opening of its instance, the lowering of
|
|
4
|
+
attachments and a lifetime graph checked in advance. [The specification](./docs/spec.md) §3 sets the vocabulary of the package; everything not named
|
|
5
|
+
here lives on `@opetope/runtime/internal`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @opetope/core @opetope/runtime
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use matching Opetope versions. For release candidates, append `@next` to every `@opetope/*` package in the command.
|
|
14
|
+
The API is ESM-only; Node 20.19+ is required. Development check commands below apply to a contributor checkout.
|
|
15
|
+
|
|
16
|
+
The normative EN/RU guides are shipped in `@opetope/runtime`: after installing it, open
|
|
17
|
+
`node_modules/@opetope/runtime/docs/spec.md` or `spec.ru.md`; recipes are in `cookbook.md` and `cookbook.ru.md`.
|
|
18
|
+
No GitHub access is needed to read those installed guides.
|
|
19
|
+
|
|
20
|
+
## Hello module
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { defineFeature, defineHostContract, openFeature } from '@opetope/runtime';
|
|
24
|
+
|
|
25
|
+
type Clock = { tick(): PromiseLike<void> | void };
|
|
26
|
+
declare const clockService: Clock;
|
|
27
|
+
|
|
28
|
+
const clockContract = defineHostContract<Clock>('example.clock.service');
|
|
29
|
+
|
|
30
|
+
const clockFeature = defineFeature({
|
|
31
|
+
id: 'example.clock',
|
|
32
|
+
imports: { service: clockContract },
|
|
33
|
+
own: ({ attach, call, imports }) => {
|
|
34
|
+
const clock = attach(imports.service, {
|
|
35
|
+
open: ({ source, timers }) => timers.interval(1_000, () => source.tick()),
|
|
36
|
+
});
|
|
37
|
+
const tick = call({ within: clock, run: (_input: void, { source }) => source.tick() });
|
|
38
|
+
|
|
39
|
+
return { clock, tick };
|
|
40
|
+
},
|
|
41
|
+
exports: ({ own }) => ({ tick: own.tick }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const instance = openFeature(clockFeature, {
|
|
45
|
+
imports: { service: clockService },
|
|
46
|
+
reporter: error => void error,
|
|
47
|
+
});
|
|
48
|
+
const ready = await instance.ready;
|
|
49
|
+
await instance.close();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`attach` derives the id from the `clock` key and takes the value from an import, so the
|
|
53
|
+
lifecycle is always bound to what the host or another feature gave. The disposer from `timers.interval`
|
|
54
|
+
is closed together with the instance.
|
|
55
|
+
`timers.delay`/`timers.interval` physically cancel a callback that has not started yet and pass the signal of the
|
|
56
|
+
current attachment generation. Cancelling asynchronous work that has already started is cooperative. An interval does
|
|
57
|
+
not wait for the Promise of the previous callback and therefore allows overlap; backpressure and execution policy
|
|
58
|
+
belong to `effect`/`CallLane`, not to the timer.
|
|
59
|
+
A cleanup failure is best-effort `report` by default; the retryable `quarantine` is enabled explicitly only for
|
|
60
|
+
resources that must have a physical release.
|
|
61
|
+
|
|
62
|
+
The data sugar lives in that same `own` section and is lowered into ordinary attachments:
|
|
63
|
+
|
|
64
|
+
- `effect({ from, when?, run, onDispose? })` takes an imported `Readable`, runs for the
|
|
65
|
+
initial snapshot and then latest-wins; the signal, the per-run timers and the returned disposer belong to that
|
|
66
|
+
exact run;
|
|
67
|
+
- `event(from, subscribe, { run })` gives the `subscribe` context a non-replayed `emit`, serialises the handler and
|
|
68
|
+
limits the queue to one pending payload; a failure and an overflow go to the application reporter.
|
|
69
|
+
Retirement aborts the handler, drops the pending payload and drains work already started before the source is released;
|
|
70
|
+
- `resource(from, target, { key, load, retry?, retention? })` watches a
|
|
71
|
+
`Readable<T | null | undefined>` returned by the target selector:
|
|
72
|
+
`null` or `undefined` closes the materialization, and a change of `key(target)` fences the old load and opens a new
|
|
73
|
+
instance. `refresh` keeps stale data only for the same key, `invalidate` clears it, and
|
|
74
|
+
`retry` repeats a failure that can be repeated, inside the current instance;
|
|
75
|
+
- `stream(from, target, { backpressure, connect, consume?, key, retention? })` applies
|
|
76
|
+
backpressure before publication and `consume`, fences the emitter of the old generation and physically closes the
|
|
77
|
+
source before the imported value is released.
|
|
78
|
+
|
|
79
|
+
`model` gives that same `own` section owned data instead of one more attachment:
|
|
80
|
+
|
|
81
|
+
- `model(Declaration, create)` needs no dependencies. `model(Declaration, { platform: imports.platform,
|
|
82
|
+
lookup: requires.lookup }, create)` supplies a readonly map of resolved dependencies when the instance opens.
|
|
83
|
+
Use the map even for one dependency. The factory declares its own dependency interface; it receives ready
|
|
84
|
+
values, not the feature context or declaration refs. Authentic imports and call refs of the current feature are
|
|
85
|
+
accepted; optional imports keep their `Readable<Lookup<…>>` projection. Raw services, port declarations, foreign
|
|
86
|
+
refs and another model ref are rejected (D169).
|
|
87
|
+
|
|
88
|
+
`ModelContext` is not generic and gives a model its own kernel (D73): `state` creates an `OwnedState`, `update(state, value)`
|
|
89
|
+
is the single writer and accepts only that, `call`, `calls`, `lane`, `effect`, `event`, `resource`, `stream`,
|
|
90
|
+
`scope`, `timers` and `cleanup` open the nodes of the model when the instance opens and close them in its drain; `calls(deps, keys, { lane })`
|
|
91
|
+
is the same selection of methods as `own.calls`, only the source is the ready dependencies of the model (D143); `from` in `resource`/`stream`/`event` is a
|
|
92
|
+
ready dependency value, not an import ref,
|
|
93
|
+
`signal` is aborted on retire, and a write after retire goes to the reporter as a
|
|
94
|
+
`FeatureError` with the code `retired` instead of being thrown at the caller. Owned state lives only in a model: `own`
|
|
95
|
+
has no `state` method, because there would be nobody to write into such state — `ctx.update` accepts only what
|
|
96
|
+
its own context created (D139). The refs of `model()` take no part in the lowering IR: they cannot be exported as a call and
|
|
97
|
+
cannot be used in `within`. A contribution component reads the models of its feature through `useModel` inside the
|
|
98
|
+
contribution mount, which binds them automatically. A single field goes outward through
|
|
99
|
+
`exports: ({ own }) => ({ x: own.model.field })`. A model call can also provide a port:
|
|
100
|
+
`port(SubmitPort, { from: own.order, select: order => order.submit })`. The selector runs once on the materialized
|
|
101
|
+
model before publication; the selected authentic `Call` is fenced by the provider's lifetime even if it passes
|
|
102
|
+
through a dependency call. The direct `port(SubmitPort, own.submit)` form remains for a declared call ref (D169).
|
|
103
|
+
|
|
104
|
+
`target` is a selector `(source) => Readable<T | null | undefined>` in both feature and model APIs.
|
|
105
|
+
TypeScript infers the payload of the returned `Readable`, whose shape is checked before opening; a scalar
|
|
106
|
+
snapshot or property-name string is not accepted. `null` and `undefined` close the materialization.
|
|
107
|
+
`retention` is optional in both APIs; `scoped({ capacity })` uses LRU eviction with no additional option.
|
|
108
|
+
|
|
109
|
+
Both APIs publish one atomic snapshot `idle | opening | ready | refreshing | error` with the
|
|
110
|
+
`key`, the generation, the attempt, the stale data and the failure. `subscribe` only observes and activates no
|
|
111
|
+
work; demand is set by a separate `retain()` lease. Retention by default merges the StrictMode
|
|
112
|
+
release/reacquire into one microtask and aborts unfinished work after the last lease. An explicit
|
|
113
|
+
`scoped({ capacity })` holds the materialization until retirement and bounds the sticky
|
|
114
|
+
cache. The UI of a feature gets an instance-bound facade through `bindFeatureResource(instance, own.x)`,
|
|
115
|
+
and the React `useResource` holds one lease per mounted consumer.
|
|
116
|
+
|
|
117
|
+
There are exactly two policies, and both have a production pilot: `scoped({ capacity })` for retention and
|
|
118
|
+
`latest()` for stream backpressure. The former `drop`, `sample` and `queue` are removed together with their branches:
|
|
119
|
+
they could not be named from the author vocabulary, so they measured only themselves (D160).
|
|
120
|
+
|
|
121
|
+
## Calls and lanes
|
|
122
|
+
|
|
123
|
+
`call(options)` declares a command; `invoke(target, input)` executes one. In a feature or model `run`, use
|
|
124
|
+
`run: (input, { invoke }) => invoke(target, input)` or `context.invoke(target, input)`. Trusted attachment steps
|
|
125
|
+
use the same name on their execution `context`. There is no execution `call` alias (D243).
|
|
126
|
+
|
|
127
|
+
`call({ within, run })` creates an instance call with a private FIFO lane by default. Several
|
|
128
|
+
calls can be serialised by one `lane({ within })`; a lane cannot be moved to another attachment.
|
|
129
|
+
A nested `context.invoke(localRef, input)` on the same lane runs without a self-deadlock, while cycles across
|
|
130
|
+
several lanes and a recursive single flight end with a diagnostic error.
|
|
131
|
+
|
|
132
|
+
`policy: 'parallel'` is always explicit. `singleFlight: fn` merges only equal keys:
|
|
133
|
+
cancelling one caller does not cancel the others, and the departure of the last one aborts the physical run. A successful
|
|
134
|
+
`once: true | fn` lives exactly one instance; a failure is not cached. `policy: 'latest'` replaces an input still
|
|
135
|
+
waiting at the end of the lane, preserving a running body and the order across other Calls (D185). Retirement first
|
|
136
|
+
fences the settlement and releases the lane, then
|
|
137
|
+
waits for the physical drain before the attachment is cleaned up.
|
|
138
|
+
|
|
139
|
+
`calls(imports.host, keys)` selects host methods; `calls(imports.feature, keys)` selects exported calls from a
|
|
140
|
+
hard feature import with the same inferred inputs and outputs. For an optional feature import it creates calls
|
|
141
|
+
that resolve the current provider at invocation and fail with `unavailable` while it is missing (D187). The import
|
|
142
|
+
itself remains a lookup; `fromOptional` from Core selects its data with an explicit missing value.
|
|
143
|
+
|
|
144
|
+
Host methods selected by `calls` may take no parameters, one input, or an input followed by `AbortSignal`;
|
|
145
|
+
optional input/signal are supported. No parameters means `Call<void, Awaited<Output>>`. Methods keep their receiver and
|
|
146
|
+
receive `(input, signal)` uniformly; the runtime never reads `Function.length` or guesses where a client expects
|
|
147
|
+
options. For `client.load(input, { signal })`, provide the ordinary adapter
|
|
148
|
+
`load: (input: Input, signal: AbortSignal) => client.load(input, { signal })`. A lone parameter named `signal`
|
|
149
|
+
is an input; inject cancellation into a signal-only method explicitly in `call({ run })` (D244).
|
|
150
|
+
|
|
151
|
+
A command's body receives its own execution signal and a nested `invoke` inherits cancellation and the lane stack.
|
|
152
|
+
`Resource.load` and `Stream.connect`/`consume` receive the current target execution's signal: changing that target,
|
|
153
|
+
refreshing or releasing the resource can abort work while the model stays alive. `ModelContext.signal` belongs to
|
|
154
|
+
the entire model. Logical cancellation does not stop a method that ignores its signal or undo its writes; physical
|
|
155
|
+
drain waits for admitted work. Plain setters and methods without I/O need no dummy `_signal` (D244).
|
|
156
|
+
|
|
157
|
+
`instance.close()` fences synchronously and returns one shared completion promise. A failed cleanup may produce a
|
|
158
|
+
`FeatureError` with `code: 'quarantined'` and an optional `retryCleanup` capability. Check that capability before
|
|
159
|
+
calling it; ordinary errors have no retry function. `Resource.retry()` and feature-demand retry remain separate
|
|
160
|
+
operations. The internal lifecycle name and cancellation code `retired` stay unchanged (D168).
|
|
161
|
+
|
|
162
|
+
## Lazy feature bodies
|
|
163
|
+
|
|
164
|
+
A lightweight header declares topology through `id`, `when`, `imports`, `requires` and `provides` metadata, plus
|
|
165
|
+
`body: () => import('./feature.body')`. Its body uses `defineFeature.body(header, { own, exports, provides })`;
|
|
166
|
+
`FeatureBody<Exports>` names the loader result without a circular dependency, and `FeatureBodyOf<Header>` types
|
|
167
|
+
the body input from the header's imports, requirements and exports. `defineFeature.preload(header)` fetches code without opening an instance (D186, D207, D208).
|
|
168
|
+
|
|
169
|
+
Eager and lazy `openFeature` validate and snapshot options, imports and requirements immediately. A loader throw
|
|
170
|
+
rejects `ready` or `preload`. Each definition shares its code load, while each instance has its own lifetime:
|
|
171
|
+
closing before code arrives rejects that instance's readiness as `retired` and completes without waiting for the
|
|
172
|
+
shared load. Other instances and preloads still use it; late success cannot reopen the closed instance and failed
|
|
173
|
+
loads may be retried. Once materialization starts, close drains the work already owned, including a reentrant
|
|
174
|
+
close from a model factory; synchronous fencing, a shared close promise and quarantine recovery match eager
|
|
175
|
+
instances (D211).
|
|
176
|
+
|
|
177
|
+
## Dynamic scopes
|
|
178
|
+
|
|
179
|
+
`scope.while`, `scope.switch` and `scope.keyed` drive a child generation from an imported
|
|
180
|
+
`Readable`; the `open(value, { signal, timers })` callback returns a disposer or `void`. They compile the
|
|
181
|
+
child into the same lifetime IR as an ordinary `attach`, and the controller only translates changes of the
|
|
182
|
+
source into an opening and a retire of the scope that already exists.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
own: ({ imports, scope }) => ({
|
|
186
|
+
authorized: scope.while({
|
|
187
|
+
from: imports.authorized,
|
|
188
|
+
open: (_authorized, { signal }) => connection.open(signal),
|
|
189
|
+
}),
|
|
190
|
+
selected: scope.switch({
|
|
191
|
+
from: imports.selected,
|
|
192
|
+
key: item => item.id,
|
|
193
|
+
open: (item, { signal }) => repository.open(item.id, signal),
|
|
194
|
+
}),
|
|
195
|
+
rows: scope.keyed({
|
|
196
|
+
from: imports.rows,
|
|
197
|
+
key: row => row.id,
|
|
198
|
+
open: (row, { signal }) => cache.retain(row.id, signal),
|
|
199
|
+
}),
|
|
200
|
+
}),
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`while` without `when` uses truthiness. `switch` treats `null`/`undefined` as the closed state,
|
|
204
|
+
closes the old generation before the new one and on `A → B → C` opens only the current `C`; an equal
|
|
205
|
+
explicit `key` keeps the current generation. `keyed` holds an independent child generation per
|
|
206
|
+
unique `string | number` key. A repeated key rejects the whole new snapshot before any change to the children that are
|
|
207
|
+
already active. Retire fences the children synchronously and waits for their physical drain.
|
|
208
|
+
|
|
209
|
+
The `provides` section declares both ports and contributions: `port` binds a call ref to a `Port`, while `slot`, `pipe` and
|
|
210
|
+
`register` declare a contribution by the kind of its target:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { definePipe } from '@opetope/core';
|
|
214
|
+
import { defineFeature } from '@opetope/runtime';
|
|
215
|
+
|
|
216
|
+
const titlePipe = definePipe<string>({ id: 'example.title' });
|
|
217
|
+
const titleFeature = defineFeature({
|
|
218
|
+
id: 'example.title-contributor',
|
|
219
|
+
provides: ({ pipe }) => ({
|
|
220
|
+
suffix: pipe(titlePipe, { fold: title => `${title} · ready` }, { priority: 20 }),
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The outer factory is synchronous and runs at `defineFeature`, so the target/id/priority are available
|
|
226
|
+
to the application compiler before an instance opens. The second argument of `pipe(...)` is a `{ fold }` descriptor:
|
|
227
|
+
the runtime binds it to the concrete instance after the critical barrier and calls the handler only on a fold (D223).
|
|
228
|
+
`slot` and `register` take either a value factory with a concrete `FeatureOpenContext`, which also runs only after
|
|
229
|
+
that barrier, or a ready contribution value. An exact plain record must
|
|
230
|
+
return every receipt exactly once; the id of an entry is derived as `featureId.key`. The whole batch goes through
|
|
231
|
+
preflight and is published atomically. The start of retirement removes the entries synchronously before the async cleanup, including
|
|
232
|
+
a reentrant retirement from a target listener; a failed opening or an error in the value factory leaves no partially
|
|
233
|
+
published contributions.
|
|
234
|
+
|
|
235
|
+
The feature API is declared by the `exports: ({ own }) => ({ … })` section: the contract is created from the feature
|
|
236
|
+
definition under the id `<feature>.exports`, and a consumer imports the definition itself — `imports: { provider: providerFeature }`.
|
|
237
|
+
Assigned missing/extra keys and incompatible `Call` signatures are rejected at compile time. Host-only values
|
|
238
|
+
use `defineHostContract<T>()`. `context.invoke`
|
|
239
|
+
accepts a local call of the same feature or a target from the imports of the current instance; a foreign local ref and a
|
|
240
|
+
target captured outside the declared imports are rejected by the runtime fence.
|
|
241
|
+
|
|
242
|
+
`onDemand(hostContract)` records a deferred connection in the import declaration for the compiled topology. The host
|
|
243
|
+
value keeps the same contract-derived type, and `attach(imports.x)` is lowered into the same lifecycle IR as
|
|
244
|
+
`attach(imports.x, { open: () => undefined })`; the wrapper accepts only one direct authentic host contract, allows no
|
|
245
|
+
re-wrapping and does not wrap a feature edge.
|
|
246
|
+
|
|
247
|
+
`optional(x)` in `imports` and `optional(port)` in `requires` are a weak edge: a consumer may outlive its
|
|
248
|
+
provider, so the value arrives as a `Readable<Lookup<…>>` projection, and a call to a weak port with no live provider
|
|
249
|
+
settles as a `CallError` with the code `unavailable`. This is an error, not a cancellation: `isCancellation` is false for it, and
|
|
250
|
+
`useCommand` returns the `failed` outcome with that error in `lastError` (D138).
|
|
251
|
+
|
|
252
|
+
## Word map
|
|
253
|
+
|
|
254
|
+
One page of the package vocabulary: RFC §3 sets the names, the README explains what each one is for.
|
|
255
|
+
|
|
256
|
+
| Word | What for |
|
|
257
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
258
|
+
| `defineFeature`, `Feature`, `FeatureBody`, `FeatureBodyOf` | the feature declaration; only `id` is required |
|
|
259
|
+
| sections `imports`, `requires`, `own`, `exports`, `provides`, `when` | what I am given, what I need, what I own and when I live |
|
|
260
|
+
| `defineHostContract`, `HostContract`, `onDemand` | the boundary with the host and a deferred connection |
|
|
261
|
+
| `optional` (its projection uses `Lookup` from `@opetope/core`) | a weak edge: a projection instead of a value |
|
|
262
|
+
| `attach`, `calls`, `call`, `lane`, `effect`, `event`, `model`, `resource`, `stream`, `scope.while`, `scope.switch`, `scope.keyed` | the methods of `own`: what a feature owns |
|
|
263
|
+
| `port`, `slot`, `pipe`, `register` | the methods of `provides`: what a feature offers extension points |
|
|
264
|
+
| `openFeature`, `FeatureInstance` | one opening of a feature: `ready`, `close` |
|
|
265
|
+
| `scoped`, `latest`, `ScopedRetention`, `LatestBackpressure`, `Resource`, `ResourceSnapshot` | retention policies and the snapshot of a resource |
|
|
266
|
+
| `defineApplication`, `Application`, `defineCondition`, `Condition`, `bind`, `openApplication`, `ApplicationExecution`, `ApplicationImportBinding` | the static topology, the conditions and the shared lifetime |
|
|
267
|
+
| `ErrorReporter`, `CleanupFailurePolicy` | where a failure goes and what to do with a failed cleanup |
|
|
268
|
+
| `FeatureError` with the codes `not-ready`, `quarantined`, `cleanup-failed`, `retired`; `ApplicationError` with the code `closed` | one class per subject, the state in the `code` field |
|
|
269
|
+
|
|
270
|
+
`defineModule`, `instantiateModule` and the whole `Module*`, `Call*`, `Attachment*` family live only in
|
|
271
|
+
`@opetope/runtime/internal`: this is the ABI for framework implementation and host integration, not the vocabulary of a feature author.
|
|
272
|
+
|
|
273
|
+
`Model` is a typed key for a record of `Readable` and `Call`. It declares the data and commands a consumer can
|
|
274
|
+
access; a model factory creates the implementation within an owned lifetime. UI components consume this contract.
|
|
275
|
+
|
|
276
|
+
An eager `defineFeature` is lowered into an immutable reusable `defineModule`; a lazy header defers implementation
|
|
277
|
+
lowering to `defineFeature.body`. `openFeature` creates an
|
|
278
|
+
isolated generation with an exact map of scopes, synchronous UI preparation, rollback, single-flight
|
|
279
|
+
retirement and quarantine retry. The advanced `defineModule`/`instantiateModule` stay available for
|
|
280
|
+
framework lowering, but are not the typical feature-authoring path.
|
|
281
|
+
`instance.ready` and `close()` are a throwing convenience facade with typed errors.
|
|
282
|
+
Detailed outcome unions are not wrapped in `.orThrow()`: they stay on the advanced path
|
|
283
|
+
`instantiateModule` → `scope.open`/`retirement.drain`, so that no second lifecycle API appears.
|
|
284
|
+
|
|
285
|
+
Consumer size scenarios are built from `size/**`, and the limits in `package.json` serve as coarse regression guards
|
|
286
|
+
with headroom, not as ratchets to the current byte. The preserve-modules inventory is deliberately not summed per
|
|
287
|
+
file: such a count grows from an architectural split without any growth of the consumer bundle. Validate the host's
|
|
288
|
+
initial and asynchronous chunks against its own budgets, and measure startup and retained heap on the application
|
|
289
|
+
workload. Package size checks alone do not establish application performance.
|
|
290
|
+
|
|
291
|
+
Static scope/attachment/call refs are available through `definition.own`; the raw target, the compiler IR
|
|
292
|
+
and the lifetime controller are not published. The exact value of a scope generation is passed into `open`, `close` and an
|
|
293
|
+
attachment-bound `call`, so one definition safely creates several instances with no shared
|
|
294
|
+
closure state.
|
|
295
|
+
|
|
296
|
+
The first `scope.open` seals the assembly. Module retirement synchronously fences the registered
|
|
297
|
+
renderer participants and every active scope, after which `drain` returns a single typed outcome.
|
|
298
|
+
`retry` walks only the quarantined frontier; workspace retirement handles stay idempotent.
|
|
299
|
+
The normal cancellation of a generation is represented by a `FeatureError` with the code `retired`; framework adapters use
|
|
300
|
+
`isCancellation`, while ordinary UI gets a `CommandOutcome` from `useCommand`. Call cancellation is the codes
|
|
301
|
+
`cancelled` and `closed`; `unavailable` and `publication-rejected` reach the product as an error.
|
|
302
|
+
An attachment callback must not call `drain`/`retry` of its own scope generation:
|
|
303
|
+
a synchronous self-wait is rejected before cleanup starts. A self-wait after an `await` of its own stays a
|
|
304
|
+
violation of the callback contract — without an async context token it cannot be told apart from a legitimate external
|
|
305
|
+
join, so the runtime keeps the single flight for external callers.
|
|
306
|
+
`call` infers the input, the output and the scope value from the callback and `within`; where necessary an author
|
|
307
|
+
annotates the `input` parameter. The partial form `call<Input, Output>(...)` is deliberately not an API: after an
|
|
308
|
+
explicit prefix TypeScript will not infer the hidden owner value without `any`.
|
|
309
|
+
|
|
310
|
+
The owner generation controller, the low-level compiler, lifetime binding, call target resolution and
|
|
311
|
+
retire receipts stay on `@opetope/runtime/internal`, not in the author vocabulary. The dependency direction is
|
|
312
|
+
one-way: the runtime uses `@opetope/core/internal`, and the core never imports the runtime.
|
|
313
|
+
|
|
314
|
+
The lowering layer gives immutable templates and independent instances: it computes the critical barrier, starts
|
|
315
|
+
deferred attachments only after the owner commits, keeps causal failures observable and returns
|
|
316
|
+
stable attachment identities in the outcomes of a failed opening and drain.
|
|
317
|
+
|
|
318
|
+
The attachments of one scope form a single wave, not a graph: there is no order between them either on opening or on
|
|
319
|
+
closing (D163). Critical attachments start before the owner commits, deferred ones after `ready`. Retire
|
|
320
|
+
first fences the whole live set, then drains it entirely; a failed attachment lands in the quarantine
|
|
321
|
+
frontier in the canonical order of slots, the rest are closed, and `retry` repeats exactly the failed ones.
|
|
322
|
+
|
|
323
|
+
A rolled-back quarantined opening recovers only through the private `retry` of the owner. A successful
|
|
324
|
+
recovery removes the dead child from the live set; the raw `retry` of the core is part of neither the lowering nor the
|
|
325
|
+
author vocabulary. A replacement child counts as proof that the old one is closed only after a settled `ready`
|
|
326
|
+
from the core.
|
|
327
|
+
|
|
328
|
+
While an instance is opening, its generation is not yet the current call authority: the lowering commits it
|
|
329
|
+
only after a successful critical barrier, and retire invalidates the generation before the parent and the children are notified.
|
|
330
|
+
The parent signal and the attachment signal form a single execution fence for steps and calls, while closing and cleanup hold
|
|
331
|
+
a separate release authority.
|
|
332
|
+
|
|
333
|
+
Opaque call receipts are returned under canonical runtime keys and compiled into frozen
|
|
334
|
+
bindings with no behaviour. A call can be bound only to a critical attachment; the physical target becomes current
|
|
335
|
+
only after the whole critical barrier of the owner commits and is fenced by retire before the children are cleaned up. The runtime mints
|
|
336
|
+
one direct target per binding and instance, and one transaction owns the publication of contributions into React.
|
|
337
|
+
|
|
338
|
+
The runtime has no temporary author entry: call target resolution, renderer participant registration and
|
|
339
|
+
the owner controllers stay an internal ABI on `@opetope/runtime/internal`.
|
|
340
|
+
|
|
341
|
+
## Compatibility contract
|
|
342
|
+
|
|
343
|
+
The runtime publishes ESM for Chrome 82+, Firefox 110+, Safari/iOS 15+, Android 82+ and Node 20.19+.
|
|
344
|
+
The required globals and the compatibility implementations belong to `@opetope/core`; the runtime installs no polyfills.
|
|
345
|
+
The raw dist passes the same syntax-target gate and the same HTTP import smoke test as the core.
|