@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +245 -10
  2. package/README.md +279 -110
  3. package/dist/attribute/call-site.d.ts +20 -2
  4. package/dist/attribute/call-site.js +17 -20
  5. package/dist/attribute/call-site.js.map +1 -1
  6. package/dist/attribute/closure.d.ts +105 -0
  7. package/dist/attribute/closure.js +180 -0
  8. package/dist/attribute/closure.js.map +1 -0
  9. package/dist/attribute/composition.d.ts +5 -3
  10. package/dist/attribute/composition.js +67 -11
  11. package/dist/attribute/composition.js.map +1 -1
  12. package/dist/attribute/divergence.js +7 -1
  13. package/dist/attribute/divergence.js.map +1 -1
  14. package/dist/attribute/index.d.ts +7 -1
  15. package/dist/attribute/index.js +4 -1
  16. package/dist/attribute/index.js.map +1 -1
  17. package/dist/attribute/landmark.d.ts +97 -0
  18. package/dist/attribute/landmark.js +144 -0
  19. package/dist/attribute/landmark.js.map +1 -0
  20. package/dist/attribute/lexicon.d.ts +112 -0
  21. package/dist/attribute/lexicon.js +210 -0
  22. package/dist/attribute/lexicon.js.map +1 -0
  23. package/dist/attribute/source.d.ts +19 -2
  24. package/dist/attribute/source.js +12 -0
  25. package/dist/attribute/source.js.map +1 -1
  26. package/dist/attribute/stack.d.ts +62 -1
  27. package/dist/attribute/stack.js +116 -5
  28. package/dist/attribute/stack.js.map +1 -1
  29. package/dist/compare/derive.d.ts +17 -0
  30. package/dist/compare/derive.js.map +1 -1
  31. package/dist/compare/diff/index.d.ts +25 -1
  32. package/dist/compare/diff/index.js +8 -5
  33. package/dist/compare/diff/index.js.map +1 -1
  34. package/dist/compare/explain.d.ts +1 -1
  35. package/dist/compare/explain.js +15 -8
  36. package/dist/compare/explain.js.map +1 -1
  37. package/dist/compare/index.d.ts +2 -2
  38. package/dist/compare/index.js +1 -1
  39. package/dist/compare/index.js.map +1 -1
  40. package/dist/compare/observability.d.ts +25 -1
  41. package/dist/compare/observability.js +54 -0
  42. package/dist/compare/observability.js.map +1 -1
  43. package/dist/compare/parting.d.ts +24 -2
  44. package/dist/compare/parting.js +14 -9
  45. package/dist/compare/parting.js.map +1 -1
  46. package/dist/compare/slice.d.ts +15 -2
  47. package/dist/compare/slice.js +6 -2
  48. package/dist/compare/slice.js.map +1 -1
  49. package/dist/format/capture.d.ts +15 -0
  50. package/dist/format/capture.js +30 -1
  51. package/dist/format/capture.js.map +1 -1
  52. package/dist/format/document.d.ts +65 -5
  53. package/dist/format/document.js +34 -0
  54. package/dist/format/document.js.map +1 -1
  55. package/dist/format/hash.d.ts +26 -0
  56. package/dist/format/hash.js +41 -0
  57. package/dist/format/hash.js.map +1 -1
  58. package/dist/format/index.d.ts +4 -3
  59. package/dist/format/index.js +3 -2
  60. package/dist/format/index.js.map +1 -1
  61. package/dist/format/sha256.d.ts +10 -2
  62. package/dist/format/sha256.js +42 -9
  63. package/dist/format/sha256.js.map +1 -1
  64. package/dist/format/stabilize.d.ts +15 -0
  65. package/dist/format/stabilize.js +22 -1
  66. package/dist/format/stabilize.js.map +1 -1
  67. package/dist/index.d.ts +3 -11
  68. package/dist/index.js +3 -11
  69. package/dist/index.js.map +1 -1
  70. package/dist/judge/inspect.js +57 -4
  71. package/dist/judge/inspect.js.map +1 -1
  72. package/dist/relate/before.d.ts +137 -0
  73. package/dist/relate/before.js +155 -0
  74. package/dist/relate/before.js.map +1 -0
  75. package/dist/relate/graph.d.ts +53 -16
  76. package/dist/relate/graph.js +33 -15
  77. package/dist/relate/graph.js.map +1 -1
  78. package/dist/relate/index.d.ts +3 -2
  79. package/dist/relate/index.js +2 -1
  80. package/dist/relate/index.js.map +1 -1
  81. package/dist/relate/merkle.d.ts +14 -5
  82. package/dist/relate/merkle.js +14 -6
  83. package/dist/relate/merkle.js.map +1 -1
  84. package/dist/relate/reach.d.ts +19 -5
  85. package/dist/relate/reach.js +10 -8
  86. package/dist/relate/reach.js.map +1 -1
  87. package/dist/relate/records.d.ts +95 -4
  88. package/dist/relate/records.js +93 -13
  89. package/dist/relate/records.js.map +1 -1
  90. package/dist/segment/index.d.ts +123 -0
  91. package/dist/segment/index.js +265 -0
  92. package/dist/segment/index.js.map +1 -0
  93. package/dist/share/index.d.ts +148 -0
  94. package/dist/share/index.js +190 -0
  95. package/dist/share/index.js.map +1 -0
  96. package/package.json +10 -2
