@objectstack/core 17.0.0-rc.1 → 17.0.0-rc.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 +248 -0
- package/dist/index.cjs +453 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -3
- package/dist/index.d.ts +234 -3
- package/dist/index.js +446 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,253 @@
|
|
|
1
1
|
# @objectstack/core
|
|
2
2
|
|
|
3
|
+
## 17.0.0-rc.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 98877c9: feat(core,platform-objects,spec): the ADR-0119 D2 migration-journal runner — a migration killed mid-run is resumable to completion or compensable to clean, with journal rows proving which (#4617)
|
|
8
|
+
|
|
9
|
+
**The gap D1 left open.** ADR-0119 D1 made `engine.transaction()` reachable
|
|
10
|
+
through the contract, which is the right answer for multi-write atomicity that
|
|
11
|
+
fits in one transaction. Migration-class work does not fit: a million-row
|
|
12
|
+
backfill cannot hold one write-lock for its duration, `driver-memory`'s
|
|
13
|
+
`beginTransaction` deep-clones the entire database (O(db) per begin),
|
|
14
|
+
`ObjectQL.transaction()` binds the **default driver only** so a multi-datasource
|
|
15
|
+
migration silently commits part of its work outside it, and a process **killed**
|
|
16
|
+
— as distinct from a thrown error — defeats in-process rollback entirely. So the
|
|
17
|
+
unit of atomicity is the _chunk_, and durability across chunks is a journal.
|
|
18
|
+
|
|
19
|
+
Four consumers had each converged on the same four moves — dry-run preflight,
|
|
20
|
+
undo journal, LIFO compensation, re-entrant forward recovery (ADR-0105 D13
|
|
21
|
+
promotion, ADR-0117 D8's ownership backfill, the org lifecycle transitions, and
|
|
22
|
+
D10 master-data distribution #4585). One copy is engineering; four is platform
|
|
23
|
+
debt, and the fourth author would have had to rediscover the invariant below
|
|
24
|
+
from scratch.
|
|
25
|
+
|
|
26
|
+
**New: `runMigrationJournal` (`@objectstack/core`).** Preflight runs every
|
|
27
|
+
step's read-only validator before any step writes, so a plan that would fail at
|
|
28
|
+
step 3 has not written step 1. Rows are chunked per the `bulk-write.ts`
|
|
29
|
+
discipline; each chunk's writes run inside `engine.transaction()`. On failure,
|
|
30
|
+
committed chunks are compensated newest-first, each in its own transaction. On
|
|
31
|
+
restart, a rediscovered run resumes forward from the first chunk lacking
|
|
32
|
+
`chunk_done`, or unwinds, per the plan's `onCrash` policy. Forward and
|
|
33
|
+
compensate callbacks receive an `attempt` counter; `attempt > 1` means the prior
|
|
34
|
+
outcome is UNKNOWN and the callback must recheck by natural key before
|
|
35
|
+
re-writing — the same at-least-once contract `bulk-write.ts` already documents,
|
|
36
|
+
reused rather than re-derived.
|
|
37
|
+
|
|
38
|
+
**The invariant that carries the design:** `chunk_done(i)` is written **inside**
|
|
39
|
+
the chunk's own transaction, so `done ⇔ committed` holds by construction;
|
|
40
|
+
`chunk_started(i)` is written autonomously **before** it. That asymmetry is what
|
|
41
|
+
gives `started ∧ ¬done` exactly one meaning — _the outcome is unknown_ — which
|
|
42
|
+
is the only state a crash can leave and the only state recovery reasons about.
|
|
43
|
+
Making both writes symmetric would look tidier and would destroy recovery.
|
|
44
|
+
|
|
45
|
+
**New: `sys_migration_journal` (`@objectstack/platform-objects`).** Rows keyed
|
|
46
|
+
`(run_id, seq)` under a unique index, so a resumed run that miscomputes its next
|
|
47
|
+
sequence fails loudly rather than double-recording an event. Registered
|
|
48
|
+
unconditionally alongside `sys_migration` because recovery must be discoverable
|
|
49
|
+
with **zero host wiring** — a journal some kernels compose and others do not is
|
|
50
|
+
a journal a boot scanner cannot rely on (ADR-0078). Distinct in grain from
|
|
51
|
+
`sys_migration`, which holds one durable verdict per named migration; this holds
|
|
52
|
+
many rows per _run_. Read-only over the API; writes go through the runner in
|
|
53
|
+
system context.
|
|
54
|
+
|
|
55
|
+
**The runner refuses rather than degrades**, in four places: the runtime cannot
|
|
56
|
+
roll back; any preflight fails; the plan declares `onCrash: 'compensate'` but a
|
|
57
|
+
step cannot compensate; or a resume's plan hash disagrees with the journal
|
|
58
|
+
(resuming a changed plan would apply chunk boundaries the journal never
|
|
59
|
+
described). A compensation failure halts and is journalled — never swallowed —
|
|
60
|
+
and the run ends `failed`, not `compensated`, because a database in a state no
|
|
61
|
+
clean story covers must not be reported as a tidy rollback.
|
|
62
|
+
|
|
63
|
+
**`engineCanRollBack` is now shared.** The two-level probe (engine method AND
|
|
64
|
+
default-driver `beginTransaction`) was the same condition written twice — here
|
|
65
|
+
and in `batchData`'s atomic gate. It now lives in `@objectstack/core` and
|
|
66
|
+
`@objectstack/metadata-protocol` imports it, as a type predicate so callers do
|
|
67
|
+
not each re-narrow the optional member by hand. Two copies of "can this runtime
|
|
68
|
+
actually roll back?" drift by one clause and leave one caller believing it has
|
|
69
|
+
atomicity it does not have.
|
|
70
|
+
|
|
71
|
+
Boot reconciliation and `os migrate resume` land separately; `findInterruptedRuns`
|
|
72
|
+
is the discovery primitive they will consume, and is exported here.
|
|
73
|
+
|
|
74
|
+
**Docs:** ADR-0118 (plugin-reachable transactions) is renumbered **ADR-0119**.
|
|
75
|
+
It merged one day after an unrelated ADR-0118 (非用户 actor 的平台契约) and the
|
|
76
|
+
earlier merge holds the number; citations of "ADR-0118 D1/D2/D3/D4" written
|
|
77
|
+
before 2026-08-03 mean the renumbered record.
|
|
78
|
+
|
|
79
|
+
- 071d0dc: feat(runtime,cli,core): boot reconciliation and `os migrate resume` for the migration journal — an interrupted run can no longer go unnoticed (ADR-0119 D2, #4617)
|
|
80
|
+
|
|
81
|
+
Completes ADR-0119 D2. The runner and `sys_migration_journal` landed in #4668; this is the discovery channel that makes an interrupted run findable by someone who does not already know it happened.
|
|
82
|
+
|
|
83
|
+
**`MigrationRecoveryPlugin` (`@objectstack/runtime`)** — at `kernel:ready`, scans the journal for runs that started and never concluded, and warns per run: how many chunks committed, which have an **unknown** outcome (`chunk_started` with no `chunk_done`), whether a compensation was left half-finished, and the exact command that will act. It also owns the `migration-plans` registry service.
|
|
84
|
+
|
|
85
|
+
**`os migrate resume` (`@objectstack/cli`)** — lists interrupted runs (read-only, the default), or acts on one with `--run <id>`, under confirmation. Exits non-zero when a run ends `failed`, so a scripted recovery cannot move on from a migration that needs a human.
|
|
86
|
+
|
|
87
|
+
**`MigrationPlanRegistry` (`@objectstack/core`)** — where a resume finds the plan it has to re-run.
|
|
88
|
+
|
|
89
|
+
## Boot discovers, the CLI acts
|
|
90
|
+
|
|
91
|
+
This is the design decision, and it is deliberate rather than incidental.
|
|
92
|
+
|
|
93
|
+
Resuming is a large, irreversible, potentially hour-long write against production data. Doing that as an unrequested side effect of a process starting is the kind of behaviour an operator finds out about from a graph. It is also not always possible at boot: a resume needs the plan's live callbacks, and the package that owns them may not be loaded in whichever process happened to restart first.
|
|
94
|
+
|
|
95
|
+
So boot surfaces the run and names the command; the command acts, under explicit operator intent. ADR-0119 D2's per-plan `onCrash` policy still decides **what** acting means — resume forward from the first chunk lacking `chunk_done`, or unwind what committed — it just does not decide **when**, and "when" is the part a human should own.
|
|
96
|
+
|
|
97
|
+
Deferring is safe precisely because of the runner's re-entrancy: `started ∧ ¬done` is durable, so an interrupted run stays exactly as recoverable an hour later as it was at boot. Nothing decays while the operator decides.
|
|
98
|
+
|
|
99
|
+
## Why a plan registry exists at all
|
|
100
|
+
|
|
101
|
+
A journal cannot hold a plan. `forward` and `compensate` are functions and `load()` reads the live database, so none of it crosses a process boundary — which is why the journal records the plan **hash**, not the plan. Recovery therefore needs the plan handed back by the code that owns it, and `migration-plans` is that seam: between "the journal knows a run stopped at chunk 7" and "something in this process knows what chunk 7 was supposed to do".
|
|
102
|
+
|
|
103
|
+
A run whose plan no loaded package registers is **reported**, never silently skipped — the operator is told which plan id is missing. "Nothing to resume" and "the code that owns this run is not here" are different facts, and only one of them is safe to ignore.
|
|
104
|
+
|
|
105
|
+
## Degradation
|
|
106
|
+
|
|
107
|
+
No engine, or no `sys_migration_journal` registered (a lean kernel that never composed platform-objects) → the scan is skipped in **silence**: such a kernel has no interrupted runs to find, and a warning there would train operators to ignore this plugin's output, which is the one thing it cannot afford. A scan that **fails**, by contrast, is reported — "I could not check" and "there is nothing to find" are different answers.
|
|
108
|
+
|
|
109
|
+
11 new tests pin the split (boot writes nothing to the journal), the three states an operator must tell apart (clean / interrupted / half-unwound), and both degradation paths.
|
|
110
|
+
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
- 833b512: fix(core): 插件 init/start 的超时守卫定时器在 race 结束时被清除,进程不再空转 `startupTimeout` (#4813)
|
|
114
|
+
|
|
115
|
+
`ObjectKernel.initPluginWithTimeout()` / `startPluginWithTimeout()` 各自 `setTimeout` armed
|
|
116
|
+
一根超时守卫,然后**把它扔了**:插件赢下 race 之后,那根定时器既没 `clearTimeout` 也没
|
|
117
|
+
`unref()`,带着 ref 一直挂到 `startupTimeout` 走完。于是每个进程在活干完之后还要空转整整
|
|
118
|
+
一个 `startupTimeout` —— `ObjectQLPlugin` 是 120 秒。
|
|
119
|
+
|
|
120
|
+
实测(`examples/app-crm`,同一条 `migrate recorded-by --json`,同一个构建链,唯一差别是本
|
|
121
|
+
改动):
|
|
122
|
+
|
|
123
|
+
| | 墙钟 |
|
|
124
|
+
| :----- | :----- |
|
|
125
|
+
| 修复前 | 122.4s |
|
|
126
|
+
| 修复后 | 3.1s |
|
|
127
|
+
|
|
128
|
+
JSON 与 `✅ Graceful shutdown complete` 两次都在 ~3 秒出现 —— 后面那 119 秒纯粹是 8 根
|
|
129
|
+
孤儿定时器(4 个 init + 4 个 start)钉着事件循环。`os serve` 里同样漏,只是那里进程本来
|
|
130
|
+
就长命,看不出来。
|
|
131
|
+
|
|
132
|
+
**为什么是 `clearTimeout` 而不是 `unref()`。** 隔壁 `shutdown()` 的守卫用的是 `unref()`,
|
|
133
|
+
但那个写法在这里是错的,而且不是风格问题:`unref()` 让定时器不再钉住事件循环,**同时也
|
|
134
|
+
让它不再是一个守卫** —— 若 hook 永不 settle 且没有别的东西撑着事件循环,Node 会在定时器
|
|
135
|
+
触发之前直接退出,超时被**静默吞掉**,谁也不会收到那个 error。守卫必须在 race 未决期间
|
|
136
|
+
保持 ref'd,在 race 落定的那一刻被回收,这正是 `finally { clearTimeout(guard) }` 表达的
|
|
137
|
+
语义。两个守卫合并为一个私有 helper `raceStartupTimeout()`,措辞与理由写在它的 doc
|
|
138
|
+
comment 里。
|
|
139
|
+
|
|
140
|
+
`startupTimeout` 的取值一个都没动 —— 慢启动的插件需要那个上限,问题从来不在时长,而在
|
|
141
|
+
没人回收。
|
|
142
|
+
|
|
143
|
+
- Updated dependencies [430dcc2]
|
|
144
|
+
- Updated dependencies [e6ac4bd]
|
|
145
|
+
- Updated dependencies [80334c7]
|
|
146
|
+
- Updated dependencies [ce5242c]
|
|
147
|
+
- Updated dependencies [a7163ea]
|
|
148
|
+
- Updated dependencies [e6e9379]
|
|
149
|
+
- Updated dependencies [98877c9]
|
|
150
|
+
- Updated dependencies [98877c9]
|
|
151
|
+
- Updated dependencies [e6b1b69]
|
|
152
|
+
- Updated dependencies [ad047d2]
|
|
153
|
+
- Updated dependencies [2826d1e]
|
|
154
|
+
- Updated dependencies [5a84d41]
|
|
155
|
+
- Updated dependencies [20b1a9e]
|
|
156
|
+
- Updated dependencies [203a449]
|
|
157
|
+
- Updated dependencies [ac37fc6]
|
|
158
|
+
- Updated dependencies [4820f55]
|
|
159
|
+
- Updated dependencies [462d9c4]
|
|
160
|
+
- Updated dependencies [7d21581]
|
|
161
|
+
- Updated dependencies [f2445c9]
|
|
162
|
+
- Updated dependencies [23338c3]
|
|
163
|
+
- Updated dependencies [5b843fb]
|
|
164
|
+
- Updated dependencies [b4487aa]
|
|
165
|
+
- Updated dependencies [65ca83a]
|
|
166
|
+
- Updated dependencies [67bf2e2]
|
|
167
|
+
- Updated dependencies [c6d1cb4]
|
|
168
|
+
- Updated dependencies [36030ff]
|
|
169
|
+
- Updated dependencies [6117f7b]
|
|
170
|
+
- Updated dependencies [e533b0b]
|
|
171
|
+
- Updated dependencies [cdf4d9a]
|
|
172
|
+
- Updated dependencies [aee1806]
|
|
173
|
+
- Updated dependencies [c13350b]
|
|
174
|
+
- Updated dependencies [c13350b]
|
|
175
|
+
- Updated dependencies [9ca2d85]
|
|
176
|
+
- Updated dependencies [c13350b]
|
|
177
|
+
- Updated dependencies [891d345]
|
|
178
|
+
- Updated dependencies [a52e2ef]
|
|
179
|
+
- Updated dependencies [5293114]
|
|
180
|
+
- Updated dependencies [20bc357]
|
|
181
|
+
- Updated dependencies [5966c2a]
|
|
182
|
+
- Updated dependencies [2382580]
|
|
183
|
+
- Updated dependencies [d9fa683]
|
|
184
|
+
- Updated dependencies [3c7bcc0]
|
|
185
|
+
- Updated dependencies [4b6cac7]
|
|
186
|
+
- Updated dependencies [7631964]
|
|
187
|
+
- Updated dependencies [ac471a0]
|
|
188
|
+
- Updated dependencies [60ae58e]
|
|
189
|
+
- Updated dependencies [ce92674]
|
|
190
|
+
- Updated dependencies [9f601e8]
|
|
191
|
+
- Updated dependencies [51c5227]
|
|
192
|
+
- Updated dependencies [a4a85c8]
|
|
193
|
+
- Updated dependencies [07a4e26]
|
|
194
|
+
- Updated dependencies [ec975f1]
|
|
195
|
+
- Updated dependencies [eb4204b]
|
|
196
|
+
- Updated dependencies [4f13be2]
|
|
197
|
+
- Updated dependencies [61cc079]
|
|
198
|
+
- Updated dependencies [0e96e46]
|
|
199
|
+
- Updated dependencies [d52d4fe]
|
|
200
|
+
- Updated dependencies [742cebb]
|
|
201
|
+
- Updated dependencies [ce92674]
|
|
202
|
+
- Updated dependencies [cf2c9b7]
|
|
203
|
+
- Updated dependencies [0f9faa2]
|
|
204
|
+
- Updated dependencies [7cf42fe]
|
|
205
|
+
- Updated dependencies [5966c2a]
|
|
206
|
+
- Updated dependencies [f78dd83]
|
|
207
|
+
- Updated dependencies [a2cd18a]
|
|
208
|
+
- Updated dependencies [4638aaa]
|
|
209
|
+
- Updated dependencies [0222d3c]
|
|
210
|
+
- Updated dependencies [0a936ea]
|
|
211
|
+
- Updated dependencies [023c00b]
|
|
212
|
+
- Updated dependencies [155507e]
|
|
213
|
+
- Updated dependencies [7bba90b]
|
|
214
|
+
- Updated dependencies [7e05d8e]
|
|
215
|
+
- Updated dependencies [061406d]
|
|
216
|
+
- Updated dependencies [c1f344b]
|
|
217
|
+
- Updated dependencies [9c93465]
|
|
218
|
+
- Updated dependencies [ebb209c]
|
|
219
|
+
- Updated dependencies [63b33e6]
|
|
220
|
+
- Updated dependencies [2a44c1d]
|
|
221
|
+
- Updated dependencies [695cfbd]
|
|
222
|
+
- Updated dependencies [7445149]
|
|
223
|
+
- Updated dependencies [071d0dc]
|
|
224
|
+
- Updated dependencies [0848bea]
|
|
225
|
+
- Updated dependencies [d51bed2]
|
|
226
|
+
- Updated dependencies [b8b3c64]
|
|
227
|
+
- Updated dependencies [0c0fbd9]
|
|
228
|
+
- Updated dependencies [f3141d8]
|
|
229
|
+
- Updated dependencies [5a84d41]
|
|
230
|
+
- Updated dependencies [fd3013a]
|
|
231
|
+
- Updated dependencies [21676eb]
|
|
232
|
+
- Updated dependencies [e336549]
|
|
233
|
+
- Updated dependencies [d40f43a]
|
|
234
|
+
- Updated dependencies [e5e7ee0]
|
|
235
|
+
- Updated dependencies [a2ebea2]
|
|
236
|
+
- Updated dependencies [800bdb0]
|
|
237
|
+
- Updated dependencies [04f1182]
|
|
238
|
+
- Updated dependencies [5647006]
|
|
239
|
+
- Updated dependencies [38f7e4f]
|
|
240
|
+
- Updated dependencies [c57f3cf]
|
|
241
|
+
- Updated dependencies [97faca3]
|
|
242
|
+
- Updated dependencies [ad5fe25]
|
|
243
|
+
- Updated dependencies [ea90179]
|
|
244
|
+
- Updated dependencies [ce92674]
|
|
245
|
+
- Updated dependencies [5ef0b5b]
|
|
246
|
+
- Updated dependencies [48fbacb]
|
|
247
|
+
- Updated dependencies [355e951]
|
|
248
|
+
- Updated dependencies [dadb43f]
|
|
249
|
+
- @objectstack/spec@17.0.0-rc.2
|
|
250
|
+
|
|
3
251
|
## 17.0.0-rc.1
|
|
4
252
|
|
|
5
253
|
### Minor Changes
|