@volter/browser-runtime 0.1.84 → 0.1.86
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/browser-image-layers.d.ts +11 -2
- package/dist/browser-image-layers.d.ts.map +1 -1
- package/dist/browser-image-layers.js +11 -3
- package/dist/browser-image-layers.js.map +1 -1
- package/dist/browser-image-range-worker.js +441 -69
- package/dist/browser-image-range-worker.js.map +1 -1
- package/dist/browser-image-ranges.d.ts +58 -2
- package/dist/browser-image-ranges.d.ts.map +1 -1
- package/dist/browser-image-ranges.js +271 -13
- package/dist/browser-image-ranges.js.map +1 -1
- package/dist/browser-pack-store.d.ts +32 -6
- package/dist/browser-pack-store.d.ts.map +1 -1
- package/dist/browser-pack-store.js +74 -22
- package/dist/browser-pack-store.js.map +1 -1
- package/dist/browser-store-layout.d.ts +13 -0
- package/dist/browser-store-layout.d.ts.map +1 -0
- package/dist/browser-store-layout.js +20 -0
- package/dist/browser-store-layout.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { encodeSharedIndex, OverlaidChildren, OverlaidIndex, SharedIndex, sameIndexEntry } from "./browser-pack-index.js";
|
|
2
|
+
import { PACKAGES_STORE, STORE_BASE_DIRECTORY, STORE_STATE_FILE } from "./browser-store-layout.js";
|
|
2
3
|
import { ImageRangeReader, IMAGE_RANGES_FILE, validateImageRanges } from "./browser-image-ranges.js";
|
|
3
4
|
/** The one shared store of an origin: every package version's files, once, under `/packages/<name>@<version>/`. */
|
|
4
|
-
export
|
|
5
|
+
export { PACKAGES_STORE };
|
|
5
6
|
export const PACKAGES_ROOT = "/packages";
|
|
6
7
|
/**
|
|
7
8
|
* A project's live-pack retention roots (W62): links that keep an installed
|
|
@@ -22,7 +23,7 @@ export const PACKAGES_LOCK = "supercode-browser-packages";
|
|
|
22
23
|
export const PACKAGES_OPEN_LOCK = "supercode-browser-packages-open";
|
|
23
24
|
const encoder = new TextEncoder();
|
|
24
25
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
25
|
-
const BASE_DIRECTORY =
|
|
26
|
+
const BASE_DIRECTORY = STORE_BASE_DIRECTORY;
|
|
26
27
|
/**
|
|
27
28
|
* A store whose name begins so is a tree a host installed (ADR-0048), not a
|
|
28
29
|
* project: it links no shared packages, and a sweep of them leaves it alone.
|
|
@@ -31,7 +32,7 @@ const BASE_DIRECTORY = "supercode-browser-filesystems";
|
|
|
31
32
|
export const TREE_STORE_PREFIX = "host-tree.";
|
|
32
33
|
const GENERATIONS_DIRECTORY = "generations";
|
|
33
34
|
const STAGING_DIRECTORY = "atomic-staging";
|
|
34
|
-
const STATE_FILE =
|
|
35
|
+
const STATE_FILE = STORE_STATE_FILE;
|
|
35
36
|
const INDEX_FILE = "index.json";
|
|
36
37
|
const LOG_FILE = "index-log.bin";
|
|
37
38
|
const PACK_FILE = "pack.bin";
|
|
@@ -85,6 +86,7 @@ export function createPackStore(environment, options = {}) {
|
|
|
85
86
|
let imageReader;
|
|
86
87
|
let deferredImage;
|
|
87
88
|
let receivedImageCompletion;
|
|
89
|
+
let receivedImageLine;
|
|
88
90
|
let generationInitializedAt = 0;
|
|
89
91
|
let logAccess;
|
|
90
92
|
/** Committed package bytes captured under the writer lock; never mutable workspace bytes. */
|
|
@@ -220,7 +222,8 @@ export function createPackStore(environment, options = {}) {
|
|
|
220
222
|
function imageCompletionSeed() {
|
|
221
223
|
if (!imageReader || !imageRangesDigest || !generation || initializing || transitions > 0 || closing)
|
|
222
224
|
return undefined;
|
|
223
|
-
return { generation, rangesDigest: imageRangesDigest, completed: imageReader.completionBuffer
|
|
225
|
+
return { generation, rangesDigest: imageRangesDigest, completed: imageReader.completionBuffer,
|
|
226
|
+
...(imageReader.receiptKey ? { metadata: { ranges: imageReader.verifiedRanges, receiptKey: imageReader.receiptKey } } : {}) };
|
|
224
227
|
}
|
|
225
228
|
/**
|
|
226
229
|
* The owner stands on the seed it just encoded, as its readers do: the index
|
|
@@ -417,6 +420,7 @@ export function createPackStore(environment, options = {}) {
|
|
|
417
420
|
projectId = validProjectId(requestedProjectId);
|
|
418
421
|
readOnly = openOptions.readOnly === true;
|
|
419
422
|
receivedImageCompletion = readOnly ? openOptions.imageCompletion : undefined;
|
|
423
|
+
receivedImageLine = readOnly ? openOptions.imageLine : undefined;
|
|
420
424
|
extentsOnly = readOnly && openOptions.extentsOnly === true;
|
|
421
425
|
shared = openOptions.shared === true || readOnly;
|
|
422
426
|
if (!shared)
|
|
@@ -753,17 +757,38 @@ export function createPackStore(environment, options = {}) {
|
|
|
753
757
|
logAccess = await openSyncAccess(log, "record of changes");
|
|
754
758
|
logBytes = logAccess.getSize();
|
|
755
759
|
if (imageRangesDigest) {
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
760
|
+
const supplied = receivedImageCompletion;
|
|
761
|
+
const completion = supplied && supplied.generation === generation && supplied.rangesDigest === imageRangesDigest
|
|
762
|
+
? supplied : undefined;
|
|
763
|
+
let ranges;
|
|
764
|
+
let receiptKey;
|
|
765
|
+
if (completion?.metadata) {
|
|
766
|
+
// This is the existing private owner/reader seed, not an image or
|
|
767
|
+
// guest-supplied claim. Current committed state binds its identity.
|
|
768
|
+
ranges = validateImageRanges(completion.metadata.ranges);
|
|
769
|
+
receiptKey = completion.metadata.receiptKey;
|
|
770
|
+
if (!/^[a-f0-9]{64}$/u.test(receiptKey))
|
|
771
|
+
throw new Error("The owner's immutable receipt key is invalid.");
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
const file = await (await directory.getFileHandle(IMAGE_RANGES_FILE)).getFile();
|
|
775
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
776
|
+
if (await indexDigest(bytes) !== imageRangesDigest)
|
|
777
|
+
throw new Error("The committed immutable image ranges have changed. Stored data was retained.");
|
|
778
|
+
ranges = validateImageRanges(JSON.parse(decoder.decode(bytes)));
|
|
779
|
+
// The ledger key retains ADR-0076's canonical validated-table hash,
|
|
780
|
+
// including for old files whose JSON whitespace differs.
|
|
781
|
+
receiptKey = await indexDigest(encoder.encode(JSON.stringify(ranges)));
|
|
782
|
+
}
|
|
761
783
|
if (ranges.bytes > packAccess.getSize())
|
|
762
784
|
throw new Error("The immutable image reservation is incomplete. Stored data was retained.");
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
785
|
+
// The owner's line serves only the generation and table its completion names.
|
|
786
|
+
const line = completion ? receivedImageLine : undefined;
|
|
787
|
+
if (!line)
|
|
788
|
+
receivedImageLine?.port.close();
|
|
789
|
+
receivedImageLine = undefined;
|
|
790
|
+
imageReader = new ImageRangeReader(ranges, directory, `${projectId}.${generation}`, completion?.completed, receiptKey, line);
|
|
791
|
+
await imageReader.initialize();
|
|
767
792
|
deferredImage = ranges.image;
|
|
768
793
|
}
|
|
769
794
|
}
|
|
@@ -1900,7 +1925,7 @@ export function createPackStore(environment, options = {}) {
|
|
|
1900
1925
|
if (!shared)
|
|
1901
1926
|
await discardGenerations(name);
|
|
1902
1927
|
}
|
|
1903
|
-
async function installPreparedImage(prepared, json, binary, suppliedRanges) {
|
|
1928
|
+
async function installPreparedImage(prepared, json, binary, suppliedRanges, verified = false) {
|
|
1904
1929
|
assertWritable();
|
|
1905
1930
|
if (!shared || projectId !== PACKAGES_STORE)
|
|
1906
1931
|
throw new Error("Only the shared package store can reserve immutable image extents.");
|
|
@@ -1911,7 +1936,7 @@ export function createPackStore(environment, options = {}) {
|
|
|
1911
1936
|
const ranges = validateImageRanges(suppliedRanges);
|
|
1912
1937
|
if (ranges.bytes !== prepared.packBytes || prepared.entries > MAX_ENTRIES || prepared.liveBytes > prepared.packBytes
|
|
1913
1938
|
|| json.byteLength !== prepared.json.raw || binary.byteLength !== prepared.binary.raw
|
|
1914
|
-
|| await
|
|
1939
|
+
|| !await matchesDigest(json, prepared.json.sha256, verified) || !await matchesDigest(binary, prepared.binary.sha256, verified)) {
|
|
1915
1940
|
throw new Error("The image's prepared metadata does not match its document.");
|
|
1916
1941
|
}
|
|
1917
1942
|
// The pinned host emits the authoritative JSON checkpoint and its matching
|
|
@@ -1923,7 +1948,8 @@ export function createPackStore(environment, options = {}) {
|
|
|
1923
1948
|
const retained = JSON.parse(`${decoder.decode(json.subarray(0, newline - 1))}}`);
|
|
1924
1949
|
const measures = { packBytes: prepared.packBytes, liveBytes: prepared.liveBytes, linked: 0, linkedBytes: 0 };
|
|
1925
1950
|
const header = retained.retainedIndex;
|
|
1926
|
-
|
|
1951
|
+
// The binary's digest is the descriptor's, now matched: it is not hashed a second time.
|
|
1952
|
+
if (header.version !== 1 || header.digest !== await retainedIndexDigestOf(prepared.binary.sha256, measures)
|
|
1927
1953
|
|| header.packBytes !== measures.packBytes || header.liveBytes !== measures.liveBytes || header.linked !== 0 || header.linkedBytes !== 0) {
|
|
1928
1954
|
throw new Error("The prepared index's retained measures are inconsistent.");
|
|
1929
1955
|
}
|
|
@@ -1951,14 +1977,14 @@ export function createPackStore(environment, options = {}) {
|
|
|
1951
1977
|
return true;
|
|
1952
1978
|
});
|
|
1953
1979
|
}
|
|
1954
|
-
async function installPreparedProject(prepared, json, binary) {
|
|
1980
|
+
async function installPreparedProject(prepared, json, binary, verified = false, imageInstalled) {
|
|
1955
1981
|
assertWritable();
|
|
1956
|
-
if (shared || !options.packages
|
|
1982
|
+
if (shared || !options.packages || (!imageInstalled && !options.packages.deferredImage))
|
|
1957
1983
|
throw new Error("Prepared project paths require a verified shared image.");
|
|
1958
1984
|
if (packBytes !== 0 || [...entries.values()].some(entry => entry.type !== "directory"))
|
|
1959
1985
|
return false;
|
|
1960
1986
|
if (json.byteLength !== prepared.json.raw || binary.byteLength !== prepared.binary.raw
|
|
1961
|
-
|| await
|
|
1987
|
+
|| !await matchesDigest(json, prepared.json.sha256, verified) || !await matchesDigest(binary, prepared.binary.sha256, verified)) {
|
|
1962
1988
|
throw new Error("The image's prepared project does not match its descriptor.");
|
|
1963
1989
|
}
|
|
1964
1990
|
const newline = json.subarray(0, 4096).indexOf(10);
|
|
@@ -1966,7 +1992,7 @@ export function createPackStore(environment, options = {}) {
|
|
|
1966
1992
|
throw new Error("The prepared project has no bounded retained header.");
|
|
1967
1993
|
const header = JSON.parse(`${decoder.decode(json.subarray(0, newline - 1))}}`).retainedIndex;
|
|
1968
1994
|
const measures = { packBytes: 0, liveBytes: 0, linked: prepared.linked, linkedBytes: prepared.linkedBytes };
|
|
1969
|
-
if (header.version !== 1 || header.digest !== await
|
|
1995
|
+
if (header.version !== 1 || header.digest !== await retainedIndexDigestOf(prepared.binary.sha256, measures)
|
|
1970
1996
|
|| header.packBytes !== 0 || header.liveBytes !== 0 || header.linked !== prepared.linked || header.linkedBytes !== prepared.linkedBytes) {
|
|
1971
1997
|
throw new Error("The prepared project's retained measures are inconsistent.");
|
|
1972
1998
|
}
|
|
@@ -1988,6 +2014,12 @@ export function createPackStore(environment, options = {}) {
|
|
|
1988
2014
|
}
|
|
1989
2015
|
await writeBytesAtomically(directory, RETAINED_INDEX_FILE, binary);
|
|
1990
2016
|
await writeBytesAtomically(directory, INDEX_FILE, json);
|
|
2017
|
+
// Written beside the shared image's install; committed only over it. A
|
|
2018
|
+
// generation left uncommitted is swept with any other on the next open.
|
|
2019
|
+
if (imageInstalled && !await imageInstalled)
|
|
2020
|
+
return false;
|
|
2021
|
+
if (!options.packages?.deferredImage)
|
|
2022
|
+
throw new Error("Prepared project paths require a verified shared image.");
|
|
1991
2023
|
await commitGeneration(name, directory, new Map(), 0, { index, ...measures });
|
|
1992
2024
|
return true;
|
|
1993
2025
|
});
|
|
@@ -2061,6 +2093,18 @@ export function createPackStore(environment, options = {}) {
|
|
|
2061
2093
|
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
2062
2094
|
return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2063
2095
|
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Whether these bytes are the ones a digest names: hashed here, unless they
|
|
2098
|
+
* were already checked against this same digest where they were inflated.
|
|
2099
|
+
*/
|
|
2100
|
+
async function matchesDigest(bytes, digest, verified) {
|
|
2101
|
+
return verified || await indexDigest(bytes) === digest;
|
|
2102
|
+
}
|
|
2103
|
+
/** The retained index digest from the binary index's own SHA-256, when that is already known. */
|
|
2104
|
+
function retainedIndexDigestOf(binaryDigest, measures) {
|
|
2105
|
+
const { packBytes, liveBytes, linked, linkedBytes, journalPrefix } = measures;
|
|
2106
|
+
return indexDigest(encoder.encode(`${JSON.stringify({ packBytes, liveBytes, linked, linkedBytes, ...(journalPrefix ? { journalPrefix } : {}) })}:${binaryDigest}`));
|
|
2107
|
+
}
|
|
2064
2108
|
async function retainedIndexDigest(bytes, measures) {
|
|
2065
2109
|
const { packBytes, liveBytes, linked, linkedBytes, journalPrefix } = measures;
|
|
2066
2110
|
// Bind the byte measures too: none may be trusted independently of the
|
|
@@ -2651,6 +2695,14 @@ export function createPackStore(environment, options = {}) {
|
|
|
2651
2695
|
clock,
|
|
2652
2696
|
readerSeed,
|
|
2653
2697
|
imageCompletionSeed,
|
|
2698
|
+
imageLine: () => imageReader?.lineForReader() ?? options.packages?.imageLine(),
|
|
2699
|
+
imageStartupSet: () => imageReader?.readDigests() ?? imageReader?.completedDigests() ?? [],
|
|
2700
|
+
prefetchImage: (digests, streams) => imageReader?.prefetch(digests, streams) ?? { queued: 0, done: Promise.resolve() },
|
|
2701
|
+
imageBlockedReads: () => {
|
|
2702
|
+
const own = imageReader?.blockedReads ?? { ranges: 0, ms: 0 };
|
|
2703
|
+
const linked = options.packages?.imageBlockedReads() ?? { ranges: 0, ms: 0 };
|
|
2704
|
+
return { ranges: own.ranges + linked.ranges, ms: own.ms + linked.ms };
|
|
2705
|
+
},
|
|
2654
2706
|
seeded: () => startedFromSeed,
|
|
2655
2707
|
get logBytes() { return logBytes; },
|
|
2656
2708
|
checkpoint,
|
|
@@ -2682,10 +2734,10 @@ export function createPackStore(environment, options = {}) {
|
|
|
2682
2734
|
* the origin's packages it links, both open for reading beside their
|
|
2683
2735
|
* owners. Undefined when the project has no store yet.
|
|
2684
2736
|
*/
|
|
2685
|
-
export async function openPackReader(environment, projectId, seed) {
|
|
2737
|
+
export async function openPackReader(environment, projectId, seed, imageLine) {
|
|
2686
2738
|
const packages = createPackStore(environment);
|
|
2687
2739
|
await packages.initialize(PACKAGES_STORE, { readOnly: true, extentsOnly: true,
|
|
2688
|
-
...(seed?.packagesImage ? { imageCompletion: seed.packagesImage } : {}) });
|
|
2740
|
+
...(seed?.packagesImage ? { imageCompletion: seed.packagesImage, ...(imageLine ? { imageLine } : {}) } : {}) });
|
|
2689
2741
|
const project = createPackStore(environment, { packages, ownsPackages: true });
|
|
2690
2742
|
let opened;
|
|
2691
2743
|
try {
|