@opetope/runtime 0.1.0 → 0.2.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +24 -13
  3. package/README.ru.md +26 -13
  4. package/dist/feature-authoring-types.d.ts +6 -10
  5. package/dist/feature-authoring.js +1 -1
  6. package/dist/feature-authoring.js.map +1 -1
  7. package/dist/feature-body.d.ts +1 -1
  8. package/dist/feature-body.js.map +1 -1
  9. package/dist/feature-contribution.d.ts +5 -3
  10. package/dist/feature-contribution.js +1 -1
  11. package/dist/feature-contribution.js.map +1 -1
  12. package/dist/feature-definition-api.d.ts +1 -1
  13. package/dist/feature-generation.js +1 -1
  14. package/dist/feature-generation.js.map +1 -1
  15. package/dist/feature-lazy-generation.d.ts +1 -1
  16. package/dist/feature-lazy-generation.js +1 -1
  17. package/dist/feature-lazy-generation.js.map +1 -1
  18. package/dist/feature-own-values.d.ts +10 -0
  19. package/dist/feature-own-values.js +2 -0
  20. package/dist/feature-own-values.js.map +1 -0
  21. package/dist/feature-port-binding.d.ts +2 -1
  22. package/dist/feature-port-binding.js +1 -1
  23. package/dist/feature-port-binding.js.map +1 -1
  24. package/dist/feature-port.d.ts +13 -23
  25. package/dist/feature-port.js +1 -1
  26. package/dist/feature-port.js.map +1 -1
  27. package/dist/public-module-types.d.ts +1 -1
  28. package/dist/public-module.d.ts +1 -1
  29. package/docs/agent-guide.md +11 -4
  30. package/docs/agent-guide.ru.md +11 -4
  31. package/docs/cookbook.md +9 -7
  32. package/docs/cookbook.ru.md +9 -7
  33. package/docs/decisions.md +15 -1
  34. package/docs/how-it-works.md +20 -5
  35. package/docs/how-it-works.ru.md +19 -5
  36. package/docs/releases.md +38 -17
  37. package/docs/releases.ru.md +37 -17
  38. package/docs/spec.md +31 -17
  39. package/docs/spec.ru.md +32 -18
  40. 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, own }) => ({
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
- // here `own` is already materialized: both the port and the model field arrived as live `Call`s
109
- exports: ({ own }) => ({ lookup: own.lookup, submit: own.order.submit }),
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, ({ exports, model }) => ({
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 }) => invoke(exports.submit, { amount, itemId: props.getSnapshot().itemId }),
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` and the outer `provides` factory see refs; `exports` and nested contribution factories see materialized instance values |
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. `provides.port(SubmitPort, { from: own.order, select: order => order.submit })` selects an authentic call
273
- once from the materialized model before publication and adds the provider's lifetime fence, including passthrough
274
- calls. A local call ref still uses `port(SubmitPort, own.submit)` (D169).
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: exports.submit`) or wraps them in `ctx.call` when the UI contract differs in
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
@@ -437,17 +442,26 @@ remain. Resource retry and feature-demand retry keep their own meaning. The form
437
442
  `event` the source and the carrier of type inference are positional: `resource(from, target, {…})`,
438
443
  `stream(from, target, {…})`, `event(from, subscribe, {…})`; the key order inside the options object is free. One
439
444
  option carries the same word for both: a stream requires `backpressure: latest()` and an `event` admits it. The
440
- methods of `provides`: `port`, `slot`, `pipe`, `register`; contributions take the target first, a value or an
445
+ outer `provides` factory receives exactly `{ port, slot, pipe, register }`: only builders, with no `own` or other
446
+ instance values. `port(Port, ({ own }) => own.model.call)` records the target at declaration and selects a Call
447
+ when the instance prepares. Contributions take the target first, a value or an
441
448
  instance factory second, and options third: `slot(target, contribution, { priority, when })`,
442
449
  `pipe(target, { fold }, { priority, when })`, `register(target, entry, { priority, when })`, where `pipe`'s second
443
450
  argument is a descriptor because a bare function would be indistinguishable from the instance factory. The
444
- contribution factory context is `{ exports, imports, instance, model, own }`, and its `model(Decl, create)` builds
445
- the mount's UI model, where `create` receives `(ctx, props)`. `when` is a `Readable<boolean>` or a predicate of the
446
- instance `({ exports, imports, own, read }) => boolean` whose `read` records what the answer depends on: while
451
+ `register` factory receives `{ own, imports }`; a `slot` factory receives `{ own, imports, model }`, where
452
+ `model(Decl, create)` builds the mount's UI model and `create` receives `(ctx, props)`. These contexts contain only
453
+ the fields named here: there is no `exports` or `instance`, and `register` has no model builder (D255).
454
+ In either nested factory, `own` contains this instance's materialized models, calls and resources, just as in `exports`.
455
+ `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))` can publish a private model call
456
+ without an `exports` section. Both port selectors and contribution factories select live values through their
457
+ own callback context. Attachments, scopes, effects and events
458
+ have no readable value form; unselected entries do not prevent access to the model or call alongside them.
459
+ `when` is a `Readable<boolean>` or a predicate of the
460
+ instance — `({ own, imports, read }) => boolean` — whose `read` records what the answer depends on: while
447
461
  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 factory context carries.
462
+ the materialized form `exports` sees, so a model field is a `Readable` and not the ref returned by the `own` builder.
449
463
  A `pipe` descriptor's `fold` receives that same evaluation context as its third argument —
450
- `(value, meta, { exports, imports, own, read }) => …` — bound to the instance once when the contribution publishes
464
+ `(value, meta, { own, imports, read }) => …` — bound to the instance once when the contribution publishes
451
465
  and called only by a `fold`, never at declaration or preload; `target.fold(value, meta, read?)` accepts the reader
452
466
  as its third parameter, so `computed({ read: read => target.fold(0, undefined, read) })` subscribes both to
453
467
  `entries` and to everything the handlers read; without a reader the handlers read the current snapshot. Contexts:
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, own }) => ({
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
- // здесь `own` уже материализован: и порт, и поле модели пришли живыми `Call`
107
- exports: ({ own }) => ({ lookup: own.lookup, submit: own.order.submit }),
106
+ // потребители получают публичный Call lookup; submit остаётся внутри этой фичи
107
+ exports: ({ own }) => ({ lookup: own.lookup }),
108
108
  provides: ({ slot, pipe }) => ({
109
109
  // вклад это значение, фабрика экземпляра или дескриптор pipe; `priority` и `when` в опциях третьим
110
- content: slot(orderFormContentSlot, ({ exports, model }) => ({
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 }) => invoke(exports.submit, { amount, itemId: props.getSnapshot().itemId }),
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` и внешняя фабрика `provides` видят refs; `exports` и вложенные фабрики вкладов видят материализованные значения экземпляра |
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
- зависимостей, не видя контекста фичи. `provides.port(SubmitPort, { from: own.order, select: order => order.submit })`
271
- один раз выбирает подлинный вызов из готовой модели до публикации и добавляет фенс провайдера, включая
272
- переданные напрямую вызовы. Локальный call ref по-прежнему использует `port(SubmitPort, own.submit)` (D169).
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: exports.submit`)
281
+ получает вторым аргументом `Readable` пропсов этого монтирования и берёт поля как есть (`submit: own.order.submit`)
277
282
  или оборачивает их в `ctx.call`, когда контракт UI отличается входом, формой результата или числом фичевых вызовов.
278
283
  Отдельного понятия View нет.
279
284
 
@@ -437,17 +442,26 @@ scope: отдельный концепт, а не второе имя фичи.
437
442
  `event`, `resource`, `stream`, `scope.while`, `scope.switch`, `scope.keyed`, `attach`. У `resource`,
438
443
  `stream` и `event` источник и носитель вывода типа позиционные: `resource(from, target, {…})`,
439
444
  `stream(from, target, {…})`, `event(from, subscribe, {…})`; порядок ключей в объекте опций свободен. Одна опция
440
- несёт для обоих одно слово: `stream` требует `backpressure: latest()`, а `event` его допускает. Методы `provides`:
441
- `port`, `slot`, `pipe`, `register`; у вкладов цель первым аргументом, значение или фабрика экземпляра вторым, опции
445
+ несёт для обоих одно слово: `stream` требует `backpressure: latest()`, а `event` его допускает. Внешняя фабрика
446
+ `provides` получает ровно `{ port, slot, pipe, register }`: только builders, без `own` и других значений экземпляра.
447
+ `port(Port, ({ own }) => own.model.call)` записывает цель при объявлении, а Call выбирает при подготовке экземпляра.
448
+ У вкладов цель первым аргументом, значение или фабрика экземпляра вторым, опции
442
449
  третьим: `slot(target, contribution, { priority, when })`, `pipe(target, { fold }, { priority, when })`,
443
450
  `register(target, entry, { priority, when })`, причём у `pipe` второй аргумент это дескриптор, потому что голую
444
- функцию было бы не отличить от фабрики экземпляра. Контекст фабрики вклада это `{ exports, imports, instance, model, own }`, и его
445
- `model(Decl, create)` строит UI-модель монтирования, где `create` получает `(ctx, props)`. `when` это
446
- `Readable<boolean>` или предикат экземпляра `({ exports, imports, own, read }) => boolean`, — чей `read`
451
+ функцию было бы не отличить от фабрики экземпляра. Фабрика `register` получает `{ own, imports }`, а фабрика
452
+ `slot` — `{ own, imports, model }`, где `model(Decl, create)` строит UI-модель монтирования и `create` получает
453
+ `(ctx, props)`. Контексты содержат только перечисленные поля: `exports` и `instance` отсутствуют, а у `register`
454
+ нет построителя моделей (D255). В обеих вложенных фабриках `own` содержит материализованные модели, вызовы и
455
+ ресурсы этого экземпляра, как в `exports`. `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))`
456
+ публикует вызов внутренней модели без секции `exports`. Селекторы портов и фабрики вкладов выбирают готовые
457
+ значения через собственный контекст колбэка. У вложений, scopes,
458
+ effects и events нет доступной для чтения формы значения; невыбранные поля не мешают обращаться к соседней
459
+ модели или вызову. `when` это
460
+ `Readable<boolean>` или предикат экземпляра — `({ own, imports, read }) => boolean`, — чей `read`
447
461
  записывает, от чего зависит ответ: пока ответ `false`, вклад не входит в `entries` цели. В этом контексте
448
462
  вычисления `own` дан в материализованной форме, которую видит `exports`: поле модели это `Readable`, а не ref из
449
- контекста фабрики. `fold` дескриптора `pipe` получает третьим аргументом тот же контекст вычисления —
450
- `(value, meta, { exports, imports, own, read }) => …`, — связанный с экземпляром один раз при публикации вклада и
463
+ builder `own`. `fold` дескриптора `pipe` получает третьим аргументом тот же контекст вычисления —
464
+ `(value, meta, { own, imports, read }) => …`, — связанный с экземпляром один раз при публикации вклада и
451
465
  вызываемый только сверткой, а не при объявлении или предзагрузке; а `target.fold(value, meta, read?)` принимает
452
466
  читателя третьим параметром, поэтому `computed({ read: read => target.fold(0, undefined, read) })` подписывается и
453
467
  на `entries`, и на всё, что прочитали обработчики; без читателя обработчики читают текущий snapshot. Контексты:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/runtime",
3
- "version": "0.1.0",
3
+ "version": "0.2.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.1.0"
55
+ "@opetope/core": "0.2.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@opetope/core": "0.1.0"
58
+ "@opetope/core": "0.2.0"
59
59
  },
60
60
  "sideEffects": false,
61
61
  "description": "Feature composition and owned lifecycles for Opetope.",