@@ -0,0 +1,190 @@
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
+ * Hold an implementation to the rule.
58
+ *
59
+ * Backends are built out of a disk, a socket or a bucket, every one of which
60
+ * throws, and every one of them would otherwise have to remember not to.
61
+ * Wrapping at construction makes the rule a property of the value rather than a
62
+ * promise in a comment.
63
+ */
64
+ export function neverFails(cache) {
65
+ return {
66
+ async get(key) {
67
+ try {
68
+ return await cache.get(key);
69
+ }
70
+ catch {
71
+ // A miss and a failure are the same instruction: derive it.
72
+ return null;
73
+ }
74
+ },
75
+ async put(key, bytes) {
76
+ try {
77
+ await cache.put(key, bytes);
78
+ }
79
+ catch {
80
+ // Nothing to report and nothing to retry. The next run publishes again.
81
+ }
82
+ },
83
+ };
84
+ }
85
+ /**
86
+ * The key an artifact is published under.
87
+ *
88
+ * `project` is a namespace and not a secret: two suites in one monorepo share a
89
+ * bucket and must not share a key. `artifact` names the format, so a reader that
90
+ * does not understand version 2 of something asks for a key version 2 was never
91
+ * written to rather than parsing bytes it will reject.
92
+ */
93
+ export function shareKey(parts) {
94
+ return `${segment(parts.project)}/${segment(parts.artifact)}/${segment(parts.commit)}.bin`;
95
+ }
96
+ /**
97
+ * The newest published artifact among commits this tree descends from.
98
+ *
99
+ * Newest-first because a nearer ancestor is a better description of the same
100
+ * suite, and `behind` is returned rather than discarded because it is the one
101
+ * number that tells an operator their publishing is failing: a share that is
102
+ * always forty commits behind is a share nothing is writing to, and it is
103
+ * otherwise indistinguishable from a healthy one.
104
+ *
105
+ * Stops at the first hit rather than collecting all of them. There is no merge
106
+ * across generations to perform — an evaluation describes one tree — and asking
107
+ * a network for forty keys that will be discarded is the cost this exists to
108
+ * avoid.
109
+ */
110
+ export async function firstShared(cache, lineage, of) {
111
+ for (let behind = 0; behind < lineage.length; behind += 1) {
112
+ const commit = lineage[behind];
113
+ const bytes = await cache.get(of(commit));
114
+ if (bytes !== null)
115
+ return { commit, behind, bytes };
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * A cache in this process, for tests and for a run that shares with itself.
121
+ *
122
+ * Kept here rather than in a test helper because it is also the honest answer
123
+ * to "share, but I have nowhere to put it": the seam is exercised, the code
124
+ * path is the real one, and nothing survives the process.
125
+ */
126
+ export function memoryShare() {
127
+ const held = new Map();
128
+ return {
129
+ async get(key) {
130
+ return held.get(key) ?? null;
131
+ },
132
+ async put(key, bytes) {
133
+ held.set(key, bytes.slice());
134
+ },
135
+ };
136
+ }
137
+ /**
138
+ * A share over HTTP: `GET` to read, `PUT` to write, the key as the path.
139
+ *
140
+ * One backend rather than four because a bucket, a signed URL, a static host
141
+ * and a tribunal deployment differ in exactly two ways — the base and the
142
+ * headers — and every one of them speaks this. S3 with `PUT` object access is
143
+ * this with an `Authorization` header; a presigned base is this with none; a
144
+ * deployment is this with a token. Writing three clients would have produced
145
+ * three sets of error handling for an object whose entire error handling is
146
+ * "return null".
147
+ *
148
+ * Nothing here signs anything. Credentials that must be computed per request —
149
+ * SigV4 against a raw bucket — belong to whatever already holds the operator's
150
+ * credentials, and the shape that reaches this is a base URL it produced. That
151
+ * keeps a secret out of this package and out of the config file it would
152
+ * otherwise have to be written in.
153
+ */
154
+ export function httpShare(options) {
155
+ const base = options.endpoint.replace(/\/+$/, '');
156
+ const headers = options.headers ?? {};
157
+ return {
158
+ async get(key) {
159
+ const response = await fetch(`${base}/${key}`, { headers });
160
+ // A 404 is the common answer and not an event. Every other refusal is
161
+ // read the same way, because the caller's response to all of them is one
162
+ // response, and a cache that distinguished them would be inviting a
163
+ // caller to act on the difference.
164
+ if (!response.ok)
165
+ return null;
166
+ return new Uint8Array(await response.arrayBuffer());
167
+ },
168
+ async put(key, bytes) {
169
+ await fetch(`${base}/${key}`, {
170
+ method: options.method ?? 'PUT',
171
+ headers: { 'content-type': 'application/octet-stream', ...headers },
172
+ body: bytes,
173
+ });
174
+ },
175
+ };
176
+ }
177
+ /**
178
+ * One path segment, with everything that is not a key character removed.
179
+ *
180
+ * A project name comes from a config file and a commit from `git`, and both end
181
+ * up in a URL and on a filesystem. Escaping would be reversible and is not
182
+ * wanted: a name that needs escaping is a name whose two spellings should not
183
+ * address two different entries, so the mapping is deliberately many-to-one and
184
+ * the cost of a collision is a cache miss.
185
+ */
186
+ function segment(value) {
187
+ const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '');
188
+ return cleaned === '' ? 'unnamed' : cleaned;
189
+ }
190
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@variance-authority/core/share` — one machine's evaluation, reachable from another.\n *\n * A run derives a great deal that is not about the run: what the suite is made\n * of, which subjects exist, every name each one carries, which files declare\n * which components. All of it is a fact about a *commit*, and all of it is\n * expensive — a source scan, a browser, a composition pass — while being\n * identical on every machine that starts from the same tree.\n *\n * So the second machine should not derive it again. A pull-request runner wants\n * what mainline already knows, and mainline already knew it an hour ago on a\n * runner that has since been destroyed. This module is the seam between those\n * two: a place bytes can be left under a key, and a place they can be looked\n * for, with nothing in between that knows what the bytes are.\n *\n * ## The rule, inherited\n *\n * > **A `SharedCache` never throws.**\n *\n * `get` answers `null` for a miss, an outage, a permission error, a truncated\n * body and a response nobody can parse. `put` resolves whether or not anything\n * was written. This is the same rule\n * [`RenderCache`](../../../raster/src/store.ts) is defined by, and it is safe\n * here for the same reason: everything a share holds is derivable from the tree\n * it was derived at. A miss costs the derivation, which is the answer that was\n * always available.\n *\n * What that costs, stated rather than hidden: a cache that is failing looks\n * exactly like a cache that is cold, so a broken share makes CI slow and never\n * makes it red. An operator watching the wall clock has to go looking. The\n * alternative — failing a build because a bucket answered 503 — is worse.\n *\n * ## Why a key is a commit\n *\n * Everything shared here is derived, so the only question a reader has is *of\n * what*, and the answer is a tree. A commit names one; a branch names whichever\n * one it points at this second, and two runners resolving `main` four minutes\n * apart would silently compare against different suites.\n *\n * Mainline moves while a branch is open, though, which is why the lookup is\n * {@link firstShared} over a lineage rather than a single `get`: the runner asks\n * for the commits its branch actually descends from, newest first, and takes the\n * first one anybody published. What it gets back says which commit it is, so a\n * reader is never in doubt about what it is holding.\n *\n * ## What this is not\n *\n * Not a baseline store. A baseline is the thing a comparison is *against*, it\n * cannot be re-derived, and losing one silently is how a suite records whatever\n * happened to be on screen — which is why\n * [`RasterStore`](../../../raster/src/store.ts) throws on every failure and this\n * throws on none. The two are configured separately because they are answers to\n * different questions, and a share is disposable in a way a baseline may never\n * be.\n */\n\n// Declared rather than imported, as elsewhere in this package: `core` takes no\n// platform's types, and `fetch` is present in every runtime it targets. Only the\n// three members used are named, so a runtime with a narrower one still fits.\ndeclare const fetch: (\n url: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: Uint8Array;\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\n/**\n * Bytes under a key, on terms where losing them is free.\n *\n * Deliberately the smallest interface that can carry an evaluation: no listing,\n * no deletion, no expiry. A backend that can do more is welcome to; nothing\n * here may depend on it, because the set of things every one of a directory, an\n * action cache, a bucket and a deployment can do is this.\n */\nexport interface SharedCache {\n get(key: string): Promise<Uint8Array | null>;\n put(key: string, bytes: Uint8Array): Promise<void>;\n}\n\n/**\n * Hold an implementation to the rule.\n *\n * Backends are built out of a disk, a socket or a bucket, every one of which\n * throws, and every one of them would otherwise have to remember not to.\n * Wrapping at construction makes the rule a property of the value rather than a\n * promise in a comment.\n */\nexport function neverFails(cache: SharedCache): SharedCache {\n return {\n async get(key): Promise<Uint8Array | null> {\n try {\n return await cache.get(key);\n } catch {\n // A miss and a failure are the same instruction: derive it.\n return null;\n }\n },\n async put(key, bytes): Promise<void> {\n try {\n await cache.put(key, bytes);\n } catch {\n // Nothing to report and nothing to retry. The next run publishes again.\n }\n },\n };\n}\n\n/**\n * The key an artifact is published under.\n *\n * `project` is a namespace and not a secret: two suites in one monorepo share a\n * bucket and must not share a key. `artifact` names the format, so a reader that\n * does not understand version 2 of something asks for a key version 2 was never\n * written to rather than parsing bytes it will reject.\n */\nexport function shareKey(parts: {\n readonly project: string;\n readonly artifact: string;\n readonly commit: string;\n}): string {\n return `${segment(parts.project)}/${segment(parts.artifact)}/${segment(parts.commit)}.bin`;\n}\n\n/** What a lookup over a lineage found, and where. */\nexport interface SharedHit {\n /** The commit the bytes were published at — never the one that was asked for first. */\n readonly commit: string;\n /** How many commits back that was, zero being the newest candidate offered. */\n readonly behind: number;\n readonly bytes: Uint8Array;\n}\n\n/**\n * The newest published artifact among commits this tree descends from.\n *\n * Newest-first because a nearer ancestor is a better description of the same\n * suite, and `behind` is returned rather than discarded because it is the one\n * number that tells an operator their publishing is failing: a share that is\n * always forty commits behind is a share nothing is writing to, and it is\n * otherwise indistinguishable from a healthy one.\n *\n * Stops at the first hit rather than collecting all of them. There is no merge\n * across generations to perform — an evaluation describes one tree — and asking\n * a network for forty keys that will be discarded is the cost this exists to\n * avoid.\n */\nexport async function firstShared(\n cache: SharedCache,\n lineage: readonly string[],\n of: (commit: string) => string,\n): Promise<SharedHit | null> {\n for (let behind = 0; behind < lineage.length; behind += 1) {\n const commit = lineage[behind]!;\n const bytes = await cache.get(of(commit));\n if (bytes !== null) return { commit, behind, bytes };\n }\n return null;\n}\n\n/**\n * A cache in this process, for tests and for a run that shares with itself.\n *\n * Kept here rather than in a test helper because it is also the honest answer\n * to \"share, but I have nowhere to put it\": the seam is exercised, the code\n * path is the real one, and nothing survives the process.\n */\nexport function memoryShare(): SharedCache {\n const held = new Map<string, Uint8Array>();\n return {\n async get(key) {\n return held.get(key) ?? null;\n },\n async put(key, bytes) {\n held.set(key, bytes.slice());\n },\n };\n}\n\n/** What an HTTP share needs to reach its endpoint. */\nexport interface HttpShareOptions {\n /** Base URL. A key is appended to it, with exactly one `/` between. */\n readonly endpoint: string;\n /** Sent on every request. A bucket wants `Authorization`; a signed URL wants nothing. */\n readonly headers?: Readonly<Record<string, string>>;\n /** The verb a write uses. `PUT` for a bucket, `POST` for a deployment that routes on it. */\n readonly method?: 'PUT' | 'POST';\n}\n\n/**\n * A share over HTTP: `GET` to read, `PUT` to write, the key as the path.\n *\n * One backend rather than four because a bucket, a signed URL, a static host\n * and a tribunal deployment differ in exactly two ways — the base and the\n * headers — and every one of them speaks this. S3 with `PUT` object access is\n * this with an `Authorization` header; a presigned base is this with none; a\n * deployment is this with a token. Writing three clients would have produced\n * three sets of error handling for an object whose entire error handling is\n * \"return null\".\n *\n * Nothing here signs anything. Credentials that must be computed per request —\n * SigV4 against a raw bucket — belong to whatever already holds the operator's\n * credentials, and the shape that reaches this is a base URL it produced. That\n * keeps a secret out of this package and out of the config file it would\n * otherwise have to be written in.\n */\nexport function httpShare(options: HttpShareOptions): SharedCache {\n const base = options.endpoint.replace(/\\/+$/, '');\n const headers = options.headers ?? {};\n return {\n async get(key) {\n const response = await fetch(`${base}/${key}`, { headers });\n // A 404 is the common answer and not an event. Every other refusal is\n // read the same way, because the caller's response to all of them is one\n // response, and a cache that distinguished them would be inviting a\n // caller to act on the difference.\n if (!response.ok) return null;\n return new Uint8Array(await response.arrayBuffer());\n },\n async put(key, bytes) {\n await fetch(`${base}/${key}`, {\n method: options.method ?? 'PUT',\n headers: { 'content-type': 'application/octet-stream', ...headers },\n body: bytes,\n });\n },\n };\n}\n\n/**\n * One path segment, with everything that is not a key character removed.\n *\n * A project name comes from a config file and a commit from `git`, and both end\n * up in a URL and on a filesystem. Escaping would be reversible and is not\n * wanted: a name that needs escaping is a name whose two spellings should not\n * address two different entries, so the mapping is deliberately many-to-one and\n * the cost of a collision is a cache miss.\n */\nfunction segment(value: string): string {\n const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '');\n return cleaned === '' ? 'unnamed' : cleaned;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@variance-authority/core",
3
- "version": "0.1.1",
3
+ "version": "0.3.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",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "author": "Machine Garden",
14
- "homepage": "https://variance-authority.dev#packages",
14
+ "homepage": "https://variance-authority.dev/reference/packages/core",
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/Variance-Authority/variance-authority.git",
@@ -31,6 +31,14 @@
31
31
  "types": "./dist/format/index.d.ts",
32
32
  "default": "./dist/format/index.js"
33
33
  },
34
+ "./share": {
35
+ "types": "./dist/share/index.d.ts",
36
+ "default": "./dist/share/index.js"
37
+ },
38
+ "./segment": {
39
+ "types": "./dist/segment/index.d.ts",
40
+ "default": "./dist/segment/index.js"
41
+ },
34
42
  "./rules": {
35
43
  "types": "./dist/rules/index.d.ts",
36
44
  "default": "./dist/rules/index.js"