@opetope/runtime 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +19 -9
- package/README.ru.md +21 -9
- package/dist/feature-authoring-types.d.ts +6 -10
- package/dist/feature-authoring.js +1 -1
- package/dist/feature-authoring.js.map +1 -1
- package/dist/feature-body.d.ts +1 -1
- package/dist/feature-body.js.map +1 -1
- package/dist/feature-contribution.d.ts +5 -3
- package/dist/feature-contribution.js +1 -1
- package/dist/feature-contribution.js.map +1 -1
- package/dist/feature-definition-api.d.ts +1 -1
- package/dist/feature-generation.js +1 -1
- package/dist/feature-generation.js.map +1 -1
- package/dist/feature-lazy-generation.d.ts +1 -1
- package/dist/feature-lazy-generation.js +1 -1
- package/dist/feature-lazy-generation.js.map +1 -1
- package/dist/feature-own-values.d.ts +10 -0
- package/dist/feature-own-values.js +2 -0
- package/dist/feature-own-values.js.map +1 -0
- package/dist/feature-port-binding.d.ts +2 -1
- package/dist/feature-port-binding.js +1 -1
- package/dist/feature-port-binding.js.map +1 -1
- package/dist/feature-port.d.ts +13 -23
- package/dist/feature-port.js +1 -1
- package/dist/feature-port.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +1 -1
- package/dist/public-module-types.d.ts +1 -1
- package/dist/public-module.d.ts +1 -1
- package/docs/agent-guide.md +11 -4
- package/docs/agent-guide.ru.md +11 -4
- package/docs/cookbook.md +9 -7
- package/docs/cookbook.ru.md +9 -7
- package/docs/decisions.md +40 -0
- package/docs/how-it-works.md +30 -5
- package/docs/how-it-works.ru.md +29 -5
- package/docs/spec.md +60 -18
- package/docs/spec.ru.md +61 -19
- package/package.json +3 -3
package/docs/spec.md
CHANGED
|
@@ -57,8 +57,8 @@ const catalogCatalogFeature = defineFeature({
|
|
|
57
57
|
...calls(imports.platform, ['resolveItem']), // host methods as Calls, one to one
|
|
58
58
|
}),
|
|
59
59
|
exports: ({ own }) => ({ resolveItem: own.resolveItem }), // for features that import this definition
|
|
60
|
-
provides: ({ port
|
|
61
|
-
resolve: port(resolveItemPort, own.resolveItem), // the port for whoever requires it
|
|
60
|
+
provides: ({ port }) => ({
|
|
61
|
+
resolve: port(resolveItemPort, ({ own }) => own.resolveItem), // the port for whoever requires it
|
|
62
62
|
}),
|
|
63
63
|
});
|
|
64
64
|
```
|
|
@@ -105,17 +105,18 @@ const createFormFeature = defineFeature({
|
|
|
105
105
|
order: model(OrderModel, { platform: imports.platform }, (ctx, { platform }) => createOrderModel(ctx, platform)),
|
|
106
106
|
lookup: requires.resolveItem, // inside `own` a port stays a ref
|
|
107
107
|
}),
|
|
108
|
-
//
|
|
109
|
-
exports: ({ own }) => ({ lookup: own.lookup
|
|
108
|
+
// consumers get the public lookup Call; submit stays inside this feature
|
|
109
|
+
exports: ({ own }) => ({ lookup: own.lookup }),
|
|
110
110
|
provides: ({ slot, pipe }) => ({
|
|
111
111
|
// a contribution is a value, an instance factory or a pipe descriptor; `priority` and `when` go in the options
|
|
112
|
-
content: slot(orderFormContentSlot, ({
|
|
112
|
+
content: slot(orderFormContentSlot, ({ own, model }) => ({
|
|
113
113
|
Component: CreateForm,
|
|
114
114
|
// the contribution's UI model: created per mount, and it sees the `props` of exactly that mount
|
|
115
115
|
models: [
|
|
116
116
|
model(OrderActions, (ctx, props: Readable<OrderFormProps>) => ({
|
|
117
117
|
submit: ctx.call({
|
|
118
|
-
run: (amount: number, { invoke }) =>
|
|
118
|
+
run: (amount: number, { invoke }) =>
|
|
119
|
+
invoke(own.order.submit, { amount, itemId: props.getSnapshot().itemId }),
|
|
119
120
|
}),
|
|
120
121
|
})),
|
|
121
122
|
],
|
|
@@ -181,7 +182,7 @@ publication barrier. All of it belongs to the runtime and its integration layer.
|
|
|
181
182
|
| `id` | What the feature is called | `'feature.entity'`, dots only, no suffixes |
|
|
182
183
|
| `imports` | What the outside gives me | a record of `defineHostContract` contracts and other feature definitions; `optional(x)` is a weak edge, `onDemand(hostContract)` defers binding to the first use |
|
|
183
184
|
| `requires` | Which port I need | a record of `port` or `optional(port)`; each `requires.x` is a ref, and the `Call` appears on the instance |
|
|
184
|
-
| ref vs Call | What is visible at which stage | `own`
|
|
185
|
+
| ref vs Call | What is visible at which stage | the `own` builder sees dependency refs; `provides` sees only builders; `exports`, port selectors and contribution factories see materialized instance values |
|
|
185
186
|
| `own` | What I own | builder: `model`, `call`, `calls`, `lane`, `effect`, `event`, `resource`, `stream`, `scope`, `attach` |
|
|
186
187
|
| `exports` | What I hand to features that import me | `({ own }) => a record of Call, Readable and Resource` computed when the instance opens |
|
|
187
188
|
| `provides` | What I offer to extension points | `({ port, slot, pipe, register, own }) => ...` |
|
|
@@ -269,14 +270,18 @@ the second factory argument is a readonly map of resolved values with inferred t
|
|
|
269
270
|
source, or `model(Decl, create)` for none. Only authentic imports and call refs of the current feature are accepted;
|
|
270
271
|
optional imports retain `Readable<Lookup<…>>`. Raw host objects, port declarations, foreign refs and model refs do
|
|
271
272
|
not form dependency entries. A named factory may declare its own dependency interface without seeing the feature
|
|
272
|
-
context. `
|
|
273
|
-
once
|
|
274
|
-
|
|
273
|
+
context. `port(SubmitPort, ({ own }) => own.order.submit)` selects an authentic call from the current instance's
|
|
274
|
+
`own` once during preparation, before readiness and contribution publication, and adds the provider's lifetime
|
|
275
|
+
fence, including passthrough calls. `port(SubmitPort, ({ own }) => own.submit)` selects a call declared directly in
|
|
276
|
+
`own`. The selector receives only `{ own }`; its result must be an authentic Call exposed by a value it selects
|
|
277
|
+
from this `own`, including a model field. Foreign Calls and plain functions are rejected. Direct refs and
|
|
278
|
+
`{ from, select }` descriptors are not accepted. Retirement during a selector prevents later selectors from running
|
|
279
|
+
and drains the work already owned (D255).
|
|
275
280
|
|
|
276
281
|
A contribution has two kinds of model, both declared with the same `defineModel`. `own` models live with the feature
|
|
277
282
|
instance and are served to a mount automatically. A contribution's UI model lives with the mount: it is listed in the
|
|
278
283
|
contribution's `models`, its factory receives the `Readable` of that mount's props as its second argument and either
|
|
279
|
-
takes fields as they are (`submit:
|
|
284
|
+
takes fields as they are (`submit: own.order.submit`) or wraps them in `ctx.call` when the UI contract differs in
|
|
280
285
|
input, in result shape or in the number of feature calls. There is no separate View concept.
|
|
281
286
|
|
|
282
287
|
### 2.3 UI
|
|
@@ -290,7 +295,9 @@ takes them as they are, with a `props` adapter it takes exactly the adapter's re
|
|
|
290
295
|
|
|
291
296
|
`opetope/require-declared-models` in `@opetope/lint` recommended checks visible `slot` contributions and model readers within one module. It resolves import aliases and local bindings, including named components. Imported implementations and dynamic declarations remain unverified; no diagnostic does not prove their requirements complete (D250).
|
|
292
297
|
|
|
293
|
-
|
|
298
|
+
Every mount is a boundary: a contribution whose render or commit throws is contained there, reported to the feature
|
|
299
|
+
that published it, and replaced by the content of `ContributionBoundary` — one host policy declared once, above every
|
|
300
|
+
slot, never per slot (D256). Hooks: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Components: `Slot`, and
|
|
294
301
|
`FeatureBoundary` from `@opetope/react/integration`, which holds a feature open and renders `fallback` or `error`.
|
|
295
302
|
Both of its branches take either a node or a render callback: `children` receives the ready instance typed by the
|
|
296
303
|
demand, `error` receives `{ error, retry }`, only the branch that is shown runs, and a ready consumer needs no
|
|
@@ -386,6 +393,17 @@ the call site. The optional `cleanupFailure` carries the same two words as `open
|
|
|
386
393
|
the host that chooses `quarantine` keeps a failed cleanup as an exact frontier on `FeatureError.retryCleanup`
|
|
387
394
|
instead of a report, and the devtools control port can then retry it (D182).
|
|
388
395
|
|
|
396
|
+
The root of the application is where a host states what a failed contribution shows, once for every mount below it:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
// bootstrap/root.tsx — the application root, above every slot
|
|
400
|
+
const Root = () => (
|
|
401
|
+
<ContributionBoundary error={({ error, retry }) => <FailedContribution error={error} onRetry={retry} />}>
|
|
402
|
+
<Slot target={applicationSurface} />
|
|
403
|
+
</ContributionBoundary>
|
|
404
|
+
);
|
|
405
|
+
```
|
|
406
|
+
|
|
389
407
|
---
|
|
390
408
|
|
|
391
409
|
## 3. Public vocabulary
|
|
@@ -437,17 +455,26 @@ remain. Resource retry and feature-demand retry keep their own meaning. The form
|
|
|
437
455
|
`event` the source and the carrier of type inference are positional: `resource(from, target, {…})`,
|
|
438
456
|
`stream(from, target, {…})`, `event(from, subscribe, {…})`; the key order inside the options object is free. One
|
|
439
457
|
option carries the same word for both: a stream requires `backpressure: latest()` and an `event` admits it. The
|
|
440
|
-
|
|
458
|
+
outer `provides` factory receives exactly `{ port, slot, pipe, register }`: only builders, with no `own` or other
|
|
459
|
+
instance values. `port(Port, ({ own }) => own.model.call)` records the target at declaration and selects a Call
|
|
460
|
+
when the instance prepares. Contributions take the target first, a value or an
|
|
441
461
|
instance factory second, and options third: `slot(target, contribution, { priority, when })`,
|
|
442
462
|
`pipe(target, { fold }, { priority, when })`, `register(target, entry, { priority, when })`, where `pipe`'s second
|
|
443
463
|
argument is a descriptor because a bare function would be indistinguishable from the instance factory. The
|
|
444
|
-
|
|
445
|
-
the mount's UI model
|
|
446
|
-
|
|
464
|
+
`register` factory receives `{ own, imports }`; a `slot` factory receives `{ own, imports, model }`, where
|
|
465
|
+
`model(Decl, create)` builds the mount's UI model and `create` receives `(ctx, props)`. These contexts contain only
|
|
466
|
+
the fields named here: there is no `exports` or `instance`, and `register` has no model builder (D255).
|
|
467
|
+
In either nested factory, `own` contains this instance's materialized models, calls and resources, just as in `exports`.
|
|
468
|
+
`register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))` can publish a private model call
|
|
469
|
+
without an `exports` section. Both port selectors and contribution factories select live values through their
|
|
470
|
+
own callback context. Attachments, scopes, effects and events
|
|
471
|
+
have no readable value form; unselected entries do not prevent access to the model or call alongside them.
|
|
472
|
+
`when` is a `Readable<boolean>` or a predicate of the
|
|
473
|
+
instance — `({ own, imports, read }) => boolean` — whose `read` records what the answer depends on: while
|
|
447
474
|
the answer is `false` the contribution does not enter the target's `entries`. In that evaluation context `own` is
|
|
448
|
-
the materialized form `exports` sees, so a model field is a `Readable` and not the ref the
|
|
475
|
+
the materialized form `exports` sees, so a model field is a `Readable` and not the ref returned by the `own` builder.
|
|
449
476
|
A `pipe` descriptor's `fold` receives that same evaluation context as its third argument —
|
|
450
|
-
`(value, meta, {
|
|
477
|
+
`(value, meta, { own, imports, read }) => …` — bound to the instance once when the contribution publishes
|
|
451
478
|
and called only by a `fold`, never at declaration or preload; `target.fold(value, meta, read?)` accepts the reader
|
|
452
479
|
as its third parameter, so `computed({ read: read => target.fold(0, undefined, read) })` subscribes both to
|
|
453
480
|
`entries` and to everything the handlers read; without a reader the handlers read the current snapshot. Contexts:
|
|
@@ -591,6 +618,21 @@ Each law is proved by package tests and survives renaming.
|
|
|
591
618
|
thrown away — the second StrictMode copy, an abandoned concurrent pass, a subtree suspended by a lazy sibling —
|
|
592
619
|
creates nothing at all, so nothing has to be swept, adopted or rebuilt, and a child layout effect never sees a
|
|
593
620
|
closed mount.
|
|
621
|
+
- **Contained contributions.** A mount is the boundary of the contribution it renders (D256). A render or commit that
|
|
622
|
+
throws — the component, an ungranted model, a source read, the creation of the mount's own UI models — stops there,
|
|
623
|
+
is reported to the feature that published the contribution, and never reaches the boundary of the host. The other
|
|
624
|
+
mounts of the same target and the rest of the tree keep rendering. Containment is unconditional; what a failed mount
|
|
625
|
+
shows is the `error` content of `ContributionBoundary`, declared once above every slot, and nothing at all without
|
|
626
|
+
it; that content lives in a stable ref, so an inline error branch re-renders no mount and content replaced
|
|
627
|
+
mid-failure lands on the next one. Its `retry` remounts the subtree, so the models of the contribution are created again rather than reused. Error
|
|
628
|
+
content is foreign code too: a failure inside it is contained by a second boundary that exists only after the first
|
|
629
|
+
failure, reported the same way, and leaves the mount empty instead of escalating.
|
|
630
|
+
- **Named failures.** A contained failure names itself (D257): `contribution` is the published entry,
|
|
631
|
+
`<feature>.<provides key>`, `target` is the slot it rendered in, and `feature` is the publishing feature or
|
|
632
|
+
`undefined` for a test fixture. The error content receives the three next to the raw `error` and its `retry`; the
|
|
633
|
+
reporter receives them on a `ContributionError` whose `cause` is that same error, with code `render-failed` for the
|
|
634
|
+
contribution and `error-content-failed` for error content that failed in its place. A contained failure that was a
|
|
635
|
+
cancellation stays one: the wrapper carries the same brand, so `isCancellation` answers as it did before.
|
|
594
636
|
- **Structural readers.** `Readable`, `Resource` and the demand source are structural contracts, so a host may
|
|
595
637
|
implement one with an object whose members are methods. Every hook calls them through the object it was handed and
|
|
596
638
|
never as a detached function, so an adapter may rely on its own `this` (D199).
|
package/docs/spec.ru.md
CHANGED
|
@@ -55,8 +55,8 @@ const catalogCatalogFeature = defineFeature({
|
|
|
55
55
|
...calls(imports.platform, ['resolveItem']), // методы хоста как Call, один к одному
|
|
56
56
|
}),
|
|
57
57
|
exports: ({ own }) => ({ resolveItem: own.resolveItem }), // для фич, импортирующих это определение
|
|
58
|
-
provides: ({ port
|
|
59
|
-
resolve: port(resolveItemPort, own.resolveItem), // порт для тех, кто его требует
|
|
58
|
+
provides: ({ port }) => ({
|
|
59
|
+
resolve: port(resolveItemPort, ({ own }) => own.resolveItem), // порт для тех, кто его требует
|
|
60
60
|
}),
|
|
61
61
|
});
|
|
62
62
|
```
|
|
@@ -103,17 +103,18 @@ const createFormFeature = defineFeature({
|
|
|
103
103
|
order: model(OrderModel, { platform: imports.platform }, (ctx, { platform }) => createOrderModel(ctx, platform)),
|
|
104
104
|
lookup: requires.resolveItem, // в `own` порт остаётся ref-ом
|
|
105
105
|
}),
|
|
106
|
-
//
|
|
107
|
-
exports: ({ own }) => ({ lookup: own.lookup
|
|
106
|
+
// потребители получают публичный Call lookup; submit остаётся внутри этой фичи
|
|
107
|
+
exports: ({ own }) => ({ lookup: own.lookup }),
|
|
108
108
|
provides: ({ slot, pipe }) => ({
|
|
109
109
|
// вклад это значение, фабрика экземпляра или дескриптор pipe; `priority` и `when` в опциях третьим
|
|
110
|
-
content: slot(orderFormContentSlot, ({
|
|
110
|
+
content: slot(orderFormContentSlot, ({ own, model }) => ({
|
|
111
111
|
Component: CreateForm,
|
|
112
112
|
// UI-модель вклада: создаётся на каждое монтирование и видит `props` именно этого монтирования
|
|
113
113
|
models: [
|
|
114
114
|
model(OrderActions, (ctx, props: Readable<OrderFormProps>) => ({
|
|
115
115
|
submit: ctx.call({
|
|
116
|
-
run: (amount: number, { invoke }) =>
|
|
116
|
+
run: (amount: number, { invoke }) =>
|
|
117
|
+
invoke(own.order.submit, { amount, itemId: props.getSnapshot().itemId }),
|
|
117
118
|
}),
|
|
118
119
|
})),
|
|
119
120
|
],
|
|
@@ -179,7 +180,7 @@ publication barrier. Всё это принадлежит runtime и его inte
|
|
|
179
180
|
| `id` | Как фича называется | `'feature.entity'`, только точки, без суффиксов |
|
|
180
181
|
| `imports` | Что мне дадут снаружи | запись из `defineHostContract` контрактов и определений других фич; `optional(x)` это слабое ребро, `onDemand(hostContract)` откладывает подключение до первого вызова |
|
|
181
182
|
| `requires` | Какой порт мне нужен | запись `port` или `optional(port)`; каждое `requires.x` это ref, а `Call` появляется у экземпляра |
|
|
182
|
-
| ref vs Call | Что видно в какой стадии | `own`
|
|
183
|
+
| ref vs Call | Что видно в какой стадии | builder `own` видит refs зависимостей; `provides` видит только builders; `exports`, селекторы портов и фабрики вкладов видят готовые значения экземпляра |
|
|
183
184
|
| `own` | Чем я владею | builder: `model`, `call`, `calls`, `lane`, `effect`, `event`, `resource`, `stream`, `scope`, `attach` |
|
|
184
185
|
| `exports` | Что я отдаю фичам, которые меня импортируют | `({ own }) => запись Call, Readable и Resource` на открытии экземпляра |
|
|
185
186
|
| `provides` | Что я предлагаю точкам расширения | `({ port, slot, pipe, register, own }) => ...` |
|
|
@@ -267,13 +268,17 @@ input, а не автоматически переданным токеном о
|
|
|
267
268
|
используется та же запись, без зависимостей — `model(Decl, create)`. Принимаются только подлинные импорты и call
|
|
268
269
|
refs текущей фичи; optional-импорты сохраняют `Readable<Lookup<…>>`. Сырые объекты хоста, декларации портов,
|
|
269
270
|
чужие refs и refs моделей не являются элементами зависимостей. Именованная фабрика может объявить свой интерфейс
|
|
270
|
-
зависимостей, не видя контекста фичи. `
|
|
271
|
-
|
|
272
|
-
переданные напрямую вызовы.
|
|
271
|
+
зависимостей, не видя контекста фичи. `port(SubmitPort, ({ own }) => own.order.submit)` один раз выбирает подлинный
|
|
272
|
+
вызов из `own` текущего экземпляра при подготовке, до readiness и публикации вкладов, и добавляет фенс провайдера,
|
|
273
|
+
включая переданные напрямую вызовы. `port(SubmitPort, ({ own }) => own.submit)` выбирает вызов, объявленный прямо
|
|
274
|
+
в `own`. Селектор получает только `{ own }`; результатом должен быть подлинный Call из значения, выбранного им
|
|
275
|
+
в этом `own`, в том числе из поля модели. Чужие Calls и обычные функции отвергаются. Прямые refs и дескрипторы
|
|
276
|
+
`{ from, select }` не принимаются. Retirement внутри селектора прекращает запуск следующих селекторов и дренирует
|
|
277
|
+
уже владеемую работу (D255).
|
|
273
278
|
|
|
274
279
|
Моделей у вклада два вида, и обе объявлены одним `defineModel`. Модели `own` живут с экземпляром фичи и служатся
|
|
275
280
|
монтированию автоматически. UI-модель вклада живёт с монтированием: она перечислена в `models` вклада, её фабрика
|
|
276
|
-
получает вторым аргументом `Readable` пропсов этого монтирования и берёт поля как есть (`submit:
|
|
281
|
+
получает вторым аргументом `Readable` пропсов этого монтирования и берёт поля как есть (`submit: own.order.submit`)
|
|
277
282
|
или оборачивает их в `ctx.call`, когда контракт UI отличается входом, формой результата или числом фичевых вызовов.
|
|
278
283
|
Отдельного понятия View нет.
|
|
279
284
|
|
|
@@ -290,7 +295,9 @@ per-mount UI-модель обязан объявить тот компонен
|
|
|
290
295
|
|
|
291
296
|
`opetope/require-declared-models` из recommended `@opetope/lint` проверяет видимые `slot`-вклады и readers моделей в пределах одного модуля. Он учитывает aliases импортов и локальные bindings, включая именованные компоненты. Импортированные реализации и динамические объявления остаются непроверенными; отсутствие диагностики не доказывает полноту их требований (D250).
|
|
292
297
|
|
|
293
|
-
|
|
298
|
+
Каждое монтирование — граница: сбой рендера или коммита вклада остаётся на нём, уходит в reporter опубликовавшей его
|
|
299
|
+
фичи и заменяется содержимым `ContributionBoundary` — одной политикой хоста, объявленной один раз над всеми слотами,
|
|
300
|
+
а не на каждый слот (D256). Хуки: `useModel`, `useReadable`, `useSelector`, `useCommand`, `useCommands`, `useResource`. Компоненты: `Slot` и `FeatureBoundary`
|
|
294
301
|
из `@opetope/react/integration`, который держит фичу открытой и показывает `fallback` или `error`. Обе ветви
|
|
295
302
|
принимают либо узел, либо render-колбэк: `children` получает готовый экземпляр, типизированный по `demand`, `error`
|
|
296
303
|
получает `{ error, retry }`, выполняется только показанная ветвь, и готовому потребителю не нужны второй
|
|
@@ -386,6 +393,17 @@ devtools-плане, отдельного объявления группы не
|
|
|
386
393
|
провалившуюся уборку точным фронтиром на `FeatureError.retryCleanup` вместо отчёта, и порт управления devtools может
|
|
387
394
|
её повторить (D182).
|
|
388
395
|
|
|
396
|
+
Корень приложения — место, где хост один раз говорит, что показывает упавший вклад, для всех монтирований ниже:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
// bootstrap/root.tsx — корень приложения, выше любого слота
|
|
400
|
+
const Root = () => (
|
|
401
|
+
<ContributionBoundary error={({ error, retry }) => <FailedContribution error={error} onRetry={retry} />}>
|
|
402
|
+
<Slot target={applicationSurface} />
|
|
403
|
+
</ContributionBoundary>
|
|
404
|
+
);
|
|
405
|
+
```
|
|
406
|
+
|
|
389
407
|
---
|
|
390
408
|
|
|
391
409
|
## 3. Публичный словарь
|
|
@@ -437,17 +455,26 @@ scope: отдельный концепт, а не второе имя фичи.
|
|
|
437
455
|
`event`, `resource`, `stream`, `scope.while`, `scope.switch`, `scope.keyed`, `attach`. У `resource`,
|
|
438
456
|
`stream` и `event` источник и носитель вывода типа позиционные: `resource(from, target, {…})`,
|
|
439
457
|
`stream(from, target, {…})`, `event(from, subscribe, {…})`; порядок ключей в объекте опций свободен. Одна опция
|
|
440
|
-
несёт для обоих одно слово: `stream` требует `backpressure: latest()`, а `event` его допускает.
|
|
441
|
-
`port
|
|
458
|
+
несёт для обоих одно слово: `stream` требует `backpressure: latest()`, а `event` его допускает. Внешняя фабрика
|
|
459
|
+
`provides` получает ровно `{ port, slot, pipe, register }`: только builders, без `own` и других значений экземпляра.
|
|
460
|
+
`port(Port, ({ own }) => own.model.call)` записывает цель при объявлении, а Call выбирает при подготовке экземпляра.
|
|
461
|
+
У вкладов цель первым аргументом, значение или фабрика экземпляра вторым, опции
|
|
442
462
|
третьим: `slot(target, contribution, { priority, when })`, `pipe(target, { fold }, { priority, when })`,
|
|
443
463
|
`register(target, entry, { priority, when })`, причём у `pipe` второй аргумент это дескриптор, потому что голую
|
|
444
|
-
функцию было бы не отличить от фабрики экземпляра.
|
|
445
|
-
`model(Decl, create)` строит UI-модель
|
|
446
|
-
`
|
|
464
|
+
функцию было бы не отличить от фабрики экземпляра. Фабрика `register` получает `{ own, imports }`, а фабрика
|
|
465
|
+
`slot` — `{ own, imports, model }`, где `model(Decl, create)` строит UI-модель монтирования и `create` получает
|
|
466
|
+
`(ctx, props)`. Контексты содержат только перечисленные поля: `exports` и `instance` отсутствуют, а у `register`
|
|
467
|
+
нет построителя моделей (D255). В обеих вложенных фабриках `own` содержит материализованные модели, вызовы и
|
|
468
|
+
ресурсы этого экземпляра, как в `exports`. `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))`
|
|
469
|
+
публикует вызов внутренней модели без секции `exports`. Селекторы портов и фабрики вкладов выбирают готовые
|
|
470
|
+
значения через собственный контекст колбэка. У вложений, scopes,
|
|
471
|
+
effects и events нет доступной для чтения формы значения; невыбранные поля не мешают обращаться к соседней
|
|
472
|
+
модели или вызову. `when` это
|
|
473
|
+
`Readable<boolean>` или предикат экземпляра — `({ own, imports, read }) => boolean`, — чей `read`
|
|
447
474
|
записывает, от чего зависит ответ: пока ответ `false`, вклад не входит в `entries` цели. В этом контексте
|
|
448
475
|
вычисления `own` дан в материализованной форме, которую видит `exports`: поле модели это `Readable`, а не ref из
|
|
449
|
-
|
|
450
|
-
`(value, meta, {
|
|
476
|
+
builder `own`. `fold` дескриптора `pipe` получает третьим аргументом тот же контекст вычисления —
|
|
477
|
+
`(value, meta, { own, imports, read }) => …`, — связанный с экземпляром один раз при публикации вклада и
|
|
451
478
|
вызываемый только сверткой, а не при объявлении или предзагрузке; а `target.fold(value, meta, read?)` принимает
|
|
452
479
|
читателя третьим параметром, поэтому `computed({ read: read => target.fold(0, undefined, read) })` подписывается и
|
|
453
480
|
на `entries`, и на всё, что прочитали обработчики; без читателя обработчики читают текущий snapshot. Контексты:
|
|
@@ -599,6 +626,21 @@ scope: отдельный концепт, а не второе имя фичи.
|
|
|
599
626
|
рендер — вторая копия StrictMode, прерванный concurrent-проход, поддерево, приостановленное ленивым соседом, — не
|
|
600
627
|
создаёт ничего, поэтому подметать, усыновлять и пересобирать нечего, а layout-эффект ребёнка никогда не видит
|
|
601
628
|
закрытое монтирование.
|
|
629
|
+
- **Изолированные вклады.** Монтирование — граница того вклада, который оно рендерит (D256). Сбой рендера или
|
|
630
|
+
коммита — компонента, невыданной модели, чтения источника, создания собственных UI-моделей монтирования —
|
|
631
|
+
останавливается на нём, уходит в reporter опубликовавшей вклад фичи и не доходит до границы хоста. Остальные
|
|
632
|
+
монтирования той же цели и остальное дерево продолжают рендериться. Изоляция безусловна; что показывает упавшее
|
|
633
|
+
монтирование, задаёт содержимое `error` у `ContributionBoundary`, объявленного один раз над всеми слотами, и без
|
|
634
|
+
него — ничего; содержимое живёт в стабильной ссылке, поэтому инлайновая ветвь ошибки не перерисовывает
|
|
635
|
+
монтирования, а переписанное во время сбоя содержимое попадает в следующий сбой. Его `retry` перемонтирует поддерево, поэтому модели вклада создаются заново, а не переиспользуются.
|
|
636
|
+
Содержимое ошибки — тоже чужой код: его сбой держит вторая граница, существующая только после первого сбоя, он
|
|
637
|
+
сообщается тем же путём и оставляет монтирование пустым вместо эскалации.
|
|
638
|
+
- **Названный сбой.** Изолированный сбой называет себя (D257): `contribution` — опубликованная запись вида
|
|
639
|
+
`<фича>.<ключ provides>`, `target` — слот, в котором она рендерилась, `feature` — опубликовавшая фича или
|
|
640
|
+
`undefined` у тестовой фикстуры. Содержимое ошибки получает эти три рядом с сырым `error` и его `retry`, а
|
|
641
|
+
reporter — на `ContributionError`, у которого в `cause` та же ошибка, с кодом `render-failed` для вклада и
|
|
642
|
+
`error-content-failed` для содержимого ошибки, упавшего вместо него. Изолированный сбой, бывший отменой, остаётся
|
|
643
|
+
отменой: обёртка несёт тот же бренд, поэтому `isCancellation` отвечает как прежде.
|
|
602
644
|
- **Структурные читатели.** `Readable`, `Resource` и источник спроса это структурные контракты, поэтому хост вправе
|
|
603
645
|
реализовать их объектом, члены которого — методы. Любой хук зовёт их через тот объект, который ему передали, и
|
|
604
646
|
никогда как оторванную функцию, поэтому адаптер вправе опираться на свой `this` (D199).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opetope/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20.19.0"
|
|
6
6
|
},
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@opetope/core": "0.
|
|
55
|
+
"@opetope/core": "0.4.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@opetope/core": "0.
|
|
58
|
+
"@opetope/core": "0.4.0"
|
|
59
59
|
},
|
|
60
60
|
"sideEffects": false,
|
|
61
61
|
"description": "Feature composition and owned lifecycles for Opetope.",
|