@jini-ai/platform 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +201 -0
  2. package/dist/asset-cache.d.ts +80 -0
  3. package/dist/asset-cache.d.ts.map +1 -0
  4. package/dist/asset-cache.js +423 -0
  5. package/dist/asset-cache.js.map +1 -0
  6. package/dist/aws-sigv4.d.ts +51 -0
  7. package/dist/aws-sigv4.d.ts.map +1 -0
  8. package/dist/aws-sigv4.js +104 -0
  9. package/dist/aws-sigv4.js.map +1 -0
  10. package/dist/blob-storage.d.ts +89 -0
  11. package/dist/blob-storage.d.ts.map +1 -0
  12. package/dist/blob-storage.js +315 -0
  13. package/dist/blob-storage.js.map +1 -0
  14. package/dist/command.d.ts +32 -0
  15. package/dist/command.d.ts.map +1 -0
  16. package/dist/command.js +101 -0
  17. package/dist/command.js.map +1 -0
  18. package/dist/download.d.ts +92 -0
  19. package/dist/download.d.ts.map +1 -0
  20. package/dist/download.js +856 -0
  21. package/dist/download.js.map +1 -0
  22. package/dist/fs.d.ts +55 -0
  23. package/dist/fs.d.ts.map +1 -0
  24. package/dist/fs.js +124 -0
  25. package/dist/fs.js.map +1 -0
  26. package/dist/home-expansion.d.ts +18 -0
  27. package/dist/home-expansion.d.ts.map +1 -0
  28. package/dist/home-expansion.js +58 -0
  29. package/dist/home-expansion.js.map +1 -0
  30. package/dist/http.d.ts +14 -0
  31. package/dist/http.d.ts.map +1 -0
  32. package/dist/http.js +39 -0
  33. package/dist/http.js.map +1 -0
  34. package/dist/index.d.ts +59 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +45 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/process.d.ts +159 -0
  39. package/dist/process.d.ts.map +1 -0
  40. package/dist/process.js +362 -0
  41. package/dist/process.js.map +1 -0
  42. package/dist/proxy-env.d.ts +50 -0
  43. package/dist/proxy-env.d.ts.map +1 -0
  44. package/dist/proxy-env.js +373 -0
  45. package/dist/proxy-env.js.map +1 -0
  46. package/dist/resource-paths.d.ts +68 -0
  47. package/dist/resource-paths.d.ts.map +1 -0
  48. package/dist/resource-paths.js +137 -0
  49. package/dist/resource-paths.js.map +1 -0
  50. package/dist/sandbox-env.d.ts +86 -0
  51. package/dist/sandbox-env.d.ts.map +1 -0
  52. package/dist/sandbox-env.js +215 -0
  53. package/dist/sandbox-env.js.map +1 -0
  54. package/dist/shell.d.ts +46 -0
  55. package/dist/shell.d.ts.map +1 -0
  56. package/dist/shell.js +78 -0
  57. package/dist/shell.js.map +1 -0
  58. package/dist/terminal.d.ts +137 -0
  59. package/dist/terminal.d.ts.map +1 -0
  60. package/dist/terminal.js +398 -0
  61. package/dist/terminal.js.map +1 -0
  62. package/dist/toolchain.d.ts +17 -0
  63. package/dist/toolchain.d.ts.map +1 -0
  64. package/dist/toolchain.js +251 -0
  65. package/dist/toolchain.js.map +1 -0
  66. package/package.json +47 -0
