@variance-authority/core 0.1.1 → 0.3.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 +245 -10
- package/README.md +279 -110
- package/dist/attribute/call-site.d.ts +20 -2
- package/dist/attribute/call-site.js +17 -20
- package/dist/attribute/call-site.js.map +1 -1
- package/dist/attribute/closure.d.ts +105 -0
- package/dist/attribute/closure.js +180 -0
- package/dist/attribute/closure.js.map +1 -0
- package/dist/attribute/composition.d.ts +5 -3
- package/dist/attribute/composition.js +67 -11
- package/dist/attribute/composition.js.map +1 -1
- package/dist/attribute/divergence.js +7 -1
- package/dist/attribute/divergence.js.map +1 -1
- package/dist/attribute/index.d.ts +7 -1
- package/dist/attribute/index.js +4 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/landmark.d.ts +97 -0
- package/dist/attribute/landmark.js +144 -0
- package/dist/attribute/landmark.js.map +1 -0
- package/dist/attribute/lexicon.d.ts +112 -0
- package/dist/attribute/lexicon.js +210 -0
- package/dist/attribute/lexicon.js.map +1 -0
- package/dist/attribute/source.d.ts +19 -2
- package/dist/attribute/source.js +12 -0
- package/dist/attribute/source.js.map +1 -1
- package/dist/attribute/stack.d.ts +62 -1
- package/dist/attribute/stack.js +116 -5
- package/dist/attribute/stack.js.map +1 -1
- package/dist/compare/derive.d.ts +17 -0
- package/dist/compare/derive.js.map +1 -1
- package/dist/compare/diff/index.d.ts +25 -1
- package/dist/compare/diff/index.js +8 -5
- package/dist/compare/diff/index.js.map +1 -1
- package/dist/compare/explain.d.ts +1 -1
- package/dist/compare/explain.js +15 -8
- package/dist/compare/explain.js.map +1 -1
- package/dist/compare/index.d.ts +2 -2
- package/dist/compare/index.js +1 -1
- package/dist/compare/index.js.map +1 -1
- package/dist/compare/observability.d.ts +25 -1
- package/dist/compare/observability.js +54 -0
- package/dist/compare/observability.js.map +1 -1
- package/dist/compare/parting.d.ts +24 -2
- package/dist/compare/parting.js +14 -9
- package/dist/compare/parting.js.map +1 -1
- package/dist/compare/slice.d.ts +15 -2
- package/dist/compare/slice.js +6 -2
- package/dist/compare/slice.js.map +1 -1
- package/dist/format/capture.d.ts +15 -0
- package/dist/format/capture.js +30 -1
- package/dist/format/capture.js.map +1 -1
- package/dist/format/document.d.ts +65 -5
- package/dist/format/document.js +34 -0
- package/dist/format/document.js.map +1 -1
- package/dist/format/hash.d.ts +26 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -1
- package/dist/format/index.d.ts +4 -3
- package/dist/format/index.js +3 -2
- package/dist/format/index.js.map +1 -1
- package/dist/format/sha256.d.ts +10 -2
- package/dist/format/sha256.js +42 -9
- package/dist/format/sha256.js.map +1 -1
- package/dist/format/stabilize.d.ts +15 -0
- package/dist/format/stabilize.js +22 -1
- package/dist/format/stabilize.js.map +1 -1
- package/dist/index.d.ts +3 -11
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/judge/inspect.js +57 -4
- package/dist/judge/inspect.js.map +1 -1
- package/dist/relate/before.d.ts +137 -0
- package/dist/relate/before.js +155 -0
- package/dist/relate/before.js.map +1 -0
- package/dist/relate/graph.d.ts +53 -16
- package/dist/relate/graph.js +33 -15
- package/dist/relate/graph.js.map +1 -1
- package/dist/relate/index.d.ts +3 -2
- package/dist/relate/index.js +2 -1
- package/dist/relate/index.js.map +1 -1
- package/dist/relate/merkle.d.ts +14 -5
- package/dist/relate/merkle.js +14 -6
- package/dist/relate/merkle.js.map +1 -1
- package/dist/relate/reach.d.ts +19 -5
- package/dist/relate/reach.js +10 -8
- package/dist/relate/reach.js.map +1 -1
- package/dist/relate/records.d.ts +95 -4
- package/dist/relate/records.js +93 -13
- package/dist/relate/records.js.map +1 -1
- package/dist/segment/index.d.ts +123 -0
- package/dist/segment/index.js +265 -0
- package/dist/segment/index.js.map +1 -0
- package/dist/share/index.d.ts +148 -0
- package/dist/share/index.js +190 -0
- package/dist/share/index.js.map +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/segment` — a column of facts, as bytes.
|
|
3
|
+
*
|
|
4
|
+
* Two things in this repository already had to be written down and read back on
|
|
5
|
+
* another machine: the source index a scan reuses, and now the suite index a run
|
|
6
|
+
* leaves behind. Both are the same shape underneath — a pile of repeated strings
|
|
7
|
+
* and a pile of small integers — and both were going to grow the same four
|
|
8
|
+
* hundred lines of offset arithmetic independently.
|
|
9
|
+
*
|
|
10
|
+
* So the arithmetic is here and the schemas are not. This module knows about
|
|
11
|
+
* named sections, alignment, interned strings and the validation that a decode
|
|
12
|
+
* must perform before it believes a file. It knows nothing about what any
|
|
13
|
+
* section means, which is the property that lets one format change without
|
|
14
|
+
* touching the other.
|
|
15
|
+
*
|
|
16
|
+
* ## Why columns
|
|
17
|
+
*
|
|
18
|
+
* The reader that matters next is not this one. Every field is a run of
|
|
19
|
+
* fixed-width little-endian values at a computable offset, so a reader in
|
|
20
|
+
* another language takes a slice where this one takes a loop — the same bargain
|
|
21
|
+
* [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)
|
|
22
|
+
* already made for the coverage segments.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the header is JSON
|
|
25
|
+
*
|
|
26
|
+
* It is the one part that is read before anything is known, it is a few hundred
|
|
27
|
+
* bytes against a payload measured in megabytes, and being self-describing is
|
|
28
|
+
* what lets a decoder reject a file by name instead of by crash. The payload —
|
|
29
|
+
* the part that repeats — never is.
|
|
30
|
+
*
|
|
31
|
+
* ## The one rule
|
|
32
|
+
*
|
|
33
|
+
* A malformed reference rejects the whole file. There is no partial read and no
|
|
34
|
+
* salvage: a segment is a cache or a baseline, both of which may be rebuilt, and
|
|
35
|
+
* half of either is worse than neither.
|
|
36
|
+
*/
|
|
37
|
+
/** The sentinel a `u32` column uses for "no value here". */
|
|
38
|
+
export declare const NONE = 4294967295;
|
|
39
|
+
/** A column, by the width its element type already declares. */
|
|
40
|
+
export type Column = Uint8Array | Uint32Array;
|
|
41
|
+
/**
|
|
42
|
+
* Write named columns as one aligned segment.
|
|
43
|
+
*
|
|
44
|
+
* Width is taken from the array's own type rather than from a convention about
|
|
45
|
+
* the section's name, so a column that is read back as `u8` is one that was
|
|
46
|
+
* written as `Uint8Array` and a mismatch is a compile error rather than a
|
|
47
|
+
* decode that succeeds against the wrong stride.
|
|
48
|
+
*/
|
|
49
|
+
export declare function encodeSegment(format: string, version: number, columns: Readonly<Record<string, Column>>): Uint8Array;
|
|
50
|
+
/** Columns of one opened segment, by name. Every accessor may reject the file. */
|
|
51
|
+
export interface OpenSegment {
|
|
52
|
+
/** A `u8` column. Absent or of the wrong width rejects the file. */
|
|
53
|
+
u8(name: string): Uint8Array;
|
|
54
|
+
/** A `u32` column. Absent or of the wrong width rejects the file. */
|
|
55
|
+
u32(name: string): Uint32Array;
|
|
56
|
+
/** A `u32` column a writer of this version may legitimately not have written. */
|
|
57
|
+
maybeU32(name: string): Uint32Array;
|
|
58
|
+
/** Refuse the file, in the words the caller's format uses. */
|
|
59
|
+
reject(): Error;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Open a segment, refusing anything that is not one of the named format and
|
|
63
|
+
* version.
|
|
64
|
+
*
|
|
65
|
+
* `what` is the noun a rejection is phrased with — "source index", "suite index"
|
|
66
|
+
* — because the one thing a caller of this can usefully be told is which reader
|
|
67
|
+
* refused, and a shared message would name none of them.
|
|
68
|
+
*/
|
|
69
|
+
export declare function openSegment(format: string, version: number, input: Uint8Array, what: string): OpenSegment;
|
|
70
|
+
/**
|
|
71
|
+
* Every distinct string, sorted, and the number each one is written as.
|
|
72
|
+
*
|
|
73
|
+
* Sorted rather than first-seen because two encodes of equal facts must produce
|
|
74
|
+
* equal bytes: a segment is content-addressed by whoever caches it, and an
|
|
75
|
+
* identical index that hashes differently defeats every transport that would
|
|
76
|
+
* otherwise have skipped the upload.
|
|
77
|
+
*/
|
|
78
|
+
export declare function intern(values: Iterable<string>): {
|
|
79
|
+
readonly strings: readonly string[];
|
|
80
|
+
readonly id: (value: string) => number;
|
|
81
|
+
/** `NONE` for `undefined`, so an absent field costs a column and not a branch. */
|
|
82
|
+
readonly optionalId: (value: string | undefined) => number;
|
|
83
|
+
};
|
|
84
|
+
/** The two columns a dictionary is written as: the bytes, and where each one ends. */
|
|
85
|
+
export declare function stringColumns(strings: readonly string[]): {
|
|
86
|
+
readonly blob: Uint8Array;
|
|
87
|
+
readonly off: Uint32Array;
|
|
88
|
+
};
|
|
89
|
+
/** Read strings back out of a dictionary, rejecting any id the blob cannot hold. */
|
|
90
|
+
export declare function stringReader(blob: Uint8Array, off: Uint32Array, reject: () => Error): {
|
|
91
|
+
readonly text: (id: number) => string;
|
|
92
|
+
readonly optional: (id: number) => string | undefined;
|
|
93
|
+
};
|
|
94
|
+
/** A running-total column: `result[n]` is where row `n` starts, `result.at(-1)` the end. */
|
|
95
|
+
export declare function offsetsOf(lengths: readonly number[]): Uint32Array;
|
|
96
|
+
/**
|
|
97
|
+
* Check an offset column before any row is read through it.
|
|
98
|
+
*
|
|
99
|
+
* Checked here rather than per row because an offset column is the one thing in
|
|
100
|
+
* a segment that can turn a corrupt file into a *plausible* value: a pair that
|
|
101
|
+
* runs backwards reads a neighbouring row's data as this row's, and nothing
|
|
102
|
+
* downstream would ever notice.
|
|
103
|
+
*/
|
|
104
|
+
export declare function validateOffsets(column: Uint32Array, end: number, rows: number, reject: () => Error): void;
|
|
105
|
+
/** Columns that describe the same rows must agree about how many there are. */
|
|
106
|
+
export declare function sameLength(length: number, columns: readonly {
|
|
107
|
+
readonly length: number;
|
|
108
|
+
}[], reject: () => Error): void;
|
|
109
|
+
/** The indices row `row` owns in the column an offset column points into. */
|
|
110
|
+
export declare function rangeOf(offsets: Uint32Array, row: number, reject: () => Error): number[];
|
|
111
|
+
/** A stored boolean, refusing the file for anything that is not one. */
|
|
112
|
+
export declare function flagOf(value: number | undefined, reject: () => Error): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Order two strings by UTF-16 code unit, which is the order a segment is sorted in.
|
|
115
|
+
*
|
|
116
|
+
* Exported because a schema that sorts its own values before interning them has
|
|
117
|
+
* to sort them the same way this does, and `localeCompare` is the trap: it is
|
|
118
|
+
* locale-dependent, so an index written on one machine would interleave
|
|
119
|
+
* differently on another and two encodes of equal facts would stop producing
|
|
120
|
+
* equal bytes.
|
|
121
|
+
*/
|
|
122
|
+
export declare function codeUnitOrder(left: string, right: string): number;
|
|
123
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/segment` — a column of facts, as bytes.
|
|
3
|
+
*
|
|
4
|
+
* Two things in this repository already had to be written down and read back on
|
|
5
|
+
* another machine: the source index a scan reuses, and now the suite index a run
|
|
6
|
+
* leaves behind. Both are the same shape underneath — a pile of repeated strings
|
|
7
|
+
* and a pile of small integers — and both were going to grow the same four
|
|
8
|
+
* hundred lines of offset arithmetic independently.
|
|
9
|
+
*
|
|
10
|
+
* So the arithmetic is here and the schemas are not. This module knows about
|
|
11
|
+
* named sections, alignment, interned strings and the validation that a decode
|
|
12
|
+
* must perform before it believes a file. It knows nothing about what any
|
|
13
|
+
* section means, which is the property that lets one format change without
|
|
14
|
+
* touching the other.
|
|
15
|
+
*
|
|
16
|
+
* ## Why columns
|
|
17
|
+
*
|
|
18
|
+
* The reader that matters next is not this one. Every field is a run of
|
|
19
|
+
* fixed-width little-endian values at a computable offset, so a reader in
|
|
20
|
+
* another language takes a slice where this one takes a loop — the same bargain
|
|
21
|
+
* [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)
|
|
22
|
+
* already made for the coverage segments.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the header is JSON
|
|
25
|
+
*
|
|
26
|
+
* It is the one part that is read before anything is known, it is a few hundred
|
|
27
|
+
* bytes against a payload measured in megabytes, and being self-describing is
|
|
28
|
+
* what lets a decoder reject a file by name instead of by crash. The payload —
|
|
29
|
+
* the part that repeats — never is.
|
|
30
|
+
*
|
|
31
|
+
* ## The one rule
|
|
32
|
+
*
|
|
33
|
+
* A malformed reference rejects the whole file. There is no partial read and no
|
|
34
|
+
* salvage: a segment is a cache or a baseline, both of which may be rebuilt, and
|
|
35
|
+
* half of either is worse than neither.
|
|
36
|
+
*/
|
|
37
|
+
const ALIGNMENT = 8;
|
|
38
|
+
const utf8 = { encode: new TextEncoder(), decode: new TextDecoder() };
|
|
39
|
+
/** The sentinel a `u32` column uses for "no value here". */
|
|
40
|
+
export const NONE = 0xffff_ffff;
|
|
41
|
+
/**
|
|
42
|
+
* Write named columns as one aligned segment.
|
|
43
|
+
*
|
|
44
|
+
* Width is taken from the array's own type rather than from a convention about
|
|
45
|
+
* the section's name, so a column that is read back as `u8` is one that was
|
|
46
|
+
* written as `Uint8Array` and a mismatch is a compile error rather than a
|
|
47
|
+
* decode that succeeds against the wrong stride.
|
|
48
|
+
*/
|
|
49
|
+
export function encodeSegment(format, version, columns) {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
const sections = [];
|
|
52
|
+
let offset = 0;
|
|
53
|
+
for (const [name, column] of Object.entries(columns)) {
|
|
54
|
+
const value = new Uint8Array(column.buffer, column.byteOffset, column.byteLength);
|
|
55
|
+
sections.push({
|
|
56
|
+
name,
|
|
57
|
+
offset,
|
|
58
|
+
length: value.length,
|
|
59
|
+
width: column instanceof Uint8Array ? 1 : 4,
|
|
60
|
+
});
|
|
61
|
+
chunks.push(value);
|
|
62
|
+
offset += value.length;
|
|
63
|
+
const padding = aligned(offset) - offset;
|
|
64
|
+
if (padding > 0)
|
|
65
|
+
chunks.push(new Uint8Array(padding));
|
|
66
|
+
offset += padding;
|
|
67
|
+
}
|
|
68
|
+
const header = { format, version, sections };
|
|
69
|
+
const encoded = utf8.encode.encode(JSON.stringify(header));
|
|
70
|
+
// The header is padded rather than merely written, so every section offset it
|
|
71
|
+
// names is an aligned offset into the file and not only into the payload.
|
|
72
|
+
const headerLength = aligned(4 + encoded.length) - 4;
|
|
73
|
+
const prefix = new Uint8Array(4);
|
|
74
|
+
new DataView(prefix.buffer).setUint32(0, headerLength, true);
|
|
75
|
+
return concat([prefix, encoded, new Uint8Array(headerLength - encoded.length), ...chunks]);
|
|
76
|
+
}
|
|
77
|
+
function concat(parts) {
|
|
78
|
+
const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
79
|
+
let offset = 0;
|
|
80
|
+
for (const part of parts) {
|
|
81
|
+
result.set(part, offset);
|
|
82
|
+
offset += part.length;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Open a segment, refusing anything that is not one of the named format and
|
|
88
|
+
* version.
|
|
89
|
+
*
|
|
90
|
+
* `what` is the noun a rejection is phrased with — "source index", "suite index"
|
|
91
|
+
* — because the one thing a caller of this can usefully be told is which reader
|
|
92
|
+
* refused, and a shared message would name none of them.
|
|
93
|
+
*/
|
|
94
|
+
export function openSegment(format, version, input, what) {
|
|
95
|
+
const reject = () => new Error(`not a variance-authority ${what}`);
|
|
96
|
+
// Copied when it does not start on an alignment boundary, because the whole
|
|
97
|
+
// point of the padding is that a `u32` column can be a view rather than a
|
|
98
|
+
// parse, and a view is only legal at a multiple of its own width. A read that
|
|
99
|
+
// lands mid-pool — which is what a small file off a disk usually is — pays one
|
|
100
|
+
// copy for that guarantee instead of losing it.
|
|
101
|
+
const raw = input.byteOffset % ALIGNMENT === 0 ? input : input.slice();
|
|
102
|
+
if (raw.length < 4)
|
|
103
|
+
throw reject();
|
|
104
|
+
const headerLength = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0, true);
|
|
105
|
+
if (headerLength > raw.length - 4)
|
|
106
|
+
throw reject();
|
|
107
|
+
let header;
|
|
108
|
+
try {
|
|
109
|
+
const text = utf8.decode.decode(raw.subarray(4, 4 + headerLength));
|
|
110
|
+
header = JSON.parse(text.replace(/\0+$/, ''));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw reject();
|
|
114
|
+
}
|
|
115
|
+
if (header.format !== format ||
|
|
116
|
+
header.version !== version ||
|
|
117
|
+
!validSections(header.sections, raw.length - 4 - headerLength))
|
|
118
|
+
throw reject();
|
|
119
|
+
const base = 4 + headerLength;
|
|
120
|
+
const found = new Map(header.sections.map((section) => [section.name, section]));
|
|
121
|
+
const at = (name, width) => {
|
|
122
|
+
const section = found.get(name);
|
|
123
|
+
if (section === undefined || section.width !== width)
|
|
124
|
+
throw reject();
|
|
125
|
+
return section;
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
reject,
|
|
129
|
+
u8(name) {
|
|
130
|
+
const section = at(name, 1);
|
|
131
|
+
return new Uint8Array(raw.buffer, raw.byteOffset + base + section.offset, section.length);
|
|
132
|
+
},
|
|
133
|
+
u32(name) {
|
|
134
|
+
const section = at(name, 4);
|
|
135
|
+
if (section.length % 4 !== 0)
|
|
136
|
+
throw reject();
|
|
137
|
+
return new Uint32Array(raw.buffer, raw.byteOffset + base + section.offset, section.length / 4);
|
|
138
|
+
},
|
|
139
|
+
maybeU32(name) {
|
|
140
|
+
const section = found.get(name);
|
|
141
|
+
if (section === undefined)
|
|
142
|
+
return new Uint32Array();
|
|
143
|
+
if (section.width !== 4 || section.length % 4 !== 0)
|
|
144
|
+
throw reject();
|
|
145
|
+
return new Uint32Array(raw.buffer, raw.byteOffset + base + section.offset, section.length / 4);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Every distinct string, sorted, and the number each one is written as.
|
|
151
|
+
*
|
|
152
|
+
* Sorted rather than first-seen because two encodes of equal facts must produce
|
|
153
|
+
* equal bytes: a segment is content-addressed by whoever caches it, and an
|
|
154
|
+
* identical index that hashes differently defeats every transport that would
|
|
155
|
+
* otherwise have skipped the upload.
|
|
156
|
+
*/
|
|
157
|
+
export function intern(values) {
|
|
158
|
+
const strings = [...new Set(values)].sort(codeUnitOrder);
|
|
159
|
+
const ids = new Map(strings.map((value, index) => [value, index]));
|
|
160
|
+
const id = (value) => {
|
|
161
|
+
const found = ids.get(value);
|
|
162
|
+
if (found === undefined)
|
|
163
|
+
throw new Error(`string not interned: ${JSON.stringify(value)}`);
|
|
164
|
+
return found;
|
|
165
|
+
};
|
|
166
|
+
return { strings, id, optionalId: (value) => (value === undefined ? NONE : id(value)) };
|
|
167
|
+
}
|
|
168
|
+
/** The two columns a dictionary is written as: the bytes, and where each one ends. */
|
|
169
|
+
export function stringColumns(strings) {
|
|
170
|
+
const encoded = strings.map((value) => utf8.encode.encode(value));
|
|
171
|
+
return { blob: concat(encoded), off: offsetsOf(encoded.map((value) => value.length)) };
|
|
172
|
+
}
|
|
173
|
+
/** Read strings back out of a dictionary, rejecting any id the blob cannot hold. */
|
|
174
|
+
export function stringReader(blob, off, reject) {
|
|
175
|
+
validateOffsets(off, blob.length, off.length - 1, reject);
|
|
176
|
+
const text = (id) => {
|
|
177
|
+
const start = off[id];
|
|
178
|
+
const end = off[id + 1];
|
|
179
|
+
if (start === undefined || end === undefined)
|
|
180
|
+
throw reject();
|
|
181
|
+
return utf8.decode.decode(blob.subarray(start, end));
|
|
182
|
+
};
|
|
183
|
+
return { text, optional: (id) => (id === NONE ? undefined : text(id)) };
|
|
184
|
+
}
|
|
185
|
+
/** A running-total column: `result[n]` is where row `n` starts, `result.at(-1)` the end. */
|
|
186
|
+
export function offsetsOf(lengths) {
|
|
187
|
+
const result = new Uint32Array(lengths.length + 1);
|
|
188
|
+
for (let index = 0; index < lengths.length; index += 1) {
|
|
189
|
+
result[index + 1] = result[index] + lengths[index];
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Check an offset column before any row is read through it.
|
|
195
|
+
*
|
|
196
|
+
* Checked here rather than per row because an offset column is the one thing in
|
|
197
|
+
* a segment that can turn a corrupt file into a *plausible* value: a pair that
|
|
198
|
+
* runs backwards reads a neighbouring row's data as this row's, and nothing
|
|
199
|
+
* downstream would ever notice.
|
|
200
|
+
*/
|
|
201
|
+
export function validateOffsets(column, end, rows, reject) {
|
|
202
|
+
if (column.length !== rows + 1 || column[0] !== 0 || column[column.length - 1] !== end) {
|
|
203
|
+
throw reject();
|
|
204
|
+
}
|
|
205
|
+
for (let index = 1; index < column.length; index += 1) {
|
|
206
|
+
if (column[index] < column[index - 1])
|
|
207
|
+
throw reject();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Columns that describe the same rows must agree about how many there are. */
|
|
211
|
+
export function sameLength(length, columns, reject) {
|
|
212
|
+
if (columns.some((column) => column.length !== length))
|
|
213
|
+
throw reject();
|
|
214
|
+
}
|
|
215
|
+
/** The indices row `row` owns in the column an offset column points into. */
|
|
216
|
+
export function rangeOf(offsets, row, reject) {
|
|
217
|
+
const start = offsets[row];
|
|
218
|
+
const end = offsets[row + 1];
|
|
219
|
+
if (start === undefined || end === undefined)
|
|
220
|
+
throw reject();
|
|
221
|
+
return Array.from({ length: end - start }, (_, index) => start + index);
|
|
222
|
+
}
|
|
223
|
+
/** A stored boolean, refusing the file for anything that is not one. */
|
|
224
|
+
export function flagOf(value, reject) {
|
|
225
|
+
if (value !== 0 && value !== 1)
|
|
226
|
+
throw reject();
|
|
227
|
+
return value === 1;
|
|
228
|
+
}
|
|
229
|
+
function validSections(sections, available) {
|
|
230
|
+
if (!Array.isArray(sections) || sections.length === 0)
|
|
231
|
+
return false;
|
|
232
|
+
if (new Set(sections.map((section) => section.name)).size !== sections.length)
|
|
233
|
+
return false;
|
|
234
|
+
const ordered = [...sections].sort((left, right) => left.offset - right.offset);
|
|
235
|
+
let end = 0;
|
|
236
|
+
for (const section of ordered) {
|
|
237
|
+
if (typeof section.name !== 'string' ||
|
|
238
|
+
!Number.isSafeInteger(section.offset) ||
|
|
239
|
+
!Number.isSafeInteger(section.length) ||
|
|
240
|
+
section.offset < end ||
|
|
241
|
+
section.offset % ALIGNMENT !== 0 ||
|
|
242
|
+
section.length < 0 ||
|
|
243
|
+
section.length > available - section.offset ||
|
|
244
|
+
(section.width !== 1 && section.width !== 4))
|
|
245
|
+
return false;
|
|
246
|
+
end = section.offset + section.length;
|
|
247
|
+
}
|
|
248
|
+
return ordered[0]?.offset === 0;
|
|
249
|
+
}
|
|
250
|
+
function aligned(value) {
|
|
251
|
+
return Math.ceil(value / ALIGNMENT) * ALIGNMENT;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Order two strings by UTF-16 code unit, which is the order a segment is sorted in.
|
|
255
|
+
*
|
|
256
|
+
* Exported because a schema that sorts its own values before interning them has
|
|
257
|
+
* to sort them the same way this does, and `localeCompare` is the trap: it is
|
|
258
|
+
* locale-dependent, so an index written on one machine would interleave
|
|
259
|
+
* differently on another and two encodes of equal facts would stop producing
|
|
260
|
+
* equal bytes.
|
|
261
|
+
*/
|
|
262
|
+
export function codeUnitOrder(left, right) {
|
|
263
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/segment/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@variance-authority/core/segment` — a column of facts, as bytes.\n *\n * Two things in this repository already had to be written down and read back on\n * another machine: the source index a scan reuses, and now the suite index a run\n * leaves behind. Both are the same shape underneath — a pile of repeated strings\n * and a pile of small integers — and both were going to grow the same four\n * hundred lines of offset arithmetic independently.\n *\n * So the arithmetic is here and the schemas are not. This module knows about\n * named sections, alignment, interned strings and the validation that a decode\n * must perform before it believes a file. It knows nothing about what any\n * section means, which is the property that lets one format change without\n * touching the other.\n *\n * ## Why columns\n *\n * The reader that matters next is not this one. Every field is a run of\n * fixed-width little-endian values at a computable offset, so a reader in\n * another language takes a slice where this one takes a loop — the same bargain\n * [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)\n * already made for the coverage segments.\n *\n * ## Why the header is JSON\n *\n * It is the one part that is read before anything is known, it is a few hundred\n * bytes against a payload measured in megabytes, and being self-describing is\n * what lets a decoder reject a file by name instead of by crash. The payload —\n * the part that repeats — never is.\n *\n * ## The one rule\n *\n * A malformed reference rejects the whole file. There is no partial read and no\n * salvage: a segment is a cache or a baseline, both of which may be rebuilt, and\n * half of either is worse than neither.\n */\n\nconst ALIGNMENT = 8;\n\n// Declared rather than imported. This package takes no platform's types — it is\n// the layer every other one is allowed to run wherever it likes — and these two\n// are the standard text codecs that every runtime it targets already has. A\n// `Buffer` would have been shorter and would have made `core` a Node package.\ndeclare const TextEncoder: { new (): { encode(input: string): Uint8Array } };\ndeclare const TextDecoder: { new (): { decode(input: Uint8Array): string } };\n\nconst utf8 = { encode: new TextEncoder(), decode: new TextDecoder() };\n\n/** The sentinel a `u32` column uses for \"no value here\". */\nexport const NONE = 0xffff_ffff;\n\ninterface Section {\n readonly name: string;\n readonly offset: number;\n readonly length: number;\n readonly width: 1 | 4;\n}\n\ninterface Header {\n readonly format: string;\n readonly version: number;\n readonly sections: readonly Section[];\n}\n\n/** A column, by the width its element type already declares. */\nexport type Column = Uint8Array | Uint32Array;\n\n/**\n * Write named columns as one aligned segment.\n *\n * Width is taken from the array's own type rather than from a convention about\n * the section's name, so a column that is read back as `u8` is one that was\n * written as `Uint8Array` and a mismatch is a compile error rather than a\n * decode that succeeds against the wrong stride.\n */\nexport function encodeSegment(\n format: string,\n version: number,\n columns: Readonly<Record<string, Column>>,\n): Uint8Array {\n const chunks: Uint8Array[] = [];\n const sections: Section[] = [];\n let offset = 0;\n\n for (const [name, column] of Object.entries(columns)) {\n const value = new Uint8Array(column.buffer, column.byteOffset, column.byteLength);\n sections.push({\n name,\n offset,\n length: value.length,\n width: column instanceof Uint8Array ? 1 : 4,\n });\n chunks.push(value);\n offset += value.length;\n const padding = aligned(offset) - offset;\n if (padding > 0) chunks.push(new Uint8Array(padding));\n offset += padding;\n }\n\n const header: Header = { format, version, sections };\n const encoded = utf8.encode.encode(JSON.stringify(header));\n // The header is padded rather than merely written, so every section offset it\n // names is an aligned offset into the file and not only into the payload.\n const headerLength = aligned(4 + encoded.length) - 4;\n const prefix = new Uint8Array(4);\n new DataView(prefix.buffer).setUint32(0, headerLength, true);\n return concat([prefix, encoded, new Uint8Array(headerLength - encoded.length), ...chunks]);\n}\n\nfunction concat(parts: readonly Uint8Array[]): Uint8Array {\n const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/** Columns of one opened segment, by name. Every accessor may reject the file. */\nexport interface OpenSegment {\n /** A `u8` column. Absent or of the wrong width rejects the file. */\n u8(name: string): Uint8Array;\n /** A `u32` column. Absent or of the wrong width rejects the file. */\n u32(name: string): Uint32Array;\n /** A `u32` column a writer of this version may legitimately not have written. */\n maybeU32(name: string): Uint32Array;\n /** Refuse the file, in the words the caller's format uses. */\n reject(): Error;\n}\n\n/**\n * Open a segment, refusing anything that is not one of the named format and\n * version.\n *\n * `what` is the noun a rejection is phrased with — \"source index\", \"suite index\"\n * — because the one thing a caller of this can usefully be told is which reader\n * refused, and a shared message would name none of them.\n */\nexport function openSegment(\n format: string,\n version: number,\n input: Uint8Array,\n what: string,\n): OpenSegment {\n const reject = (): Error => new Error(`not a variance-authority ${what}`);\n // Copied when it does not start on an alignment boundary, because the whole\n // point of the padding is that a `u32` column can be a view rather than a\n // parse, and a view is only legal at a multiple of its own width. A read that\n // lands mid-pool — which is what a small file off a disk usually is — pays one\n // copy for that guarantee instead of losing it.\n const raw = input.byteOffset % ALIGNMENT === 0 ? input : input.slice();\n if (raw.length < 4) throw reject();\n const headerLength = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0, true);\n if (headerLength > raw.length - 4) throw reject();\n\n let header: Header;\n try {\n const text = utf8.decode.decode(raw.subarray(4, 4 + headerLength));\n header = JSON.parse(text.replace(/\\0+$/, '')) as Header;\n } catch {\n throw reject();\n }\n if (\n header.format !== format ||\n header.version !== version ||\n !validSections(header.sections, raw.length - 4 - headerLength)\n ) throw reject();\n\n const base = 4 + headerLength;\n const found = new Map(header.sections.map((section) => [section.name, section]));\n const at = (name: string, width: 1 | 4): Section => {\n const section = found.get(name);\n if (section === undefined || section.width !== width) throw reject();\n return section;\n };\n\n return {\n reject,\n u8(name) {\n const section = at(name, 1);\n return new Uint8Array(raw.buffer, raw.byteOffset + base + section.offset, section.length);\n },\n u32(name) {\n const section = at(name, 4);\n if (section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n maybeU32(name) {\n const section = found.get(name);\n if (section === undefined) return new Uint32Array();\n if (section.width !== 4 || section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n };\n}\n\n/**\n * Every distinct string, sorted, and the number each one is written as.\n *\n * Sorted rather than first-seen because two encodes of equal facts must produce\n * equal bytes: a segment is content-addressed by whoever caches it, and an\n * identical index that hashes differently defeats every transport that would\n * otherwise have skipped the upload.\n */\nexport function intern(values: Iterable<string>): {\n readonly strings: readonly string[];\n readonly id: (value: string) => number;\n /** `NONE` for `undefined`, so an absent field costs a column and not a branch. */\n readonly optionalId: (value: string | undefined) => number;\n} {\n const strings = [...new Set(values)].sort(codeUnitOrder);\n const ids = new Map(strings.map((value, index) => [value, index]));\n const id = (value: string): number => {\n const found = ids.get(value);\n if (found === undefined) throw new Error(`string not interned: ${JSON.stringify(value)}`);\n return found;\n };\n return { strings, id, optionalId: (value) => (value === undefined ? NONE : id(value)) };\n}\n\n/** The two columns a dictionary is written as: the bytes, and where each one ends. */\nexport function stringColumns(strings: readonly string[]): {\n readonly blob: Uint8Array;\n readonly off: Uint32Array;\n} {\n const encoded = strings.map((value) => utf8.encode.encode(value));\n return { blob: concat(encoded), off: offsetsOf(encoded.map((value) => value.length)) };\n}\n\n/** Read strings back out of a dictionary, rejecting any id the blob cannot hold. */\nexport function stringReader(\n blob: Uint8Array,\n off: Uint32Array,\n reject: () => Error,\n): { readonly text: (id: number) => string; readonly optional: (id: number) => string | undefined } {\n validateOffsets(off, blob.length, off.length - 1, reject);\n const text = (id: number): string => {\n const start = off[id];\n const end = off[id + 1];\n if (start === undefined || end === undefined) throw reject();\n return utf8.decode.decode(blob.subarray(start, end));\n };\n return { text, optional: (id) => (id === NONE ? undefined : text(id)) };\n}\n\n/** A running-total column: `result[n]` is where row `n` starts, `result.at(-1)` the end. */\nexport function offsetsOf(lengths: readonly number[]): Uint32Array {\n const result = new Uint32Array(lengths.length + 1);\n for (let index = 0; index < lengths.length; index += 1) {\n result[index + 1] = result[index]! + lengths[index]!;\n }\n return result;\n}\n\n/**\n * Check an offset column before any row is read through it.\n *\n * Checked here rather than per row because an offset column is the one thing in\n * a segment that can turn a corrupt file into a *plausible* value: a pair that\n * runs backwards reads a neighbouring row's data as this row's, and nothing\n * downstream would ever notice.\n */\nexport function validateOffsets(\n column: Uint32Array,\n end: number,\n rows: number,\n reject: () => Error,\n): void {\n if (column.length !== rows + 1 || column[0] !== 0 || column[column.length - 1] !== end) {\n throw reject();\n }\n for (let index = 1; index < column.length; index += 1) {\n if (column[index]! < column[index - 1]!) throw reject();\n }\n}\n\n/** Columns that describe the same rows must agree about how many there are. */\nexport function sameLength(\n length: number,\n columns: readonly { readonly length: number }[],\n reject: () => Error,\n): void {\n if (columns.some((column) => column.length !== length)) throw reject();\n}\n\n/** The indices row `row` owns in the column an offset column points into. */\nexport function rangeOf(offsets: Uint32Array, row: number, reject: () => Error): number[] {\n const start = offsets[row];\n const end = offsets[row + 1];\n if (start === undefined || end === undefined) throw reject();\n return Array.from({ length: end - start }, (_, index) => start + index);\n}\n\n/** A stored boolean, refusing the file for anything that is not one. */\nexport function flagOf(value: number | undefined, reject: () => Error): boolean {\n if (value !== 0 && value !== 1) throw reject();\n return value === 1;\n}\n\nfunction validSections(sections: readonly Section[], available: number): boolean {\n if (!Array.isArray(sections) || sections.length === 0) return false;\n if (new Set(sections.map((section) => section.name)).size !== sections.length) return false;\n const ordered = [...sections].sort((left, right) => left.offset - right.offset);\n let end = 0;\n for (const section of ordered) {\n if (\n typeof section.name !== 'string' ||\n !Number.isSafeInteger(section.offset) ||\n !Number.isSafeInteger(section.length) ||\n section.offset < end ||\n section.offset % ALIGNMENT !== 0 ||\n section.length < 0 ||\n section.length > available - section.offset ||\n (section.width !== 1 && section.width !== 4)\n ) return false;\n end = section.offset + section.length;\n }\n return ordered[0]?.offset === 0;\n}\n\nfunction aligned(value: number): number {\n return Math.ceil(value / ALIGNMENT) * ALIGNMENT;\n}\n\n/**\n * Order two strings by UTF-16 code unit, which is the order a segment is sorted in.\n *\n * Exported because a schema that sorts its own values before interning them has\n * to sort them the same way this does, and `localeCompare` is the trap: it is\n * locale-dependent, so an index written on one machine would interleave\n * differently on another and two encodes of equal facts would stop producing\n * equal bytes.\n */\nexport function codeUnitOrder(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/share` — one machine's evaluation, reachable from another.
|
|
3
|
+
*
|
|
4
|
+
* A run derives a great deal that is not about the run: what the suite is made
|
|
5
|
+
* of, which subjects exist, every name each one carries, which files declare
|
|
6
|
+
* which components. All of it is a fact about a *commit*, and all of it is
|
|
7
|
+
* expensive — a source scan, a browser, a composition pass — while being
|
|
8
|
+
* identical on every machine that starts from the same tree.
|
|
9
|
+
*
|
|
10
|
+
* So the second machine should not derive it again. A pull-request runner wants
|
|
11
|
+
* what mainline already knows, and mainline already knew it an hour ago on a
|
|
12
|
+
* runner that has since been destroyed. This module is the seam between those
|
|
13
|
+
* two: a place bytes can be left under a key, and a place they can be looked
|
|
14
|
+
* for, with nothing in between that knows what the bytes are.
|
|
15
|
+
*
|
|
16
|
+
* ## The rule, inherited
|
|
17
|
+
*
|
|
18
|
+
* > **A `SharedCache` never throws.**
|
|
19
|
+
*
|
|
20
|
+
* `get` answers `null` for a miss, an outage, a permission error, a truncated
|
|
21
|
+
* body and a response nobody can parse. `put` resolves whether or not anything
|
|
22
|
+
* was written. This is the same rule
|
|
23
|
+
* [`RenderCache`](../../../raster/src/store.ts) is defined by, and it is safe
|
|
24
|
+
* here for the same reason: everything a share holds is derivable from the tree
|
|
25
|
+
* it was derived at. A miss costs the derivation, which is the answer that was
|
|
26
|
+
* always available.
|
|
27
|
+
*
|
|
28
|
+
* What that costs, stated rather than hidden: a cache that is failing looks
|
|
29
|
+
* exactly like a cache that is cold, so a broken share makes CI slow and never
|
|
30
|
+
* makes it red. An operator watching the wall clock has to go looking. The
|
|
31
|
+
* alternative — failing a build because a bucket answered 503 — is worse.
|
|
32
|
+
*
|
|
33
|
+
* ## Why a key is a commit
|
|
34
|
+
*
|
|
35
|
+
* Everything shared here is derived, so the only question a reader has is *of
|
|
36
|
+
* what*, and the answer is a tree. A commit names one; a branch names whichever
|
|
37
|
+
* one it points at this second, and two runners resolving `main` four minutes
|
|
38
|
+
* apart would silently compare against different suites.
|
|
39
|
+
*
|
|
40
|
+
* Mainline moves while a branch is open, though, which is why the lookup is
|
|
41
|
+
* {@link firstShared} over a lineage rather than a single `get`: the runner asks
|
|
42
|
+
* for the commits its branch actually descends from, newest first, and takes the
|
|
43
|
+
* first one anybody published. What it gets back says which commit it is, so a
|
|
44
|
+
* reader is never in doubt about what it is holding.
|
|
45
|
+
*
|
|
46
|
+
* ## What this is not
|
|
47
|
+
*
|
|
48
|
+
* Not a baseline store. A baseline is the thing a comparison is *against*, it
|
|
49
|
+
* cannot be re-derived, and losing one silently is how a suite records whatever
|
|
50
|
+
* happened to be on screen — which is why
|
|
51
|
+
* [`RasterStore`](../../../raster/src/store.ts) throws on every failure and this
|
|
52
|
+
* throws on none. The two are configured separately because they are answers to
|
|
53
|
+
* different questions, and a share is disposable in a way a baseline may never
|
|
54
|
+
* be.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* Bytes under a key, on terms where losing them is free.
|
|
58
|
+
*
|
|
59
|
+
* Deliberately the smallest interface that can carry an evaluation: no listing,
|
|
60
|
+
* no deletion, no expiry. A backend that can do more is welcome to; nothing
|
|
61
|
+
* here may depend on it, because the set of things every one of a directory, an
|
|
62
|
+
* action cache, a bucket and a deployment can do is this.
|
|
63
|
+
*/
|
|
64
|
+
export interface SharedCache {
|
|
65
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
66
|
+
put(key: string, bytes: Uint8Array): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Hold an implementation to the rule.
|
|
70
|
+
*
|
|
71
|
+
* Backends are built out of a disk, a socket or a bucket, every one of which
|
|
72
|
+
* throws, and every one of them would otherwise have to remember not to.
|
|
73
|
+
* Wrapping at construction makes the rule a property of the value rather than a
|
|
74
|
+
* promise in a comment.
|
|
75
|
+
*/
|
|
76
|
+
export declare function neverFails(cache: SharedCache): SharedCache;
|
|
77
|
+
/**
|
|
78
|
+
* The key an artifact is published under.
|
|
79
|
+
*
|
|
80
|
+
* `project` is a namespace and not a secret: two suites in one monorepo share a
|
|
81
|
+
* bucket and must not share a key. `artifact` names the format, so a reader that
|
|
82
|
+
* does not understand version 2 of something asks for a key version 2 was never
|
|
83
|
+
* written to rather than parsing bytes it will reject.
|
|
84
|
+
*/
|
|
85
|
+
export declare function shareKey(parts: {
|
|
86
|
+
readonly project: string;
|
|
87
|
+
readonly artifact: string;
|
|
88
|
+
readonly commit: string;
|
|
89
|
+
}): string;
|
|
90
|
+
/** What a lookup over a lineage found, and where. */
|
|
91
|
+
export interface SharedHit {
|
|
92
|
+
/** The commit the bytes were published at — never the one that was asked for first. */
|
|
93
|
+
readonly commit: string;
|
|
94
|
+
/** How many commits back that was, zero being the newest candidate offered. */
|
|
95
|
+
readonly behind: number;
|
|
96
|
+
readonly bytes: Uint8Array;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The newest published artifact among commits this tree descends from.
|
|
100
|
+
*
|
|
101
|
+
* Newest-first because a nearer ancestor is a better description of the same
|
|
102
|
+
* suite, and `behind` is returned rather than discarded because it is the one
|
|
103
|
+
* number that tells an operator their publishing is failing: a share that is
|
|
104
|
+
* always forty commits behind is a share nothing is writing to, and it is
|
|
105
|
+
* otherwise indistinguishable from a healthy one.
|
|
106
|
+
*
|
|
107
|
+
* Stops at the first hit rather than collecting all of them. There is no merge
|
|
108
|
+
* across generations to perform — an evaluation describes one tree — and asking
|
|
109
|
+
* a network for forty keys that will be discarded is the cost this exists to
|
|
110
|
+
* avoid.
|
|
111
|
+
*/
|
|
112
|
+
export declare function firstShared(cache: SharedCache, lineage: readonly string[], of: (commit: string) => string): Promise<SharedHit | null>;
|
|
113
|
+
/**
|
|
114
|
+
* A cache in this process, for tests and for a run that shares with itself.
|
|
115
|
+
*
|
|
116
|
+
* Kept here rather than in a test helper because it is also the honest answer
|
|
117
|
+
* to "share, but I have nowhere to put it": the seam is exercised, the code
|
|
118
|
+
* path is the real one, and nothing survives the process.
|
|
119
|
+
*/
|
|
120
|
+
export declare function memoryShare(): SharedCache;
|
|
121
|
+
/** What an HTTP share needs to reach its endpoint. */
|
|
122
|
+
export interface HttpShareOptions {
|
|
123
|
+
/** Base URL. A key is appended to it, with exactly one `/` between. */
|
|
124
|
+
readonly endpoint: string;
|
|
125
|
+
/** Sent on every request. A bucket wants `Authorization`; a signed URL wants nothing. */
|
|
126
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
127
|
+
/** The verb a write uses. `PUT` for a bucket, `POST` for a deployment that routes on it. */
|
|
128
|
+
readonly method?: 'PUT' | 'POST';
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A share over HTTP: `GET` to read, `PUT` to write, the key as the path.
|
|
132
|
+
*
|
|
133
|
+
* One backend rather than four because a bucket, a signed URL, a static host
|
|
134
|
+
* and a tribunal deployment differ in exactly two ways — the base and the
|
|
135
|
+
* headers — and every one of them speaks this. S3 with `PUT` object access is
|
|
136
|
+
* this with an `Authorization` header; a presigned base is this with none; a
|
|
137
|
+
* deployment is this with a token. Writing three clients would have produced
|
|
138
|
+
* three sets of error handling for an object whose entire error handling is
|
|
139
|
+
* "return null".
|
|
140
|
+
*
|
|
141
|
+
* Nothing here signs anything. Credentials that must be computed per request —
|
|
142
|
+
* SigV4 against a raw bucket — belong to whatever already holds the operator's
|
|
143
|
+
* credentials, and the shape that reaches this is a base URL it produced. That
|
|
144
|
+
* keeps a secret out of this package and out of the config file it would
|
|
145
|
+
* otherwise have to be written in.
|
|
146
|
+
*/
|
|
147
|
+
export declare function httpShare(options: HttpShareOptions): SharedCache;
|
|
148
|
+
//# sourceMappingURL=index.d.ts.map
|