@opetope/react 0.10.1 → 0.12.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 +359 -17
- package/README.md +114 -71
- package/README.ru.md +60 -70
- 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/model-selection-snapshot.d.ts +20 -3
- 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 -5
- package/dist/contribution-frame-QRJSP2W3.js +0 -2
- package/dist/contribution-frame-QRJSP2W3.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,347 @@
|
|
|
1
1
|
# @opetope/react
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 34bdbfb: **An options record is exact by its keys, and the guard that was weaker than no guard is gone (D406).** Exactness is
|
|
8
|
+
checked against the set of keys the argument carries, never against the record the compiler infers from it. The old
|
|
9
|
+
form asked the record to witness itself — `Actual & NoExtraKeys<Actual, Shape> & Shape` — and a record assigned to a
|
|
10
|
+
name first and sharing no key with the shape gave that inference nothing: the guard collapsed and took the weak-type
|
|
11
|
+
check down with it, so the guarded surface admitted what the bare shape refused.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// the record a migration carries: assigned first, sharing no key with the options of this node
|
|
15
|
+
const carriedOver = { concurency: 'queue' as const };
|
|
16
|
+
|
|
17
|
+
// before: `own.command`, `own.effect`, `own.event` and both `own.scope` forms accepted this in silence
|
|
18
|
+
// after: refused where it is written, and the message names the key
|
|
19
|
+
command(orders, () => undefined, carriedOver);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Measured on the auditor's own probe: six misspelled records on six feature nodes, **one refusal before and six
|
|
23
|
+
after**, with no correct declaration in this repository changing.
|
|
24
|
+
- The gate now stands on every authoring record that had one, on `ctx.command`, `ctx.effect`, `ctx.event` and both
|
|
25
|
+
`ctx.scope` forms, which had none, and on the top-level record of `resource.load`/`resource.live` on both
|
|
26
|
+
surfaces. The nested records of the Resource family (`identity`, `apply`, `delivery`, `pagination`) keep the
|
|
27
|
+
runtime refusal of D345 and no type gate: the decision measures why.
|
|
28
|
+
- `queueBy` on a feature used to print `concurrency: { by }`, which is a member of a model command and of nothing
|
|
29
|
+
else, so the author walked from one refusal into the next. It now names the queue a feature has and the surface
|
|
30
|
+
that mints the keyed one, and a negative beside the positive holds that the advice compiles.
|
|
31
|
+
- A stack navigator refuses `persistence` and `defaultScreen` — the words of switch mode — instead of accepting and
|
|
32
|
+
ignoring them. The union of the two option records had no forbidding field, so an assigned record simply matched
|
|
33
|
+
the branch with fewer words and the host persistence contract was never called. `createNavigator` refuses the same
|
|
34
|
+
keys at run time, so a caller without types hears it too.
|
|
35
|
+
- Breaking: `ExactInput` and `NoExtraKeys` are gone from `@opetope/core/internal`; `ExactKeys` is the one gate. A
|
|
36
|
+
record with a key its node does not name stops compiling, as do a feature `queueBy` and a stack navigator carrying
|
|
37
|
+
switch-mode words.
|
|
38
|
+
- Measured: both runtime size rows are byte-identical — 48 067 B for the application graph consumer and 39 500 B for
|
|
39
|
+
the public feature consumer. The navigator's run-time refusal is isolated at **+70 B**: the navigator consumer is
|
|
40
|
+
1 943 B without it and 2 013 B with it.
|
|
41
|
+
- Measured: the compiler does less work than before, not more. Type-stress instantiations are 11 647 / 85 822 /
|
|
42
|
+
156 926 over the three slices against a recorded baseline of 11 582 / 90 074 / 165 874 — inferring a key set is
|
|
43
|
+
cheaper than inferring the record, and the decision records which spelling of the gate costs what.
|
|
44
|
+
|
|
45
|
+
- 34bdbfb: **A word of a Resource declaration is its key, not its value — this is a breaking change (D407).** Every option of
|
|
46
|
+
`resource.load` and `resource.live`, and the host `grace` of `openFeature` and `openApplication`, is now read from
|
|
47
|
+
the key the record carries. A record keeps a key whose value is `undefined`, so a record built by a conditional
|
|
48
|
+
spread was until now indistinguishable from one that never wrote the word — and it was accepted in silence.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// refused now, and silently a singleton before: the key is written, so the request is written
|
|
52
|
+
ctx.resource.load({ ...shared, request: keyed ? account : undefined });
|
|
53
|
+
|
|
54
|
+
// write the two declarations instead; the half they share is still written once
|
|
55
|
+
keyed ? ctx.resource.load({ ...shared, request: account }) : ctx.resource.load(shared);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
What a written `undefined` used to mean, and what it means now:
|
|
59
|
+
|
|
60
|
+
| Written | Was, in silence | Is now |
|
|
61
|
+
| ------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
62
|
+
| `request: undefined` | a singleton, and the key of every cache entry with it | `… request must be a value or a Readable.` |
|
|
63
|
+
| `enabled: undefined` | a Resource nothing gates | `… enabled must be a Readable.` |
|
|
64
|
+
| `grace: undefined` | the host's window, and accepted beside `lifetime: 'owner'` | the refusal of the word beside an owner, of the number otherwise |
|
|
65
|
+
| `identity: undefined` | no key and no scope: one shared materialization | `… identity must be a plain object record.` |
|
|
66
|
+
| `identity: { key: undefined }` | the request naming itself, or nothing on a singleton | `… identity.key must be a function.`, or the refusal of the word |
|
|
67
|
+
| `identity: { scope: undefined }` | a materialization shared where one was scoped | `… identity.scope must be a Readable.` |
|
|
68
|
+
| `load: undefined` on `live` | no load channel, and `apply.load` refused for it | `… load must be a function.` |
|
|
69
|
+
| `cache: undefined` | no cache | `… cache must be a plain object record.` |
|
|
70
|
+
| `pagination: undefined` | no pagination capability | `… pagination must be a plain object record.` |
|
|
71
|
+
| `delivery: undefined` | the default delivery | `… delivery must be a plain object record.` |
|
|
72
|
+
| `delivery.overflow: undefined` | the answer of a full capacity D351 wrote | `… delivery overflow must be flush-oldest or reject.` |
|
|
73
|
+
| `delivery.pending: undefined` | the field the record owes, reported as missing | `Unknown model resource delivery undefined.` |
|
|
74
|
+
| `apply: { load: undefined }` beside `load` | `apply.load is required beside load.` | `… apply.load must be a function.` |
|
|
75
|
+
| `grace: undefined` on `openFeature` / `openApplication` | the `0` of a host without the word | the refusal of the number |
|
|
76
|
+
|
|
77
|
+
Each refusal is this node's own sentence for a value the word cannot take — the sentence the request of an `event`
|
|
78
|
+
has printed for the same mistake since D395 — followed by the clause that names the remedy the sentence alone
|
|
79
|
+
cannot: `A key written as undefined is a word this declaration wrote: leave the key out where the word is not
|
|
80
|
+
meant.` Where the key is owed rather than optional — `apply.load` beside a load channel, `pending` inside a
|
|
81
|
+
`delivery` — the refusal names the debt and offers no such remedy.
|
|
82
|
+
|
|
83
|
+
`createScenario` of `@opetope/react` forwards the same word, and forwarded it by value: a `grace` written as
|
|
84
|
+
`undefined` there was dropped into the `0` of a host that wrote no window at all. It now travels to
|
|
85
|
+
`openApplication` and is refused there, beside the `conditions` and `imports` of the same record, which already
|
|
86
|
+
asked the key.
|
|
87
|
+
|
|
88
|
+
**Rewrite by building the record, not the value.** Branch on the record (`flag ? f({ ...shared, word }) : f(shared)`),
|
|
89
|
+
or spread a record that carries the key only when it is meant (`...(flag ? { word } : {})`). A conditional value
|
|
90
|
+
(`word: flag ? v : undefined`) is the form that breaks, and it is the form this decision exists to refuse.
|
|
91
|
+
|
|
92
|
+
- aaaa92c: **One word for a command, one for its order (D385–D389).** The primitive is `Command<Input, Output>`: the type was
|
|
93
|
+
the last place the vocabulary still said `Call`, while the hook was already `useCommand`, the record `CommandHook`,
|
|
94
|
+
the outcome `CommandOutcome` and the fixture `command(...)`. `CallError` is `CommandError`, `runCall` is
|
|
95
|
+
`runCommand`, `RunCallOptions` is `RunCommandOptions`, `CallTimeoutError` is `CommandTimeoutError` and
|
|
96
|
+
`CallInputArgs` is `CommandInputArgs`. There are no aliases.
|
|
97
|
+
|
|
98
|
+
- **A body takes one argument, its context (D386).** `ctx.command(({ input, update }) => …)` and
|
|
99
|
+
`own.command(within, ({ input, source, signal }) => …)`. `Input` and `Output` default to `void`, so the
|
|
100
|
+
`_input: void` placeholder is gone from every command that takes none. A typed input is declared once, by
|
|
101
|
+
annotating the destructured context: `({ input, update }: ModelCommandContext<Deposit>)` or
|
|
102
|
+
`({ input, source }: FeatureCommandContext<typeof polling, Deposit>)`. Both types are new public exports. Give a
|
|
103
|
+
model factory a result type — `function createCounter(context: ModelContext): ModelOf<typeof Counter>`, the form
|
|
104
|
+
the examples use: inside an inline arrow handed straight to `openModel(Decl, ctx => …)` or `model(Decl, ctx => …)`
|
|
105
|
+
the expected type is not instantiated yet, so a body that names no input reads as `Command<never, …>` instead of
|
|
106
|
+
taking the `void` default.
|
|
107
|
+
- **`concurrency` replaces `policy`, `queueBy` and `lane` (D387).** One word that also takes the queue node itself:
|
|
108
|
+
`'parallel' | 'queue' | 'latest' | lane | { by } | { by, lane } | { lane, pending: 'latest' }`. A lane is a
|
|
109
|
+
reference commands share, which is why the word accepts it; `'parallel'` beside a lane and a bare `by` without a
|
|
110
|
+
queue are now unwritable rather than refused. `dedupe` stays a sibling option and `'latest'` still refuses it.
|
|
111
|
+
A selection writes the same word with the members it can answer for — the keyed record is not one of them, and
|
|
112
|
+
a feature has no keyed queue either, so both are refused with the reason. The retired `policy`, `queueBy`, `lane`
|
|
113
|
+
and `once` are named by each options record, so they are refused in a written literal and in an assigned record
|
|
114
|
+
alike — on `ctx.command`, on `own.command` and on `ctx.select`, which also refuses `memoize` and `dedupe`.
|
|
115
|
+
- **Declaring and selecting are two words (D388).** `ctx.command(run, options?)` declares;
|
|
116
|
+
`ctx.select(source, 'name' | ['a', 'b'], options?)` selects what a dependency already declares — one name answers
|
|
117
|
+
one command, a list answers a record. `ctx.calls` and `own.calls` are gone, and so is `useCommands`: a record of command consumers
|
|
118
|
+
comes from `useModel(Declaration, select)`, which has an owner and a lifetime. `bind` of `@opetope/runtime` is
|
|
119
|
+
unchanged.
|
|
120
|
+
- **`once` is `memoize` (D389).** The cache keeps its behaviour and loses a name the platform already owns:
|
|
121
|
+
`addEventListener(type, handler, { once: true })` means "then stop", which is not what this option ever did.
|
|
122
|
+
|
|
123
|
+
`@opetope/lint` follows the vocabulary: `opetope/capture-command-cleanup` reads the context off the body's one
|
|
124
|
+
parameter and recognises `concurrency: 'parallel'`, and `opetope/no-command-in-deps` names the selection a record
|
|
125
|
+
comes from.
|
|
126
|
+
|
|
127
|
+
- 34bdbfb: **One word for one thing, and one sentence for one question (D410, D411).** A vocabulary pass over the public API:
|
|
128
|
+
the refusals that asked one question in five sentences now ask it in one, the position a signature calls `source`
|
|
129
|
+
is called `source` everywhere, and eight words that named one behaviour twice are one word apiece. No branch of the
|
|
130
|
+
runtime changes: the same checks run in the same order.
|
|
131
|
+
|
|
132
|
+
**Breaking, word by word.** Every row is a source change with no alias; `opetope/no-retired-vocabulary` reports each
|
|
133
|
+
one and names the replacement.
|
|
134
|
+
|
|
135
|
+
| Before | After |
|
|
136
|
+
| -------------------------------------------------------------- | --------------------------------------------------- |
|
|
137
|
+
| `defineCondition(id, { from, select })` | `defineCondition(id, { select, source })` |
|
|
138
|
+
| `slot(target, value, { when })`, same at `pipe`, `register` | `slot(target, value, { enabled })` |
|
|
139
|
+
| `resource.live({ delivery: { overflow: 'reject' } })` | `resource.live({ delivery: { overflow: 'drop' } })` |
|
|
140
|
+
| `outcome.status === 'ok' \| 'failed' \| 'cancelled'` | `outcome.kind === 'ok' \| 'failed' \| 'cancelled'` |
|
|
141
|
+
| `ResourceActivity` from `@opetope/devtools` | `ResourceNodeActivity` from `@opetope/devtools` |
|
|
142
|
+
| `ResourceDisposer` from `@opetope/core/internal` | `Disposer` from `@opetope/core/internal` |
|
|
143
|
+
| `NavigatorPolicy` from `@opetope/navigation` | `NavigatorCatalogue` |
|
|
144
|
+
| `surface.policy` | `surface.catalogue` |
|
|
145
|
+
| `ApplicationConditionBinding` from `@opetope/runtime/internal` | the same name, from `@opetope/runtime` |
|
|
146
|
+
|
|
147
|
+
`@opetope/lint` renames the rule that read that field: `opetope/when-predicate` is `opetope/enabled-predicate`,
|
|
148
|
+
and its message ids are `enabledIsAsync`, `enabledReturnsBareSource` and `enabledReturnsSource`. A configuration
|
|
149
|
+
that named the old rule has to name the new one; `configs.recommended` and the shipped `oxlintrc.json` fragment
|
|
150
|
+
already do.
|
|
151
|
+
|
|
152
|
+
Two of these are record keys, so a source with `sort-keys` reorders the literal as well as renaming the key:
|
|
153
|
+
`{ from, select }` becomes `{ select, source }`, and `{ Component, when }` becomes `{ Component, enabled }`.
|
|
154
|
+
`{ status: 'ok', value }` becomes `{ kind: 'ok', value }`, which sorts unchanged, while
|
|
155
|
+
`{ reason, status: 'cancelled' }` becomes `{ kind: 'cancelled', reason }`, which does not.
|
|
156
|
+
|
|
157
|
+
**Why each one.** `source` is the word every context already answers the position with, and `from*` stays the
|
|
158
|
+
prefix of a factory that names where a `Readable` comes from (`fromExternal`, `fromMaybe`). `enabled` is the truth
|
|
159
|
+
the runtime reads while a node exists — one word at a contribution and at a Resource — while `when` keeps the other
|
|
160
|
+
mechanism, the declared `Condition`s an application resolves before the node exists; `scope.while(source, open,
|
|
161
|
+
{ when })` and an effect's `filter` are untouched. `drop` is what a full capacity does with the value that does not
|
|
162
|
+
fit, at both nodes that admit a producer; `flush-oldest` is still the default of a keyed delivery and still the
|
|
163
|
+
answer for a producer that may lose nothing. `kind` is the discriminant eleven settled answers already carried.
|
|
164
|
+
`NavigatorCatalogue` is named for its job, beside `NavigatorPersistence`, because a policy is data (D291) and this
|
|
165
|
+
is four callbacks a navigator asks.
|
|
166
|
+
|
|
167
|
+
**Not breaking, and worth reading.** Every options record now refuses an unknown field with the sentence spec §3
|
|
168
|
+
promised — `<subject> <node> <record> contains unknown field <key>.` — and says which field it owes with
|
|
169
|
+
`<subject> <node> <record> requires <key>.` Five sentences became one, and the three that never printed the field
|
|
170
|
+
at all (`requires exact data fields`, `own.attach takes open, or open and close, and nothing else`) now print it. A
|
|
171
|
+
node that reads a source says so in two sentences per surface instead of three, and the word of the position is the
|
|
172
|
+
author's. `own.select` lost two of its four overloads: a call that writes the options record has one candidate of
|
|
173
|
+
its arity now, so a fault in that record is reported as the record's own refusal instead of
|
|
174
|
+
`Argument of type '"echo"' is not assignable to parameter of type 'never'`, and the record names the five words a
|
|
175
|
+
selection does not take — `concurrency`, `dedupe`, `lane`, `memoize`, `policy` — each with what to write instead.
|
|
176
|
+
|
|
177
|
+
**Measured.** Runtime size consumers, rebuilt: `application graph consumer` 48 248 B against a ceiling of 49 000
|
|
178
|
+
(48 318 before), `public feature consumer` 39 579 B against 41 000 (39 712 before). Type stress: 11 695 / 87 068 /
|
|
179
|
+
159 371 instantiations, unchanged to the digit. No ceiling moved.
|
|
180
|
+
|
|
181
|
+
- aaaa92c: **The destructive verb is `reset()` (D373).** `Resource.invalidate()` is now `Resource.reset()`, and the verb of the
|
|
182
|
+
React hook is renamed with it: `useResource(resource)` answers `{ pagination, refresh, reset, retry, state }`. Nothing
|
|
183
|
+
about the operation changes — it clears the whole cache, drops the current data, opens a new epoch and restarts every
|
|
184
|
+
configured channel, exactly as D355 wrote it.
|
|
185
|
+
|
|
186
|
+
The old name promised the opposite of what it did. `invalidate` means «mark stale, keep the data» everywhere else —
|
|
187
|
+
TanStack Query's `invalidateQueries` keeps the cached data until a new answer arrives, and Apollo's `INVALIDATE`
|
|
188
|
+
marks a field stale without removing its value — so an author who came from there wrote it expecting a background
|
|
189
|
+
refresh over the rows still on screen and got an empty state instead.
|
|
190
|
+
|
|
191
|
+
- `resource.reset()` and `useResource(resource).reset.run()` are the current names; there is no alias, and a
|
|
192
|
+
Resource object has no `invalidate` member at all.
|
|
193
|
+
- A host that implements the `Resource` contract itself renames its member too: the runtime recognises a Resource
|
|
194
|
+
field of a model shape by the names of its verbs.
|
|
195
|
+
- `invalidate` now names no operation of a Resource. It is left free for a future operation that would mark data
|
|
196
|
+
stale and keep them — an intent recorded in D373, not a promise of one.
|
|
197
|
+
|
|
198
|
+
### Patch Changes
|
|
199
|
+
|
|
200
|
+
- Updated dependencies [bb31983]
|
|
201
|
+
- Updated dependencies [bb31983]
|
|
202
|
+
- Updated dependencies [bb31983]
|
|
203
|
+
- Updated dependencies [dae5079]
|
|
204
|
+
- Updated dependencies [dae5079]
|
|
205
|
+
- Updated dependencies [bb31983]
|
|
206
|
+
- Updated dependencies [aaaa92c]
|
|
207
|
+
- Updated dependencies [34bdbfb]
|
|
208
|
+
- Updated dependencies [dae5079]
|
|
209
|
+
- Updated dependencies [34bdbfb]
|
|
210
|
+
- Updated dependencies [bb05f21]
|
|
211
|
+
- Updated dependencies [dae5079]
|
|
212
|
+
- Updated dependencies [34bdbfb]
|
|
213
|
+
- Updated dependencies [aaaa92c]
|
|
214
|
+
- Updated dependencies [34bdbfb]
|
|
215
|
+
- Updated dependencies [aaaa92c]
|
|
216
|
+
- Updated dependencies [34bdbfb]
|
|
217
|
+
- Updated dependencies [bb05f21]
|
|
218
|
+
- Updated dependencies [aaaa92c]
|
|
219
|
+
- Updated dependencies [aaaa92c]
|
|
220
|
+
- Updated dependencies [bb31983]
|
|
221
|
+
- Updated dependencies [34bdbfb]
|
|
222
|
+
- Updated dependencies [dae5079]
|
|
223
|
+
- Updated dependencies [aaaa92c]
|
|
224
|
+
- Updated dependencies [aaaa92c]
|
|
225
|
+
- Updated dependencies [aaaa92c]
|
|
226
|
+
- Updated dependencies [aaaa92c]
|
|
227
|
+
- Updated dependencies [dae5079]
|
|
228
|
+
- Updated dependencies [bb05f21]
|
|
229
|
+
- Updated dependencies [aaaa92c]
|
|
230
|
+
- Updated dependencies [dae5079]
|
|
231
|
+
- Updated dependencies [dae5079]
|
|
232
|
+
- Updated dependencies [aaaa92c]
|
|
233
|
+
- Updated dependencies [aaaa92c]
|
|
234
|
+
- Updated dependencies [34bdbfb]
|
|
235
|
+
- Updated dependencies [bb05f21]
|
|
236
|
+
- Updated dependencies [aaaa92c]
|
|
237
|
+
- Updated dependencies [bb31983]
|
|
238
|
+
- Updated dependencies [34bdbfb]
|
|
239
|
+
- Updated dependencies [dae5079]
|
|
240
|
+
- Updated dependencies [aaaa92c]
|
|
241
|
+
- Updated dependencies [aaaa92c]
|
|
242
|
+
- Updated dependencies [dae5079]
|
|
243
|
+
- Updated dependencies [bb05f21]
|
|
244
|
+
- Updated dependencies [dae5079]
|
|
245
|
+
- Updated dependencies [bb31983]
|
|
246
|
+
- Updated dependencies [bb31983]
|
|
247
|
+
- Updated dependencies [34bdbfb]
|
|
248
|
+
- Updated dependencies [bb05f21]
|
|
249
|
+
- Updated dependencies [34bdbfb]
|
|
250
|
+
- Updated dependencies [34bdbfb]
|
|
251
|
+
- @opetope/runtime@0.12.0
|
|
252
|
+
- @opetope/core@0.12.0
|
|
253
|
+
|
|
254
|
+
## 0.11.0
|
|
255
|
+
|
|
256
|
+
### Minor Changes
|
|
257
|
+
|
|
258
|
+
- 6f23026: **A retirement stops a source, it never breaks it (D364).** `Readable.subscribe` promises «I call you, you read
|
|
259
|
+
me», and `State.close()` was the only operation in the framework that notified and then refused the read. Every
|
|
260
|
+
foreign listener that followed the protocol literally therefore threw, the framework collected that throw into an
|
|
261
|
+
`AggregateError`, and reported it on behalf of a consumer that had done nothing wrong — with a Resource in the
|
|
262
|
+
picture, with a plain `ctx.state` cell and no Resource at all, and across an `optional` weak edge where a consumer
|
|
263
|
+
legitimately outlives its provider. D362 and D363 taught the framework's own nodes to read a close as a lifetime
|
|
264
|
+
fact; neither could reach a listener that is not a framework node.
|
|
265
|
+
|
|
266
|
+
- **The law.** A Resource says `idle('retired')`, a cell keeps its last value, and a read never fails because a
|
|
267
|
+
lifetime ended. `createState.close()` stops accepting writes, lets its listeners go and answers its last value
|
|
268
|
+
for ever; it notifies nobody, because nothing changed, and a later `subscribe` is inert. The controller of a
|
|
269
|
+
Resource publishes one terminal record at its fence and **seals** its publisher behind it, so a retired Resource
|
|
270
|
+
answers that record for as long as a consumer holds it and its controller's publisher retains none of the listener
|
|
271
|
+
closures it handed out. The terminal word belongs to the object whose life ended: a Resource a model owns says
|
|
272
|
+
`retired`, while a feature's `own` or exported Resource is a reference that outlives the instance, so the
|
|
273
|
+
retirement of its controller reaches it as a detachment and it answers `unleased` — one frame, and its verbs
|
|
274
|
+
answer the word its state answers.
|
|
275
|
+
- **One new type name, `ResourceIdleReason`, exported from `@opetope/core` and re-exported by `@opetope/runtime`.**
|
|
276
|
+
The compiler prints `'retired'` to a consumer whose exhaustive branch on `state.reason` breaks, and a union that
|
|
277
|
+
has no name to import is hand-copied instead — which is the one artefact that keeps compiling when a member is
|
|
278
|
+
added. The pagination reasons became one internal type and stay off the public surface.
|
|
279
|
+
- **Two new words, both inhabitants of types that already existed.** `ResourceIdleReason` gains `'retired'` — the
|
|
280
|
+
word the verbs of a retired Resource already answer, which made `state.getSnapshot()` the only member of the
|
|
281
|
+
object that refused the question the rest of it answered. `PaginationState` gains `unavailable('retired')` and
|
|
282
|
+
`PaginationOutcome` `skipped('retired')`: `resource-not-ready` reads as «not yet» and invites a retry loop, and
|
|
283
|
+
`end` would render «everything is loaded» over a Resource that is gone.
|
|
284
|
+
- **`ReadableError` is removed**, with `isClosedReadable`, the followed/unfollowed classification of a committed
|
|
285
|
+
failure, the close branches of `effect`, `scope` and the Resource inputs, the `unreadable` selection decision, both
|
|
286
|
+
catches of the feature `own` facade, and `keepLastOnClose`, the selection's closed-source memory and
|
|
287
|
+
`useResource`'s `lastGoodSnapshot` in `@opetope/react`. A write after the stop raises a `TypeError`; model writes
|
|
288
|
+
are already dropped in silence, so the only reachable caller is a test holding `testReadable`. An exhaustive
|
|
289
|
+
`switch` or a record over `state.reason` breaks at compile time, which is the migration this release asks for.
|
|
290
|
+
- **Inspection.** The frame schema is `opetope.devtools-frame/5`: the `readable` failure subject leaves the union
|
|
291
|
+
with the class that produced it, and the decoder accepts only `/5`. The subscriber count of a Resource is frozen
|
|
292
|
+
when its publisher seals instead of being zeroed, so the row of a fenced materialization reads «fenced, and N
|
|
293
|
+
consumers were still following it».
|
|
294
|
+
- **What did not change.** A producer failure is not a lifetime fact: `resource.state` still refuses a read after
|
|
295
|
+
`publisher.fail` and after a broken `nextCursor`, a `derive` still throws whatever its calculation threw, and
|
|
296
|
+
every such refusal still reaches the reader and is reported once. The weak-edge `Maybe` still says `none` before
|
|
297
|
+
the provider's cells stop, and that is the channel to read.
|
|
298
|
+
- **What it costs.** Stale is quiet where it was loud, and a React reader of `resource.state` renders one more —
|
|
299
|
+
idle — frame while a tree is torn down, where the close used to be swallowed and the last frame stood. A consumer
|
|
300
|
+
that keeps a handle on a stopped cell keeps its last value: measured at 7.99 MB over an 8 MB source, released in
|
|
301
|
+
full when the handle goes, and bounded by the consumer's own reference. A Resource is the opposite: the fence
|
|
302
|
+
releases its `Data`, its cache and its pagination anchor before the terminal record is published, so holding
|
|
303
|
+
`resource.state` for ever costs nothing. The disposer `subscribe` answers is built outside the cell now, so a
|
|
304
|
+
consumer that kept only the disposer — and had therefore let the `Readable` go — no longer pins the last value
|
|
305
|
+
through it.
|
|
306
|
+
|
|
307
|
+
### Patch Changes
|
|
308
|
+
|
|
309
|
+
- 0eb82ba: **A close is recognized as this source's own, and its memory belongs to the mount (D363).** D362 shipped in 0.10.1
|
|
310
|
+
applied in fewer places than it claimed, and the predicate behind it could not tell three different things apart.
|
|
311
|
+
|
|
312
|
+
- **The scope input of a Resource.** An independent `identity.scope` was read inside the same `try` as
|
|
313
|
+
`resourceKeyOf` and `scopeOf`, so its close became a broken selector: one `ReadableError` in the reporter, and —
|
|
314
|
+
with no materialization current — a published `idle('missing-request')`, which moved the state a close must never
|
|
315
|
+
move. The source is read in its own `try` now; a throwing selector is still `broken`, with one record per
|
|
316
|
+
transition.
|
|
317
|
+
- **`useSelector` with an inline selector.** The memory of «what this reader last read» lived inside a `useMemo`
|
|
318
|
+
whose deps include the selector, so `useReadable` was safe and `useSelector(readable, inline)` — the documented
|
|
319
|
+
form — rebuilt the reader with an empty memory and failed mid-render after a close. The memory of `useSelector`
|
|
320
|
+
and of a `useModel` selection now lives in the ref the mount already holds, so a memo React discarded changes
|
|
321
|
+
nothing.
|
|
322
|
+
- **A source a node never read.** An `effect`, a `scope` or a Resource whose source was already closed when it
|
|
323
|
+
opened swallowed that close: the run never started, no child and no materialization ever opened, and nothing was
|
|
324
|
+
said anywhere. A close with nothing to keep is now reported, as React already did — the law is about not
|
|
325
|
+
reporting the end of work the runtime itself is ending.
|
|
326
|
+
- **A close raised inside a calculation.** A `derive` whose computation reads an unrelated closed `Readable` raised
|
|
327
|
+
a bare close that every observer swallowed, turning a programming fault into silence. Such a failure travels on
|
|
328
|
+
as `A calculation read a closed readable state.` with the close in its `cause`; a close that came out of a source the
|
|
329
|
+
node follows is unchanged.
|
|
330
|
+
- **Two copies of Core.** The predicate was `instanceof`, so a vendored copy beside an installed one raised a close
|
|
331
|
+
that was reported as a foreign failure. It now asks the shape the `Readable` contract already promises — an
|
|
332
|
+
`Error` named `ReadableError` with code `closed` — so the close of either copy is the same fact.
|
|
333
|
+
- **Two React readers that swallowed everything.** `useResource` and the passive reads of a selection caught every
|
|
334
|
+
refusal with a bare `catch`, including a failed publisher cell and a broken `nextCursor`. They apply the same
|
|
335
|
+
predicate now and rethrow anything else.
|
|
336
|
+
- **The selection fast path** records a closed source once instead of re-reading it on every render, so a mount
|
|
337
|
+
whose source closed stops rebuilding its selection — and stops allocating a failure per read — for the rest of
|
|
338
|
+
its life.
|
|
339
|
+
|
|
340
|
+
- Updated dependencies [6f23026]
|
|
341
|
+
- Updated dependencies [0eb82ba]
|
|
342
|
+
- @opetope/core@0.11.0
|
|
343
|
+
- @opetope/runtime@0.11.0
|
|
344
|
+
|
|
3
345
|
## 0.10.1
|
|
4
346
|
|
|
5
347
|
### Patch Changes
|
|
@@ -183,7 +525,7 @@
|
|
|
183
525
|
|
|
184
526
|
**`ResourceHook` (D321).** `useResource(resource)` answers `{ snapshot, refresh, retry }`, and a field of a
|
|
185
527
|
`useModel` selection whose value is a request answers the same hook and takes the same lease. Nine models of one
|
|
186
|
-
application had wrapped `resource.refresh()` in a `
|
|
528
|
+
application had wrapped `resource.refresh()` in a `Command<void, void>` for nothing but the `inFlight` of a button.
|
|
187
529
|
Recognition asks the same question the type asks: `Resource` is a structural contract (D199), so a host adapter that
|
|
188
530
|
satisfies it is classified and leased exactly like a materialization the runtime built, which stays the fast path.
|
|
189
531
|
`read(resource)` still answers the snapshot and leases nothing. One pair of controllers per resource identity keeps
|
|
@@ -215,11 +557,11 @@
|
|
|
215
557
|
|
|
216
558
|
### Patch Changes
|
|
217
559
|
|
|
218
|
-
- b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет
|
|
560
|
+
- b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет Command из
|
|
219
561
|
effect и сериализует независимые объекты через очередь по ключу. Stream умеет накапливать кадры в границах
|
|
220
562
|
открытия. Контекст данных отделён от ключа, а ёмкость кэша перенесена из retention в самостоятельную опцию cache.
|
|
221
563
|
Retention теперь задаётся литералом, model resource/stream принимает готовый Readable, а `queueBy` создаёт
|
|
222
|
-
приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного
|
|
564
|
+
приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного Command
|
|
223
565
|
через `rethrowIfCancelled`.
|
|
224
566
|
- Updated dependencies [b964772]
|
|
225
567
|
- @opetope/core@0.9.3
|
|
@@ -239,7 +581,7 @@
|
|
|
239
581
|
|
|
240
582
|
- d7f346a: A command hook is `{ run, inFlight, outcome }`, and an owned attachment step is `step(key, run, cleanup)`.
|
|
241
583
|
|
|
242
|
-
`CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `
|
|
584
|
+
`CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `Command` fields of a
|
|
243
585
|
`useModel` selection. It bought one arrow in JSX and nothing the types did not already hold: `onClick={logout.run}`
|
|
244
586
|
never compiled, because a `MouseEvent` is not a `void` input. A command without input now binds as
|
|
245
587
|
`onClick={() => void logout.run()}` (D292).
|
|
@@ -314,17 +656,17 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
|
|
|
314
656
|
`timeoutMs`, so an expiry does not read like a single failed assertion. A throw that is not an `Error` is rethrown
|
|
315
657
|
untouched.
|
|
316
658
|
|
|
317
|
-
`
|
|
659
|
+
`runCommand(target, input?, { signal, timeoutMs })` takes a deadline on that one run. `run` is the top-level execution
|
|
318
660
|
the consumer owns (D292), so the deadline cancels the way that consumer cancels — by aborting the signal this run
|
|
319
|
-
supplied — and rejects with a `
|
|
661
|
+
supplied — and rejects with a `CommandTimeoutError` naming the call and the deadline. A `signal` stands beside it and
|
|
320
662
|
the first of the two to abort supplies the reason, so a cancelled outcome is never reported as a timeout.
|
|
321
663
|
|
|
322
|
-
`command(run)` is now published by `@opetope/core/testing` as a `
|
|
664
|
+
`command(run)` is now published by `@opetope/core/testing` as a `Command` fixture with no binding: the body is
|
|
323
665
|
positional and receives the signal of the run, and there is no lane, queue, dedupe or retirement — so no `inFlight`
|
|
324
666
|
to read, no fence, and two runs of one fixture overlap. A model opened with `openModel` takes it as a dependency,
|
|
325
|
-
which removes the second model a test used to open for one
|
|
667
|
+
which removes the second model a test used to open for one Command. `@opetope/react/testing` keeps its own `command`,
|
|
326
668
|
which is this one plus the contribution binding a mount reads, and also re-exports `yieldTurn`, `eventually` and
|
|
327
|
-
`
|
|
669
|
+
`CommandTimeoutError`.
|
|
328
670
|
|
|
329
671
|
`settled` and `waitFor` accept the `ApplicationExecution` an `openApplication` returned. An application contributes
|
|
330
672
|
the models of every generation it holds right now and a fifth fact of its own: the instances whose retirement has
|
|
@@ -334,7 +676,7 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
|
|
|
334
676
|
|
|
335
677
|
Migration: `flush()` (a `setImmediate` or `setTimeout(0)` helper) → `yieldTurn()`; a local generic
|
|
336
678
|
`waitFor<Value>(read, predicate)` → the library `waitFor`, which returns the value; a local `eventually` → the
|
|
337
|
-
library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `
|
|
679
|
+
library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `runCommand` →
|
|
338
680
|
`{ timeoutMs: 500 }`; `command` from `@opetope/react/testing` in a test that renders nothing →
|
|
339
681
|
`command` from `@opetope/core/testing`; and a hand-written poll loop for an application → `settled(application)`.
|
|
340
682
|
|
|
@@ -362,9 +704,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
362
704
|
|
|
363
705
|
- 0318472: Give every package a testing entry, and open a model without a feature.
|
|
364
706
|
|
|
365
|
-
`@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `
|
|
707
|
+
`@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `runCommand` — moved
|
|
366
708
|
here from `@opetope/react/testing`, because it reaches the existing invoker and knows about neither a renderer nor a
|
|
367
|
-
feature — and `
|
|
709
|
+
feature — and `CommandInputArgs`, so a test can wrap `runCommand` without naming `./internal` (D285).
|
|
368
710
|
|
|
369
711
|
`@opetope/runtime/testing` adds `openModel(Decl, dependencies?, create, { reporter })`, which opens one model with
|
|
370
712
|
the same kernel, lanes and retirement the feature path builds and none of what a feature adds (D286), and
|
|
@@ -435,9 +777,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
435
777
|
|
|
436
778
|
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.
|
|
437
779
|
|
|
438
|
-
- 538cdfe: Replace singleFlight with dedupe on custom Calls. Use dedupe: true to share one pending or running execution per
|
|
780
|
+
- 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.
|
|
439
781
|
|
|
440
|
-
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
|
|
782
|
+
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.
|
|
441
783
|
|
|
442
784
|
- Updated dependencies [538cdfe]
|
|
443
785
|
- Updated dependencies [538cdfe]
|
|
@@ -475,14 +817,14 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
475
817
|
runtime nodes or adding ownership relations. Preserve diagnostic group names in the tooltip and inspector.
|
|
476
818
|
|
|
477
819
|
- a6a5f73: Remove the redundant `Command` type synonym and the `CommandOutcome` re-export from
|
|
478
|
-
`@opetope/react/integration`. Import `
|
|
820
|
+
`@opetope/react/integration`. Import `Command` from `@opetope/core` and the UI `CommandOutcome`
|
|
479
821
|
from `@opetope/react`. Alpha releases keep one current API without compatibility aliases.
|
|
480
822
|
|
|
481
823
|
### Patch Changes
|
|
482
824
|
|
|
483
825
|
- a6a5f73: Allow nested `invoke(target)` without an input placeholder for Calls accepting `void` or `undefined`, including
|
|
484
826
|
model, feature and attachment execution contexts. Keep required inputs inferred from the target, preserve generic
|
|
485
|
-
forwarding, and share the no-input rule with React `run` and testing `
|
|
827
|
+
forwarding, and share the no-input rule with React `run` and testing `runCommand`. Calls accepting `never` cannot be
|
|
486
828
|
invoked without input.
|
|
487
829
|
- 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.
|
|
488
830
|
- Updated dependencies [a6a5f73]
|
|
@@ -496,7 +838,7 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
|
|
|
496
838
|
|
|
497
839
|
### Minor Changes
|
|
498
840
|
|
|
499
|
-
- 410f2f0: Run authentic Calls in headless model tests with `
|
|
841
|
+
- 410f2f0: Run authentic Calls in headless model tests with `runCommand` from `@opetope/react/testing`, preserving their existing policies, cancellation, errors and owner lifetime.
|
|
500
842
|
|
|
501
843
|
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.
|
|
502
844
|
|