@@ -0,0 +1,856 @@
1
+ /**
2
+ * @module download
3
+ *
4
+ * A managed-download engine: content-addressable atomic downloads with resume, checksum
5
+ * verification, cross-process locking, a durable manifest, retention pruning, and a
6
+ * copy-and-clear helper for one-shot consumers. Builds on this package's own `fs`/`process`
7
+ * primitives (`atomicCopyFile`, `pathContains`, `removePathBestEffort`, `isProcessAlive`) — no
8
+ * other cross-module dependency.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { createReadStream, createWriteStream } from "node:fs";
12
+ import { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
13
+ import { dirname, isAbsolute, join, resolve } from "node:path";
14
+ import { Readable, Transform } from "node:stream";
15
+ import { pipeline } from "node:stream/promises";
16
+ import { atomicCopyFile, pathContains, removePathBestEffort } from "./fs.js";
17
+ import { isProcessAlive } from "./process.js";
18
+ const STORE_SENTINEL = ".jini-download-root.json";
19
+ const STATE_DIR = ".state";
20
+ const PARTIAL_DIR = ".partial";
21
+ const LOCK_DIR = ".locks";
22
+ const STORE_SCHEMA_VERSION = 1;
23
+ const MANIFEST_SCHEMA_VERSION = 1;
24
+ const STORE_KIND = "jini-managed-download-root";
25
+ const MANIFEST_KIND = "jini-managed-download";
26
+ const DEFAULT_MAX_ATTEMPTS = 3;
27
+ const DEFAULT_PRUNE_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
28
+ const PID_REUSE_GRACE_MS = 1000;
29
+ const PROCESS_STARTED_AT_MS = Date.now() - process.uptime() * 1000;
30
+ export const MANAGED_DOWNLOAD_ERROR_CODES = Object.freeze({
31
+ ABORTED: "aborted",
32
+ CHECKSUM_MISMATCH: "checksum-mismatch",
33
+ INVALID_TARGET: "invalid-target",
34
+ NETWORK_EXHAUSTED: "network-exhausted",
35
+ OUTPUT_CONFLICT: "output-conflict",
36
+ STORE_CORRUPT: "store-corrupt",
37
+ STORE_NOT_OWNED: "store-not-owned",
38
+ TARGET_CONFLICT: "target-conflict",
39
+ TARGET_LOCKED: "target-locked",
40
+ });
41
+ export class ManagedDownloadError extends Error {
42
+ code;
43
+ details;
44
+ constructor(code, message, details) {
45
+ super(message);
46
+ this.name = "ManagedDownloadError";
47
+ this.code = code;
48
+ if (details !== undefined)
49
+ this.details = details;
50
+ }
51
+ }
52
+ const activeTasks = new Map();
53
+ const activeTargets = new Map();
54
+ const copyLeases = new Map();
55
+ function errorCode(error) {
56
+ if (typeof error !== "object" || error == null || !("code" in error))
57
+ return null;
58
+ const code = error.code;
59
+ return code == null ? null : String(code);
60
+ }
61
+ function errorMessage(error) {
62
+ return error instanceof Error ? error.message : String(error);
63
+ }
64
+ function digest(value) {
65
+ return createHash("sha256").update(value).digest("hex");
66
+ }
67
+ function normalizeChecksum(input) {
68
+ if (input.algorithm !== "sha256" && input.algorithm !== "sha512") {
69
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, `unsupported checksum algorithm: ${String(input.algorithm)}`);
70
+ }
71
+ const value = input.value.trim().toLowerCase();
72
+ const expectedLength = input.algorithm === "sha256" ? 64 : 128;
73
+ if (!new RegExp(`^[0-9a-f]{${expectedLength}}$`).test(value)) {
74
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, `${input.algorithm} checksum must be ${expectedLength} hex characters`);
75
+ }
76
+ return { algorithm: input.algorithm, value };
77
+ }
78
+ function normalizeSegment(value, label) {
79
+ if (typeof value !== "string" || value.length === 0) {
80
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, `${label} must be a non-empty string`);
81
+ }
82
+ // `:` blocks a Windows drive-relative segment (e.g. "C:foo") — win32's
83
+ // `isAbsolute`/`[\\/]` checks above don't reject it, but `path.win32.resolve`
84
+ // treats it as a real drive reference, which can escape `basePath` onto a
85
+ // different drive entirely.
86
+ if (value === "." || value === ".." || value.includes("\0") || value.includes(":") || /[\\/]/.test(value) || isAbsolute(value)) {
87
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, `${label} must be a safe single path segment`);
88
+ }
89
+ return value;
90
+ }
91
+ function normalizeUrl(value) {
92
+ try {
93
+ const url = new URL(value);
94
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
95
+ throw new Error("only http and https URLs are supported");
96
+ }
97
+ return url.toString();
98
+ }
99
+ catch (error) {
100
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, errorMessage(error));
101
+ }
102
+ }
103
+ function normalizeBasePath(basePath) {
104
+ if (typeof basePath !== "string" || basePath.length === 0 || basePath.includes("\0")) {
105
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, "basePath must be a non-empty path");
106
+ }
107
+ // `path.resolve()` always returns an absolute path by contract (resolving
108
+ // against `process.cwd()` when given a relative input), so this can never
109
+ // be false for any string input.
110
+ return resolve(basePath);
111
+ }
112
+ function targetFromOptions(options) {
113
+ const basePath = normalizeBasePath(options.basePath);
114
+ const bucket = normalizeSegment(options.bucket, "bucket");
115
+ const fileName = normalizeSegment(options.fileName, "fileName");
116
+ const checksum = normalizeChecksum(options.payload.checksum);
117
+ const url = normalizeUrl(options.payload.url);
118
+ const urlDigest = digest(url);
119
+ const identityDigest = digest(`${url}\0${checksum.algorithm}\0${checksum.value}`);
120
+ const targetKey = digest(`${bucket}\0${fileName}`);
121
+ const finalPath = resolve(basePath, bucket, fileName);
122
+ const manifestPath = resolve(basePath, STATE_DIR, `${targetKey}.json`);
123
+ const partialPath = resolve(basePath, PARTIAL_DIR, `${targetKey}.partial`);
124
+ const lockPath = resolve(basePath, LOCK_DIR, `${targetKey}.lock`);
125
+ // Real, reachable guard — NOT dead code: `normalizeSegment` only rejects a
126
+ // segment that is *exactly* "." or "..", not one that merely starts with
127
+ // ".." (e.g. "..evil" is a syntactically ordinary filename, not a parent
128
+ // reference), so `pathContains`'s own naive `!rel.startsWith("..")` string
129
+ // check (see fs.ts) flags such a segment as escaping even though
130
+ // `path.resolve` never actually walked upward. Empirically verified this
131
+ // session: a 300k-segment fuzz found ~0.2% of `normalizeSegment`-valid
132
+ // segments (every one starting with literal "..") trip this exact guard —
133
+ // see source-map.md's 2026-07-22 entry and this file's own test for a
134
+ // concrete example (bucket "..evil").
135
+ for (const path of [finalPath, manifestPath, partialPath, lockPath]) {
136
+ if (!pathContains(basePath, path)) {
137
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, "resolved managed download path escaped basePath");
138
+ }
139
+ }
140
+ return { basePath, bucket, checksum, fileName, finalPath, identityDigest, lockPath, manifestPath, partialPath, targetKey, url, urlDigest };
141
+ }
142
+ async function pathExists(path) {
143
+ try {
144
+ await access(path);
145
+ return true;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }
151
+ async function readJson(path) {
152
+ try {
153
+ return JSON.parse(await readFile(path, "utf8"));
154
+ }
155
+ catch {
156
+ return null;
157
+ }
158
+ }
159
+ async function writeJson(path, payload) {
160
+ await mkdir(dirname(path), { recursive: true });
161
+ const tmp = `${path}.${process.pid}.${Date.now().toString(36)}.tmp`;
162
+ await writeFile(tmp, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
163
+ await rename(tmp, path);
164
+ }
165
+ async function directoryIsEmpty(path) {
166
+ const entries = await readdir(path);
167
+ return entries.length === 0;
168
+ }
169
+ function isStoreSentinel(value) {
170
+ if (typeof value !== "object" || value == null || Array.isArray(value))
171
+ return false;
172
+ const record = value;
173
+ return record.kind === STORE_KIND && record.schemaVersion === STORE_SCHEMA_VERSION && typeof record.createdAt === "string";
174
+ }
175
+ async function writeSentinel(basePath) {
176
+ await writeJson(join(basePath, STORE_SENTINEL), {
177
+ createdAt: new Date().toISOString(),
178
+ kind: STORE_KIND,
179
+ schemaVersion: STORE_SCHEMA_VERSION,
180
+ });
181
+ }
182
+ async function resetOwnedBase(basePath) {
183
+ const sentinel = await readJson(join(basePath, STORE_SENTINEL));
184
+ if (!isStoreSentinel(sentinel)) {
185
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED, `download base is not owned: ${basePath}`);
186
+ }
187
+ const entries = await readdir(basePath).catch(() => []);
188
+ for (const entry of entries) {
189
+ await rm(join(basePath, entry), { force: true, recursive: true }).catch(() => undefined);
190
+ }
191
+ await writeSentinel(basePath);
192
+ await ensureStoreDirs(basePath);
193
+ }
194
+ async function ensureStoreDirs(basePath) {
195
+ await mkdir(join(basePath, STATE_DIR), { recursive: true });
196
+ await mkdir(join(basePath, PARTIAL_DIR), { recursive: true });
197
+ await mkdir(join(basePath, LOCK_DIR), { recursive: true });
198
+ }
199
+ async function ensureManagedBase(basePath) {
200
+ await mkdir(basePath, { recursive: true });
201
+ const entry = await lstat(basePath);
202
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
203
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED, `download base is not an owned directory: ${basePath}`);
204
+ }
205
+ const sentinelPath = join(basePath, STORE_SENTINEL);
206
+ const sentinel = await readJson(sentinelPath);
207
+ if (sentinel == null) {
208
+ if (!(await directoryIsEmpty(basePath))) {
209
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED, `download base is not empty and has no ownership marker: ${basePath}`);
210
+ }
211
+ await writeSentinel(basePath);
212
+ }
213
+ else if (!isStoreSentinel(sentinel)) {
214
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED, `download base has an invalid ownership marker: ${basePath}`);
215
+ }
216
+ await ensureStoreDirs(basePath);
217
+ }
218
+ function isChecksum(value) {
219
+ if (typeof value !== "object" || value == null || Array.isArray(value))
220
+ return false;
221
+ const checksum = value;
222
+ return ((checksum.algorithm === "sha256" || checksum.algorithm === "sha512") &&
223
+ typeof checksum.value === "string");
224
+ }
225
+ function isManifest(value) {
226
+ if (typeof value !== "object" || value == null || Array.isArray(value))
227
+ return false;
228
+ const record = value;
229
+ return (record.kind === MANIFEST_KIND &&
230
+ record.schemaVersion === MANIFEST_SCHEMA_VERSION &&
231
+ typeof record.bucket === "string" &&
232
+ typeof record.fileName === "string" &&
233
+ typeof record.targetKey === "string" &&
234
+ typeof record.identityDigest === "string" &&
235
+ typeof record.urlDigest === "string" &&
236
+ isChecksum(record.checksum) &&
237
+ (record.state === "complete" || record.state === "partial") &&
238
+ typeof record.createdAt === "string" &&
239
+ typeof record.updatedAt === "string");
240
+ }
241
+ function isDownloadLockFile(value) {
242
+ if (typeof value !== "object" || value == null || Array.isArray(value))
243
+ return false;
244
+ const record = value;
245
+ return typeof record.createdAt === "string" &&
246
+ typeof record.pid === "number" &&
247
+ Number.isInteger(record.pid) &&
248
+ record.pid > 0 &&
249
+ (record.processStartedAt == null || typeof record.processStartedAt === "string");
250
+ }
251
+ function parseTimeMs(value) {
252
+ if (value == null)
253
+ return null;
254
+ const parsed = Date.parse(value);
255
+ return Number.isFinite(parsed) ? parsed : null;
256
+ }
257
+ function lockBelongsToCurrentProcess(lock) {
258
+ // Its only caller (`isLockProcessAlive`) already gates this call behind
259
+ // `lock.pid === process.pid`, so that comparison can never be true here —
260
+ // no redundant re-check.
261
+ const ownerStartedAtMs = parseTimeMs(lock.processStartedAt);
262
+ if (ownerStartedAtMs != null)
263
+ return ownerStartedAtMs >= PROCESS_STARTED_AT_MS - PID_REUSE_GRACE_MS;
264
+ // Older locks only carried a pid. If Windows reuses that pid for this
265
+ // process, a lock that predates this process start is definitely stale.
266
+ const lockCreatedAtMs = parseTimeMs(lock.createdAt);
267
+ return lockCreatedAtMs == null || lockCreatedAtMs >= PROCESS_STARTED_AT_MS - PID_REUSE_GRACE_MS;
268
+ }
269
+ function isLockProcessAlive(lock) {
270
+ if (!isProcessAlive(lock.pid))
271
+ return false;
272
+ if (lock.pid === process.pid)
273
+ return lockBelongsToCurrentProcess(lock);
274
+ return true;
275
+ }
276
+ async function readManifest(path) {
277
+ try {
278
+ const parsed = JSON.parse(await readFile(path, "utf8"));
279
+ return isManifest(parsed) ? parsed : "invalid";
280
+ }
281
+ catch (error) {
282
+ if (errorCode(error) === "ENOENT")
283
+ return null;
284
+ return "invalid";
285
+ }
286
+ }
287
+ function manifestMatchesTarget(manifest, target) {
288
+ return (manifest.bucket === target.bucket &&
289
+ manifest.fileName === target.fileName &&
290
+ manifest.targetKey === target.targetKey &&
291
+ manifest.identityDigest === target.identityDigest &&
292
+ manifest.urlDigest === target.urlDigest &&
293
+ manifest.checksum.algorithm === target.checksum.algorithm &&
294
+ manifest.checksum.value.toLowerCase() === target.checksum.value);
295
+ }
296
+ function createManifest(target, state, patch = {}) {
297
+ const now = new Date().toISOString();
298
+ return {
299
+ bucket: target.bucket,
300
+ checksum: target.checksum,
301
+ createdAt: now,
302
+ fileName: target.fileName,
303
+ identityDigest: target.identityDigest,
304
+ kind: MANIFEST_KIND,
305
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
306
+ state,
307
+ targetKey: target.targetKey,
308
+ updatedAt: now,
309
+ urlDigest: target.urlDigest,
310
+ ...patch,
311
+ };
312
+ }
313
+ async function hashFile(path, algorithm) {
314
+ const hash = createHash(algorithm);
315
+ await pipeline(createReadStream(path), hash);
316
+ return hash.digest("hex");
317
+ }
318
+ async function statFileSize(path) {
319
+ try {
320
+ const entry = await stat(path);
321
+ return entry.isFile() ? entry.size : null;
322
+ }
323
+ catch {
324
+ return null;
325
+ }
326
+ }
327
+ async function emitExistingProgress(path, totalBytes, emit) {
328
+ // `totalBytes` is required (not `number | undefined`): its only real call
329
+ // site (`tryResumeDownload`) always resolves one, via a `??` chain whose
330
+ // last resort is arithmetic (`partialBytes + (contentLength(response) ??
331
+ // 0)`) — never `undefined`.
332
+ const existing = await statFileSize(path);
333
+ if (existing == null || existing <= 0)
334
+ return;
335
+ emit({ receivedBytes: existing, sessionReceivedBytes: 0, totalBytes });
336
+ }
337
+ function contentLength(response) {
338
+ const raw = response.headers.get("content-length");
339
+ if (raw == null)
340
+ return undefined;
341
+ const parsed = Number(raw);
342
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
343
+ }
344
+ function validatorsFromResponse(response) {
345
+ const etag = response.headers.get("etag") ?? undefined;
346
+ const lastModified = response.headers.get("last-modified") ?? undefined;
347
+ return etag == null && lastModified == null ? undefined : { ...(etag == null ? {} : { etag }), ...(lastModified == null ? {} : { lastModified }) };
348
+ }
349
+ function validatorsConflict(saved, response) {
350
+ if (saved == null)
351
+ return false;
352
+ const etag = response.headers.get("etag") ?? undefined;
353
+ const lastModified = response.headers.get("last-modified") ?? undefined;
354
+ if (saved.etag != null && etag != null && saved.etag !== etag)
355
+ return true;
356
+ if (saved.lastModified != null && lastModified != null && saved.lastModified !== lastModified)
357
+ return true;
358
+ return false;
359
+ }
360
+ function parseContentRange(value) {
361
+ if (value == null)
362
+ return null;
363
+ const match = /^bytes\s+(\d+)-(\d+)\/(\d+|\*)$/i.exec(value.trim());
364
+ if (match?.[1] == null || match[2] == null || match[3] == null)
365
+ return null;
366
+ const start = Number(match[1]);
367
+ const end = Number(match[2]);
368
+ const totalBytes = match[3] === "*" ? undefined : Number(match[3]);
369
+ if (!Number.isInteger(start) || !Number.isInteger(end) || end < start)
370
+ return null;
371
+ if (totalBytes != null && (!Number.isInteger(totalBytes) || totalBytes <= end))
372
+ return null;
373
+ return { start, end, ...(totalBytes == null ? {} : { totalBytes }) };
374
+ }
375
+ async function writeResponseBodyToPartial(response, target, options) {
376
+ if (response.body == null)
377
+ throw new Error("download response did not include a body");
378
+ let receivedBytes = options.startBytes;
379
+ let sessionReceivedBytes = 0;
380
+ const meter = new Transform({
381
+ transform(chunk, _encoding, callback) {
382
+ receivedBytes += chunk.byteLength;
383
+ sessionReceivedBytes += chunk.byteLength;
384
+ options.emit({
385
+ receivedBytes,
386
+ sessionReceivedBytes,
387
+ ...(options.totalBytes == null ? {} : { totalBytes: options.totalBytes }),
388
+ });
389
+ callback(null, chunk);
390
+ },
391
+ });
392
+ await pipeline(Readable.fromWeb(response.body), meter, createWriteStream(target.partialPath, { flags: options.startBytes > 0 ? "a" : "w" }));
393
+ }
394
+ async function tryResumeDownload(target, manifest, fetchImpl, emit, requestHeaders) {
395
+ const partialBytes = await statFileSize(target.partialPath);
396
+ if (partialBytes == null || partialBytes <= 0)
397
+ return "restart";
398
+ const response = await fetchImpl(target.url, {
399
+ headers: {
400
+ ...(requestHeaders ?? {}),
401
+ ...(manifest.validators?.etag == null ? {} : { "If-Range": manifest.validators.etag }),
402
+ Range: `bytes=${partialBytes}-`,
403
+ },
404
+ });
405
+ if (response.status !== 206)
406
+ return "restart";
407
+ const range = parseContentRange(response.headers.get("content-range"));
408
+ if (range == null || range.start !== partialBytes || validatorsConflict(manifest.validators, response)) {
409
+ return "restart";
410
+ }
411
+ const totalBytes = range.totalBytes ?? manifest.totalBytes ?? partialBytes + (contentLength(response) ?? 0);
412
+ await emitExistingProgress(target.partialPath, totalBytes, emit);
413
+ await writeJson(target.manifestPath, {
414
+ ...manifest,
415
+ state: "partial",
416
+ totalBytes,
417
+ updatedAt: new Date().toISOString(),
418
+ validators: manifest.validators ?? validatorsFromResponse(response),
419
+ });
420
+ await writeResponseBodyToPartial(response, target, { emit, startBytes: partialBytes, totalBytes });
421
+ return { resumed: true, totalBytes };
422
+ }
423
+ async function downloadFromZero(target, fetchImpl, emit, requestHeaders) {
424
+ await rm(target.partialPath, { force: true }).catch(() => undefined);
425
+ const response = await fetchImpl(target.url, {
426
+ ...(requestHeaders == null ? {} : { headers: requestHeaders }),
427
+ });
428
+ if (!response.ok)
429
+ throw new Error(`download request returned HTTP ${response.status}`);
430
+ const totalBytes = contentLength(response);
431
+ const validators = validatorsFromResponse(response);
432
+ await writeJson(target.manifestPath, createManifest(target, "partial", { totalBytes, validators }));
433
+ await writeResponseBodyToPartial(response, target, { emit, startBytes: 0, totalBytes });
434
+ return { resumed: false, totalBytes };
435
+ }
436
+ async function downloadWithRetries(target, manifest, options) {
437
+ let lastError;
438
+ let nextManifest = manifest;
439
+ let resumed = false;
440
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt += 1) {
441
+ try {
442
+ if (nextManifest?.state === "partial") {
443
+ const resume = await tryResumeDownload(target, nextManifest, options.fetchImpl, options.emit, options.requestHeaders);
444
+ if (resume !== "restart")
445
+ return { ...resume, resumed: true };
446
+ await rm(target.partialPath, { force: true }).catch(() => undefined);
447
+ nextManifest = null;
448
+ }
449
+ const full = await downloadFromZero(target, options.fetchImpl, options.emit, options.requestHeaders);
450
+ resumed = resumed || full.resumed;
451
+ return { ...full, resumed };
452
+ }
453
+ catch (error) {
454
+ lastError = error;
455
+ const partialBytes = await statFileSize(target.partialPath);
456
+ if (partialBytes != null && partialBytes > 0) {
457
+ nextManifest = {
458
+ ...(nextManifest ?? createManifest(target, "partial")),
459
+ state: "partial",
460
+ updatedAt: new Date().toISOString(),
461
+ };
462
+ await writeJson(target.manifestPath, nextManifest).catch(() => undefined);
463
+ }
464
+ }
465
+ }
466
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.NETWORK_EXHAUSTED, `download failed after ${options.maxAttempts} attempts: ${errorMessage(lastError)}`);
467
+ }
468
+ async function acquireLock(target) {
469
+ await mkdir(dirname(target.lockPath), { recursive: true });
470
+ // The `continue` below is only reachable on `attempt === 0`, so by the time
471
+ // `attempt` would reach 2 (the old loop bound), the branch above it has
472
+ // already thrown — there's no way to fall through to a third iteration, so
473
+ // no trailing fallback throw is needed. Every path out of this loop body is
474
+ // a `return` or a `throw`; V8 still instruments the loop's closing brace as
475
+ // a branch (whether execution "falls off" the loop normally), but that
476
+ // outcome is structurally impossible here — see source-map.md's
477
+ // 2026-07-22 entry.
478
+ for (let attempt = 0;; attempt += 1) {
479
+ try {
480
+ await writeFile(target.lockPath, `${JSON.stringify({
481
+ createdAt: new Date().toISOString(),
482
+ pid: process.pid,
483
+ processStartedAt: new Date(PROCESS_STARTED_AT_MS).toISOString(),
484
+ })}\n`, { flag: "wx" });
485
+ return { path: target.lockPath };
486
+ }
487
+ catch (error) {
488
+ if (errorCode(error) === "EEXIST") {
489
+ const staleLockCleared = attempt === 0 && await clearStaleLock(target);
490
+ if (staleLockCleared)
491
+ continue;
492
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_LOCKED, `download target is locked: ${target.bucket}/${target.fileName}`);
493
+ }
494
+ throw error;
495
+ }
496
+ }
497
+ }
498
+ async function clearStaleLock(target) {
499
+ const lock = await readJson(target.lockPath);
500
+ if (!isDownloadLockFile(lock) || isLockProcessAlive(lock))
501
+ return false;
502
+ await rm(target.lockPath, { force: true }).catch(() => undefined);
503
+ return true;
504
+ }
505
+ async function releaseLock(lock) {
506
+ if (lock == null)
507
+ return;
508
+ await rm(lock.path, { force: true }).catch(() => undefined);
509
+ }
510
+ async function suspiciousReset(target) {
511
+ await resetOwnedBase(target.basePath);
512
+ return { manifest: null, reset: true };
513
+ }
514
+ async function loadReusableState(target) {
515
+ const manifest = await readManifest(target.manifestPath);
516
+ const finalExists = await pathExists(target.finalPath);
517
+ const partialExists = await pathExists(target.partialPath);
518
+ if (manifest === "invalid") {
519
+ return await suspiciousReset(target);
520
+ }
521
+ if (manifest == null) {
522
+ if (finalExists || partialExists)
523
+ return await suspiciousReset(target);
524
+ return { manifest: null };
525
+ }
526
+ if (!manifestMatchesTarget(manifest, target)) {
527
+ return await suspiciousReset(target);
528
+ }
529
+ if (manifest.state === "complete") {
530
+ if (!finalExists)
531
+ return await suspiciousReset(target);
532
+ const actual = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
533
+ if (actual !== target.checksum.value)
534
+ return await suspiciousReset(target);
535
+ const bytes = await statFileSize(target.finalPath);
536
+ if (bytes == null)
537
+ return await suspiciousReset(target);
538
+ return {
539
+ manifest,
540
+ result: {
541
+ bucket: target.bucket,
542
+ bytes,
543
+ checksum: target.checksum,
544
+ fileName: target.fileName,
545
+ path: target.finalPath,
546
+ reusedComplete: true,
547
+ resumed: false,
548
+ urlDigest: target.urlDigest,
549
+ },
550
+ };
551
+ }
552
+ if (!partialExists)
553
+ return await suspiciousReset(target);
554
+ return { manifest };
555
+ }
556
+ async function runManagedDownload(target, options) {
557
+ await ensureManagedBase(target.basePath);
558
+ await pruneManagedDownloads({ basePath: target.basePath }).catch(() => undefined);
559
+ let lock = await acquireLock(target);
560
+ try {
561
+ let state = await loadReusableState(target);
562
+ if (state.result != null)
563
+ return state.result;
564
+ if (state.reset === true) {
565
+ // A reset removes the lock as part of the managed base cleanup, so
566
+ // reacquire it before writing the fresh target state.
567
+ await releaseLock(lock);
568
+ lock = null;
569
+ await ensureManagedBase(target.basePath);
570
+ lock = await acquireLock(target);
571
+ state = await loadReusableState(target);
572
+ if (state.result != null)
573
+ return state.result;
574
+ if (state.reset === true) {
575
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "download state kept resetting after base cleanup");
576
+ }
577
+ }
578
+ const download = await downloadWithRetries(target, state.manifest, options);
579
+ const actual = await hashFile(target.partialPath, target.checksum.algorithm).catch((error) => {
580
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, `downloaded partial could not be hashed: ${errorMessage(error)}`);
581
+ });
582
+ if (actual !== target.checksum.value) {
583
+ await resetOwnedBase(target.basePath).catch(() => undefined);
584
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.CHECKSUM_MISMATCH, "downloaded file checksum did not match requested payload", {
585
+ actual,
586
+ expected: target.checksum.value,
587
+ });
588
+ }
589
+ await mkdir(dirname(target.finalPath), { recursive: true });
590
+ if (await pathExists(target.finalPath)) {
591
+ const existing = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
592
+ if (existing !== target.checksum.value) {
593
+ await resetOwnedBase(target.basePath).catch(() => undefined);
594
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "existing complete file did not match requested payload");
595
+ }
596
+ await rm(target.partialPath, { force: true }).catch(() => undefined);
597
+ }
598
+ else {
599
+ await rename(target.partialPath, target.finalPath);
600
+ }
601
+ const bytes = await statFileSize(target.finalPath);
602
+ if (bytes == null)
603
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "complete file is missing after promotion");
604
+ await writeJson(target.manifestPath, createManifest(target, "complete", { totalBytes: download.totalBytes }));
605
+ return {
606
+ bucket: target.bucket,
607
+ bytes,
608
+ checksum: target.checksum,
609
+ fileName: target.fileName,
610
+ path: target.finalPath,
611
+ reusedComplete: false,
612
+ resumed: download.resumed,
613
+ urlDigest: target.urlDigest,
614
+ };
615
+ }
616
+ finally {
617
+ await releaseLock(lock);
618
+ }
619
+ }
620
+ function activeKey(target) {
621
+ return `${target.basePath}\0${target.targetKey}\0${target.identityDigest}`;
622
+ }
623
+ function targetActiveKey(target) {
624
+ return `${target.basePath}\0${target.targetKey}`;
625
+ }
626
+ function waitForTask(task, options) {
627
+ return new Promise((resolveWait, rejectWait) => {
628
+ if (options.signal?.aborted) {
629
+ rejectWait(new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.ABORTED, "download wait was aborted"));
630
+ return;
631
+ }
632
+ const listener = options.onProgress;
633
+ if (listener != null)
634
+ task.listeners.add(listener);
635
+ const cleanup = () => {
636
+ if (listener != null)
637
+ task.listeners.delete(listener);
638
+ options.signal?.removeEventListener("abort", onAbort);
639
+ };
640
+ const onAbort = () => {
641
+ cleanup();
642
+ rejectWait(new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.ABORTED, "download wait was aborted"));
643
+ };
644
+ options.signal?.addEventListener("abort", onAbort, { once: true });
645
+ task.promise.then((result) => {
646
+ cleanup();
647
+ resolveWait(result);
648
+ }, (error) => {
649
+ cleanup();
650
+ rejectWait(error);
651
+ });
652
+ });
653
+ }
654
+ export async function managedDownload(options) {
655
+ const target = targetFromOptions(options);
656
+ const key = activeKey(target);
657
+ const targetKey = targetActiveKey(target);
658
+ const existingForTarget = activeTargets.get(targetKey);
659
+ if (existingForTarget != null && existingForTarget !== key) {
660
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_CONFLICT, `download target is already active with a different identity: ${target.bucket}/${target.fileName}`);
661
+ }
662
+ const existing = activeTasks.get(key);
663
+ if (existing != null)
664
+ return await waitForTask(existing, options);
665
+ const listeners = new Set();
666
+ const emit = (progress) => {
667
+ for (const listener of listeners)
668
+ listener(progress);
669
+ };
670
+ const task = {
671
+ listeners,
672
+ targetKey,
673
+ promise: runManagedDownload(target, {
674
+ emit,
675
+ fetchImpl: options.fetch ?? globalThis.fetch,
676
+ maxAttempts: options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
677
+ requestHeaders: options.payload.headers,
678
+ }),
679
+ };
680
+ activeTasks.set(key, task);
681
+ activeTargets.set(targetKey, key);
682
+ task.promise.finally(() => {
683
+ activeTasks.delete(key);
684
+ if (activeTargets.get(targetKey) === key)
685
+ activeTargets.delete(targetKey);
686
+ }).catch(() => undefined);
687
+ return await waitForTask(task, options);
688
+ }
689
+ function acquireCopyLease(target) {
690
+ const key = targetActiveKey(target);
691
+ const state = copyLeases.get(key) ?? { clearRequested: false, count: 0, removeOptions: null };
692
+ state.count += 1;
693
+ copyLeases.set(key, state);
694
+ return { key };
695
+ }
696
+ async function requestClearAfterCopy(target) {
697
+ const key = targetActiveKey(target);
698
+ const state = copyLeases.get(key);
699
+ if (state != null && state.count > 1) {
700
+ state.clearRequested = true;
701
+ state.removeOptions = { basePath: target.basePath, bucket: target.bucket, fileName: target.fileName };
702
+ return "deferred";
703
+ }
704
+ await removeManagedDownload({ basePath: target.basePath, bucket: target.bucket, fileName: target.fileName });
705
+ return "removed";
706
+ }
707
+ async function releaseCopyLease(lease) {
708
+ // `acquireCopyLease` always populates `copyLeases.get(key)` synchronously
709
+ // before returning a lease, and this module's only caller
710
+ // (`downloadCopyAndClear`) releases each lease exactly once, in its own
711
+ // `finally` block — both functions are fully synchronous (no `await`
712
+ // inside either), so there is no interleaving window in which another
713
+ // caller could delete the entry first. The map entry is therefore always
714
+ // present here.
715
+ const state = copyLeases.get(lease.key);
716
+ state.count -= 1;
717
+ if (state.count > 0)
718
+ return;
719
+ copyLeases.delete(lease.key);
720
+ if (state.clearRequested && state.removeOptions != null) {
721
+ await removeManagedDownload(state.removeOptions).catch(() => undefined);
722
+ }
723
+ }
724
+ export async function downloadCopyAndClear(options) {
725
+ const target = targetFromOptions(options);
726
+ if (typeof options.outputPath !== "string" || options.outputPath.length === 0 || options.outputPath.includes("\0")) {
727
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.INVALID_TARGET, "outputPath must be a non-empty path");
728
+ }
729
+ // `path.resolve()` always returns an absolute path by contract (same
730
+ // guarantee `normalizeBasePath` above already relies on), so a follow-up
731
+ // `isAbsolute` check here could never be false for any string input.
732
+ const outputPath = resolve(options.outputPath);
733
+ const lease = acquireCopyLease(target);
734
+ try {
735
+ const downloaded = await managedDownload(options);
736
+ const existingOutput = await statFileSize(outputPath);
737
+ if (existingOutput != null) {
738
+ const existingDigest = await hashFile(outputPath, target.checksum.algorithm).catch(() => null);
739
+ if (existingDigest !== target.checksum.value) {
740
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.OUTPUT_CONFLICT, `output already exists with different bytes: ${outputPath}`);
741
+ }
742
+ }
743
+ else {
744
+ await atomicCopyFile(downloaded.path, outputPath);
745
+ const copiedDigest = await hashFile(outputPath, target.checksum.algorithm);
746
+ if (copiedDigest !== target.checksum.value) {
747
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.CHECKSUM_MISMATCH, "copied output checksum did not match requested payload");
748
+ }
749
+ }
750
+ let cleanup = "removed";
751
+ let cleanupWarning;
752
+ try {
753
+ cleanup = await requestClearAfterCopy(target);
754
+ }
755
+ catch (error) {
756
+ cleanupWarning = errorMessage(error);
757
+ }
758
+ return {
759
+ bytes: downloaded.bytes,
760
+ cleanup,
761
+ ...(cleanupWarning == null ? {} : { cleanupWarning }),
762
+ outputPath,
763
+ reusedComplete: downloaded.reusedComplete,
764
+ resumed: downloaded.resumed,
765
+ };
766
+ }
767
+ finally {
768
+ await releaseCopyLease(lease);
769
+ }
770
+ }
771
+ export async function inspectManagedDownload(options) {
772
+ const target = targetFromOptions({
773
+ ...options,
774
+ payload: {
775
+ checksum: { algorithm: "sha256", value: "0".repeat(64) },
776
+ url: "https://example.invalid/",
777
+ },
778
+ });
779
+ await ensureManagedBase(target.basePath);
780
+ const rawManifest = await readManifest(target.manifestPath);
781
+ const manifest = rawManifest === "invalid" ? "unreadable" : rawManifest?.state ?? "missing";
782
+ return {
783
+ bucket: target.bucket,
784
+ complete: await pathExists(target.finalPath),
785
+ fileName: target.fileName,
786
+ manifest,
787
+ path: target.finalPath,
788
+ };
789
+ }
790
+ export async function removeManagedDownload(options) {
791
+ const target = targetFromOptions({
792
+ ...options,
793
+ payload: {
794
+ checksum: { algorithm: "sha256", value: "0".repeat(64) },
795
+ url: "https://example.invalid/",
796
+ },
797
+ });
798
+ if (activeTargets.has(targetActiveKey(target))) {
799
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_LOCKED, `download target is active: ${target.bucket}/${target.fileName}`);
800
+ }
801
+ await ensureManagedBase(target.basePath);
802
+ await Promise.all([
803
+ removePathBestEffort(target.finalPath),
804
+ removePathBestEffort(target.partialPath, { recursive: false }),
805
+ removePathBestEffort(target.manifestPath, { recursive: false }),
806
+ removePathBestEffort(target.lockPath, { recursive: false }),
807
+ ]);
808
+ const bucketPath = join(target.basePath, target.bucket);
809
+ const entries = await readdir(bucketPath).catch(() => null);
810
+ if (entries != null && entries.length === 0)
811
+ await rm(bucketPath, { force: true, recursive: true }).catch(() => undefined);
812
+ return { removed: true };
813
+ }
814
+ async function removeEntriesOlderThan(root, olderThan) {
815
+ const warnings = [];
816
+ let removed = 0;
817
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
818
+ for (const entry of entries) {
819
+ const path = join(root, entry.name);
820
+ const info = await stat(path).catch(() => null);
821
+ if (info == null || info.mtimeMs > olderThan)
822
+ continue;
823
+ const result = await removePathBestEffort(path);
824
+ if (result.removed)
825
+ removed += 1;
826
+ else if (result.error != null)
827
+ warnings.push(result.error);
828
+ }
829
+ return { removed, warnings };
830
+ }
831
+ export async function pruneManagedDownloads(options) {
832
+ const basePath = normalizeBasePath(options.basePath);
833
+ await ensureManagedBase(basePath);
834
+ const olderThan = (options.now?.getTime() ?? Date.now()) - (options.olderThanMs ?? DEFAULT_PRUNE_OLDER_THAN_MS);
835
+ const roots = [join(basePath, STATE_DIR), join(basePath, PARTIAL_DIR), join(basePath, LOCK_DIR)];
836
+ let removed = 0;
837
+ const warnings = [];
838
+ for (const root of roots) {
839
+ const result = await removeEntriesOlderThan(root, olderThan);
840
+ removed += result.removed;
841
+ warnings.push(...result.warnings);
842
+ }
843
+ const bucketEntries = await readdir(basePath, { withFileTypes: true }).catch(() => []);
844
+ for (const entry of bucketEntries) {
845
+ if (!entry.isDirectory() || entry.name.startsWith("."))
846
+ continue;
847
+ const result = await removeEntriesOlderThan(join(basePath, entry.name), olderThan);
848
+ removed += result.removed;
849
+ warnings.push(...result.warnings);
850
+ const remaining = await readdir(join(basePath, entry.name)).catch(() => null);
851
+ if (remaining != null && remaining.length === 0)
852
+ await rm(join(basePath, entry.name), { force: true, recursive: true }).catch(() => undefined);
853
+ }
854
+ return { removed, warnings };
855
+ }
856
+ //# sourceMappingURL=download.js.map