@opetope/react 0.11.0 → 0.12.1
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 +412 -17
- package/README.md +41 -360
- package/dist/command-hook-controller.d.ts +4 -4
- package/dist/command-hook.d.ts +3 -3
- package/dist/command.d.ts +8 -8
- package/dist/contribution-frame-CCKnOxZR.js +2 -0
- package/dist/contribution-frame-CCKnOxZR.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model-binding.d.ts +4 -4
- package/dist/model-hook.d.ts +2 -2
- package/dist/resource-command.d.ts +11 -4
- package/dist/scenario-slot.d.ts +6 -3
- package/dist/scenario-types.d.ts +2 -2
- package/dist/scenario.d.ts +1 -2
- package/dist/{commands-hook.d.ts → selection-hooks.d.ts} +4 -11
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +5 -6
- package/README.ru.md +0 -414
- package/dist/contribution-frame-DkNmjChZ.js +0 -2
- package/dist/contribution-frame-DkNmjChZ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,400 @@
|
|
|
1
1
|
# @opetope/react
|
|
2
2
|
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 997137b: **Documentation only: four statements that had drifted from the tree are corrected, and the copies they drifted
|
|
8
|
+
out of are replaced by the one place each law is written (D460).** No source file changes, no public name moves,
|
|
9
|
+
no gate budget moves and no compiled example is edited. The root README gains a map, «Where each contract is
|
|
10
|
+
written once», naming for every subject the one normative section, the worked examples over it and nothing else.
|
|
11
|
+
|
|
12
|
+
- **The runtime word map promised two Resource names and the entry publishes nineteen.** `Resource` and
|
|
13
|
+
`ResourceState` were described as «the two words this entry publishes for a materialization», with the rest of
|
|
14
|
+
the family «named from `@opetope/core`». The entry re-exports the family whole — every `Resource*` word plus
|
|
15
|
+
`EventDelivery`, `LiveDelivery`, `PaginationOutcome` and `PaginationState` — and the comment above that block
|
|
16
|
+
names the decision that made it so. The row now says what the file does, which is what lets a feature author
|
|
17
|
+
write one import line for a declaration and for the `ResourceData<Data>` its `apply.change` annotates.
|
|
18
|
+
- **`@opetope/core` said it stays `private`, and its manifest carries no such field.** The manifest has
|
|
19
|
+
`publishConfig.access: public` and no `private`. The sentence keeps what was true — the API is experimental
|
|
20
|
+
while the design is under review — and says what that costs an upgrader: an alpha removes a superseded name
|
|
21
|
+
instead of keeping it beside its replacement, so an upgrade is read through the release notes. It makes no claim
|
|
22
|
+
about what is or is not on npm today.
|
|
23
|
+
- **Three pages still taught the pre-`LostWrite` law.** The runtime README said a dropped write is lost «in
|
|
24
|
+
silence rather than thrown at the caller or recorded by the reporter»; the primitives reference said «nothing is
|
|
25
|
+
reported»; and the spec's own model-layer section said «dropped without a reporter record». The law changed two
|
|
26
|
+
decisions ago and the normative sections that state it — §2.11 and §4 «Failures after abort» — were already
|
|
27
|
+
right: the first write an authority loses after ending inside a write into a cell its source reads, its own or
|
|
28
|
+
one of a command it invoked, reaches the reporter as `LostWrite`, once per run, and every other drop is silent.
|
|
29
|
+
All three now say that and defer to §4 for which is which. `update` is still `void`: nothing here promises a
|
|
30
|
+
throw at the caller. The primitives tables that listed a dropped write as always silent are corrected with them.
|
|
31
|
+
- **Two code tuples had already lost codes, and both are replaced by a link.** The authoring guide listed the
|
|
32
|
+
codes of every error class and was missing two of the six of `ContributionError`; the core README's boundary
|
|
33
|
+
list was missing the `CancellationError` its own word map names one paragraph above. Which class carries which
|
|
34
|
+
code, and which codes are cancellations rather than product answers, is the spec's «Errors»; which class is
|
|
35
|
+
thrown by whom is the how-it-works error table. The React README's outcome bullet, a third copy of the same law
|
|
36
|
+
plus the spec's «Command outcomes», goes the same way.
|
|
37
|
+
- **The rule that looked stale is not, and its reason was.** The authoring guide requires a result type on a model
|
|
38
|
+
factory, because an inline arrow handed straight to `openModel(Decl, ctx => …)` is not instantiated when its
|
|
39
|
+
body is checked. A probe over the current sources confirms the rule and refutes the explanation: a body that
|
|
40
|
+
names no input is inferred as `Command<void, void>`, taking the `void` default instead of the `never` the guide
|
|
41
|
+
claimed — so the declaration's input never arrives, a body that reads `input` is refused on the read with the
|
|
42
|
+
four-position instruction, and a body that reads nothing is refused against the record the factory returns. The
|
|
43
|
+
reason is rewritten to that; the rule stands.
|
|
44
|
+
- **The repetitions removed, and what was left in their place.** The primitives reference's shared-laws section
|
|
45
|
+
stated the fence-and-drain, writer, cancellation and reporter laws in full while declaring the spec normative
|
|
46
|
+
for all four; it now states what a primitive owes each law and links. The runtime README restated the reason
|
|
47
|
+
behind `opetope/no-snapshot-in-update`, which that rule's own README writes out, and restated the model
|
|
48
|
+
context's lanes and its `invoke`-from-an-effect verbatim from the core README, which declares them. Each task
|
|
49
|
+
page keeps its short explanation and its warnings.
|
|
50
|
+
|
|
51
|
+
- 8aec982: **A command's one waiting place is written as the record that names it: `concurrency: { pending: 'latest' }`
|
|
52
|
+
(D459).** The scalar `concurrency: 'latest'` named the input that wins and never named what it wins — «the latest»
|
|
53
|
+
reads as a place in a queue, as the body already running and as a cached answer, and only the first was true. The
|
|
54
|
+
shared form of the same fact never had that gap: `{ lane, pending: 'latest' }` says the place out loud, with the
|
|
55
|
+
`pending` an event and a live Resource already write for their own one waiting place. One fact was being written in
|
|
56
|
+
two vocabularies, and the shorter one left out the part that mattered. The word is removed rather than kept beside
|
|
57
|
+
the record, which makes this a breaking change to the authoring surface of a model command, of a model selection
|
|
58
|
+
and of a feature command.
|
|
59
|
+
|
|
60
|
+
- **Migrate by writing the place.** `concurrency: 'latest'` becomes `concurrency: { pending: 'latest' }` in
|
|
61
|
+
`ctx.command`, `ctx.select` and `own.command`. `concurrency: { lane, pending: 'latest' }` is unchanged: the lane
|
|
62
|
+
is the one parameter this record takes, and it is optional now instead of required. Nothing else about the option
|
|
63
|
+
moves — the same record, the same siblings, the same `dedupe` prohibition beside it.
|
|
64
|
+
- **No law moved with the spelling.** While call 1 runs, calls 2 and 3 take one waiting place: the body of 2 never
|
|
65
|
+
starts, its wait ends as `CommandError('cancelled', 'Command <id> replaced a waiting input.')`, and 3 runs when 1
|
|
66
|
+
finishes. A newer request never reaches the call in flight. The regression that holds this reads `signal.aborted`
|
|
67
|
+
inside the running body — after both newer requests were admitted and before the body returns — rather than
|
|
68
|
+
counting abort events, because the abort that follows a call's own end belongs to its cleanup and says nothing
|
|
69
|
+
about the policy. It was proved by substitution: with the kernel changed to cancel whatever is in flight, the
|
|
70
|
+
test fails on that line.
|
|
71
|
+
- **The retired word is answered with its replacement, not with a list of everything else.** The runtime throws
|
|
72
|
+
`Model command concurrency latest is now a record: write { pending: 'latest' }, or { lane, pending: 'latest' }.`
|
|
73
|
+
— and `Model select`, `Feature command` the same. Two neighbouring messages move with it: the choice sentence
|
|
74
|
+
loses the word — `concurrency must be queue, parallel, a lane of this surface or a record.` — and the record's own
|
|
75
|
+
refusal now says the lane is optional. A record that names `lane` and hands it `undefined` is still refused, by
|
|
76
|
+
the type under `exactOptionalPropertyTypes` and by the runtime, which reads the shape of the record by its keys.
|
|
77
|
+
- **The compiler names the replacement too, because the literal stays in the union.** The options record of a
|
|
78
|
+
command gained a third member — the retired word beside a key an author cannot write — so a refusal arrives as a
|
|
79
|
+
missing property carrying the instruction. Without it the union held no string at all, every word written there
|
|
80
|
+
widened to `string`, and one sentence answered a typo and a retired word alike. What the two now print, the same
|
|
81
|
+
on a model and on a feature: `{ concurrency: 'quee' }` is a `TS2820` that names the word and suggests `'queue'`,
|
|
82
|
+
which is better than it was before this change, and `{ concurrency: 'latest' }` is a `TS2345` whose elaboration
|
|
83
|
+
carries «`concurrency: "latest"` is now a record: write `{ pending: "latest" }`, or `{ lane, pending: "latest" }`».
|
|
84
|
+
A selection is the one surface where the compiler names the word written and not its replacement — its options
|
|
85
|
+
record is not a union — and there the runtime and the lint rule carry it.
|
|
86
|
+
- **`opetope/no-retired-vocabulary` reports it.** `concurrency: 'latest'` is a retired _value_ of a live key, the
|
|
87
|
+
shape `overflow: 'reject'` already had, so the rule now reads the values of a command's options record as well as
|
|
88
|
+
its keys and reports on the word that was written. A project that pins the rule's output by its own fixtures gains
|
|
89
|
+
one row.
|
|
90
|
+
- **What the gates read.** Instantiations are unchanged — 11 756, 87 710 and 160 268 against ceilings of 12 200,
|
|
91
|
+
99 062 and 181 237 — because a scalar became a record that already existed. The tightest size row, the headless
|
|
92
|
+
session consumer of `@opetope/devtools`, stays at 6.95 kB of 7.1 kB. The two runtime rows carry the new branch and
|
|
93
|
+
its message: the application graph consumer moves 48.56 → 48.66 kB of 49 kB and the public feature consumer
|
|
94
|
+
39.88 → 39.92 kB of 41 kB, measured by rebuilding the validator both ways. No budget moves.
|
|
95
|
+
|
|
96
|
+
- 4e65393: **The published archive of every package loses `README.ru.md`, and `@opetope/runtime` loses the Russian pages of
|
|
97
|
+
its `docs/` as well (D456).** The Russian half of the documentation is removed: nineteen `*.ru.md` files,
|
|
98
|
+
**2 027 997 bytes**, which is 39.0 % of what `ci:docs` counts as a document and 35.1 % of every `*.md` in the tree.
|
|
99
|
+
Documentation is written once, in English, from here on. No public name, no `exports` entry and no subpath moves —
|
|
100
|
+
a `.ru.md` was never a resolvable specifier, only a file read by its path — and no line of shipped JavaScript
|
|
101
|
+
changes, which is why this is a patch. The composition of what is published does change, and this is the line that
|
|
102
|
+
says so.
|
|
103
|
+
|
|
104
|
+
- **What goes.** `CONTRIBUTING.ru.md`, `README.ru.md`, the README of the minimal React example, the nine guides of
|
|
105
|
+
`docs/` — agent guide, cookbook, devtools, how-it-works, primitives, releases, both migration guides and the
|
|
106
|
+
spec — and the `README.ru.md` of all seven packages. `docs/decisions.md` stays Russian and keeps no pair, as it
|
|
107
|
+
never had one. The nine copies under `packages/runtime/docs/` are generated by packaging and leave on their own.
|
|
108
|
+
- **The rule that required a pair is cancelled, with its gate.** `CLAUDE.md` said «README and user guides have
|
|
109
|
+
English/Russian pairs, updated together» with four exceptions; it now says a document is written once, in
|
|
110
|
+
English, and gets no second copy. `ci:docs` no longer looks for a `*.ru.md` beside a document and no longer
|
|
111
|
+
compares the heading skeleton of a pair, and its report reads «links and example section order passed».
|
|
112
|
+
- **One gate is lost outright, and nothing replaces it.** The fixture generator held every English example against
|
|
113
|
+
its Russian twin as the same program — syntax without comments and without JSX text — which is how D441 caught a
|
|
114
|
+
real drift in a spec §2 block, a string literal that differed in one copy. The defect class that stops being
|
|
115
|
+
caught is a silent edit to a detail of an example that still compiles: a string literal, a number, the order of
|
|
116
|
+
two arguments of the same type. `ci:type` accepts any literal of the right type, `ci:docs` reads section order
|
|
117
|
+
and links rather than values, `ci:asserted-refusals` holds pragmas, and the generator's own `--check` compares a
|
|
118
|
+
fixture with the very document it was lifted from, so one `fixtures:generate` makes any such edit the new
|
|
119
|
+
baseline. The twin was the only place in this repository where one program was written twice and the two
|
|
120
|
+
writings were compared. It also never knew which copy was right — in the one case it fired, the wrong copy was
|
|
121
|
+
the Russian one — and it protected above all the copy that nothing compiled.
|
|
122
|
+
- **Where the archive change is written down, so it cannot drift from its gate.** The composition of an archive is
|
|
123
|
+
stated in six places and all six are rewritten: the `files` of the seven manifests; the required-file list and
|
|
124
|
+
the archive-member allowlist of the pack archive check; the fixture of that check's own unit test; the
|
|
125
|
+
allowlists of the two real pack smoke tests; and what packaging demands of each package before it packs. The
|
|
126
|
+
requirement that `@opetope/runtime` ship `docs/spec.ru.md` is gone; `docs/spec.md` is required exactly as before.
|
|
127
|
+
- **The numbers the gates print.** `ci:docs` reads 28 documents instead of 46 — nineteen leave and this changeset
|
|
128
|
+
is itself a document — with 4 pending changesets instead of 3 and 424 decision identifiers instead of 423. Its
|
|
129
|
+
feature examples halve, 64 to 32: that gate lints every fenced example that declares a feature, and it was
|
|
130
|
+
linting both copies of each one. Packaging prepares 10 documents instead of 19, and the merge-marker scan reads
|
|
131
|
+
1101 text files instead of 1117 — seventeen of the nineteen removed files live under a scanned root, and this
|
|
132
|
+
changeset is one file back. The document examples do not move at all: 38 spec §2 blocks and 132 blocks of 11
|
|
133
|
+
other documents, 2 fragments, 14 asserted refusals, exactly as before, because only the English block was ever
|
|
134
|
+
lifted into a fixture.
|
|
135
|
+
|
|
136
|
+
- Updated dependencies [997137b]
|
|
137
|
+
- Updated dependencies [ba75469]
|
|
138
|
+
- Updated dependencies [d09568e]
|
|
139
|
+
- Updated dependencies [8aa4d85]
|
|
140
|
+
- Updated dependencies [8aec982]
|
|
141
|
+
- Updated dependencies [be0613e]
|
|
142
|
+
- Updated dependencies [4e65393]
|
|
143
|
+
- Updated dependencies [e12b0b6]
|
|
144
|
+
- @opetope/core@0.12.1
|
|
145
|
+
- @opetope/runtime@0.12.1
|
|
146
|
+
|
|
147
|
+
## 0.12.0
|
|
148
|
+
|
|
149
|
+
### Minor Changes
|
|
150
|
+
|
|
151
|
+
- 34bdbfb: **An options record is exact by its keys, and the guard that was weaker than no guard is gone (D406).** Exactness is
|
|
152
|
+
checked against the set of keys the argument carries, never against the record the compiler infers from it. The old
|
|
153
|
+
form asked the record to witness itself — `Actual & NoExtraKeys<Actual, Shape> & Shape` — and a record assigned to a
|
|
154
|
+
name first and sharing no key with the shape gave that inference nothing: the guard collapsed and took the weak-type
|
|
155
|
+
check down with it, so the guarded surface admitted what the bare shape refused.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// the record a migration carries: assigned first, sharing no key with the options of this node
|
|
159
|
+
const carriedOver = { concurency: 'queue' as const };
|
|
160
|
+
|
|
161
|
+
// before: `own.command`, `own.effect`, `own.event` and both `own.scope` forms accepted this in silence
|
|
162
|
+
// after: refused where it is written, and the message names the key
|
|
163
|
+
command(orders, () => undefined, carriedOver);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- Measured on the auditor's own probe: six misspelled records on six feature nodes, **one refusal before and six
|
|
167
|
+
after**, with no correct declaration in this repository changing.
|
|
168
|
+
- The gate now stands on every authoring record that had one, on `ctx.command`, `ctx.effect`, `ctx.event` and both
|
|
169
|
+
`ctx.scope` forms, which had none, and on the top-level record of `resource.load`/`resource.live` on both
|
|
170
|
+
surfaces. The nested records of the Resource family (`identity`, `apply`, `delivery`, `pagination`) keep the
|
|
171
|
+
runtime refusal of D345 and no type gate: the decision measures why.
|
|
172
|
+
- `queueBy` on a feature used to print `concurrency: { by }`, which is a member of a model command and of nothing
|
|
173
|
+
else, so the author walked from one refusal into the next. It now names the queue a feature has and the surface
|
|
174
|
+
that mints the keyed one, and a negative beside the positive holds that the advice compiles.
|
|
175
|
+
- A stack navigator refuses `persistence` and `defaultScreen` — the words of switch mode — instead of accepting and
|
|
176
|
+
ignoring them. The union of the two option records had no forbidding field, so an assigned record simply matched
|
|
177
|
+
the branch with fewer words and the host persistence contract was never called. `createNavigator` refuses the same
|
|
178
|
+
keys at run time, so a caller without types hears it too.
|
|
179
|
+
- Breaking: `ExactInput` and `NoExtraKeys` are gone from `@opetope/core/internal`; `ExactKeys` is the one gate. A
|
|
180
|
+
record with a key its node does not name stops compiling, as do a feature `queueBy` and a stack navigator carrying
|
|
181
|
+
switch-mode words.
|
|
182
|
+
- Measured: both runtime size rows are byte-identical — 48 067 B for the application graph consumer and 39 500 B for
|
|
183
|
+
the public feature consumer. The navigator's run-time refusal is isolated at **+70 B**: the navigator consumer is
|
|
184
|
+
1 943 B without it and 2 013 B with it.
|
|
185
|
+
- Measured: the compiler does less work than before, not more. Type-stress instantiations are 11 647 / 85 822 /
|
|
186
|
+
156 926 over the three slices against a recorded baseline of 11 582 / 90 074 / 165 874 — inferring a key set is
|
|
187
|
+
cheaper than inferring the record, and the decision records which spelling of the gate costs what.
|
|
188
|
+
|
|
189
|
+
- 34bdbfb: **A word of a Resource declaration is its key, not its value — this is a breaking change (D407).** Every option of
|
|
190
|
+
`resource.load` and `resource.live`, and the host `grace` of `openFeature` and `openApplication`, is now read from
|
|
191
|
+
the key the record carries. A record keeps a key whose value is `undefined`, so a record built by a conditional
|
|
192
|
+
spread was until now indistinguishable from one that never wrote the word — and it was accepted in silence.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
// refused now, and silently a singleton before: the key is written, so the request is written
|
|
196
|
+
ctx.resource.load({ ...shared, request: keyed ? account : undefined });
|
|
197
|
+
|
|
198
|
+
// write the two declarations instead; the half they share is still written once
|
|
199
|
+
keyed ? ctx.resource.load({ ...shared, request: account }) : ctx.resource.load(shared);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
What a written `undefined` used to mean, and what it means now:
|
|
203
|
+
|
|
204
|
+
| Written | Was, in silence | Is now |
|
|
205
|
+
| ------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
206
|
+
| `request: undefined` | a singleton, and the key of every cache entry with it | `… request must be a value or a Readable.` |
|
|
207
|
+
| `enabled: undefined` | a Resource nothing gates | `… enabled must be a Readable.` |
|
|
208
|
+
| `grace: undefined` | the host's window, and accepted beside `lifetime: 'owner'` | the refusal of the word beside an owner, of the number otherwise |
|
|
209
|
+
| `identity: undefined` | no key and no scope: one shared materialization | `… identity must be a plain object record.` |
|
|
210
|
+
| `identity: { key: undefined }` | the request naming itself, or nothing on a singleton | `… identity.key must be a function.`, or the refusal of the word |
|
|
211
|
+
| `identity: { scope: undefined }` | a materialization shared where one was scoped | `… identity.scope must be a Readable.` |
|
|
212
|
+
| `load: undefined` on `live` | no load channel, and `apply.load` refused for it | `… load must be a function.` |
|
|
213
|
+
| `cache: undefined` | no cache | `… cache must be a plain object record.` |
|
|
214
|
+
| `pagination: undefined` | no pagination capability | `… pagination must be a plain object record.` |
|
|
215
|
+
| `delivery: undefined` | the default delivery | `… delivery must be a plain object record.` |
|
|
216
|
+
| `delivery.overflow: undefined` | the answer of a full capacity D351 wrote | `… delivery overflow must be flush-oldest or reject.` |
|
|
217
|
+
| `delivery.pending: undefined` | the field the record owes, reported as missing | `Unknown model resource delivery undefined.` |
|
|
218
|
+
| `apply: { load: undefined }` beside `load` | `apply.load is required beside load.` | `… apply.load must be a function.` |
|
|
219
|
+
| `grace: undefined` on `openFeature` / `openApplication` | the `0` of a host without the word | the refusal of the number |
|
|
220
|
+
|
|
221
|
+
Each refusal is this node's own sentence for a value the word cannot take — the sentence the request of an `event`
|
|
222
|
+
has printed for the same mistake since D395 — followed by the clause that names the remedy the sentence alone
|
|
223
|
+
cannot: `A key written as undefined is a word this declaration wrote: leave the key out where the word is not
|
|
224
|
+
meant.` Where the key is owed rather than optional — `apply.load` beside a load channel, `pending` inside a
|
|
225
|
+
`delivery` — the refusal names the debt and offers no such remedy.
|
|
226
|
+
|
|
227
|
+
`createScenario` of `@opetope/react` forwards the same word, and forwarded it by value: a `grace` written as
|
|
228
|
+
`undefined` there was dropped into the `0` of a host that wrote no window at all. It now travels to
|
|
229
|
+
`openApplication` and is refused there, beside the `conditions` and `imports` of the same record, which already
|
|
230
|
+
asked the key.
|
|
231
|
+
|
|
232
|
+
**Rewrite by building the record, not the value.** Branch on the record (`flag ? f({ ...shared, word }) : f(shared)`),
|
|
233
|
+
or spread a record that carries the key only when it is meant (`...(flag ? { word } : {})`). A conditional value
|
|
234
|
+
(`word: flag ? v : undefined`) is the form that breaks, and it is the form this decision exists to refuse.
|
|
235
|
+
|
|
236
|
+
- aaaa92c: **One word for a command, one for its order (D385–D389).** The primitive is `Command<Input, Output>`: the type was
|
|
237
|
+
the last place the vocabulary still said `Call`, while the hook was already `useCommand`, the record `CommandHook`,
|
|
238
|
+
the outcome `CommandOutcome` and the fixture `command(...)`. `CallError` is `CommandError`, `runCall` is
|
|
239
|
+
`runCommand`, `RunCallOptions` is `RunCommandOptions`, `CallTimeoutError` is `CommandTimeoutError` and
|
|
240
|
+
`CallInputArgs` is `CommandInputArgs`. There are no aliases.
|
|
241
|
+
|
|
242
|
+
- **A body takes one argument, its context (D386).** `ctx.command(({ input, update }) => …)` and
|
|
243
|
+
`own.command(within, ({ input, source, signal }) => …)`. `Input` and `Output` default to `void`, so the
|
|
244
|
+
`_input: void` placeholder is gone from every command that takes none. A typed input is declared once, by
|
|
245
|
+
annotating the destructured context: `({ input, update }: ModelCommandContext<Deposit>)` or
|
|
246
|
+
`({ input, source }: FeatureCommandContext<typeof polling, Deposit>)`. Both types are new public exports. Give a
|
|
247
|
+
model factory a result type — `function createCounter(context: ModelContext): ModelOf<typeof Counter>`, the form
|
|
248
|
+
the examples use: inside an inline arrow handed straight to `openModel(Decl, ctx => …)` or `model(Decl, ctx => …)`
|
|
249
|
+
the expected type is not instantiated yet, so a body that names no input reads as `Command<never, …>` instead of
|
|
250
|
+
taking the `void` default.
|
|
251
|
+
- **`concurrency` replaces `policy`, `queueBy` and `lane` (D387).** One word that also takes the queue node itself:
|
|
252
|
+
`'parallel' | 'queue' | 'latest' | lane | { by } | { by, lane } | { lane, pending: 'latest' }`. A lane is a
|
|
253
|
+
reference commands share, which is why the word accepts it; `'parallel'` beside a lane and a bare `by` without a
|
|
254
|
+
queue are now unwritable rather than refused. `dedupe` stays a sibling option and `'latest'` still refuses it.
|
|
255
|
+
A selection writes the same word with the members it can answer for — the keyed record is not one of them, and
|
|
256
|
+
a feature has no keyed queue either, so both are refused with the reason. The retired `policy`, `queueBy`, `lane`
|
|
257
|
+
and `once` are named by each options record, so they are refused in a written literal and in an assigned record
|
|
258
|
+
alike — on `ctx.command`, on `own.command` and on `ctx.select`, which also refuses `memoize` and `dedupe`.
|
|
259
|
+
- **Declaring and selecting are two words (D388).** `ctx.command(run, options?)` declares;
|
|
260
|
+
`ctx.select(source, 'name' | ['a', 'b'], options?)` selects what a dependency already declares — one name answers
|
|
261
|
+
one command, a list answers a record. `ctx.calls` and `own.calls` are gone, and so is `useCommands`: a record of command consumers
|
|
262
|
+
comes from `useModel(Declaration, select)`, which has an owner and a lifetime. `bind` of `@opetope/runtime` is
|
|
263
|
+
unchanged.
|
|
264
|
+
- **`once` is `memoize` (D389).** The cache keeps its behaviour and loses a name the platform already owns:
|
|
265
|
+
`addEventListener(type, handler, { once: true })` means "then stop", which is not what this option ever did.
|
|
266
|
+
|
|
267
|
+
`@opetope/lint` follows the vocabulary: `opetope/capture-command-cleanup` reads the context off the body's one
|
|
268
|
+
parameter and recognises `concurrency: 'parallel'`, and `opetope/no-command-in-deps` names the selection a record
|
|
269
|
+
comes from.
|
|
270
|
+
|
|
271
|
+
- 34bdbfb: **One word for one thing, and one sentence for one question (D410, D411).** A vocabulary pass over the public API:
|
|
272
|
+
the refusals that asked one question in five sentences now ask it in one, the position a signature calls `source`
|
|
273
|
+
is called `source` everywhere, and eight words that named one behaviour twice are one word apiece. No branch of the
|
|
274
|
+
runtime changes: the same checks run in the same order.
|
|
275
|
+
|
|
276
|
+
**Breaking, word by word.** Every row is a source change with no alias; `opetope/no-retired-vocabulary` reports each
|
|
277
|
+
one and names the replacement.
|
|
278
|
+
|
|
279
|
+
| Before | After |
|
|
280
|
+
| -------------------------------------------------------------- | --------------------------------------------------- |
|
|
281
|
+
| `defineCondition(id, { from, select })` | `defineCondition(id, { select, source })` |
|
|
282
|
+
| `slot(target, value, { when })`, same at `pipe`, `register` | `slot(target, value, { enabled })` |
|
|
283
|
+
| `resource.live({ delivery: { overflow: 'reject' } })` | `resource.live({ delivery: { overflow: 'drop' } })` |
|
|
284
|
+
| `outcome.status === 'ok' \| 'failed' \| 'cancelled'` | `outcome.kind === 'ok' \| 'failed' \| 'cancelled'` |
|
|
285
|
+
| `ResourceActivity` from `@opetope/devtools` | `ResourceNodeActivity` from `@opetope/devtools` |
|
|
286
|
+
| `ResourceDisposer` from `@opetope/core/internal` | `Disposer` from `@opetope/core/internal` |
|
|
287
|
+
| `NavigatorPolicy` from `@opetope/navigation` | `NavigatorCatalogue` |
|
|
288
|
+
| `surface.policy` | `surface.catalogue` |
|
|
289
|
+
| `ApplicationConditionBinding` from `@opetope/runtime/internal` | the same name, from `@opetope/runtime` |
|
|
290
|
+
|
|
291
|
+
`@opetope/lint` renames the rule that read that field: `opetope/when-predicate` is `opetope/enabled-predicate`,
|
|
292
|
+
and its message ids are `enabledIsAsync`, `enabledReturnsBareSource` and `enabledReturnsSource`. A configuration
|
|
293
|
+
that named the old rule has to name the new one; `configs.recommended` and the shipped `oxlintrc.json` fragment
|
|
294
|
+
already do.
|
|
295
|
+
|
|
296
|
+
Two of these are record keys, so a source with `sort-keys` reorders the literal as well as renaming the key:
|
|
297
|
+
`{ from, select }` becomes `{ select, source }`, and `{ Component, when }` becomes `{ Component, enabled }`.
|
|
298
|
+
`{ status: 'ok', value }` becomes `{ kind: 'ok', value }`, which sorts unchanged, while
|
|
299
|
+
`{ reason, status: 'cancelled' }` becomes `{ kind: 'cancelled', reason }`, which does not.
|
|
300
|
+
|
|
301
|
+
**Why each one.** `source` is the word every context already answers the position with, and `from*` stays the
|
|
302
|
+
prefix of a factory that names where a `Readable` comes from (`fromExternal`, `fromMaybe`). `enabled` is the truth
|
|
303
|
+
the runtime reads while a node exists — one word at a contribution and at a Resource — while `when` keeps the other
|
|
304
|
+
mechanism, the declared `Condition`s an application resolves before the node exists; `scope.while(source, open,
|
|
305
|
+
{ when })` and an effect's `filter` are untouched. `drop` is what a full capacity does with the value that does not
|
|
306
|
+
fit, at both nodes that admit a producer; `flush-oldest` is still the default of a keyed delivery and still the
|
|
307
|
+
answer for a producer that may lose nothing. `kind` is the discriminant eleven settled answers already carried.
|
|
308
|
+
`NavigatorCatalogue` is named for its job, beside `NavigatorPersistence`, because a policy is data (D291) and this
|
|
309
|
+
is four callbacks a navigator asks.
|
|
310
|
+
|
|
311
|
+
**Not breaking, and worth reading.** Every options record now refuses an unknown field with the sentence spec §3
|
|
312
|
+
promised — `<subject> <node> <record> contains unknown field <key>.` — and says which field it owes with
|
|
313
|
+
`<subject> <node> <record> requires <key>.` Five sentences became one, and the three that never printed the field
|
|
314
|
+
at all (`requires exact data fields`, `own.attach takes open, or open and close, and nothing else`) now print it. A
|
|
315
|
+
node that reads a source says so in two sentences per surface instead of three, and the word of the position is the
|
|
316
|
+
author's. `own.select` lost two of its four overloads: a call that writes the options record has one candidate of
|
|
317
|
+
its arity now, so a fault in that record is reported as the record's own refusal instead of
|
|
318
|
+
`Argument of type '"echo"' is not assignable to parameter of type 'never'`, and the record names the five words a
|
|
319
|
+
selection does not take — `concurrency`, `dedupe`, `lane`, `memoize`, `policy` — each with what to write instead.
|
|
320
|
+
|
|
321
|
+
**Measured.** Runtime size consumers, rebuilt: `application graph consumer` 48 248 B against a ceiling of 49 000
|
|
322
|
+
(48 318 before), `public feature consumer` 39 579 B against 41 000 (39 712 before). Type stress: 11 695 / 87 068 /
|
|
323
|
+
159 371 instantiations, unchanged to the digit. No ceiling moved.
|
|
324
|
+
|
|
325
|
+
- aaaa92c: **The destructive verb is `reset()` (D373).** `Resource.invalidate()` is now `Resource.reset()`, and the verb of the
|
|
326
|
+
React hook is renamed with it: `useResource(resource)` answers `{ pagination, refresh, reset, retry, state }`. Nothing
|
|
327
|
+
about the operation changes — it clears the whole cache, drops the current data, opens a new epoch and restarts every
|
|
328
|
+
configured channel, exactly as D355 wrote it.
|
|
329
|
+
|
|
330
|
+
The old name promised the opposite of what it did. `invalidate` means «mark stale, keep the data» everywhere else —
|
|
331
|
+
TanStack Query's `invalidateQueries` keeps the cached data until a new answer arrives, and Apollo's `INVALIDATE`
|
|
332
|
+
marks a field stale without removing its value — so an author who came from there wrote it expecting a background
|
|
333
|
+
refresh over the rows still on screen and got an empty state instead.
|
|
334
|
+
|
|
335
|
+
- `resource.reset()` and `useResource(resource).reset.run()` are the current names; there is no alias, and a
|
|
336
|
+
Resource object has no `invalidate` member at all.
|
|
337
|
+
- A host that implements the `Resource` contract itself renames its member too: the runtime recognises a Resource
|
|
338
|
+
field of a model shape by the names of its verbs.
|
|
339
|
+
- `invalidate` now names no operation of a Resource. It is left free for a future operation that would mark data
|
|
340
|
+
stale and keep them — an intent recorded in D373, not a promise of one.
|
|
341
|
+
|
|
342
|
+
### Patch Changes
|
|
343
|
+
|
|
344
|
+
- Updated dependencies [bb31983]
|
|
345
|
+
- Updated dependencies [bb31983]
|
|
346
|
+
- Updated dependencies [bb31983]
|
|
347
|
+
- Updated dependencies [dae5079]
|
|
348
|
+
- Updated dependencies [dae5079]
|
|
349
|
+
- Updated dependencies [bb31983]
|
|
350
|
+
- Updated dependencies [aaaa92c]
|
|
351
|
+
- Updated dependencies [34bdbfb]
|
|
352
|
+
- Updated dependencies [dae5079]
|
|
353
|
+
- Updated dependencies [34bdbfb]
|
|
354
|
+
- Updated dependencies [bb05f21]
|
|
355
|
+
- Updated dependencies [dae5079]
|
|
356
|
+
- Updated dependencies [34bdbfb]
|
|
357
|
+
- Updated dependencies [aaaa92c]
|
|
358
|
+
- Updated dependencies [34bdbfb]
|
|
359
|
+
- Updated dependencies [aaaa92c]
|
|
360
|
+
- Updated dependencies [34bdbfb]
|
|
361
|
+
- Updated dependencies [bb05f21]
|
|
362
|
+
- Updated dependencies [aaaa92c]
|
|
363
|
+
- Updated dependencies [aaaa92c]
|
|
364
|
+
- Updated dependencies [bb31983]
|
|
365
|
+
- Updated dependencies [34bdbfb]
|
|
366
|
+
- Updated dependencies [dae5079]
|
|
367
|
+
- Updated dependencies [aaaa92c]
|
|
368
|
+
- Updated dependencies [aaaa92c]
|
|
369
|
+
- Updated dependencies [aaaa92c]
|
|
370
|
+
- Updated dependencies [aaaa92c]
|
|
371
|
+
- Updated dependencies [dae5079]
|
|
372
|
+
- Updated dependencies [bb05f21]
|
|
373
|
+
- Updated dependencies [aaaa92c]
|
|
374
|
+
- Updated dependencies [dae5079]
|
|
375
|
+
- Updated dependencies [dae5079]
|
|
376
|
+
- Updated dependencies [aaaa92c]
|
|
377
|
+
- Updated dependencies [aaaa92c]
|
|
378
|
+
- Updated dependencies [34bdbfb]
|
|
379
|
+
- Updated dependencies [bb05f21]
|
|
380
|
+
- Updated dependencies [aaaa92c]
|
|
381
|
+
- Updated dependencies [bb31983]
|
|
382
|
+
- Updated dependencies [34bdbfb]
|
|
383
|
+
- Updated dependencies [dae5079]
|
|
384
|
+
- Updated dependencies [aaaa92c]
|
|
385
|
+
- Updated dependencies [aaaa92c]
|
|
386
|
+
- Updated dependencies [dae5079]
|
|
387
|
+
- Updated dependencies [bb05f21]
|
|
388
|
+
- Updated dependencies [dae5079]
|
|
389
|
+
- Updated dependencies [bb31983]
|
|
390
|
+
- Updated dependencies [bb31983]
|
|
391
|
+
- Updated dependencies [34bdbfb]
|
|
392
|
+
- Updated dependencies [bb05f21]
|
|
393
|
+
- Updated dependencies [34bdbfb]
|
|
394
|
+
- Updated dependencies [34bdbfb]
|
|
395
|
+
- @opetope/runtime@0.12.0
|
|
396
|
+
- @opetope/core@0.12.0
|
|
397
|
+
|
|
3
398
|
## 0.11.0
|
|
4
399
|
|
|
5
400
|
### Minor Changes
|
|
@@ -274,7 +669,7 @@
|
|
|
274
669
|
|
|
275
670
|
**`ResourceHook` (D321).** `useResource(resource)` answers `{ snapshot, refresh, retry }`, and a field of a
|
|
276
671
|
`useModel` selection whose value is a request answers the same hook and takes the same lease. Nine models of one
|
|
277
|
-
application had wrapped `resource.refresh()` in a `
|
|
672
|
+
application had wrapped `resource.refresh()` in a `Command<void, void>` for nothing but the `inFlight` of a button.
|
|
278
673
|
Recognition asks the same question the type asks: `Resource` is a structural contract (D199), so a host adapter that
|
|
279
674
|
satisfies it is classified and leased exactly like a materialization the runtime built, which stays the fast path.
|
|
280
675
|
`read(resource)` still answers the snapshot and leases nothing. One pair of controllers per resource identity keeps
|
|
@@ -306,11 +701,11 @@
|
|
|
306
701
|
|
|
307
702
|
### Patch Changes
|
|
308
703
|
|
|
309
|
-
- b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет
|
|
704
|
+
- b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет Command из
|
|
310
705
|
effect и сериализует независимые объекты через очередь по ключу. Stream умеет накапливать кадры в границах
|
|
311
706
|
открытия. Контекст данных отделён от ключа, а ёмкость кэша перенесена из retention в самостоятельную опцию cache.
|
|
312
707
|
Retention теперь задаётся литералом, model resource/stream принимает готовый Readable, а `queueBy` создаёт
|
|
313
|
-
приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного
|
|
708
|
+
приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного Command
|
|
314
709
|
через `rethrowIfCancelled`.
|
|
315
710
|
- Updated dependencies [b964772]
|
|
316
711
|
- @opetope/core@0.9.3
|
|
@@ -330,7 +725,7 @@
|
|
|
330
725
|
|
|
331
726
|
- d7f346a: A command hook is `{ run, inFlight, outcome }`, and an owned attachment step is `step(key, run, cleanup)`.
|
|
332
727
|
|
|
333
|
-
`CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `
|
|
728
|
+
`CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `Command` fields of a
|
|
334
729
|
`useModel` selection. It bought one arrow in JSX and nothing the types did not already hold: `onClick={logout.run}`
|
|
335
730
|
never compiled, because a `MouseEvent` is not a `void` input. A command without input now binds as
|
|
336
731
|
`onClick={() => void logout.run()}` (D292).
|
|
@@ -405,17 +800,17 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
|
|
|
405
800
|
`timeoutMs`, so an expiry does not read like a single failed assertion. A throw that is not an `Error` is rethrown
|
|
406
801
|
untouched.
|
|
407
802
|
|
|
408
|
-
`
|
|
803
|
+
`runCommand(target, input?, { signal, timeoutMs })` takes a deadline on that one run. `run` is the top-level execution
|
|
409
804
|
the consumer owns (D292), so the deadline cancels the way that consumer cancels — by aborting the signal this run
|
|
410
|
-
supplied — and rejects with a `
|
|
805
|
+
supplied — and rejects with a `CommandTimeoutError` naming the call and the deadline. A `signal` stands beside it and
|
|
411
806
|
the first of the two to abort supplies the reason, so a cancelled outcome is never reported as a timeout.
|
|
412
807
|
|
|
413
|
-
`command(run)` is now published by `@opetope/core/testing` as a `
|
|
808
|
+
`command(run)` is now published by `@opetope/core/testing` as a `Command` fixture with no binding: the body is
|
|
414
809
|
positional and receives the signal of the run, and there is no lane, queue, dedupe or retirement — so no `inFlight`
|
|
415
810
|
to read, no fence, and two runs of one fixture overlap. A model opened with `openModel` takes it as a dependency,
|
|
416
|
-
which removes the second model a test used to open for one
|
|
811
|
+
which removes the second model a test used to open for one Command. `@opetope/react/testing` keeps its own `command`,
|
|
417
812
|
which is this one plus the contribution binding a mount reads, and also re-exports `yieldTurn`, `eventually` and
|
|
418
|
-
`
|
|
813
|
+
`CommandTimeoutError`.
|
|
419
814
|
|
|
420
815
|
`settled` and `waitFor` accept the `ApplicationExecution` an `openApplication` returned. An application contributes
|
|
421
816
|
the models of every generation it holds right now and a fifth fact of its own: the instances whose retirement has
|
|
@@ -425,7 +820,7 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
|
|
|
425
820
|
|
|
426
821
|
Migration: `flush()` (a `setImmediate` or `setTimeout(0)` helper) → `yieldTurn()`; a local generic
|
|
427
822
|
`waitFor<Value>(read, predicate)` → the library `waitFor`, which returns the value; a local `eventually` → the
|
|
428
|
-
library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `
|
|
823
|
+
library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `runCommand` →
|
|
429
824
|
`{ timeoutMs: 500 }`; `command` from `@opetope/react/testing` in a test that renders nothing →
|
|
430
825
|
`command` from `@opetope/core/testing`; and a hand-written poll loop for an application → `settled(application)`.
|
|
431
826
|
|
|
@@ -453,9 +848,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
453
848
|
|
|
454
849
|
- 0318472: Give every package a testing entry, and open a model without a feature.
|
|
455
850
|
|
|
456
|
-
`@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `
|
|
851
|
+
`@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `runCommand` — moved
|
|
457
852
|
here from `@opetope/react/testing`, because it reaches the existing invoker and knows about neither a renderer nor a
|
|
458
|
-
feature — and `
|
|
853
|
+
feature — and `CommandInputArgs`, so a test can wrap `runCommand` without naming `./internal` (D285).
|
|
459
854
|
|
|
460
855
|
`@opetope/runtime/testing` adds `openModel(Decl, dependencies?, create, { reporter })`, which opens one model with
|
|
461
856
|
the same kernel, lanes and retirement the feature path builds and none of what a feature adds (D286), and
|
|
@@ -526,9 +921,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
526
921
|
|
|
527
922
|
Keep derive({ from, select }) for explicit sources and preserve the existing reactive scheduling and lifetime behavior. Migrate framework consumers and add a compile-checked model example in the English and Russian guides.
|
|
528
923
|
|
|
529
|
-
- 538cdfe: Replace singleFlight with dedupe on custom Calls. Use dedupe: true to share one pending or running execution per
|
|
924
|
+
- 538cdfe: Replace singleFlight with dedupe on custom Calls. Use dedupe: true to share one pending or running execution per Command and generation, or a key function to share equivalent inputs. Retain the first input and independent caller cancellation. Remove the former name without a compatibility alias.
|
|
530
925
|
|
|
531
|
-
Reject both forms of dedupe with policy: latest in types and at runtime. Different keys keep one FIFO under queue and run independently under parallel. Preserve once caching before deduplication and keep scheduling options on
|
|
926
|
+
Reject both forms of dedupe with policy: latest in types and at runtime. Different keys keep one FIFO under queue and run independently under parallel. Preserve once caching before deduplication and keep scheduling options on Command declarations rather than React hooks. Add a compile-checked model example and update the English and Russian guides.
|
|
532
927
|
|
|
533
928
|
- Updated dependencies [538cdfe]
|
|
534
929
|
- Updated dependencies [538cdfe]
|
|
@@ -566,14 +961,14 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
566
961
|
runtime nodes or adding ownership relations. Preserve diagnostic group names in the tooltip and inspector.
|
|
567
962
|
|
|
568
963
|
- a6a5f73: Remove the redundant `Command` type synonym and the `CommandOutcome` re-export from
|
|
569
|
-
`@opetope/react/integration`. Import `
|
|
964
|
+
`@opetope/react/integration`. Import `Command` from `@opetope/core` and the UI `CommandOutcome`
|
|
570
965
|
from `@opetope/react`. Alpha releases keep one current API without compatibility aliases.
|
|
571
966
|
|
|
572
967
|
### Patch Changes
|
|
573
968
|
|
|
574
969
|
- a6a5f73: Allow nested `invoke(target)` without an input placeholder for Calls accepting `void` or `undefined`, including
|
|
575
970
|
model, feature and attachment execution contexts. Keep required inputs inferred from the target, preserve generic
|
|
576
|
-
forwarding, and share the no-input rule with React `run` and testing `
|
|
971
|
+
forwarding, and share the no-input rule with React `run` and testing `runCommand`. Calls accepting `never` cannot be
|
|
577
972
|
invoked without input.
|
|
578
973
|
- a6a5f73: Allow omitting `conditions` from `openApplication` and `createScenario` when no enabled feature requires a host-bound condition. Missing external bindings and explicitly invalid condition records still fail validation.
|
|
579
974
|
- Updated dependencies [a6a5f73]
|
|
@@ -587,7 +982,7 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
587
982
|
|
|
588
983
|
### Minor Changes
|
|
589
984
|
|
|
590
|
-
- 410f2f0: Run authentic Calls in headless model tests with `
|
|
985
|
+
- 410f2f0: Run authentic Calls in headless model tests with `runCommand` from `@opetope/react/testing`, preserving their existing policies, cancellation, errors and owner lifetime.
|
|
591
986
|
|
|
592
987
|
Add the recommended `opetope/no-internal-imports` rule for static imports, re-exports, literal dynamic imports, unshadowed `require` and TypeScript import types. `configs.internalImports` uses this rule for JavaScript and TypeScript, including tests, without replacing the host's `no-restricted-imports`. The redundant `internalImportPattern` export is removed; allow trusted implementation files through the config or an explicit rule override.
|
|
593
988
|
|