@variance-authority/core 0.9.0 → 0.10.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 +17 -0
- package/README.md +46 -20
- package/dist/attribute/boundary.d.ts +21 -14
- package/dist/attribute/boundary.js +23 -29
- package/dist/attribute/boundary.js.map +1 -1
- package/dist/attribute/component-hash.d.ts +21 -0
- package/dist/attribute/component-hash.js +48 -3
- package/dist/attribute/component-hash.js.map +1 -1
- package/dist/attribute/index.d.ts +3 -3
- package/dist/attribute/index.js +1 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/lexicon.d.ts +37 -0
- package/dist/attribute/lexicon.js +40 -12
- package/dist/attribute/lexicon.js.map +1 -1
- package/dist/attribute/rename.d.ts +18 -0
- package/dist/attribute/rename.js +24 -0
- package/dist/attribute/rename.js.map +1 -0
- package/dist/compare/cascade.d.ts +2 -0
- package/dist/compare/cascade.js +5 -1
- package/dist/compare/cascade.js.map +1 -1
- package/dist/format/hash.d.ts +20 -0
- package/dist/format/hash.js +27 -1
- package/dist/format/hash.js.map +1 -1
- package/dist/format/index.d.ts +1 -1
- package/dist/format/index.js +1 -1
- package/dist/format/index.js.map +1 -1
- package/dist/format/snapshot.d.ts +15 -0
- package/dist/format/snapshot.js.map +1 -1
- package/dist/share/http-line.d.ts +30 -0
- package/dist/share/http-line.js +164 -0
- package/dist/share/http-line.js.map +1 -0
- package/dist/share/index.d.ts +9 -126
- package/dist/share/index.js +9 -168
- package/dist/share/index.js.map +1 -1
- package/dist/share/line.d.ts +153 -0
- package/dist/share/line.js +184 -0
- package/dist/share/line.js.map +1 -0
- package/dist/share/publish.d.ts +79 -0
- package/dist/share/publish.js +231 -0
- package/dist/share/publish.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What may replace what on a line, and what a reader is told.
|
|
3
|
+
*
|
|
4
|
+
* Decided here, once, over the {@link LineCell} a backend provides, so a
|
|
5
|
+
* directory, a bucket and a git ref hold a line by the same rules.
|
|
6
|
+
*/
|
|
7
|
+
import { type HeldEntry, type LineCell, type LineManifest, type ShareEntry, type ShareLine, type ShareMiss } from './line.js';
|
|
8
|
+
/**
|
|
9
|
+
* Whether `descendant` strictly descends from `ancestor`, or `undefined` when
|
|
10
|
+
* the history needed to say is not at hand. Git owns this answer; the caller
|
|
11
|
+
* asks it.
|
|
12
|
+
*/
|
|
13
|
+
export type Descends = (descendant: string, ancestor: string) => Promise<boolean | undefined>;
|
|
14
|
+
/** What a publish did, entry by entry. */
|
|
15
|
+
export interface Published {
|
|
16
|
+
/** Entries this publish wrote. */
|
|
17
|
+
readonly written: readonly string[];
|
|
18
|
+
/** Entries the line kept instead, and why. */
|
|
19
|
+
readonly kept: readonly {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly commit: string;
|
|
22
|
+
readonly because: 'newer-commit' | 'newer-format';
|
|
23
|
+
}[];
|
|
24
|
+
/**
|
|
25
|
+
* Entries written although nobody could say whether the held one was newer.
|
|
26
|
+
* Replacing is the answer then, because a line that refuses when it cannot
|
|
27
|
+
* tell is a line one force-push jams for good.
|
|
28
|
+
*/
|
|
29
|
+
readonly unanswered: readonly string[];
|
|
30
|
+
/** How many times the manifest was read before a write held. */
|
|
31
|
+
readonly attempts: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PublishOptions {
|
|
34
|
+
readonly descends: Descends;
|
|
35
|
+
/** The bytes of an image the line does not hold yet, by digest. */
|
|
36
|
+
readonly image: (digest: string) => Promise<Uint8Array>;
|
|
37
|
+
/** Reads of the manifest before giving up on a line other writers keep moving. Defaults to 8. */
|
|
38
|
+
readonly attempts?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Offer entries to a line.
|
|
42
|
+
*
|
|
43
|
+
* Each entry replaces the held entry in its slot — same family, same qualifier,
|
|
44
|
+
* any version — except on a mainline, where the held one is kept when its
|
|
45
|
+
* commit strictly descends from the offered one: a slow run of an older commit
|
|
46
|
+
* finishing last. A held entry in a newer format is kept on any line, so an old
|
|
47
|
+
* CLI cannot take a line back a version. Every other held entry is left as it
|
|
48
|
+
* is, which is how suites published by different jobs land on one line.
|
|
49
|
+
*
|
|
50
|
+
* A write that loses to another writer reads the line again and decides again.
|
|
51
|
+
*/
|
|
52
|
+
export declare function publishLine(cell: LineCell, line: ShareLine, entries: readonly ShareEntry[], options: PublishOptions): Promise<Published | ShareMiss>;
|
|
53
|
+
/** A line as a reader holds it: the manifest, and its blobs on demand. */
|
|
54
|
+
export interface HeldLine {
|
|
55
|
+
readonly line: ShareLine;
|
|
56
|
+
readonly manifest: LineManifest;
|
|
57
|
+
/** The bytes of one entry, checked against the digest the manifest names. */
|
|
58
|
+
entry(entry: HeldEntry): Promise<Uint8Array | ShareMiss>;
|
|
59
|
+
image(digest: string): Promise<Uint8Array | ShareMiss>;
|
|
60
|
+
}
|
|
61
|
+
/** Read what a line holds, or why it cannot be read. */
|
|
62
|
+
export declare function readLine(cell: LineCell, line: ShareLine): Promise<HeldLine | ShareMiss>;
|
|
63
|
+
/**
|
|
64
|
+
* The held entry a reader asked for by name, or why there is none.
|
|
65
|
+
*
|
|
66
|
+
* A reader of `suite-v1/web` that finds `suite-v2/web` is told `newer`, not
|
|
67
|
+
* `absent`: the line has what it wants, in a format it has to upgrade to read.
|
|
68
|
+
*/
|
|
69
|
+
export declare function findEntry(manifest: LineManifest, name: string): HeldEntry | ShareMiss;
|
|
70
|
+
/**
|
|
71
|
+
* A line cell in this process, for tests and for a run that shares with itself.
|
|
72
|
+
*
|
|
73
|
+
* Holds real versions, so a test can stage a writer that moves the line between
|
|
74
|
+
* another writer's read and its write.
|
|
75
|
+
*/
|
|
76
|
+
export declare function memoryLineCell(): LineCell & {
|
|
77
|
+
readonly held: Map<string, Uint8Array>;
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What may replace what on a line, and what a reader is told.
|
|
3
|
+
*
|
|
4
|
+
* Decided here, once, over the {@link LineCell} a backend provides, so a
|
|
5
|
+
* directory, a bucket and a git ref hold a line by the same rules.
|
|
6
|
+
*/
|
|
7
|
+
import { decodeManifest, encodeManifest, entryDigest, entryProblem, parseEntryName, sameSlot, } from './line.js';
|
|
8
|
+
/**
|
|
9
|
+
* Offer entries to a line.
|
|
10
|
+
*
|
|
11
|
+
* Each entry replaces the held entry in its slot — same family, same qualifier,
|
|
12
|
+
* any version — except on a mainline, where the held one is kept when its
|
|
13
|
+
* commit strictly descends from the offered one: a slow run of an older commit
|
|
14
|
+
* finishing last. A held entry in a newer format is kept on any line, so an old
|
|
15
|
+
* CLI cannot take a line back a version. Every other held entry is left as it
|
|
16
|
+
* is, which is how suites published by different jobs land on one line.
|
|
17
|
+
*
|
|
18
|
+
* A write that loses to another writer reads the line again and decides again.
|
|
19
|
+
*/
|
|
20
|
+
export async function publishLine(cell, line, entries, options) {
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const problem = entryProblem(entry);
|
|
23
|
+
if (problem !== undefined)
|
|
24
|
+
throw new Error(`cannot publish ${problem}`);
|
|
25
|
+
}
|
|
26
|
+
const limit = options.attempts ?? 8;
|
|
27
|
+
for (let attempt = 1; attempt <= limit; attempt += 1) {
|
|
28
|
+
const loaded = await cell.load(line);
|
|
29
|
+
let held;
|
|
30
|
+
let expected;
|
|
31
|
+
if ('manifest' in loaded) {
|
|
32
|
+
const decoded = decodeManifest(loaded.manifest);
|
|
33
|
+
// A line a newer writer owns is not this writer's to replace; one that is
|
|
34
|
+
// unreadable is, since nothing can read it anyway.
|
|
35
|
+
if ('kind' in decoded) {
|
|
36
|
+
if (decoded.kind === 'newer')
|
|
37
|
+
return decoded;
|
|
38
|
+
held = { format: 1, entries: [] };
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
held = decoded;
|
|
42
|
+
}
|
|
43
|
+
expected = loaded.version;
|
|
44
|
+
}
|
|
45
|
+
else if (loaded.kind === 'absent') {
|
|
46
|
+
held = { format: 1, entries: [] };
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return loaded;
|
|
50
|
+
}
|
|
51
|
+
const plan = await decide(line, held, entries, options.descends);
|
|
52
|
+
if (plan.write.length === 0) {
|
|
53
|
+
return { written: [], kept: plan.kept, unanswered: [], attempts: attempt };
|
|
54
|
+
}
|
|
55
|
+
const next = nextManifest(held, plan.write);
|
|
56
|
+
const blobs = await blobsFor(held, next, plan.write, options.image);
|
|
57
|
+
const outcome = await cell.store(line, {
|
|
58
|
+
manifest: encodeManifest(next),
|
|
59
|
+
blobs,
|
|
60
|
+
...(expected !== undefined ? { expected } : {}),
|
|
61
|
+
});
|
|
62
|
+
if (outcome === 'written') {
|
|
63
|
+
return {
|
|
64
|
+
written: plan.write.map((entry) => entry.name),
|
|
65
|
+
kept: plan.kept,
|
|
66
|
+
unanswered: plan.unanswered,
|
|
67
|
+
attempts: attempt,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (outcome !== 'conflict')
|
|
71
|
+
return outcome;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
kind: 'unreachable',
|
|
75
|
+
detail: `the line moved under ${String(limit)} writes in a row; nothing was published`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function decide(line, held, entries, descends) {
|
|
79
|
+
const write = [];
|
|
80
|
+
const kept = [];
|
|
81
|
+
const unanswered = [];
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const offered = parseEntryName(entry.name);
|
|
84
|
+
let occupant;
|
|
85
|
+
let version = 0;
|
|
86
|
+
for (const candidate of held.entries) {
|
|
87
|
+
const slot = parseEntryName(candidate.name);
|
|
88
|
+
if (sameSlot(slot, offered) && slot.version > version) {
|
|
89
|
+
occupant = candidate;
|
|
90
|
+
version = slot.version;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (occupant === undefined) {
|
|
94
|
+
write.push(entry);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (version > offered.version) {
|
|
98
|
+
kept.push({ name: occupant.name, commit: occupant.commit, because: 'newer-format' });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// TODO: shards of one run publish at one commit, so each replaces the
|
|
102
|
+
// last one's `suite-index-v1` and `report-v1`, and the line keeps the index
|
|
103
|
+
// and report of whichever shard published last, covering its subjects only.
|
|
104
|
+
// Nothing here merges two entries of one slot.
|
|
105
|
+
if (line.kind === 'branch' || occupant.commit === entry.commit) {
|
|
106
|
+
write.push(entry);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const newer = await descends(occupant.commit, entry.commit);
|
|
110
|
+
if (newer === true) {
|
|
111
|
+
kept.push({ name: occupant.name, commit: occupant.commit, because: 'newer-commit' });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (newer === undefined)
|
|
115
|
+
unanswered.push(entry.name);
|
|
116
|
+
write.push(entry);
|
|
117
|
+
}
|
|
118
|
+
return { write, kept, unanswered };
|
|
119
|
+
}
|
|
120
|
+
function nextManifest(held, write) {
|
|
121
|
+
const replaced = write.map((entry) => parseEntryName(entry.name));
|
|
122
|
+
const survivors = held.entries.filter((entry) => !replaced.some((slot) => sameSlot(slot, parseEntryName(entry.name))));
|
|
123
|
+
const added = write.map((entry) => ({
|
|
124
|
+
name: entry.name,
|
|
125
|
+
commit: entry.commit,
|
|
126
|
+
...(entry.head !== undefined ? { head: entry.head } : {}),
|
|
127
|
+
...(entry.images !== undefined ? { images: [...new Set(entry.images)] } : {}),
|
|
128
|
+
digest: entryDigest(entry.bytes),
|
|
129
|
+
size: entry.bytes.length,
|
|
130
|
+
}));
|
|
131
|
+
return { format: 1, entries: [...survivors, ...added] };
|
|
132
|
+
}
|
|
133
|
+
/** Only what the held line lacks: entry bytes are new by construction, images often are not. */
|
|
134
|
+
async function blobsFor(held, next, write, image) {
|
|
135
|
+
const blobs = new Map();
|
|
136
|
+
const heldDigests = new Set(held.entries.map((entry) => entry.digest));
|
|
137
|
+
for (const entry of write) {
|
|
138
|
+
const digest = entryDigest(entry.bytes);
|
|
139
|
+
if (!heldDigests.has(digest))
|
|
140
|
+
blobs.set(`entries/${digest}`, entry.bytes);
|
|
141
|
+
}
|
|
142
|
+
const heldImages = new Set(held.entries.flatMap((entry) => entry.images ?? []));
|
|
143
|
+
for (const digest of new Set(next.entries.flatMap((entry) => entry.images ?? []))) {
|
|
144
|
+
if (!heldImages.has(digest))
|
|
145
|
+
blobs.set(`images/${digest}`, await image(digest));
|
|
146
|
+
}
|
|
147
|
+
return blobs;
|
|
148
|
+
}
|
|
149
|
+
/** Read what a line holds, or why it cannot be read. */
|
|
150
|
+
export async function readLine(cell, line) {
|
|
151
|
+
const loaded = await cell.load(line);
|
|
152
|
+
if (!('manifest' in loaded))
|
|
153
|
+
return loaded;
|
|
154
|
+
const manifest = decodeManifest(loaded.manifest);
|
|
155
|
+
if ('kind' in manifest)
|
|
156
|
+
return manifest;
|
|
157
|
+
return {
|
|
158
|
+
line,
|
|
159
|
+
manifest,
|
|
160
|
+
async entry(entry) {
|
|
161
|
+
const bytes = await cell.blob(line, `entries/${entry.digest}`);
|
|
162
|
+
if (!(bytes instanceof Uint8Array))
|
|
163
|
+
return bytes;
|
|
164
|
+
if (entryDigest(bytes) !== entry.digest) {
|
|
165
|
+
return { kind: 'unreadable', detail: `${entry.name}: the bytes do not match the manifest's digest` };
|
|
166
|
+
}
|
|
167
|
+
return bytes;
|
|
168
|
+
},
|
|
169
|
+
image(digest) {
|
|
170
|
+
return cell.blob(line, `images/${digest}`);
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The held entry a reader asked for by name, or why there is none.
|
|
176
|
+
*
|
|
177
|
+
* A reader of `suite-v1/web` that finds `suite-v2/web` is told `newer`, not
|
|
178
|
+
* `absent`: the line has what it wants, in a format it has to upgrade to read.
|
|
179
|
+
*/
|
|
180
|
+
export function findEntry(manifest, name) {
|
|
181
|
+
const wanted = parseEntryName(name);
|
|
182
|
+
if (wanted === undefined)
|
|
183
|
+
throw new Error(`"${name}" is not <family>-v<version>[/<qualifier>]`);
|
|
184
|
+
const exact = manifest.entries.find((entry) => entry.name === name);
|
|
185
|
+
if (exact !== undefined)
|
|
186
|
+
return exact;
|
|
187
|
+
const newer = manifest.entries
|
|
188
|
+
.filter((entry) => {
|
|
189
|
+
const held = parseEntryName(entry.name);
|
|
190
|
+
return sameSlot(held, wanted) && held.version > wanted.version;
|
|
191
|
+
})
|
|
192
|
+
.map((entry) => entry.name);
|
|
193
|
+
return newer.length > 0 ? { kind: 'newer', names: newer } : { kind: 'absent' };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* A line cell in this process, for tests and for a run that shares with itself.
|
|
197
|
+
*
|
|
198
|
+
* Holds real versions, so a test can stage a writer that moves the line between
|
|
199
|
+
* another writer's read and its write.
|
|
200
|
+
*/
|
|
201
|
+
export function memoryLineCell() {
|
|
202
|
+
const held = new Map();
|
|
203
|
+
const versions = new Map();
|
|
204
|
+
const key = (line) => `${line.kind}\u0000${line.name}`;
|
|
205
|
+
return {
|
|
206
|
+
held,
|
|
207
|
+
async load(line) {
|
|
208
|
+
const manifest = held.get(`${key(line)}\u0000manifest`);
|
|
209
|
+
if (manifest === undefined)
|
|
210
|
+
return { kind: 'absent' };
|
|
211
|
+
return { manifest, version: String(versions.get(key(line)) ?? 0) };
|
|
212
|
+
},
|
|
213
|
+
async blob(line, path) {
|
|
214
|
+
const bytes = held.get(path.startsWith('images/') ? path : `${key(line)}\u0000${path}`);
|
|
215
|
+
return bytes === undefined ? { kind: 'absent' } : bytes.slice();
|
|
216
|
+
},
|
|
217
|
+
async store(line, write) {
|
|
218
|
+
const current = versions.get(key(line));
|
|
219
|
+
const now = current === undefined ? undefined : String(current);
|
|
220
|
+
if (now !== write.expected)
|
|
221
|
+
return 'conflict';
|
|
222
|
+
for (const [path, bytes] of write.blobs) {
|
|
223
|
+
held.set(path.startsWith('images/') ? path : `${key(line)}\u0000${path}`, bytes.slice());
|
|
224
|
+
}
|
|
225
|
+
held.set(`${key(line)}\u0000manifest`, write.manifest.slice());
|
|
226
|
+
versions.set(key(line), (current ?? 0) + 1);
|
|
227
|
+
return 'written';
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/share/publish.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,cAAc,EACd,cAAc,EACd,WAAW,EACX,YAAY,EACZ,cAAc,EACd,QAAQ,GAQT,MAAM,WAAW,CAAC;AAiCnB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAc,EACd,IAAe,EACf,OAA8B,EAC9B,OAAuB;IAEvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;IACpC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,IAAkB,CAAC;QACvB,IAAI,QAA4B,CAAC;QACjC,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,0EAA0E;YAC1E,mDAAmD;YACnD,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;gBACtB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;oBAAE,OAAO,OAAO,CAAC;gBAC7C,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,OAAO,CAAC;YACjB,CAAC;YACD,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;QAC5B,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpC,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAC7E,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACrC,QAAQ,EAAE,cAAc,CAAC,IAAI,CAAC;YAC9B,KAAK;YACL,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAC;QACH,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC9C,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,QAAQ,EAAE,OAAO;aAClB,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,KAAK,UAAU;YAAE,OAAO,OAAO,CAAC;IAC7C,CAAC;IACD,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,wBAAwB,MAAM,CAAC,KAAK,CAAC,yCAAyC;KACvF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,MAAM,CACnB,IAAe,EACf,IAAkB,EAClB,OAA8B,EAC9B,QAAkB;IAElB,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAgC,EAAE,CAAC;IAC7C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC5C,IAAI,QAA+B,CAAC;QACpC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,cAAc,CAAC,SAAS,CAAC,IAAI,CAAE,CAAC;YAC7C,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC;gBACtD,QAAQ,GAAG,SAAS,CAAC;gBACrB,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACzB,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,SAAS;QACX,CAAC;QACD,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YACrF,SAAS;QACX,CAAC;QACD,sEAAsE;QACtE,4EAA4E;QAC5E,4EAA4E;QAC5E,+CAA+C;QAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YACrF,SAAS;QACX,CAAC;QACD,IAAI,KAAK,KAAK,SAAS;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,YAAY,CAAC,IAAkB,EAAE,KAA4B;IACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CACnC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC,CAAC,CACjF,CAAC;IACF,MAAM,KAAK,GAAgB,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;QAChC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;KACzB,CAAC,CAAC,CAAC;IACJ,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,SAAS,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,gGAAgG;AAChG,KAAK,UAAU,QAAQ,CACrB,IAAkB,EAClB,IAAkB,EAClB,KAA4B,EAC5B,KAA8C;IAE9C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,WAAW,MAAM,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;IAChF,KAAK,MAAM,MAAM,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAClF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,UAAU,MAAM,EAAE,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAWD,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAc,EAAE,IAAe;IAC5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,UAAU,IAAI,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3C,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACxC,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,KAAK,CAAC,KAAK,CAAC,KAAK;YACf,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC;gBAAE,OAAO,KAAK,CAAC;YACjD,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;gBACxC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,gDAAgD,EAAE,CAAC;YACvG,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,CAAC,MAAM;YACV,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,QAAsB,EAAE,IAAY;IAC5D,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,4CAA4C,CAAC,CAAC;IAChG,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACpE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;SAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QACzC,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IACjE,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACjF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,MAAM,GAAG,GAAG,CAAC,IAAe,EAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1E,OAAO;QACL,IAAI;QACJ,KAAK,CAAC,IAAI,CAAC,IAAI;YACb,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;YACtD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACrE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI;YACnB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACxF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAClE,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK;YACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,GAAG,KAAK,KAAK,CAAC,QAAQ;gBAAE,OAAO,UAAU,CAAC;YAC9C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBACxC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC3F,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YAC/D,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5C,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * What may replace what on a line, and what a reader is told.\n *\n * Decided here, once, over the {@link LineCell} a backend provides, so a\n * directory, a bucket and a git ref hold a line by the same rules.\n */\n\nimport {\n decodeManifest,\n encodeManifest,\n entryDigest,\n entryProblem,\n parseEntryName,\n sameSlot,\n type BlobPath,\n type HeldEntry,\n type LineCell,\n type LineManifest,\n type ShareEntry,\n type ShareLine,\n type ShareMiss,\n} from './line.js';\n\n/**\n * Whether `descendant` strictly descends from `ancestor`, or `undefined` when\n * the history needed to say is not at hand. Git owns this answer; the caller\n * asks it.\n */\nexport type Descends = (descendant: string, ancestor: string) => Promise<boolean | undefined>;\n\n/** What a publish did, entry by entry. */\nexport interface Published {\n /** Entries this publish wrote. */\n readonly written: readonly string[];\n /** Entries the line kept instead, and why. */\n readonly kept: readonly { readonly name: string; readonly commit: string; readonly because: 'newer-commit' | 'newer-format' }[];\n /**\n * Entries written although nobody could say whether the held one was newer.\n * Replacing is the answer then, because a line that refuses when it cannot\n * tell is a line one force-push jams for good.\n */\n readonly unanswered: readonly string[];\n /** How many times the manifest was read before a write held. */\n readonly attempts: number;\n}\n\nexport interface PublishOptions {\n readonly descends: Descends;\n /** The bytes of an image the line does not hold yet, by digest. */\n readonly image: (digest: string) => Promise<Uint8Array>;\n /** Reads of the manifest before giving up on a line other writers keep moving. Defaults to 8. */\n readonly attempts?: number;\n}\n\n/**\n * Offer entries to a line.\n *\n * Each entry replaces the held entry in its slot — same family, same qualifier,\n * any version — except on a mainline, where the held one is kept when its\n * commit strictly descends from the offered one: a slow run of an older commit\n * finishing last. A held entry in a newer format is kept on any line, so an old\n * CLI cannot take a line back a version. Every other held entry is left as it\n * is, which is how suites published by different jobs land on one line.\n *\n * A write that loses to another writer reads the line again and decides again.\n */\nexport async function publishLine(\n cell: LineCell,\n line: ShareLine,\n entries: readonly ShareEntry[],\n options: PublishOptions,\n): Promise<Published | ShareMiss> {\n for (const entry of entries) {\n const problem = entryProblem(entry);\n if (problem !== undefined) throw new Error(`cannot publish ${problem}`);\n }\n\n const limit = options.attempts ?? 8;\n for (let attempt = 1; attempt <= limit; attempt += 1) {\n const loaded = await cell.load(line);\n let held: LineManifest;\n let expected: string | undefined;\n if ('manifest' in loaded) {\n const decoded = decodeManifest(loaded.manifest);\n // A line a newer writer owns is not this writer's to replace; one that is\n // unreadable is, since nothing can read it anyway.\n if ('kind' in decoded) {\n if (decoded.kind === 'newer') return decoded;\n held = { format: 1, entries: [] };\n } else {\n held = decoded;\n }\n expected = loaded.version;\n } else if (loaded.kind === 'absent') {\n held = { format: 1, entries: [] };\n } else {\n return loaded;\n }\n\n const plan = await decide(line, held, entries, options.descends);\n if (plan.write.length === 0) {\n return { written: [], kept: plan.kept, unanswered: [], attempts: attempt };\n }\n\n const next = nextManifest(held, plan.write);\n const blobs = await blobsFor(held, next, plan.write, options.image);\n const outcome = await cell.store(line, {\n manifest: encodeManifest(next),\n blobs,\n ...(expected !== undefined ? { expected } : {}),\n });\n if (outcome === 'written') {\n return {\n written: plan.write.map((entry) => entry.name),\n kept: plan.kept,\n unanswered: plan.unanswered,\n attempts: attempt,\n };\n }\n if (outcome !== 'conflict') return outcome;\n }\n return {\n kind: 'unreachable',\n detail: `the line moved under ${String(limit)} writes in a row; nothing was published`,\n };\n}\n\nasync function decide(\n line: ShareLine,\n held: LineManifest,\n entries: readonly ShareEntry[],\n descends: Descends,\n): Promise<{ write: ShareEntry[]; kept: Published['kept'][number][]; unanswered: string[] }> {\n const write: ShareEntry[] = [];\n const kept: Published['kept'][number][] = [];\n const unanswered: string[] = [];\n for (const entry of entries) {\n const offered = parseEntryName(entry.name)!;\n let occupant: HeldEntry | undefined;\n let version = 0;\n for (const candidate of held.entries) {\n const slot = parseEntryName(candidate.name)!;\n if (sameSlot(slot, offered) && slot.version > version) {\n occupant = candidate;\n version = slot.version;\n }\n }\n if (occupant === undefined) {\n write.push(entry);\n continue;\n }\n if (version > offered.version) {\n kept.push({ name: occupant.name, commit: occupant.commit, because: 'newer-format' });\n continue;\n }\n // TODO: shards of one run publish at one commit, so each replaces the\n // last one's `suite-index-v1` and `report-v1`, and the line keeps the index\n // and report of whichever shard published last, covering its subjects only.\n // Nothing here merges two entries of one slot.\n if (line.kind === 'branch' || occupant.commit === entry.commit) {\n write.push(entry);\n continue;\n }\n const newer = await descends(occupant.commit, entry.commit);\n if (newer === true) {\n kept.push({ name: occupant.name, commit: occupant.commit, because: 'newer-commit' });\n continue;\n }\n if (newer === undefined) unanswered.push(entry.name);\n write.push(entry);\n }\n return { write, kept, unanswered };\n}\n\nfunction nextManifest(held: LineManifest, write: readonly ShareEntry[]): LineManifest {\n const replaced = write.map((entry) => parseEntryName(entry.name)!);\n const survivors = held.entries.filter(\n (entry) => !replaced.some((slot) => sameSlot(slot, parseEntryName(entry.name)!)),\n );\n const added: HeldEntry[] = write.map((entry) => ({\n name: entry.name,\n commit: entry.commit,\n ...(entry.head !== undefined ? { head: entry.head } : {}),\n ...(entry.images !== undefined ? { images: [...new Set(entry.images)] } : {}),\n digest: entryDigest(entry.bytes),\n size: entry.bytes.length,\n }));\n return { format: 1, entries: [...survivors, ...added] };\n}\n\n/** Only what the held line lacks: entry bytes are new by construction, images often are not. */\nasync function blobsFor(\n held: LineManifest,\n next: LineManifest,\n write: readonly ShareEntry[],\n image: (digest: string) => Promise<Uint8Array>,\n): Promise<Map<BlobPath, Uint8Array>> {\n const blobs = new Map<BlobPath, Uint8Array>();\n const heldDigests = new Set(held.entries.map((entry) => entry.digest));\n for (const entry of write) {\n const digest = entryDigest(entry.bytes);\n if (!heldDigests.has(digest)) blobs.set(`entries/${digest}`, entry.bytes);\n }\n const heldImages = new Set(held.entries.flatMap((entry) => entry.images ?? []));\n for (const digest of new Set(next.entries.flatMap((entry) => entry.images ?? []))) {\n if (!heldImages.has(digest)) blobs.set(`images/${digest}`, await image(digest));\n }\n return blobs;\n}\n\n/** A line as a reader holds it: the manifest, and its blobs on demand. */\nexport interface HeldLine {\n readonly line: ShareLine;\n readonly manifest: LineManifest;\n /** The bytes of one entry, checked against the digest the manifest names. */\n entry(entry: HeldEntry): Promise<Uint8Array | ShareMiss>;\n image(digest: string): Promise<Uint8Array | ShareMiss>;\n}\n\n/** Read what a line holds, or why it cannot be read. */\nexport async function readLine(cell: LineCell, line: ShareLine): Promise<HeldLine | ShareMiss> {\n const loaded = await cell.load(line);\n if (!('manifest' in loaded)) return loaded;\n const manifest = decodeManifest(loaded.manifest);\n if ('kind' in manifest) return manifest;\n return {\n line,\n manifest,\n async entry(entry) {\n const bytes = await cell.blob(line, `entries/${entry.digest}`);\n if (!(bytes instanceof Uint8Array)) return bytes;\n if (entryDigest(bytes) !== entry.digest) {\n return { kind: 'unreadable', detail: `${entry.name}: the bytes do not match the manifest's digest` };\n }\n return bytes;\n },\n image(digest) {\n return cell.blob(line, `images/${digest}`);\n },\n };\n}\n\n/**\n * The held entry a reader asked for by name, or why there is none.\n *\n * A reader of `suite-v1/web` that finds `suite-v2/web` is told `newer`, not\n * `absent`: the line has what it wants, in a format it has to upgrade to read.\n */\nexport function findEntry(manifest: LineManifest, name: string): HeldEntry | ShareMiss {\n const wanted = parseEntryName(name);\n if (wanted === undefined) throw new Error(`\"${name}\" is not <family>-v<version>[/<qualifier>]`);\n const exact = manifest.entries.find((entry) => entry.name === name);\n if (exact !== undefined) return exact;\n const newer = manifest.entries\n .filter((entry) => {\n const held = parseEntryName(entry.name)!;\n return sameSlot(held, wanted) && held.version > wanted.version;\n })\n .map((entry) => entry.name);\n return newer.length > 0 ? { kind: 'newer', names: newer } : { kind: 'absent' };\n}\n\n/**\n * A line cell in this process, for tests and for a run that shares with itself.\n *\n * Holds real versions, so a test can stage a writer that moves the line between\n * another writer's read and its write.\n */\nexport function memoryLineCell(): LineCell & { readonly held: Map<string, Uint8Array> } {\n const held = new Map<string, Uint8Array>();\n const versions = new Map<string, number>();\n const key = (line: ShareLine): string => `${line.kind}\\u0000${line.name}`;\n return {\n held,\n async load(line) {\n const manifest = held.get(`${key(line)}\\u0000manifest`);\n if (manifest === undefined) return { kind: 'absent' };\n return { manifest, version: String(versions.get(key(line)) ?? 0) };\n },\n async blob(line, path) {\n const bytes = held.get(path.startsWith('images/') ? path : `${key(line)}\\u0000${path}`);\n return bytes === undefined ? { kind: 'absent' } : bytes.slice();\n },\n async store(line, write) {\n const current = versions.get(key(line));\n const now = current === undefined ? undefined : String(current);\n if (now !== write.expected) return 'conflict';\n for (const [path, bytes] of write.blobs) {\n held.set(path.startsWith('images/') ? path : `${key(line)}\\u0000${path}`, bytes.slice());\n }\n held.set(`${key(line)}\\u0000manifest`, write.manifest.slice());\n versions.set(key(line), (current ?? 0) + 1);\n return 'written';\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "The Variance Authority format, rules, comparison, attribution and verdicts. Pure data in, pure data out, no DOM and no I/O.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"variance-authority",
|