@opetope/lint 0.12.0 → 0.12.2
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 +264 -0
- package/README.md +70 -848
- package/dist/declaration-ingress.js +1 -1
- package/dist/declaration-ingress.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/retired-vocabulary.d.ts +5 -5
- package/dist/retired-vocabulary.js +1 -1
- package/dist/retired-vocabulary.js.map +1 -1
- package/dist/rules/no-retired-vocabulary.d.ts +1 -1
- package/dist/rules/no-retired-vocabulary.js +1 -1
- package/dist/rules/no-retired-vocabulary.js.map +1 -1
- package/package.json +1 -2
- package/README.ru.md +0 -838
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,269 @@
|
|
|
1
1
|
# @opetope/lint
|
|
2
2
|
|
|
3
|
+
## 0.12.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2d1ecfc: Two axes of a Resource state, not one word (D462)
|
|
8
|
+
|
|
9
|
+
`ResourceState` carries two independent fields, `failure` and `pending`, and each is declared on every member of the
|
|
10
|
+
union — `?: never` where the member cannot hold one. Both are therefore read on the whole state in one step, without
|
|
11
|
+
narrowing by `kind` first:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const refused = state.failure !== undefined;
|
|
15
|
+
const working = state.kind === 'loading' || state.pending !== undefined;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`ResourceActivity` is gone, `ResourceChannelFailure` is now `ResourceFailure` with an optional `channel`, and
|
|
19
|
+
`failed` carries `failure` instead of a top-level `error`.
|
|
20
|
+
|
|
21
|
+
**Why.** One frequent question — «did something fail?» — was asked in three shapes: `failed.error`,
|
|
22
|
+
`loading.failure`, and `ready.activity.kind === 'failed'`. «Is something in flight?» was asked in two. Both are one
|
|
23
|
+
read each now.
|
|
24
|
+
|
|
25
|
+
**What changes in behaviour.** The priority within an axis is unchanged: at an equal kind the connection outranks
|
|
26
|
+
the refresh. The priority _between_ the axes is gone — a failed channel no longer hides a working one, so a `ready`
|
|
27
|
+
record publishes a dead connection and a running refresh together. That is more information, not less, and it is the
|
|
28
|
+
difference between «the live stream is gone, what is shown will not update» and «a manual reload is running over a
|
|
29
|
+
live stream».
|
|
30
|
+
|
|
31
|
+
`pending` is observable structure, so **every transition of the work axis now publishes a record**. It did not
|
|
32
|
+
before: one projected word hid that axis on a dataless `loading`, which carried no `activity` at all, and under any
|
|
33
|
+
failure, because `failed` outranked `pending`. Measured on a live Resource with both channels, a connection opening
|
|
34
|
+
over a dataless Resource goes from 1 record to 2; a `refresh()` over a healthy `ready` from 5 to 6; and a
|
|
35
|
+
`refresh()` over a `ready` that already carries a dead connection from 5 to 7, because its settling used to cost no
|
|
36
|
+
record at all. Up to two extra publications over the life of one materialization, and they reach the subscribers of
|
|
37
|
+
a `ready` Resource and not only a dataless one. Each carries the fact the axes were split for.
|
|
38
|
+
|
|
39
|
+
**Migration.**
|
|
40
|
+
|
|
41
|
+
| Was | Is now |
|
|
42
|
+
| --------------------------------------------- | ------------------------------------------------- |
|
|
43
|
+
| `state.error` on `failed` | `state.failure.error` |
|
|
44
|
+
| `state.failure.channel` on `loading` | unchanged, and now on every kind |
|
|
45
|
+
| `state.activity.kind === 'failed'` | `state.failure !== undefined` |
|
|
46
|
+
| `state.activity.kind === 'pending'` | `state.pending !== undefined` |
|
|
47
|
+
| `state.activity.operation` | `state.failure?.channel` or `state.pending` |
|
|
48
|
+
| `state.activity.error` | `state.failure?.error` |
|
|
49
|
+
| `ResourceChannelFailure` from `@opetope/core` | `ResourceFailure` |
|
|
50
|
+
| `ResourceActivity` from `@opetope/core` | nothing: read `state.failure` and `state.pending` |
|
|
51
|
+
|
|
52
|
+
`failure.channel` is absent only on a `failed` record whose every configured channel died, where no single channel
|
|
53
|
+
is the answer. `opetope/no-retired-vocabulary` reports `ResourceActivity` at both entries that ever spelled it, and
|
|
54
|
+
reports `state.activity` wherever it is read rather than only inside a `kind === 'loading'` narrowing; the rename to
|
|
55
|
+
`ResourceFailure` is printed by the compiler itself.
|
|
56
|
+
|
|
57
|
+
The inspection protocol is unchanged: `opetope.runtime-activity/4` stays `/4`, and the facts now read the published
|
|
58
|
+
record instead of a second copy of the channel phases.
|
|
59
|
+
|
|
60
|
+
## 0.12.1
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- 8aa4d85: **Four positions of a call name a command's input, and the member that says so is an intersection (D455).** The
|
|
65
|
+
`input` of `ModelCommandContext<Input>` and of `FeatureCommandContext<Within, Input>` was
|
|
66
|
+
`Input extends void ? {instruction} : Input`, which is not provably `Input` while `Input` is unresolved: a generic
|
|
67
|
+
helper could read the member and could not hand it to anything asking for exactly its own type parameter
|
|
68
|
+
(`TS2345: Argument of type 'T extends void ? {…} : T' is not assignable to parameter of type 'T'`, and a constraint
|
|
69
|
+
on `T` did not cure it). It is `Input & (Input extends void ? {instruction} : unknown)` now. An intersection is
|
|
70
|
+
assignable to each of its members, so the hand-off resolves; the second branch is `unknown`, which intersects away,
|
|
71
|
+
so a named input reads exactly as before and a call that named none is refused on the read exactly as before. No
|
|
72
|
+
public name is added or removed — 59 types and 37 values before and after — and every emitted JavaScript file is
|
|
73
|
+
byte-identical apart from the lint rule's message, so no size budget moves. The type slices move
|
|
74
|
+
11 736 → 11 756, 87 612 → 87 710 and 160 090 → 160 268 instantiations against ceilings of 12 200, 99 062 and
|
|
75
|
+
181 237, which stay where they were.
|
|
76
|
+
|
|
77
|
+
- **The instruction the refusal prints names four positions in order of cost, and says why there is no fifth.**
|
|
78
|
+
D444 wrote that the annotation of the destructured context was the one place an input could be named. It is the
|
|
79
|
+
most expensive of four. `Input` is inferred from every position of the call except the body: the result type of a
|
|
80
|
+
model factory names the input of every command in the literal it returns; an option whose callback is annotated
|
|
81
|
+
(`dedupe`, `memoize`, `concurrency: { by }`) names it where a call was writing an option anyway; a constant
|
|
82
|
+
annotated `Command<Input, Output>` names it and keeps `Output` checked; and the annotation of the context is what
|
|
83
|
+
is left. `command<Deposit>(…)` is not a position — one explicit type argument pins `Output` to its default, and
|
|
84
|
+
the body that answers a value is refused for answering one. A feature has the last two only: `own.command`
|
|
85
|
+
answers a ref carrying the feature's id, which no public name annotates.
|
|
86
|
+
- **The authoring documents now teach the cheap positions instead of the expensive one.** The spec, the cookbook,
|
|
87
|
+
the authoring guide, the primitives reference, the wave migration and the lint rule and its README were all
|
|
88
|
+
teaching the context annotation as the one route; the compiled examples that sit inside a factory with a result
|
|
89
|
+
type name nothing now, and the ones bound to a constant annotate the constant. The examples that keep the context
|
|
90
|
+
annotation say in a comment why it is the route left there.
|
|
91
|
+
- **Both directions are held by type tests.** A generic helper handing `context.input` to something asking for
|
|
92
|
+
exactly its parameter compiles, bare and constrained; a pair of identically shaped generic factories, one writing
|
|
93
|
+
a typed option and one not, both compile, which is what says the intersection is the cure and not the option; and
|
|
94
|
+
a call whose only option names nothing is refused on the read, with each assertion naming the diagnostic it
|
|
95
|
+
expects — `TS2339` at a member read, `TS2345` at a hand-off, `TS2322` at the pinned `Output`.
|
|
96
|
+
|
|
97
|
+
- 8aec982: **A command's one waiting place is written as the record that names it: `concurrency: { pending: 'latest' }`
|
|
98
|
+
(D459).** The scalar `concurrency: 'latest'` named the input that wins and never named what it wins — «the latest»
|
|
99
|
+
reads as a place in a queue, as the body already running and as a cached answer, and only the first was true. The
|
|
100
|
+
shared form of the same fact never had that gap: `{ lane, pending: 'latest' }` says the place out loud, with the
|
|
101
|
+
`pending` an event and a live Resource already write for their own one waiting place. One fact was being written in
|
|
102
|
+
two vocabularies, and the shorter one left out the part that mattered. The word is removed rather than kept beside
|
|
103
|
+
the record, which makes this a breaking change to the authoring surface of a model command, of a model selection
|
|
104
|
+
and of a feature command.
|
|
105
|
+
|
|
106
|
+
- **Migrate by writing the place.** `concurrency: 'latest'` becomes `concurrency: { pending: 'latest' }` in
|
|
107
|
+
`ctx.command`, `ctx.select` and `own.command`. `concurrency: { lane, pending: 'latest' }` is unchanged: the lane
|
|
108
|
+
is the one parameter this record takes, and it is optional now instead of required. Nothing else about the option
|
|
109
|
+
moves — the same record, the same siblings, the same `dedupe` prohibition beside it.
|
|
110
|
+
- **No law moved with the spelling.** While call 1 runs, calls 2 and 3 take one waiting place: the body of 2 never
|
|
111
|
+
starts, its wait ends as `CommandError('cancelled', 'Command <id> replaced a waiting input.')`, and 3 runs when 1
|
|
112
|
+
finishes. A newer request never reaches the call in flight. The regression that holds this reads `signal.aborted`
|
|
113
|
+
inside the running body — after both newer requests were admitted and before the body returns — rather than
|
|
114
|
+
counting abort events, because the abort that follows a call's own end belongs to its cleanup and says nothing
|
|
115
|
+
about the policy. It was proved by substitution: with the kernel changed to cancel whatever is in flight, the
|
|
116
|
+
test fails on that line.
|
|
117
|
+
- **The retired word is answered with its replacement, not with a list of everything else.** The runtime throws
|
|
118
|
+
`Model command concurrency latest is now a record: write { pending: 'latest' }, or { lane, pending: 'latest' }.`
|
|
119
|
+
— and `Model select`, `Feature command` the same. Two neighbouring messages move with it: the choice sentence
|
|
120
|
+
loses the word — `concurrency must be queue, parallel, a lane of this surface or a record.` — and the record's own
|
|
121
|
+
refusal now says the lane is optional. A record that names `lane` and hands it `undefined` is still refused, by
|
|
122
|
+
the type under `exactOptionalPropertyTypes` and by the runtime, which reads the shape of the record by its keys.
|
|
123
|
+
- **The compiler names the replacement too, because the literal stays in the union.** The options record of a
|
|
124
|
+
command gained a third member — the retired word beside a key an author cannot write — so a refusal arrives as a
|
|
125
|
+
missing property carrying the instruction. Without it the union held no string at all, every word written there
|
|
126
|
+
widened to `string`, and one sentence answered a typo and a retired word alike. What the two now print, the same
|
|
127
|
+
on a model and on a feature: `{ concurrency: 'quee' }` is a `TS2820` that names the word and suggests `'queue'`,
|
|
128
|
+
which is better than it was before this change, and `{ concurrency: 'latest' }` is a `TS2345` whose elaboration
|
|
129
|
+
carries «`concurrency: "latest"` is now a record: write `{ pending: "latest" }`, or `{ lane, pending: "latest" }`».
|
|
130
|
+
A selection is the one surface where the compiler names the word written and not its replacement — its options
|
|
131
|
+
record is not a union — and there the runtime and the lint rule carry it.
|
|
132
|
+
- **`opetope/no-retired-vocabulary` reports it.** `concurrency: 'latest'` is a retired _value_ of a live key, the
|
|
133
|
+
shape `overflow: 'reject'` already had, so the rule now reads the values of a command's options record as well as
|
|
134
|
+
its keys and reports on the word that was written. A project that pins the rule's output by its own fixtures gains
|
|
135
|
+
one row.
|
|
136
|
+
- **What the gates read.** Instantiations are unchanged — 11 756, 87 710 and 160 268 against ceilings of 12 200,
|
|
137
|
+
99 062 and 181 237 — because a scalar became a record that already existed. The tightest size row, the headless
|
|
138
|
+
session consumer of `@opetope/devtools`, stays at 6.95 kB of 7.1 kB. The two runtime rows carry the new branch and
|
|
139
|
+
its message: the application graph consumer moves 48.56 → 48.66 kB of 49 kB and the public feature consumer
|
|
140
|
+
39.88 → 39.92 kB of 41 kB, measured by rebuilding the validator both ways. No budget moves.
|
|
141
|
+
|
|
142
|
+
- be0613e: **The cell is the only place that says what a write is (D457).** `ModelStateWriter.update` was
|
|
143
|
+
`update<Value>(state: OwnedState<Value>, update: (previous: NoInfer<Value>) => Value)`, and a type parameter in the
|
|
144
|
+
result of a callback is an inference site: `Value` was not fixed while the body was checked, so the object literal
|
|
145
|
+
the updater returned had no contextual type and its fields widened — and the widened candidate then passed the
|
|
146
|
+
state, because `OwnedState` is covariant. `update(sessions, previous => ({ ...previous, kind: 'loading' }))` into a
|
|
147
|
+
cell whose `kind` is `'idle' | 'loading'` was therefore refused with a type nobody wrote,
|
|
148
|
+
`(previous: NoInfer<Sessions>) => { items: readonly string[]; kind: string }`, and the remedies were the author's:
|
|
149
|
+
`as const`, an annotated updater result, or a cell widened to `string`. The signature is
|
|
150
|
+
`update: NoInfer<(previous: Value) => Value>` now, so the state is the only inference site and the updater is
|
|
151
|
+
checked against the cell. No public name is added or removed — 59 types and 37 values before and after — and every
|
|
152
|
+
emitted JavaScript file is byte-identical, so no size budget moves; the three type slices write no state at all and
|
|
153
|
+
stand at 11 756, 87 710 and 160 268 instantiations against ceilings of 12 200, 99 062 and 181 237. Measured on 1 200
|
|
154
|
+
identical writes that compile under both signatures, the change costs 2 instantiations and 2 types.
|
|
155
|
+
|
|
156
|
+
- **Breaking: three writes that used to compile are refused now, all for the same reason.** A value wider than the
|
|
157
|
+
cell (`update(sessions, () => wider)` where `wider.kind` is `string`); a member of a discriminated union that
|
|
158
|
+
leaves out a field that member declares (`update(phase, () => ({ kind: 'loading' }))` where that member also
|
|
159
|
+
declares `since`); and a word outside a union of literals (`update(mode, () => 'stopped')` into
|
|
160
|
+
`OwnedState<'off' | 'on'>`). Each was accepted because the widened inference and the covariance of `OwnedState`
|
|
161
|
+
met, and each reached the cell through an updater that takes no parameter — the one shape whose inference
|
|
162
|
+
resolved before the body was checked. Each is a `TS2769` where it is written now. Recompiled with the previous
|
|
163
|
+
signature, the four new type tests report 15 refusals of correct writes and 12 asserted refusals that do not
|
|
164
|
+
fire.
|
|
165
|
+
- **An updater written at the call needs no `as const`, no annotated result and no widened field.** A literal it
|
|
166
|
+
returns keeps its type through a spread, in a nested record and in a member of a union; a mutable array and a
|
|
167
|
+
tuple in a cell keep their own mutability, which is why the `const` type parameter that also fixes the literals
|
|
168
|
+
was rejected. The value form of `update` is untouched, and so is the rule that a function in that position is the
|
|
169
|
+
updater. Diagnostics improve with the fix: the compiler prints `kind: "done"` instead of `kind: string`.
|
|
170
|
+
- **Two limits of that promise, both measured.** An updater hoisted into a constant widens at its own declaration,
|
|
171
|
+
where there is no contextual type at all, so it still needs an annotated result — before and after this change.
|
|
172
|
+
And the updater half performs no excess-property check, because TypeScript does not apply one to a literal
|
|
173
|
+
returned from a contextually typed function: a stray key written inside the updater now compiles, where it used
|
|
174
|
+
to be caught in passing by the false refusal whenever the literal also had something to widen. The value half
|
|
175
|
+
still catches it and names the key.
|
|
176
|
+
- **`StateCommit.update` and the reactive calculations are unchanged, and the type tests say why.** A commit's
|
|
177
|
+
`Value` is already one cell, and so is a pipe's, so neither `commit.update` nor `fold` has a callback result to
|
|
178
|
+
infer from. `derive`, `computed`, `fromMaybe`, `fromExternal` and the `project` of a collection do put a type
|
|
179
|
+
parameter in the result of a callback, and none of them has this defect: a widened result has nowhere to go
|
|
180
|
+
there, because no covariant container stands in the call for a supertype to pass through — the assignment of the
|
|
181
|
+
result refuses it. The `apply.change` of a live Resource is the one public callback left where the same widening
|
|
182
|
+
is live; it belongs to a separate change.
|
|
183
|
+
- **One workaround is gone.** The `@opetope/lint` README declared its example state with `kind: string` and a
|
|
184
|
+
comment saying the widening was a defect of the writer's own signature; the field is `'idle' | 'loading'` again
|
|
185
|
+
and the comment is gone. Spec §2.38 is the new compile-checked example, and it asserts all three closed holes.
|
|
186
|
+
|
|
187
|
+
- 4e65393: **The published archive of every package loses `README.ru.md`, and `@opetope/runtime` loses the Russian pages of
|
|
188
|
+
its `docs/` as well (D456).** The Russian half of the documentation is removed: nineteen `*.ru.md` files,
|
|
189
|
+
**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.
|
|
190
|
+
Documentation is written once, in English, from here on. No public name, no `exports` entry and no subpath moves —
|
|
191
|
+
a `.ru.md` was never a resolvable specifier, only a file read by its path — and no line of shipped JavaScript
|
|
192
|
+
changes, which is why this is a patch. The composition of what is published does change, and this is the line that
|
|
193
|
+
says so.
|
|
194
|
+
|
|
195
|
+
- **What goes.** `CONTRIBUTING.ru.md`, `README.ru.md`, the README of the minimal React example, the nine guides of
|
|
196
|
+
`docs/` — agent guide, cookbook, devtools, how-it-works, primitives, releases, both migration guides and the
|
|
197
|
+
spec — and the `README.ru.md` of all seven packages. `docs/decisions.md` stays Russian and keeps no pair, as it
|
|
198
|
+
never had one. The nine copies under `packages/runtime/docs/` are generated by packaging and leave on their own.
|
|
199
|
+
- **The rule that required a pair is cancelled, with its gate.** `CLAUDE.md` said «README and user guides have
|
|
200
|
+
English/Russian pairs, updated together» with four exceptions; it now says a document is written once, in
|
|
201
|
+
English, and gets no second copy. `ci:docs` no longer looks for a `*.ru.md` beside a document and no longer
|
|
202
|
+
compares the heading skeleton of a pair, and its report reads «links and example section order passed».
|
|
203
|
+
- **One gate is lost outright, and nothing replaces it.** The fixture generator held every English example against
|
|
204
|
+
its Russian twin as the same program — syntax without comments and without JSX text — which is how D441 caught a
|
|
205
|
+
real drift in a spec §2 block, a string literal that differed in one copy. The defect class that stops being
|
|
206
|
+
caught is a silent edit to a detail of an example that still compiles: a string literal, a number, the order of
|
|
207
|
+
two arguments of the same type. `ci:type` accepts any literal of the right type, `ci:docs` reads section order
|
|
208
|
+
and links rather than values, `ci:asserted-refusals` holds pragmas, and the generator's own `--check` compares a
|
|
209
|
+
fixture with the very document it was lifted from, so one `fixtures:generate` makes any such edit the new
|
|
210
|
+
baseline. The twin was the only place in this repository where one program was written twice and the two
|
|
211
|
+
writings were compared. It also never knew which copy was right — in the one case it fired, the wrong copy was
|
|
212
|
+
the Russian one — and it protected above all the copy that nothing compiled.
|
|
213
|
+
- **Where the archive change is written down, so it cannot drift from its gate.** The composition of an archive is
|
|
214
|
+
stated in six places and all six are rewritten: the `files` of the seven manifests; the required-file list and
|
|
215
|
+
the archive-member allowlist of the pack archive check; the fixture of that check's own unit test; the
|
|
216
|
+
allowlists of the two real pack smoke tests; and what packaging demands of each package before it packs. The
|
|
217
|
+
requirement that `@opetope/runtime` ship `docs/spec.ru.md` is gone; `docs/spec.md` is required exactly as before.
|
|
218
|
+
- **The numbers the gates print.** `ci:docs` reads 28 documents instead of 46 — nineteen leave and this changeset
|
|
219
|
+
is itself a document — with 4 pending changesets instead of 3 and 424 decision identifiers instead of 423. Its
|
|
220
|
+
feature examples halve, 64 to 32: that gate lints every fenced example that declares a feature, and it was
|
|
221
|
+
linting both copies of each one. Packaging prepares 10 documents instead of 19, and the merge-marker scan reads
|
|
222
|
+
1101 text files instead of 1117 — seventeen of the nineteen removed files live under a scanned root, and this
|
|
223
|
+
changeset is one file back. The document examples do not move at all: 38 spec §2 blocks and 132 blocks of 11
|
|
224
|
+
other documents, 2 fragments, 14 asserted refusals, exactly as before, because only the English block was ever
|
|
225
|
+
lifted into a fixture.
|
|
226
|
+
|
|
227
|
+
- e12b0b6: **An event takes its handler second: `event(source, run, subscribe, options?)` (D458).** The natural declaration was
|
|
228
|
+
refused although its payload was already annotated — `emit` typed as
|
|
229
|
+
`(payload: EventPayloadNotNamed | undefined) => boolean`, and `run` as taking the marker. The cause is the order of
|
|
230
|
+
the arguments and nothing else. TypeScript types a call's arguments in order and leaves a context-sensitive callback
|
|
231
|
+
out of the first inference pass; in the second it walks them left to right, and resolves the type parameters a
|
|
232
|
+
callback's own type refers to before giving that callback a contextual type. `subscribe` stood second and its one
|
|
233
|
+
unannotated parameter is a context carrying `Payload`, so `Payload` answered its default on the way past, before the
|
|
234
|
+
annotation on `run` was ever read. With `run` second, its unannotated parameter is a run context, which carries no
|
|
235
|
+
payload at all, so nothing about it resolves `Payload` and one annotation is the whole naming site. This is a
|
|
236
|
+
breaking change to the authoring surface of both a model and a feature.
|
|
237
|
+
|
|
238
|
+
- **Migrate by swapping the two callbacks.** `ctx.event(source, subscribe, run, options?)` becomes
|
|
239
|
+
`ctx.event(source, run, subscribe, options?)`, and `own.event` of a feature the same. Nothing else about the node
|
|
240
|
+
moves: the same four arguments, the same options record, the same `request`, the same delivery policies. A call
|
|
241
|
+
left in the old order does not compile silently — each callback is checked against the other's signature.
|
|
242
|
+
- **One annotation on `run` is now the whole naming site, whatever it does with its context.** The condition the
|
|
243
|
+
previous decision recorded — that a run annotating its payload and destructuring its context names nothing — was
|
|
244
|
+
true only while `subscribe` stood first. It is gone, and so is the shape it forced: the polling recipe of the
|
|
245
|
+
cookbook and the spec no longer annotate a context they only destructure. The marker's instruction narrowed with
|
|
246
|
+
it, from «annotate every parameter of `run`, the payload first — a `run` that leaves its context unannotated names
|
|
247
|
+
nothing — or annotate a `delivery.by` parameter» to «annotate the first parameter of `run`, or annotate a
|
|
248
|
+
`delivery.by` parameter», and the diagnostics gate holds those words.
|
|
249
|
+
- **The node now reads like every other one.** `effect`, `scope.while` and `scope.switch` all take the working
|
|
250
|
+
callback straight after the source; the event was the one node with a second callback wedged between them. The
|
|
251
|
+
rule the spec states for the whole vocabulary — the source first, the work next, modifiers last — is true here
|
|
252
|
+
without an exception now.
|
|
253
|
+
- **No single-signature cure exists, and that was measured rather than assumed.** On TypeScript 7.0.2, 6.0.2 and
|
|
254
|
+
6.0.3: carrying the payload through a separate inferred parameter as `Parameters<Run>[0]` answers `never`;
|
|
255
|
+
`NoInfer` on `emit` and `NoInfer` over the whole `subscribe` type both fail, because a reference to the parameter
|
|
256
|
+
is still a reference; a separate `Emitted extends Payload` fails; and a conditional `emit` that widens to
|
|
257
|
+
`unknown` or `any` stops the complaint at `emit` and poisons the inference instead. While `subscribe` stands
|
|
258
|
+
before `run` and mentions the payload, the order is the only lever there is.
|
|
259
|
+
- **`@opetope/lint` moves with it.** The ingress of an event is the third argument now, so
|
|
260
|
+
`opetope/no-subscribe-outside-models` reads it there; a project that pins the rule's behaviour by its own fixtures
|
|
261
|
+
updates them the same way an application updates its calls.
|
|
262
|
+
- **What the gates read.** Instantiations are unchanged — 11 756, 87 710 and 160 268 against ceilings of 12 200,
|
|
263
|
+
99 062 and 181 237 — because a reorder adds no type. The tightest size row, the headless session consumer of
|
|
264
|
+
`@opetope/devtools`, stays at 6.95 kB of 7.1 kB: the only emitted JavaScript that changes is one `TypeError`
|
|
265
|
+
message, `Model event run and subscribe must be functions.`, and the argument index the lint rule carries.
|
|
266
|
+
|
|
3
267
|
## 0.12.0
|
|
4
268
|
|
|
5
269
|
### Minor Changes
|