@thallylabs/migrate 0.2.28 → 0.2.29
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/dist/index.js +94 -8
- package/dist/index.js.map +1 -1
- package/dist/url-migration.js +94 -8
- package/dist/url-migration.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13876,6 +13876,73 @@ function isPassiveSvg(content) {
|
|
|
13876
13876
|
return isPassive && sawRoot && depth === 0;
|
|
13877
13877
|
}
|
|
13878
13878
|
|
|
13879
|
+
// src/url-asset-cache.ts
|
|
13880
|
+
function createMigrationAssetCache() {
|
|
13881
|
+
const settled = /* @__PURE__ */ new Map();
|
|
13882
|
+
const pending = /* @__PURE__ */ new Map();
|
|
13883
|
+
const remember = (url, asset) => {
|
|
13884
|
+
settled.delete(url);
|
|
13885
|
+
settled.set(url, asset);
|
|
13886
|
+
if (settled.size > 480) settled.delete(settled.keys().next().value);
|
|
13887
|
+
return asset;
|
|
13888
|
+
};
|
|
13889
|
+
const get = (url) => {
|
|
13890
|
+
const asset = settled.get(url);
|
|
13891
|
+
if (asset) return Promise.resolve(remember(url, asset));
|
|
13892
|
+
return pending.get(url);
|
|
13893
|
+
};
|
|
13894
|
+
return {
|
|
13895
|
+
remember,
|
|
13896
|
+
get,
|
|
13897
|
+
has(url) {
|
|
13898
|
+
return settled.has(url) || pending.has(url);
|
|
13899
|
+
},
|
|
13900
|
+
get size() {
|
|
13901
|
+
return settled.size + pending.size;
|
|
13902
|
+
},
|
|
13903
|
+
load(url, acquire) {
|
|
13904
|
+
const existing = get(url);
|
|
13905
|
+
if (existing) return existing;
|
|
13906
|
+
const request2 = Promise.resolve().then(acquire).then((asset) => {
|
|
13907
|
+
pending.delete(url);
|
|
13908
|
+
return remember(url, asset);
|
|
13909
|
+
}, (error) => {
|
|
13910
|
+
pending.delete(url);
|
|
13911
|
+
throw error;
|
|
13912
|
+
});
|
|
13913
|
+
pending.set(url, request2);
|
|
13914
|
+
return request2;
|
|
13915
|
+
},
|
|
13916
|
+
delete(url) {
|
|
13917
|
+
settled.delete(url);
|
|
13918
|
+
}
|
|
13919
|
+
};
|
|
13920
|
+
}
|
|
13921
|
+
|
|
13922
|
+
// src/url-journal-clone.ts
|
|
13923
|
+
function cloneUrlMigrationCheckpoint(checkpoint) {
|
|
13924
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
13925
|
+
const clone = (value) => {
|
|
13926
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
13927
|
+
if (typeof value === "number") return Number.isFinite(value) ? value === 0 ? 0 : value : null;
|
|
13928
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
13929
|
+
if (typeof value !== "object") throw new TypeError("Checkpoint must contain JSON-safe values.");
|
|
13930
|
+
if (ancestors.has(value)) throw new TypeError("Checkpoint must not contain circular references.");
|
|
13931
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("Checkpoint must contain plain JSON containers.");
|
|
13932
|
+
ancestors.add(value);
|
|
13933
|
+
try {
|
|
13934
|
+
if (Array.isArray(value)) return Array.from(value, (entry) => clone(entry) ?? null);
|
|
13935
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => {
|
|
13936
|
+
const copied = clone(entry);
|
|
13937
|
+
return copied === void 0 ? [] : [[key, copied]];
|
|
13938
|
+
}));
|
|
13939
|
+
} finally {
|
|
13940
|
+
ancestors.delete(value);
|
|
13941
|
+
}
|
|
13942
|
+
};
|
|
13943
|
+
return clone(checkpoint);
|
|
13944
|
+
}
|
|
13945
|
+
|
|
13879
13946
|
// src/url-run.ts
|
|
13880
13947
|
var CONVERTER_VERSION = "url-evidence-v2";
|
|
13881
13948
|
var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
|
|
@@ -14250,7 +14317,8 @@ async function runUrlMigration(options) {
|
|
|
14250
14317
|
const batchSize = Math.max(1, Math.min(500, Math.floor(options.batchSize ?? 50)));
|
|
14251
14318
|
if (!Number.isFinite(concurrency) || !Number.isFinite(batchSize) || options.maxCaptures !== void 0 && (!Number.isInteger(options.maxCaptures) || options.maxCaptures < 1) || options.maxTotalBytes !== void 0 && (!Number.isFinite(options.maxTotalBytes) || options.maxTotalBytes < 1)) throw new Error("Migration batch, concurrency and byte limits must be positive finite numbers.");
|
|
14252
14319
|
const captureVersion = options.captureVersion ?? "fetch-v1";
|
|
14253
|
-
|
|
14320
|
+
if (options.checkpoint && options.checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
14321
|
+
const checkpoint = options.checkpoint ? cloneUrlMigrationCheckpoint(options.checkpoint) : { schemaVersion: 1, converterVersion: CONVERTER_VERSION, sourceUrl, scopePath, captureVersion, records: [], navigation: [] };
|
|
14254
14322
|
if (checkpoint.schemaVersion !== 1 || checkpoint.converterVersion !== CONVERTER_VERSION || checkpoint.sourceUrl !== sourceUrl || checkpoint.scopePath !== scopePath || checkpoint.captureVersion !== captureVersion) throw new Error("Checkpoint does not match this source, scope or capture/converter version.");
|
|
14255
14323
|
if (checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
14256
14324
|
const records = /* @__PURE__ */ new Map();
|
|
@@ -14298,9 +14366,21 @@ async function runUrlMigration(options) {
|
|
|
14298
14366
|
const finalUrls = new Map([...records.values()].filter((record) => record.evidence && record.page).map((record) => [canonical(record.evidence.finalUrl, sourceUrl), record.url]));
|
|
14299
14367
|
let imported = usedIds.size;
|
|
14300
14368
|
const assetPayloads = new Map(checkpoint.records.flatMap((record) => (record.assets ?? []).map((asset) => [asset.sha256, asset])));
|
|
14301
|
-
const
|
|
14369
|
+
const assetBytes = /* @__PURE__ */ new Map();
|
|
14370
|
+
const retainAsset = (asset) => {
|
|
14371
|
+
if (!acceptedAsset(asset)) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
14372
|
+
const sha256 = hash2(asset.content);
|
|
14373
|
+
const content = assetBytes.get(sha256) ?? Buffer.from(asset.content);
|
|
14374
|
+
assetBytes.set(sha256, content);
|
|
14375
|
+
return { ...asset, content };
|
|
14376
|
+
};
|
|
14377
|
+
const assetRequests = createMigrationAssetCache();
|
|
14302
14378
|
for (const record of checkpoint.records) for (const asset of record.assets ?? []) {
|
|
14303
|
-
if (!assetRequests.has(asset.url))
|
|
14379
|
+
if (!assetRequests.has(asset.url)) {
|
|
14380
|
+
const content = assetBytes.get(asset.sha256) ?? Buffer.from(asset.base64, "base64");
|
|
14381
|
+
assetBytes.set(asset.sha256, content);
|
|
14382
|
+
assetRequests.remember(asset.url, { url: asset.url, contentType: asset.contentType, content });
|
|
14383
|
+
}
|
|
14304
14384
|
}
|
|
14305
14385
|
let totalBytes = checkpoint.records.reduce((sum, record) => sum + (record.evidence?.byteLength ?? 0), 0) + [...assetPayloads.values()].reduce((sum, asset) => sum + Buffer.byteLength(asset.base64, "base64"), 0);
|
|
14306
14386
|
let budgetExhausted = false;
|
|
@@ -14412,10 +14492,10 @@ ${difference}`;
|
|
|
14412
14492
|
try {
|
|
14413
14493
|
let assetRequest = assetRequests.get(url);
|
|
14414
14494
|
if (!suppliedAssets.has(url) && !assetRequest && assetFetcher) {
|
|
14415
|
-
assetRequest = assetFetcher(new URL(url), options.signal);
|
|
14416
|
-
assetRequests.set(url, assetRequest);
|
|
14495
|
+
assetRequest = assetRequests.load(url, () => assetFetcher(new URL(url), options.signal).then(retainAsset));
|
|
14417
14496
|
}
|
|
14418
|
-
const
|
|
14497
|
+
const supplied = suppliedAssets.get(url);
|
|
14498
|
+
const asset = supplied ? retainAsset(supplied) : assetRequest ? await assetRequest : null;
|
|
14419
14499
|
if (!asset) throw new Error("Capture host did not supply asset bytes or a binary fetch boundary.");
|
|
14420
14500
|
const extension = acceptedAsset(asset);
|
|
14421
14501
|
if (!extension) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
@@ -14432,7 +14512,7 @@ ${difference}`;
|
|
|
14432
14512
|
const receipt = { url, path, sha256, contentType: asset.contentType, base64: existing?.base64 ?? Buffer.from(asset.content).toString("base64") };
|
|
14433
14513
|
record.assets.push(receipt);
|
|
14434
14514
|
if (!existing) assetPayloads.set(sha256, receipt);
|
|
14435
|
-
if (!assetRequests.has(url)) assetRequests.
|
|
14515
|
+
if (!assetRequests.has(url)) assetRequests.remember(url, asset);
|
|
14436
14516
|
page.body = rewriteMarkdownDestinations(page.body, (destination) => canonical(destination, final) === url ? `/${path}${new URL(destination, final).hash}` : destination);
|
|
14437
14517
|
} catch (error) {
|
|
14438
14518
|
assetRequests.delete(url);
|
|
@@ -14548,7 +14628,13 @@ ${difference}`;
|
|
|
14548
14628
|
const validation = validateUrlMigration(checkpoint, Boolean(options.signal?.aborted));
|
|
14549
14629
|
validation.issues.push(...navigationIssues);
|
|
14550
14630
|
if (navigationIssues.length && validation.status === "passed") validation.status = "partial";
|
|
14551
|
-
const assets =
|
|
14631
|
+
const assets = /* @__PURE__ */ new Map();
|
|
14632
|
+
for (const record of checkpoint.records) for (const asset of record.assets ?? []) {
|
|
14633
|
+
if (!assets.has(asset.path)) {
|
|
14634
|
+
const content = assetBytes.get(asset.sha256) ?? Buffer.from(asset.base64, "base64");
|
|
14635
|
+
assets.set(asset.path, { path: asset.path, content: Buffer.from(content.buffer, content.byteOffset, content.byteLength) });
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14552
14638
|
const bundle = {
|
|
14553
14639
|
sourceUrl,
|
|
14554
14640
|
sourceKind: "url",
|