@warmhub/sdk-ts 0.120.0 → 0.123.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 +3 -369
- package/README.md +1 -1
- package/dist/checkpoint.js +1 -1
- package/dist/{chunk-DADPXRKU.js → chunk-C5AFXXH3.js} +6 -6
- package/dist/chunk-C5AFXXH3.js.map +1 -0
- package/dist/{chunk-DQF5ACN3.js → chunk-CNDUTYB2.js} +18 -18
- package/dist/{chunk-DQF5ACN3.js.map → chunk-CNDUTYB2.js.map} +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-DADPXRKU.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,371 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @warmhub/sdk-ts changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Release 0.123.0.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
not yet published; the release pipeline is the sole version authority and
|
|
7
|
-
stamps the exact version at publish time, so no heading here ever invents one.
|
|
8
|
-
|
|
9
|
-
Within a release, four change sections appear, in this order. A section is
|
|
10
|
-
present only when it has entries; a release may add plain narrative sections
|
|
11
|
-
after them ("Behavior worth knowing", "Still supported") when the change needs
|
|
12
|
-
context that is not itself a change entry.
|
|
13
|
-
|
|
14
|
-
- **Breaking** — your code must change. Every item is written as a migration:
|
|
15
|
-
what it was → what it is now → the exact edit, with before/after code → why,
|
|
16
|
-
in one line, citing the ruling.
|
|
17
|
-
- **Added** — new surface. Nothing to do unless you want it.
|
|
18
|
-
- **Fixed** — behavior that was wrong and now is not.
|
|
19
|
-
- **Removed** — surface that is gone with nothing to put in its place. Anything
|
|
20
|
-
a caller can migrate to is filed under Breaking instead, so this section is
|
|
21
|
-
often absent.
|
|
22
|
-
|
|
23
|
-
WarmHub's source repository is private, so rulings are cited by pull-request
|
|
24
|
-
number rather than linked. Release notes live at
|
|
25
|
-
<https://docs.warmhub.ai/releases/overview/>.
|
|
26
|
-
|
|
27
|
-
The Python SDK, `warmhub`, keeps the same file in the same shape.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Unreleased
|
|
32
|
-
|
|
33
|
-
## 0.120.0
|
|
34
|
-
|
|
35
|
-
Repository export v3. The exact version is stamped at publish time by the
|
|
36
|
-
release pipeline; source manifests stay at `0.0.0-development`.
|
|
37
|
-
|
|
38
|
-
### Breaking
|
|
39
|
-
|
|
40
|
-
#### Checkpoint generation is retired — take an export instead
|
|
41
|
-
|
|
42
|
-
**What it was.** Producing a fresh snapshot of a repository meant asking the
|
|
43
|
-
server to mint a checkpoint, polling until the job completed, requesting a
|
|
44
|
-
short-lived signed URL for the archive, downloading it outside the SDK, and
|
|
45
|
-
verifying the bytes locally.
|
|
46
|
-
|
|
47
|
-
**What it is now.** One call that streams verified rows, with the header,
|
|
48
|
-
per-row canonical decode, ordering and trailer checks applied as the bytes
|
|
49
|
-
arrive. There is no job to start, no state to poll, and no separate verify
|
|
50
|
-
step — a row that reaches you has already been proven.
|
|
51
|
-
|
|
52
|
-
**Migration.**
|
|
53
|
-
|
|
54
|
-
Before:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
import { createReadStream } from 'node:fs'
|
|
58
|
-
import { writeFile } from 'node:fs/promises'
|
|
59
|
-
import { Readable } from 'node:stream'
|
|
60
|
-
import { verifyRepositoryCheckpointArchive } from '@warmhub/sdk-ts/checkpoint'
|
|
61
|
-
|
|
62
|
-
let status = await client.repo.checkpoint.generate('acme', 'catalog')
|
|
63
|
-
while (status.state !== 'complete') {
|
|
64
|
-
await new Promise((resolve) => setTimeout(resolve, 1_000))
|
|
65
|
-
status = await client.repo.checkpoint.status('acme', 'catalog', {
|
|
66
|
-
checkpointId: status.checkpointId,
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const access = await client.repo.checkpoint.getAccess('acme', 'catalog', {
|
|
71
|
-
checkpoint: 'latest',
|
|
72
|
-
artifact: 'archive',
|
|
73
|
-
})
|
|
74
|
-
// Signed URL: fetched WITHOUT the WarmHub bearer token.
|
|
75
|
-
const archive = await fetch(access.url)
|
|
76
|
-
await writeFile('catalog.zip', Readable.fromWeb(archive.body))
|
|
77
|
-
await verifyRepositoryCheckpointArchive(createReadStream('catalog.zip'))
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
After:
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
for await (const row of client.repo.export('acme', 'catalog')) {
|
|
84
|
-
console.log(row.kind, row.durableId, row.version)
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
If you were relying on `generate` returning before the download — starting the
|
|
89
|
-
build in one process and fetching it in another, or on another machine — that
|
|
90
|
-
shape survives as the async token flow:
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
// Process A: ask for it, get a token, do not read a byte.
|
|
94
|
-
const prepared = await client.repo.exportPrepare('acme', 'catalog')
|
|
95
|
-
console.log(prepared.exportToken, prepared.atRepoSeq)
|
|
96
|
-
|
|
97
|
-
// Process B: redeem it. `export` polls while the fold is still running.
|
|
98
|
-
for await (const row of client.repo.export('acme', 'catalog', {
|
|
99
|
-
token: prepared.exportToken,
|
|
100
|
-
})) {
|
|
101
|
-
// ...
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The token is redeemable only at that repository's URL, by a principal holding
|
|
106
|
-
checkpoint read there.
|
|
107
|
-
|
|
108
|
-
**Why.** Export format v3 replaces checkpoint generation outright; the whole
|
|
109
|
-
generation pipeline is deleted server-side (PR #10020).
|
|
110
|
-
|
|
111
|
-
#### `client.repo.checkpoint.generate` and `.retry` are gone
|
|
112
|
-
|
|
113
|
-
**What it was.** `generate(orgName, repoName, { atLeastRepoSeq })` minted a
|
|
114
|
-
checkpoint; `retry(orgName, repoName, checkpointId)` re-ran a failed one. Both
|
|
115
|
-
returned a `RepositoryCheckpointStatus`.
|
|
116
|
-
|
|
117
|
-
**What it is now.** Neither method exists, and the tRPC procedures behind them
|
|
118
|
-
(`repo.checkpoint.generate`, `repo.checkpoint.retry`) are no longer served. The
|
|
119
|
-
`repo:checkpoint-generate` scope no longer buys anything.
|
|
120
|
-
|
|
121
|
-
**Migration.** Replace both with `client.repo.export`.
|
|
122
|
-
|
|
123
|
-
Before:
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
const fresh = await client.repo.checkpoint.generate('acme', 'catalog', {
|
|
127
|
-
atLeastRepoSeq: 42,
|
|
128
|
-
})
|
|
129
|
-
const recovered = await client.repo.checkpoint.retry(
|
|
130
|
-
'acme',
|
|
131
|
-
'catalog',
|
|
132
|
-
failed.checkpointId,
|
|
133
|
-
)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
After:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
// `atRepoSeq` pins the fence the way `atLeastRepoSeq` asked for a floor.
|
|
140
|
-
// Omit it and the server pins current and echoes it back in the header.
|
|
141
|
-
for await (const row of client.repo.export('acme', 'catalog', {
|
|
142
|
-
atRepoSeq: 42,
|
|
143
|
-
})) {
|
|
144
|
-
// ...
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
There is no retry to replace: an export is a read, so a failed one is re-read,
|
|
149
|
-
not re-queued.
|
|
150
|
-
|
|
151
|
-
**Why.** Same retirement (PR #10020).
|
|
152
|
-
|
|
153
|
-
#### Failed checkpoint statuses now all answer `nextAction: 'none'`
|
|
154
|
-
|
|
155
|
-
**What it was.** `RepositoryCheckpointStatus` carried `nextAction: 'retry'` for
|
|
156
|
-
`deadline_exceeded` and `attempts_exhausted`, and `nextAction: 'generate'` for
|
|
157
|
-
`invalid_source`. Each named a procedure a reader could call.
|
|
158
|
-
|
|
159
|
-
**What it is now.** Those three failure codes join `repository_deleted` in a
|
|
160
|
-
single variant with `nextAction: 'none'`. The `'retry'` and `'generate'`
|
|
161
|
-
members are gone from the union, so a `switch` over `nextAction` that had cases
|
|
162
|
-
for them will no longer typecheck against them.
|
|
163
|
-
|
|
164
|
-
**Migration.**
|
|
165
|
-
|
|
166
|
-
Before:
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
if (status.state === 'failed') {
|
|
170
|
-
switch (status.nextAction) {
|
|
171
|
-
case 'retry':
|
|
172
|
-
await client.repo.checkpoint.retry('acme', 'catalog', status.checkpointId)
|
|
173
|
-
break
|
|
174
|
-
case 'generate':
|
|
175
|
-
await client.repo.checkpoint.generate('acme', 'catalog')
|
|
176
|
-
break
|
|
177
|
-
case 'contact_support':
|
|
178
|
-
report(status.checkpointId, status.failureCode)
|
|
179
|
-
break
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
After:
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
if (status.state === 'failed') {
|
|
188
|
-
if (status.nextAction === 'contact_support') {
|
|
189
|
-
report(status.checkpointId, status.failureCode)
|
|
190
|
-
} else {
|
|
191
|
-
// Terminal. Take an export instead of trying to revive the checkpoint.
|
|
192
|
-
for await (const row of client.repo.export('acme', 'catalog')) {
|
|
193
|
-
// ...
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**Why.** Each retired value pointed at a route the server no longer serves;
|
|
200
|
-
leaving them in the vocabulary would send readers to a 404 (PR #10020).
|
|
201
|
-
|
|
202
|
-
### Added
|
|
203
|
-
|
|
204
|
-
- **`client.repo.export(orgName, repoName, options?)`** — an
|
|
205
|
-
`AsyncGenerator<RepositoryExportRow>` over a verified export stream. Options:
|
|
206
|
-
`mode` (`'heads'`, the default, or `'ops'`), `sinceRepoSeq` (delta base;
|
|
207
|
-
`0` means a full export), `atRepoSeq` (pin the fence), `prefer`
|
|
208
|
-
(`'sync'` | `'async'`), `token`, `session`, `afterDurableId`,
|
|
209
|
-
`pollIntervalMs`, and `signal`.
|
|
210
|
-
|
|
211
|
-
- **`client.repo.exportPrepare(orgName, repoName, options?)`** — returns
|
|
212
|
-
`{ exportToken, atRepoSeq }` without waiting for the fold or reading the
|
|
213
|
-
body. Takes `mode`, `sinceRepoSeq`, `atRepoSeq`, and `signal`.
|
|
214
|
-
|
|
215
|
-
- **`RepositoryExportSession` / `createRepositoryExportSession()`** — the
|
|
216
|
-
rolling verification state of one logical export: the opening `header`, the
|
|
217
|
-
resume cursor `lastDurableId`, `rowCount`, `complete`, and — once the stream
|
|
218
|
-
is finalized — `contentSha256`. Pass the same session back into `export` to
|
|
219
|
-
resume a stream that broke:
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
import {
|
|
223
|
-
createRepositoryExportSession,
|
|
224
|
-
RepositoryExportError,
|
|
225
|
-
} from '@warmhub/sdk-ts'
|
|
226
|
-
|
|
227
|
-
const session = createRepositoryExportSession()
|
|
228
|
-
const rows: unknown[] = []
|
|
229
|
-
for (let attempt = 0; attempt < 3 && !session.complete; attempt += 1) {
|
|
230
|
-
try {
|
|
231
|
-
// The session's own cursor and fence pin the resumed request; do not
|
|
232
|
-
// recompute them.
|
|
233
|
-
for await (const row of client.repo.export('acme', 'catalog', {
|
|
234
|
-
session,
|
|
235
|
-
})) {
|
|
236
|
-
rows.push(row)
|
|
237
|
-
}
|
|
238
|
-
} catch (error) {
|
|
239
|
-
if (
|
|
240
|
-
!(error instanceof RepositoryExportError) ||
|
|
241
|
-
error.reason !== 'truncated'
|
|
242
|
-
) {
|
|
243
|
-
throw error
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
console.log(session.rowCount, session.contentSha256)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
- **`readRepositoryExportStream(source, session, expected, options?)`** — read
|
|
251
|
-
a response body you fetched with your own transport, verified against a
|
|
252
|
-
session as it goes. `client.repo.export` is the usual entry point.
|
|
253
|
-
|
|
254
|
-
- **`restoreRepositoryExportSession(source, options?)`** — the offline twin:
|
|
255
|
-
replay a saved export's bytes back through the same header, canonical-row,
|
|
256
|
-
ordering and trailer checks, and get the session back rather than the rows.
|
|
257
|
-
Pass `{ partial: true }` for a download that was interrupted, then resume
|
|
258
|
-
from the session it returns.
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
import { createReadStream } from 'node:fs'
|
|
262
|
-
import { restoreRepositoryExportSession } from '@warmhub/sdk-ts'
|
|
263
|
-
|
|
264
|
-
const session = await restoreRepositoryExportSession(
|
|
265
|
-
createReadStream('catalog.ndjson'),
|
|
266
|
-
{ partial: true },
|
|
267
|
-
)
|
|
268
|
-
for await (const row of client.repo.export('acme', 'catalog', { session })) {
|
|
269
|
-
// continues from session.lastDurableId
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
- **`applyRepositoryExportDelta(base, delta)`** — fold a delta export onto a
|
|
274
|
-
base by `durableId`: active rows upsert, tombstones remove. Pure; neither
|
|
275
|
-
argument is mutated. Both arguments accept either an iterable of rows or a
|
|
276
|
-
`Map` returned by a previous fold, so folds chain.
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
import { applyRepositoryExportDelta } from '@warmhub/sdk-ts'
|
|
280
|
-
|
|
281
|
-
const session = createRepositoryExportSession()
|
|
282
|
-
const base = []
|
|
283
|
-
for await (const row of client.repo.export('acme', 'catalog', { session })) {
|
|
284
|
-
base.push(row)
|
|
285
|
-
}
|
|
286
|
-
const fence = session.header?.atRepoSeq ?? 0
|
|
287
|
-
|
|
288
|
-
// Later: only what changed since that fence.
|
|
289
|
-
const delta = []
|
|
290
|
-
for await (const row of client.repo.export('acme', 'catalog', {
|
|
291
|
-
sinceRepoSeq: fence,
|
|
292
|
-
})) {
|
|
293
|
-
delta.push(row)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const current = applyRepositoryExportDelta(base, delta)
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
`wref` is an opaque rendering and is never keyed on, so the fold survives a
|
|
300
|
-
rename.
|
|
301
|
-
|
|
302
|
-
- **`RepositoryExportError`** with a typed `reason`:
|
|
303
|
-
`'count_mismatch'`, `'digest_mismatch'`, `'fence_mismatch'`,
|
|
304
|
-
`'header_invalid'`, `'row_invalid'`, `'row_out_of_order'`,
|
|
305
|
-
`'since_below_epoch_floor'`, `'trailer_invalid'`, `'truncated'`. Every reason
|
|
306
|
-
except `'since_below_epoch_floor'` is a local verification verdict — the
|
|
307
|
-
bytes did not prove what the trailer claimed. `'since_below_epoch_floor'` is
|
|
308
|
-
the server refusing a delta base it no longer retains; recover by re-running
|
|
309
|
-
the export with `sinceRepoSeq: 0`.
|
|
310
|
-
|
|
311
|
-
- **`RepositoryExportRowMap` / `RepositoryExportRows`** — the collection types
|
|
312
|
-
`applyRepositoryExportDelta` accepts and returns.
|
|
313
|
-
|
|
314
|
-
### Behavior worth knowing
|
|
315
|
-
|
|
316
|
-
- **A redeemed token's bytes come from storage, not the API, and the client
|
|
317
|
-
checks what the API said.** A complete redemption answers with a short-lived
|
|
318
|
-
presigned URL rather than the export bytes, and the SDK fetches that URL
|
|
319
|
-
*without* your WarmHub bearer: the signature in the URL is the whole
|
|
320
|
-
capability, and the storage host has no business seeing a credential. The
|
|
321
|
-
record count and content digest the session derives are then checked against
|
|
322
|
-
the ones the API named — a download that does not match raises
|
|
323
|
-
`count_mismatch` or `digest_mismatch` rather than handing you rows nothing
|
|
324
|
-
independent vouched for. A refused or expired URL re-redeems the token, which
|
|
325
|
-
is a deterministic lookup, and retries; a break after rows have already been
|
|
326
|
-
yielded surfaces as an error instead, because a silent restart would hand you
|
|
327
|
-
those rows twice.
|
|
328
|
-
|
|
329
|
-
- **Ordering is verified, in `heads` mode only.** A heads export is a set,
|
|
330
|
-
emitted in ascending `durableId` by unsigned UTF-8 bytes, and that order is
|
|
331
|
-
part of what the digest proves. A row that does not advance the cursor raises
|
|
332
|
-
`row_out_of_order`. An `ops` export is a log — it repeats identities by
|
|
333
|
-
design — and is not order-checked.
|
|
334
|
-
|
|
335
|
-
- **Resume trailers are segment-scoped.** A resumed request carries no header,
|
|
336
|
-
and its trailer covers only that segment's rows. The session keeps two
|
|
337
|
-
hashers: a per-segment one the trailer is checked against, and a whole-stream
|
|
338
|
-
one that accumulates across segments. `session.contentSha256` is the
|
|
339
|
-
whole-stream value, and is what you should record as the export's digest.
|
|
340
|
-
|
|
341
|
-
- **A trailer failure poisons the session.** `truncated` is the resumable
|
|
342
|
-
failure. `count_mismatch`, `digest_mismatch`, `fence_mismatch` and
|
|
343
|
-
`trailer_invalid` are not: the whole-stream digest has already absorbed that
|
|
344
|
-
segment's rows, so the session refuses to resume and you must start a new
|
|
345
|
-
one.
|
|
346
|
-
|
|
347
|
-
- **`afterDurableId` requires a session with a header.** A resumed request gets
|
|
348
|
-
no header back, so with nothing to check the repository and fence against the
|
|
349
|
-
call is refused before it is sent. Reuse the session the broken stream wrote,
|
|
350
|
-
or rebuild one with `restoreRepositoryExportSession`.
|
|
351
|
-
|
|
352
|
-
- **The async redemption poll has a floor of 50 ms**, and defaults to 1000 ms.
|
|
353
|
-
Each poll re-runs the full authorization gate, so `pollIntervalMs: 0` — or a
|
|
354
|
-
`NaN` from an unvalidated config value — is clamped rather than honored.
|
|
355
|
-
|
|
356
|
-
- **Flat server error bodies keep their message.** The export route answers
|
|
357
|
-
with a flat `{ code, message }` body rather than the client's usual nested
|
|
358
|
-
`{ error: { ... } }` envelope. Those responses are re-wrapped before the
|
|
359
|
-
standard mapping runs, so classification stays standard and the server's
|
|
360
|
-
message is not discarded.
|
|
361
|
-
|
|
362
|
-
### Still supported
|
|
363
|
-
|
|
364
|
-
The checkpoint **read** plane is untouched: `client.repo.checkpoint.status`,
|
|
365
|
-
`.latest`, `.getAccess`, and `verifyRepositoryCheckpointArchive` from
|
|
366
|
-
`@warmhub/sdk-ts/checkpoint` all continue to work against archives already in
|
|
367
|
-
object storage. Nothing mints new ones.
|
|
368
|
-
|
|
369
|
-
**Deprecation horizon:** those read surfaces are supported until the announced
|
|
370
|
-
cleanup, 30 days after GA. Their removal is a separately-filed post-GA change.
|
|
371
|
-
Move download-and-verify workflows to `client.repo.export` before then.
|
|
5
|
+
Changelog generation is temporarily disabled while the release integration is repaired.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
TypeScript SDK for WarmHub.
|
|
8
8
|
|
|
9
|
-
📖 **Full docs:** [docs.warmhub.ai/sdk/overview/](https://docs.warmhub.ai/sdk/overview/) · **API reference:** [docs.warmhub.ai/sdk-reference/readme/](https://docs.warmhub.ai/sdk-reference/readme/) · **Release notes:** [docs.warmhub.ai/
|
|
9
|
+
📖 **Full docs:** [docs.warmhub.ai/sdk/overview/](https://docs.warmhub.ai/sdk/overview/) · **API reference:** [docs.warmhub.ai/sdk-reference/readme/](https://docs.warmhub.ai/sdk-reference/readme/) · **Release notes:** [docs.warmhub.ai/sdk/typescript/changelog/](https://docs.warmhub.ai/sdk/typescript/changelog/)
|
|
10
10
|
|
|
11
11
|
## Upgrading
|
|
12
12
|
|
package/dist/checkpoint.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {K as K$1,J as J$1,L as L$1,P as P$1,H as H$1,M,N as N$1,O,I}from'./chunk-DQF5ACN3.js';import {mkdtemp,chmod,rm,open}from'fs/promises';import {tmpdir}from'os';import {join}from'path';import {createHash}from'crypto';import {Writable}from'stream';import {pipeline}from'stream/promises';import {callbackify}from'util';import {createInflateRaw,crc32}from'zlib';import {once}from'events';import {createWriteStream,createReadStream}from'fs';var d=class extends Error{constructor(e){super(rt(e));this.reason=e;}reason;name="RepositoryCheckpointVerificationError"};function rt(r){switch(r){case "source_failed":return "Repository checkpoint source could not be read";case "archive_invalid":return "Repository checkpoint archive is invalid";case "archive_integrity_mismatch":return "Repository checkpoint archive integrity does not match";case "manifest_invalid":return "Repository checkpoint manifest is invalid";case "chunk_invalid":return "Repository checkpoint chunk is invalid";case "duplicate_durable_id":return "Repository checkpoint contains a duplicate durable identity";case "scratch_cleanup_failed":return "Repository checkpoint verifier scratch cleanup failed"}}var H=67324752,ht=33639248,dt=134695760,ut=101075792,ft=117853008,lt=101010256,Y=2056,Z=8,R=45,q=813,z=65535,m=4294967295,W=33,yt=33188,S=64*1024,pt=new RegExp(`^records/[0-9a-f]{64}/part-\\d{${I}}\\.jsonl$`),mt=new TextDecoder("utf-8",{fatal:true}),E=class extends Error{};async function G(r,t){let e=new C(r);try{let i=[],n=new Set,s=!1;for(;await e.peekU32()===H;){if(s)throw new Error("manifest must be the final entry");let c=BigInt(e.position),h=await e.readExact(30);It(h);let f=h.readUInt16LE(26),p=h.readUInt16LE(28);if(f===0||p!==20)throw new Error("local entry metadata is invalid");let a=K(await e.readExact(f));if(St(a),n.has(a))throw new Error("archive entry is duplicated");n.add(a),s=a===J$1,bt(await e.readExact(p));let y=t(a),l=await wt(e,y),et=await e.readExact(24);kt(et,l),await y.finish(),i.push({name:a,localOffset:c,...l});}if(!s||i.length===0)throw new Error("archive manifest entry is missing");return await vt(e,i),{...await e.finish(),entryNames:i.map(c=>c.name)}}finally{await e.close();}}async function wt(r,t){let e=createInflateRaw({chunkSize:S}),i=0n,n=0n,s=0,o=callbackify(async a=>{try{await t.write(a);}catch(y){throw y instanceof Error?y:new Error("checkpoint sink failed")}s=crc32(a,s),n+=BigInt(a.byteLength);}),c=new Writable({highWaterMark:S,write(a,y,l){o(a,l);}}),h=pipeline(e,c),f=new A,p=Et(h,f);try{for(;!e.readableEnded;){let a=await r.readSome(S);if(!a)throw new Error("archive entry is truncated");let y=e.bytesWritten;await gt(e,a,f);let l=e.bytesWritten-y;if(!Number.isSafeInteger(l)||l<0||l>a.byteLength)throw new Error("archive inflater consumed invalid input");if(i+=BigInt(l),l<a.byteLength&&r.unread(a.subarray(l)),e.readableEnded)break;if(l===0)throw new Error("archive inflater made no progress")}return await h,{crc:s>>>0,compressedSize:i,uncompressedSize:n}}finally{e.destroyed||e.destroy(),await p;}}var A=class{#i;#t;fail(t){let e=t instanceof Error?t:new Error("archive inflater failed");this.#i=e,this.#t?.(e),this.#t=void 0;}async race(t){if(this.#i)throw this.#i;let e,i=new Promise((n,s)=>{e=o=>s(o),this.#t=e;});try{return await Promise.race([t,i])}finally{e&&this.#t===e&&(this.#t=void 0);}}};async function Et(r,t){try{await r;}catch(e){t.fail(e);}}async function gt(r,t,e){let i=new Promise((n,s)=>{r.write(t,o=>{o?s(o):n();});});await e.race(i);}async function vt(r,t){let e=BigInt(r.position);for(let h of t){let f=await r.readExact(46);Ut(f,h);let p=f.readUInt16LE(28),a=f.readUInt16LE(30);if(a!==28||f.readUInt16LE(32)!==0)throw new Error("central entry metadata is invalid");if(K(await r.readExact(p))!==h.name)throw new Error("central entry order differs");Lt(await r.readExact(a),h);}let i=BigInt(r.position)-e,n=BigInt(r.position),s=await r.readExact(56);if(w(s,0,ut),s.readBigUInt64LE(4)!==44n||s.readUInt16LE(12)!==q||s.readUInt16LE(14)!==R||s.readUInt32LE(16)!==0||s.readUInt32LE(20)!==0||s.readBigUInt64LE(24)!==BigInt(t.length)||s.readBigUInt64LE(32)!==BigInt(t.length)||s.readBigUInt64LE(40)!==i||s.readBigUInt64LE(48)!==e)throw new Error("ZIP64 end record is invalid");let o=await r.readExact(20);if(w(o,0,ft),o.readUInt32LE(4)!==0||o.readBigUInt64LE(8)!==n||o.readUInt32LE(16)!==1)throw new Error("ZIP64 locator is invalid");let c=await r.readExact(22);if(w(c,0,lt),c.readUInt16LE(4)!==0||c.readUInt16LE(6)!==0||c.readUInt16LE(8)!==z||c.readUInt16LE(10)!==z||c.readUInt32LE(12)!==m||c.readUInt32LE(16)!==m||c.readUInt16LE(20)!==0)throw new Error("classic ZIP end record is invalid")}function It(r){if(w(r,0,H),r.readUInt16LE(4)!==R||r.readUInt16LE(6)!==Y||r.readUInt16LE(8)!==Z||r.readUInt16LE(10)!==0||r.readUInt16LE(12)!==W||r.readUInt32LE(14)!==0||r.readUInt32LE(18)!==m||r.readUInt32LE(22)!==m)throw new Error("local entry header is invalid")}function bt(r){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==16||r.readBigUInt64LE(4)!==0n||r.readBigUInt64LE(12)!==0n)throw new Error("local ZIP64 extra is invalid")}function kt(r,t){if(w(r,0,dt),r.readUInt32LE(4)!==t.crc||r.readBigUInt64LE(8)!==t.compressedSize||r.readBigUInt64LE(16)!==t.uncompressedSize)throw new Error("archive data descriptor is invalid")}function Ut(r,t){if(w(r,0,ht),r.readUInt16LE(4)!==q||r.readUInt16LE(6)!==R||r.readUInt16LE(8)!==Y||r.readUInt16LE(10)!==Z||r.readUInt16LE(12)!==0||r.readUInt16LE(14)!==W||r.readUInt32LE(16)!==t.crc||r.readUInt32LE(20)!==m||r.readUInt32LE(24)!==m||r.readUInt16LE(34)!==0||r.readUInt16LE(36)!==0||(r.readUInt32LE(38)>>>16&65535)!==yt||r.readUInt32LE(42)!==m)throw new Error("central entry header is invalid")}function Lt(r,t){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==24||r.readBigUInt64LE(4)!==t.uncompressedSize||r.readBigUInt64LE(12)!==t.compressedSize||r.readBigUInt64LE(20)!==t.localOffset)throw new Error("central ZIP64 extra is invalid")}function St(r){if(r!==J$1&&!pt.test(r))throw new Error("archive entry name is invalid")}function K(r){let t=mt.decode(r);if(Buffer.byteLength(t,"utf8")!==r.byteLength)throw new Error("archive entry name is not canonical UTF-8");return t}function w(r,t,e){if(r.readUInt32LE(t)!==e)throw new Error("archive signature is invalid")}var C=class{#i;#t=createHash("sha256");#e=[];#r=0;#n=false;#o=0;position=0;constructor(t){this.#i=At(t)[Symbol.asyncIterator]();}async peekU32(){let t=await this.readExact(4);return this.unread(t),t.readUInt32LE(0)}async readExact(t){let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){let n=await this.readSome(t-i);if(!n)throw new Error("archive is truncated");e.set(n,i),i+=n.byteLength;}return e}async readSome(t){for(;this.#e.length===0;)if(!await this.#s())return;let e=this.#e[0];if(!e)return;let i=e.byteLength-this.#r,n=Math.min(t,i),s=e.subarray(this.#r,this.#r+n);return this.#r+=n,this.position+=n,this.#r===e.byteLength&&(this.#e.shift(),this.#r=0),s}unread(t){if(t.byteLength!==0){if(this.#r>0){let e=this.#e[0];if(!e)throw new Error("archive reader queue is inconsistent");let i=e.subarray(this.#r);this.#e[0]=i,this.#r=0;}this.#e.unshift(t),this.position-=t.byteLength;}}async finish(){if(this.#e.length>0||await this.#s())throw new Error("archive contains trailing bytes");return {byteLength:this.#o,sha256:this.#t.digest("hex")}}async close(){try{await this.#i.return?.();}catch{}}async#s(){if(this.#n)return false;for(;;){let t;try{t=await this.#i.next();}catch{throw new E}if(t.done)return this.#n=true,false;if(!(t.value instanceof Uint8Array))throw new E;if(t.value.byteLength!==0){if(this.#t.update(t.value),this.#o+=t.value.byteLength,!Number.isSafeInteger(this.#o))throw new Error("archive byte length is unsafe");return this.#e.push(t.value),true}}}};function At(r){return Symbol.asyncIterator in r?r:{async*[Symbol.asyncIterator](){let t=r.getReader(),e=false;try{for(;;){let{value:i,done:n}=await t.read();if(n){e=!0;return}yield i;}}finally{if(!e)try{await t.cancel();}catch{}t.releaseLock();}}}}var xt=4*1024*1024,j=8,J=64*1024,Tt=16*1024,u=4,$=4,Ot=1,Vt=u+Ot,X=Vt+$,g=class extends Error{},k=class{#i;#t;#e=[];#r=0;#n;constructor(t,e={}){this.#i=t;let i=e.sortBudgetBytes??xt;if(this.#n=e.mergeFanIn??j,!Number.isSafeInteger(i)||i<X||!Number.isSafeInteger(this.#n)||this.#n<2||this.#n>j)throw new Error("checkpoint identity sort limits are invalid");this.#t=new _(i);}add(t){let e=Buffer.byteLength(t,"utf8");if(!this.#t.canFit(e))throw new Error("checkpoint durable identity exceeds the sort budget");if(this.#t.canAdd(e)){this.#t.add(t,e);return}return this.#o(t,e)}async finish(){if(this.#e.length===0){this.#t.sortAndAssertUnique(),this.#t.clear();return}for(this.#t.isEmpty||await this.#o();this.#e.length>this.#n;){let e=[];for(let i=0;i<this.#e.length;i+=this.#n){let n=this.#e.slice(i,i+this.#n),s=this.#s();await Ft(n,s),await Promise.all(n.map(o=>rm(o,{force:true}))),e.push(s);}this.#e=e;}let t;await tt(this.#e,e=>{if(t&&P(t,e)===0)throw new g;t=e;});}async#o(t,e){let i=this.#s();this.#t.sortAndAssertUnique(),await Mt(i,this.#t),this.#e.push(i),this.#t.clear(),t!==void 0&&e!==void 0&&this.#t.add(t,e);}#s(){let t=join(this.#i,`identity-run-${String(this.#r).padStart(8,"0")}.bin`);return this.#r+=1,t}},_=class{byteBudget;#i;#t;#e=0;#r=0;constructor(t){this.byteBudget=t;let e=Math.floor(t/X);this.#t=new Uint32Array(e),this.#i=Buffer.allocUnsafe(t-e*$);}get isEmpty(){return this.#r===0}canAdd(t){return this.canFit(t)&&this.#r<this.#t.length&&this.#e+u+t<=this.#i.length}canFit(t){return t<=4294967295&&this.#t.length>0&&u+t<=this.#i.length}add(t,e){if(!this.canAdd(e))throw new Error("checkpoint durable identity exceeds the sort budget");let i=this.#e;this.#t[this.#r]=i,this.#i.writeUInt32BE(e,i),this.#i.write(t,i+u,e,"utf8"),this.#e+=u+e,this.#r+=1;}sortAndAssertUnique(){let t=this.#t.subarray(0,this.#r);t.sort((e,i)=>this.#n(e,i));for(let e=1;e<t.length;e+=1){let i=t[e-1],n=t[e];if(i===void 0||n===void 0)throw new Error("checkpoint identity offset is missing");if(this.#n(i,n)===0)throw new g}}clear(){this.#e=0,this.#r=0;}forEachSorted(t){return Dt(this.#t.subarray(0,this.#r),e=>t(this.#s(e)))}#n(t,e){return P(this.#o(t),this.#o(e))}#o(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t+u,t+u+e)}#s(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t,t+u+e)}};async function Mt(r,t){await Q(r,async e=>{let i=new U(e);await t.forEachSorted(n=>i.write(n)),await i.finish();});}async function Ft(r,t){await Q(t,async e=>{let i=new U(e);await tt(r,n=>i.writeValue(n)),await i.finish();});}async function Q(r,t){await(await open(r,"wx",384)).close();let i=createWriteStream(r,{flags:"a",mode:384,highWaterMark:J});try{await t(i),i.end(),await once(i,"close");}catch(n){throw i.destroy(),n}}var U=class{#i;#t=Buffer.allocUnsafe(J);#e=0;constructor(t){this.#i=t;}write(t){if(t.byteLength>this.#t.length)return this.#r(t);if(this.#e+t.byteLength>this.#t.length)return this.#n(t);this.#t.set(t,this.#e),this.#e+=t.byteLength;}writeValue(t){let e=u+t.byteLength;if(e>this.#t.length)return this.#o(t);if(this.#e+e>this.#t.length)return this.#s(t);this.#c(t);}async finish(){await this.#a();}async#r(t){await this.#a(),await b(this.#i,t);}async#n(t){await this.#a(),this.#t.set(t,this.#e),this.#e+=t.byteLength;}async#o(t){await this.#a();let e=Buffer.allocUnsafe(u);e.writeUInt32BE(t.byteLength,0),await b(this.#i,e),await b(this.#i,t);}async#s(t){await this.#a(),this.#c(t);}async#a(){if(this.#e===0)return;let t=this.#t.subarray(0,this.#e);await b(this.#i,t),this.#e=0;}#c(t){this.#t.writeUInt32BE(t.byteLength,this.#e),this.#t.set(t,this.#e+u),this.#e+=u+t.byteLength;}};async function tt(r,t){let e=r.map(i=>new B(i));try{for(await Promise.all(e.map(i=>i.advance()));;){let i;for(let s of e)s.current&&(!i||P(s.current,i.current)<0)&&(i=s);if(!i?.current)return;let n=t(i.current);n&&await n,await i.advance();}}finally{for(let i of e)i.close();}}var B=class{#i;#t;#e=Buffer.alloc(0);#r=0;current;constructor(t){this.#i=createReadStream(t,{highWaterMark:Tt}),this.#t=this.#i[Symbol.asyncIterator]();}async advance(){let t=await this.#n(u);if(!t){this.current=void 0;return}let e=t.readUInt32BE(0);if(e===0)throw new Error("checkpoint identity run is invalid");let i=await this.#n(e);if(!i)throw new Error("checkpoint identity run is truncated");this.current=i;}close(){this.#i.destroy();}async#n(t){if(t===0)return Buffer.alloc(0);if(this.#e.length-this.#r>=t){let n=this.#e.subarray(this.#r,this.#r+t);return this.#r+=t,n}let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){if(this.#r<this.#e.length){let s=Math.min(t-i,this.#e.length-this.#r);this.#e.copy(e,i,this.#r,this.#r+s),this.#r+=s,i+=s;continue}let n=await this.#t.next();if(n.done){if(i===0)return;throw new Error("checkpoint identity run is truncated")}this.#e=n.value,this.#r=0;}return e}};async function Dt(r,t){for(let e of r){let i=t(e);i&&await i;}}async function b(r,t){await new Promise((e,i)=>{r.write(t,n=>{n?i(n):e();});});}function P(r,t){let e=Math.min(r.length,t.length);for(let i=0;i<e;i+=1){let n=r[i],s=t[i];if(n===void 0||s===void 0)throw new Error("checkpoint identity byte is missing");let o=n-s;if(o!==0)return o}return r.length-t.length}var L=class{#i;#t=new Map;#e=new Map;#r=[];#n;constructor(t,e={}){this.#i=new k(t,{sortBudgetBytes:e.identitySortBudgetBytes,mergeFanIn:e.identityMergeFanIn});}openEntry(t){if(t===J$1){let e=[];return {write:async i=>{e.push(i.slice());},finish:async()=>{this.#n=Buffer.concat(e);}}}return new N(t,this.#t,this.#e,this.#i,e=>this.#r.push(e))}async finish(){if(!this.#n)throw new Error("checkpoint manifest is missing");let t=await L$1(this.#n),e=t.streams.flatMap(i=>i.chunks.map(n=>({...n,shapeName:i.shapeName})));if(e.length!==this.#r.length)throw new Error("checkpoint chunk inventory does not match");for(let[i,n]of e.entries()){let s=this.#r[i];if(s.path!==n.path||s.shapeName!==n.shapeName||s.recordCount!==n.recordCount||s.byteLength!==n.byteLength||s.sha256!==n.sha256)throw new Error("checkpoint chunk integrity does not match")}for(let i of t.streams){let n=this.#t.get(i.shapeName);if(!n||P$1(n)!==i.recordCount)throw new Error("checkpoint stream does not match its manifest")}if(this.#t.size!==t.streams.length)throw new Error("checkpoint contains an undeclared stream");return await this.#i.finish(),{manifest:t,chunkCount:e.length}}},N=class{#i;#t;#e;#r;#n;#o=createHash("sha256");#s=[];#a=0;#c;#h=0;#d=0;constructor(t,e,i,n,s){this.#i=t,this.#t=e,this.#e=i,this.#r=n,this.#n=s;}async write(t){if(this.#o.update(t),this.#d+=t.byteLength,!Number.isSafeInteger(this.#d))throw new Error("checkpoint chunk byte length is unsafe");let e=0;for(;e<t.byteLength;){let i=t.indexOf(10,e);if(i<0)break;let n=this.#u(this.#l(t.subarray(e,i+1)));n&&await n,e=i+1;}e<t.byteLength&&this.#f(t.subarray(e));}async finish(){if(this.#s.length!==0||this.#h===0||!this.#c)throw new Error("checkpoint chunk framing is invalid");if(this.#d>H$1&&this.#h!==1)throw new Error("oversized checkpoint chunk has multiple rows");this.#n({path:this.#i,shapeName:this.#c,recordCount:this.#h,byteLength:this.#d,sha256:this.#o.digest("hex")});let t=this.#e.get(this.#c);if(!t)throw new Error("checkpoint stream chunk state is missing");t.previous={byteLength:this.#d};}#u(t){let e=M(t),i=e.kind==="shape"?e.wref:e.shapeName;if(this.#c!==void 0&&this.#c!==i)throw new Error("checkpoint chunk mixes Shape streams");this.#c=i,this.#h===0&&this.#y(i,t.byteLength);let n=this.#t.get(i)??N$1(i);this.#t.set(i,n),O(n,e);let s=this.#r.add(e.durableId);if(this.#h+=1,!Number.isSafeInteger(this.#h))throw new Error("checkpoint chunk row count is unsafe");return s}#f(t){if(t.byteLength!==0&&(this.#s.push(t.slice()),this.#a+=t.byteLength,!Number.isSafeInteger(this.#a)))throw new Error("checkpoint row byte length is unsafe")}#l(t){if(this.#s.length===0)return t;let e=Buffer.concat([...this.#s,t],this.#a+t.byteLength);return this.#s=[],this.#a=0,e}#y(t,e){let i=Number(this.#i.match(/part-(\d{10})\.jsonl$/)?.[1]??-1),n=this.#e.get(t)??{nextPart:0};if(i!==n.nextPart)throw new Error("checkpoint stream chunk parts are not contiguous");if(n.previous&&n.previous.byteLength<=H$1&&n.previous.byteLength+e<=H$1)throw new Error("checkpoint stream chunk closed before the byte limit");n.nextPart+=1,this.#e.set(t,n);}};async function Gt(r,t){return Kt(r,t)}async function Kt(r,t,e={}){let i=await mkdtemp(join(e.scratchParent??tmpdir(),"warmhub-checkpoint-verify-"));await chmod(i,448);let n,s;try{let o=new L(i,e),c=await G(r,p=>o.openEntry(p)),{manifest:h,chunkCount:f}=await o.finish();jt(t,c,h),n={valid:!0,checkpointId:h.checkpointId,repoSeq:h.repoSeq,recordCount:h.recordCount,chunkCount:f,totalBytes:c.byteLength,rootDigest:h.contentSha256,archiveDigest:c.sha256};}catch(o){s=Jt(o);}try{await rm(i,{recursive:!0,force:!0});}catch{s??=new d("scratch_cleanup_failed");}if(s)throw s;if(!n)throw new d("archive_invalid");return n}function jt(r,t,e){if(r&&(K$1.parse(r.sha256),r.checkpointId!==e.checkpointId||r.repoSeq!==e.repoSeq||r.byteLength!==t.byteLength||r.sha256!==t.sha256))throw new d("archive_integrity_mismatch")}function Jt(r){if(r instanceof d)return r;if(r instanceof E)return new d("source_failed");if(r instanceof g)return new d("duplicate_durable_id");let t=r instanceof Error?r.message:"";return t.includes("manifest")?new d("manifest_invalid"):t.includes("chunk")||t.includes("row")||t.includes("Shape stream")?new d("chunk_invalid"):new d("archive_invalid")}export{d as RepositoryCheckpointVerificationError,Gt as verifyRepositoryCheckpointArchive};//# sourceMappingURL=checkpoint.js.map
|
|
1
|
+
import {K as K$1,J as J$1,L as L$1,P as P$1,H as H$1,M,N as N$1,O,I}from'./chunk-CNDUTYB2.js';import {mkdtemp,chmod,rm,open}from'fs/promises';import {tmpdir}from'os';import {join}from'path';import {createHash}from'crypto';import {Writable}from'stream';import {pipeline}from'stream/promises';import {callbackify}from'util';import {createInflateRaw,crc32}from'zlib';import {once}from'events';import {createWriteStream,createReadStream}from'fs';var d=class extends Error{constructor(e){super(rt(e));this.reason=e;}reason;name="RepositoryCheckpointVerificationError"};function rt(r){switch(r){case "source_failed":return "Repository checkpoint source could not be read";case "archive_invalid":return "Repository checkpoint archive is invalid";case "archive_integrity_mismatch":return "Repository checkpoint archive integrity does not match";case "manifest_invalid":return "Repository checkpoint manifest is invalid";case "chunk_invalid":return "Repository checkpoint chunk is invalid";case "duplicate_durable_id":return "Repository checkpoint contains a duplicate durable identity";case "scratch_cleanup_failed":return "Repository checkpoint verifier scratch cleanup failed"}}var H=67324752,ht=33639248,dt=134695760,ut=101075792,ft=117853008,lt=101010256,Y=2056,Z=8,R=45,q=813,z=65535,m=4294967295,W=33,yt=33188,S=64*1024,pt=new RegExp(`^records/[0-9a-f]{64}/part-\\d{${I}}\\.jsonl$`),mt=new TextDecoder("utf-8",{fatal:true}),E=class extends Error{};async function G(r,t){let e=new C(r);try{let i=[],n=new Set,s=!1;for(;await e.peekU32()===H;){if(s)throw new Error("manifest must be the final entry");let c=BigInt(e.position),h=await e.readExact(30);It(h);let f=h.readUInt16LE(26),p=h.readUInt16LE(28);if(f===0||p!==20)throw new Error("local entry metadata is invalid");let a=K(await e.readExact(f));if(St(a),n.has(a))throw new Error("archive entry is duplicated");n.add(a),s=a===J$1,bt(await e.readExact(p));let y=t(a),l=await wt(e,y),et=await e.readExact(24);kt(et,l),await y.finish(),i.push({name:a,localOffset:c,...l});}if(!s||i.length===0)throw new Error("archive manifest entry is missing");return await vt(e,i),{...await e.finish(),entryNames:i.map(c=>c.name)}}finally{await e.close();}}async function wt(r,t){let e=createInflateRaw({chunkSize:S}),i=0n,n=0n,s=0,o=callbackify(async a=>{try{await t.write(a);}catch(y){throw y instanceof Error?y:new Error("checkpoint sink failed")}s=crc32(a,s),n+=BigInt(a.byteLength);}),c=new Writable({highWaterMark:S,write(a,y,l){o(a,l);}}),h=pipeline(e,c),f=new A,p=Et(h,f);try{for(;!e.readableEnded;){let a=await r.readSome(S);if(!a)throw new Error("archive entry is truncated");let y=e.bytesWritten;await gt(e,a,f);let l=e.bytesWritten-y;if(!Number.isSafeInteger(l)||l<0||l>a.byteLength)throw new Error("archive inflater consumed invalid input");if(i+=BigInt(l),l<a.byteLength&&r.unread(a.subarray(l)),e.readableEnded)break;if(l===0)throw new Error("archive inflater made no progress")}return await h,{crc:s>>>0,compressedSize:i,uncompressedSize:n}}finally{e.destroyed||e.destroy(),await p;}}var A=class{#i;#t;fail(t){let e=t instanceof Error?t:new Error("archive inflater failed");this.#i=e,this.#t?.(e),this.#t=void 0;}async race(t){if(this.#i)throw this.#i;let e,i=new Promise((n,s)=>{e=o=>s(o),this.#t=e;});try{return await Promise.race([t,i])}finally{e&&this.#t===e&&(this.#t=void 0);}}};async function Et(r,t){try{await r;}catch(e){t.fail(e);}}async function gt(r,t,e){let i=new Promise((n,s)=>{r.write(t,o=>{o?s(o):n();});});await e.race(i);}async function vt(r,t){let e=BigInt(r.position);for(let h of t){let f=await r.readExact(46);Ut(f,h);let p=f.readUInt16LE(28),a=f.readUInt16LE(30);if(a!==28||f.readUInt16LE(32)!==0)throw new Error("central entry metadata is invalid");if(K(await r.readExact(p))!==h.name)throw new Error("central entry order differs");Lt(await r.readExact(a),h);}let i=BigInt(r.position)-e,n=BigInt(r.position),s=await r.readExact(56);if(w(s,0,ut),s.readBigUInt64LE(4)!==44n||s.readUInt16LE(12)!==q||s.readUInt16LE(14)!==R||s.readUInt32LE(16)!==0||s.readUInt32LE(20)!==0||s.readBigUInt64LE(24)!==BigInt(t.length)||s.readBigUInt64LE(32)!==BigInt(t.length)||s.readBigUInt64LE(40)!==i||s.readBigUInt64LE(48)!==e)throw new Error("ZIP64 end record is invalid");let o=await r.readExact(20);if(w(o,0,ft),o.readUInt32LE(4)!==0||o.readBigUInt64LE(8)!==n||o.readUInt32LE(16)!==1)throw new Error("ZIP64 locator is invalid");let c=await r.readExact(22);if(w(c,0,lt),c.readUInt16LE(4)!==0||c.readUInt16LE(6)!==0||c.readUInt16LE(8)!==z||c.readUInt16LE(10)!==z||c.readUInt32LE(12)!==m||c.readUInt32LE(16)!==m||c.readUInt16LE(20)!==0)throw new Error("classic ZIP end record is invalid")}function It(r){if(w(r,0,H),r.readUInt16LE(4)!==R||r.readUInt16LE(6)!==Y||r.readUInt16LE(8)!==Z||r.readUInt16LE(10)!==0||r.readUInt16LE(12)!==W||r.readUInt32LE(14)!==0||r.readUInt32LE(18)!==m||r.readUInt32LE(22)!==m)throw new Error("local entry header is invalid")}function bt(r){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==16||r.readBigUInt64LE(4)!==0n||r.readBigUInt64LE(12)!==0n)throw new Error("local ZIP64 extra is invalid")}function kt(r,t){if(w(r,0,dt),r.readUInt32LE(4)!==t.crc||r.readBigUInt64LE(8)!==t.compressedSize||r.readBigUInt64LE(16)!==t.uncompressedSize)throw new Error("archive data descriptor is invalid")}function Ut(r,t){if(w(r,0,ht),r.readUInt16LE(4)!==q||r.readUInt16LE(6)!==R||r.readUInt16LE(8)!==Y||r.readUInt16LE(10)!==Z||r.readUInt16LE(12)!==0||r.readUInt16LE(14)!==W||r.readUInt32LE(16)!==t.crc||r.readUInt32LE(20)!==m||r.readUInt32LE(24)!==m||r.readUInt16LE(34)!==0||r.readUInt16LE(36)!==0||(r.readUInt32LE(38)>>>16&65535)!==yt||r.readUInt32LE(42)!==m)throw new Error("central entry header is invalid")}function Lt(r,t){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==24||r.readBigUInt64LE(4)!==t.uncompressedSize||r.readBigUInt64LE(12)!==t.compressedSize||r.readBigUInt64LE(20)!==t.localOffset)throw new Error("central ZIP64 extra is invalid")}function St(r){if(r!==J$1&&!pt.test(r))throw new Error("archive entry name is invalid")}function K(r){let t=mt.decode(r);if(Buffer.byteLength(t,"utf8")!==r.byteLength)throw new Error("archive entry name is not canonical UTF-8");return t}function w(r,t,e){if(r.readUInt32LE(t)!==e)throw new Error("archive signature is invalid")}var C=class{#i;#t=createHash("sha256");#e=[];#r=0;#n=false;#o=0;position=0;constructor(t){this.#i=At(t)[Symbol.asyncIterator]();}async peekU32(){let t=await this.readExact(4);return this.unread(t),t.readUInt32LE(0)}async readExact(t){let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){let n=await this.readSome(t-i);if(!n)throw new Error("archive is truncated");e.set(n,i),i+=n.byteLength;}return e}async readSome(t){for(;this.#e.length===0;)if(!await this.#s())return;let e=this.#e[0];if(!e)return;let i=e.byteLength-this.#r,n=Math.min(t,i),s=e.subarray(this.#r,this.#r+n);return this.#r+=n,this.position+=n,this.#r===e.byteLength&&(this.#e.shift(),this.#r=0),s}unread(t){if(t.byteLength!==0){if(this.#r>0){let e=this.#e[0];if(!e)throw new Error("archive reader queue is inconsistent");let i=e.subarray(this.#r);this.#e[0]=i,this.#r=0;}this.#e.unshift(t),this.position-=t.byteLength;}}async finish(){if(this.#e.length>0||await this.#s())throw new Error("archive contains trailing bytes");return {byteLength:this.#o,sha256:this.#t.digest("hex")}}async close(){try{await this.#i.return?.();}catch{}}async#s(){if(this.#n)return false;for(;;){let t;try{t=await this.#i.next();}catch{throw new E}if(t.done)return this.#n=true,false;if(!(t.value instanceof Uint8Array))throw new E;if(t.value.byteLength!==0){if(this.#t.update(t.value),this.#o+=t.value.byteLength,!Number.isSafeInteger(this.#o))throw new Error("archive byte length is unsafe");return this.#e.push(t.value),true}}}};function At(r){return Symbol.asyncIterator in r?r:{async*[Symbol.asyncIterator](){let t=r.getReader(),e=false;try{for(;;){let{value:i,done:n}=await t.read();if(n){e=!0;return}yield i;}}finally{if(!e)try{await t.cancel();}catch{}t.releaseLock();}}}}var xt=4*1024*1024,j=8,J=64*1024,Tt=16*1024,u=4,$=4,Ot=1,Vt=u+Ot,X=Vt+$,g=class extends Error{},k=class{#i;#t;#e=[];#r=0;#n;constructor(t,e={}){this.#i=t;let i=e.sortBudgetBytes??xt;if(this.#n=e.mergeFanIn??j,!Number.isSafeInteger(i)||i<X||!Number.isSafeInteger(this.#n)||this.#n<2||this.#n>j)throw new Error("checkpoint identity sort limits are invalid");this.#t=new _(i);}add(t){let e=Buffer.byteLength(t,"utf8");if(!this.#t.canFit(e))throw new Error("checkpoint durable identity exceeds the sort budget");if(this.#t.canAdd(e)){this.#t.add(t,e);return}return this.#o(t,e)}async finish(){if(this.#e.length===0){this.#t.sortAndAssertUnique(),this.#t.clear();return}for(this.#t.isEmpty||await this.#o();this.#e.length>this.#n;){let e=[];for(let i=0;i<this.#e.length;i+=this.#n){let n=this.#e.slice(i,i+this.#n),s=this.#s();await Ft(n,s),await Promise.all(n.map(o=>rm(o,{force:true}))),e.push(s);}this.#e=e;}let t;await tt(this.#e,e=>{if(t&&P(t,e)===0)throw new g;t=e;});}async#o(t,e){let i=this.#s();this.#t.sortAndAssertUnique(),await Mt(i,this.#t),this.#e.push(i),this.#t.clear(),t!==void 0&&e!==void 0&&this.#t.add(t,e);}#s(){let t=join(this.#i,`identity-run-${String(this.#r).padStart(8,"0")}.bin`);return this.#r+=1,t}},_=class{byteBudget;#i;#t;#e=0;#r=0;constructor(t){this.byteBudget=t;let e=Math.floor(t/X);this.#t=new Uint32Array(e),this.#i=Buffer.allocUnsafe(t-e*$);}get isEmpty(){return this.#r===0}canAdd(t){return this.canFit(t)&&this.#r<this.#t.length&&this.#e+u+t<=this.#i.length}canFit(t){return t<=4294967295&&this.#t.length>0&&u+t<=this.#i.length}add(t,e){if(!this.canAdd(e))throw new Error("checkpoint durable identity exceeds the sort budget");let i=this.#e;this.#t[this.#r]=i,this.#i.writeUInt32BE(e,i),this.#i.write(t,i+u,e,"utf8"),this.#e+=u+e,this.#r+=1;}sortAndAssertUnique(){let t=this.#t.subarray(0,this.#r);t.sort((e,i)=>this.#n(e,i));for(let e=1;e<t.length;e+=1){let i=t[e-1],n=t[e];if(i===void 0||n===void 0)throw new Error("checkpoint identity offset is missing");if(this.#n(i,n)===0)throw new g}}clear(){this.#e=0,this.#r=0;}forEachSorted(t){return Dt(this.#t.subarray(0,this.#r),e=>t(this.#s(e)))}#n(t,e){return P(this.#o(t),this.#o(e))}#o(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t+u,t+u+e)}#s(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t,t+u+e)}};async function Mt(r,t){await Q(r,async e=>{let i=new U(e);await t.forEachSorted(n=>i.write(n)),await i.finish();});}async function Ft(r,t){await Q(t,async e=>{let i=new U(e);await tt(r,n=>i.writeValue(n)),await i.finish();});}async function Q(r,t){await(await open(r,"wx",384)).close();let i=createWriteStream(r,{flags:"a",mode:384,highWaterMark:J});try{await t(i),i.end(),await once(i,"close");}catch(n){throw i.destroy(),n}}var U=class{#i;#t=Buffer.allocUnsafe(J);#e=0;constructor(t){this.#i=t;}write(t){if(t.byteLength>this.#t.length)return this.#r(t);if(this.#e+t.byteLength>this.#t.length)return this.#n(t);this.#t.set(t,this.#e),this.#e+=t.byteLength;}writeValue(t){let e=u+t.byteLength;if(e>this.#t.length)return this.#o(t);if(this.#e+e>this.#t.length)return this.#s(t);this.#c(t);}async finish(){await this.#a();}async#r(t){await this.#a(),await b(this.#i,t);}async#n(t){await this.#a(),this.#t.set(t,this.#e),this.#e+=t.byteLength;}async#o(t){await this.#a();let e=Buffer.allocUnsafe(u);e.writeUInt32BE(t.byteLength,0),await b(this.#i,e),await b(this.#i,t);}async#s(t){await this.#a(),this.#c(t);}async#a(){if(this.#e===0)return;let t=this.#t.subarray(0,this.#e);await b(this.#i,t),this.#e=0;}#c(t){this.#t.writeUInt32BE(t.byteLength,this.#e),this.#t.set(t,this.#e+u),this.#e+=u+t.byteLength;}};async function tt(r,t){let e=r.map(i=>new B(i));try{for(await Promise.all(e.map(i=>i.advance()));;){let i;for(let s of e)s.current&&(!i||P(s.current,i.current)<0)&&(i=s);if(!i?.current)return;let n=t(i.current);n&&await n,await i.advance();}}finally{for(let i of e)i.close();}}var B=class{#i;#t;#e=Buffer.alloc(0);#r=0;current;constructor(t){this.#i=createReadStream(t,{highWaterMark:Tt}),this.#t=this.#i[Symbol.asyncIterator]();}async advance(){let t=await this.#n(u);if(!t){this.current=void 0;return}let e=t.readUInt32BE(0);if(e===0)throw new Error("checkpoint identity run is invalid");let i=await this.#n(e);if(!i)throw new Error("checkpoint identity run is truncated");this.current=i;}close(){this.#i.destroy();}async#n(t){if(t===0)return Buffer.alloc(0);if(this.#e.length-this.#r>=t){let n=this.#e.subarray(this.#r,this.#r+t);return this.#r+=t,n}let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){if(this.#r<this.#e.length){let s=Math.min(t-i,this.#e.length-this.#r);this.#e.copy(e,i,this.#r,this.#r+s),this.#r+=s,i+=s;continue}let n=await this.#t.next();if(n.done){if(i===0)return;throw new Error("checkpoint identity run is truncated")}this.#e=n.value,this.#r=0;}return e}};async function Dt(r,t){for(let e of r){let i=t(e);i&&await i;}}async function b(r,t){await new Promise((e,i)=>{r.write(t,n=>{n?i(n):e();});});}function P(r,t){let e=Math.min(r.length,t.length);for(let i=0;i<e;i+=1){let n=r[i],s=t[i];if(n===void 0||s===void 0)throw new Error("checkpoint identity byte is missing");let o=n-s;if(o!==0)return o}return r.length-t.length}var L=class{#i;#t=new Map;#e=new Map;#r=[];#n;constructor(t,e={}){this.#i=new k(t,{sortBudgetBytes:e.identitySortBudgetBytes,mergeFanIn:e.identityMergeFanIn});}openEntry(t){if(t===J$1){let e=[];return {write:async i=>{e.push(i.slice());},finish:async()=>{this.#n=Buffer.concat(e);}}}return new N(t,this.#t,this.#e,this.#i,e=>this.#r.push(e))}async finish(){if(!this.#n)throw new Error("checkpoint manifest is missing");let t=await L$1(this.#n),e=t.streams.flatMap(i=>i.chunks.map(n=>({...n,shapeName:i.shapeName})));if(e.length!==this.#r.length)throw new Error("checkpoint chunk inventory does not match");for(let[i,n]of e.entries()){let s=this.#r[i];if(s.path!==n.path||s.shapeName!==n.shapeName||s.recordCount!==n.recordCount||s.byteLength!==n.byteLength||s.sha256!==n.sha256)throw new Error("checkpoint chunk integrity does not match")}for(let i of t.streams){let n=this.#t.get(i.shapeName);if(!n||P$1(n)!==i.recordCount)throw new Error("checkpoint stream does not match its manifest")}if(this.#t.size!==t.streams.length)throw new Error("checkpoint contains an undeclared stream");return await this.#i.finish(),{manifest:t,chunkCount:e.length}}},N=class{#i;#t;#e;#r;#n;#o=createHash("sha256");#s=[];#a=0;#c;#h=0;#d=0;constructor(t,e,i,n,s){this.#i=t,this.#t=e,this.#e=i,this.#r=n,this.#n=s;}async write(t){if(this.#o.update(t),this.#d+=t.byteLength,!Number.isSafeInteger(this.#d))throw new Error("checkpoint chunk byte length is unsafe");let e=0;for(;e<t.byteLength;){let i=t.indexOf(10,e);if(i<0)break;let n=this.#u(this.#l(t.subarray(e,i+1)));n&&await n,e=i+1;}e<t.byteLength&&this.#f(t.subarray(e));}async finish(){if(this.#s.length!==0||this.#h===0||!this.#c)throw new Error("checkpoint chunk framing is invalid");if(this.#d>H$1&&this.#h!==1)throw new Error("oversized checkpoint chunk has multiple rows");this.#n({path:this.#i,shapeName:this.#c,recordCount:this.#h,byteLength:this.#d,sha256:this.#o.digest("hex")});let t=this.#e.get(this.#c);if(!t)throw new Error("checkpoint stream chunk state is missing");t.previous={byteLength:this.#d};}#u(t){let e=M(t),i=e.kind==="shape"?e.wref:e.shapeName;if(this.#c!==void 0&&this.#c!==i)throw new Error("checkpoint chunk mixes Shape streams");this.#c=i,this.#h===0&&this.#y(i,t.byteLength);let n=this.#t.get(i)??N$1(i);this.#t.set(i,n),O(n,e);let s=this.#r.add(e.durableId);if(this.#h+=1,!Number.isSafeInteger(this.#h))throw new Error("checkpoint chunk row count is unsafe");return s}#f(t){if(t.byteLength!==0&&(this.#s.push(t.slice()),this.#a+=t.byteLength,!Number.isSafeInteger(this.#a)))throw new Error("checkpoint row byte length is unsafe")}#l(t){if(this.#s.length===0)return t;let e=Buffer.concat([...this.#s,t],this.#a+t.byteLength);return this.#s=[],this.#a=0,e}#y(t,e){let i=Number(this.#i.match(/part-(\d{10})\.jsonl$/)?.[1]??-1),n=this.#e.get(t)??{nextPart:0};if(i!==n.nextPart)throw new Error("checkpoint stream chunk parts are not contiguous");if(n.previous&&n.previous.byteLength<=H$1&&n.previous.byteLength+e<=H$1)throw new Error("checkpoint stream chunk closed before the byte limit");n.nextPart+=1,this.#e.set(t,n);}};async function Gt(r,t){return Kt(r,t)}async function Kt(r,t,e={}){let i=await mkdtemp(join(e.scratchParent??tmpdir(),"warmhub-checkpoint-verify-"));await chmod(i,448);let n,s;try{let o=new L(i,e),c=await G(r,p=>o.openEntry(p)),{manifest:h,chunkCount:f}=await o.finish();jt(t,c,h),n={valid:!0,checkpointId:h.checkpointId,repoSeq:h.repoSeq,recordCount:h.recordCount,chunkCount:f,totalBytes:c.byteLength,rootDigest:h.contentSha256,archiveDigest:c.sha256};}catch(o){s=Jt(o);}try{await rm(i,{recursive:!0,force:!0});}catch{s??=new d("scratch_cleanup_failed");}if(s)throw s;if(!n)throw new d("archive_invalid");return n}function jt(r,t,e){if(r&&(K$1.parse(r.sha256),r.checkpointId!==e.checkpointId||r.repoSeq!==e.repoSeq||r.byteLength!==t.byteLength||r.sha256!==t.sha256))throw new d("archive_integrity_mismatch")}function Jt(r){if(r instanceof d)return r;if(r instanceof E)return new d("source_failed");if(r instanceof g)return new d("duplicate_durable_id");let t=r instanceof Error?r.message:"";return t.includes("manifest")?new d("manifest_invalid"):t.includes("chunk")||t.includes("row")||t.includes("Shape stream")?new d("chunk_invalid"):new d("archive_invalid")}export{d as RepositoryCheckpointVerificationError,Gt as verifyRepositoryCheckpointArchive};//# sourceMappingURL=checkpoint.js.map
|
|
2
2
|
//# sourceMappingURL=checkpoint.js.map
|