@variance-authority/core 0.6.0 → 0.8.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @variance-authority/core
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 97e1ce6: A module can name a file it reads without importing it:
8
+ `/// <depends path="./schema.graphql" />`, anywhere in the file. The scan draws
9
+ a `depends` edge to that file, so a change to it reaches the tests that load
10
+ the module. TypeScript and every runtime read the line as a comment. A directive
11
+ that names no `path` is reported in the file's `unknown`. The source index
12
+ format moves to version 11, so an existing index is read again once.
13
+
14
+ ## 0.7.0
15
+
16
+ Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
17
+
3
18
  ## 0.6.0
4
19
 
5
20
  ### Minor Changes
@@ -95,7 +95,9 @@ export declare const EDGE_KINDS: readonly [
95
95
  /** A component to the file that declares it. */
96
96
  'declared-in',
97
97
  /** A package to a package its install resolved beneath it. */
98
- 'depends-on'];
98
+ 'depends-on',
99
+ /** `/// <depends path="./y" />` — a file the module reads without importing it. */
100
+ 'depends'];
99
101
  export type EdgeKind = (typeof EDGE_KINDS)[number];
100
102
  /**
101
103
  * The edge kinds a runtime loads through: every kind but `type`. The
@@ -95,6 +95,8 @@ export const EDGE_KINDS = [
95
95
  'declared-in',
96
96
  /** A package to a package its install resolved beneath it. */
97
97
  'depends-on',
98
+ /** `/// <depends path="./y" />` — a file the module reads without importing it. */
99
+ 'depends',
98
100
  ];
99
101
  /**
100
102
  * The edge kinds a runtime loads through: every kind but `type`. The
@@ -1 +1 @@
1
- {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * Three kinds and the joins between them: a `file` graph built from imports, the\n * `component` nodes the source index already knows how to find, and a `package`\n * for each name an install resolved. That is deliberate rather than incidental. A\n * component relation — *`TodoFooter` renders `Chip`* — is another edge kind\n * between nodes of another kind in this same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * A `package` earns its kind for the same reason and one more: it is the far end\n * of the same line. A run starts at the harness, arrives at our code through the\n * tests, and leaves it again at the packages — so a bumped dependency is a node\n * like any other, and *which of our files a new `jsdom` can affect* is the\n * one traversal this file already performs, seeded on the other side\n * ([`docs/selecting.md`](../../../../docs/selecting.md)).\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component', 'package'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding. One of them also\n * decides a walk: `type` is erased by every compiler, so nothing behind it runs\n * and nothing behind it renders, and the default traversal leaves it out\n * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a\n * runtime — a docgen that reads prop types, a type-check gate — passes\n * `EDGE_KINDS` and walks it.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n /** A package to a package its install resolved beneath it. */\n 'depends-on',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\n/**\n * The edge kinds a runtime loads through: every kind but `type`. The\n * default for a traversal, and the list a caller widens from. A closure folds a\n * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and\n * one of these kinds leads nowhere any bytes are.\n */\nexport const RUNTIME_EDGES: readonly EdgeKind[] = EDGE_KINDS.filter((kind) => kind !== 'type');\n\nexport interface Node {\n readonly kind: NodeKind;\n /**\n * Repository-relative for a file, the identifier for a component, and for a\n * package the name source code imports it by — `@mui/material`, never a\n * version and never a resolution. Which copy a resolver handed any one\n * importer is unanswerable without running that resolver, and the question\n * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).\n */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. Kept for whoever reports on the scan. No walk reads it:\n * the edge a reader could not see is one a recorded run sees load.\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n\n /**\n * Per file, the files its run never reaches at any depth: what a mock\n * replaces for the whole of a test's run.\n *\n * Carried with the graph rather than handed to each walk, because the graph\n * crosses every seam a walk does and a table left behind at one of them is a\n * selection that quietly widened. Empty for a graph nobody tainted. A walk\n * against the arrows consults it: a change affects a file only when some\n * trail from the change arrives without crossing one of that file's shadows.\n */\n readonly shadows: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of a few\n * fixed words and none contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\n/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\n/** The node an id names: its kind and name, or `undefined` past the end. */\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * Three kinds and the joins between them: a `file` graph built from imports, the\n * `component` nodes the source index already knows how to find, and a `package`\n * for each name an install resolved. That is deliberate rather than incidental. A\n * component relation — *`TodoFooter` renders `Chip`* — is another edge kind\n * between nodes of another kind in this same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * A `package` earns its kind for the same reason and one more: it is the far end\n * of the same line. A run starts at the harness, arrives at our code through the\n * tests, and leaves it again at the packages — so a bumped dependency is a node\n * like any other, and *which of our files a new `jsdom` can affect* is the\n * one traversal this file already performs, seeded on the other side\n * ([`docs/selecting.md`](../../../../docs/selecting.md)).\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component', 'package'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding. One of them also\n * decides a walk: `type` is erased by every compiler, so nothing behind it runs\n * and nothing behind it renders, and the default traversal leaves it out\n * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a\n * runtime — a docgen that reads prop types, a type-check gate — passes\n * `EDGE_KINDS` and walks it.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n /** A package to a package its install resolved beneath it. */\n 'depends-on',\n /** `/// <depends path=\"./y\" />` — a file the module reads without importing it. */\n 'depends',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\n/**\n * The edge kinds a runtime loads through: every kind but `type`. The\n * default for a traversal, and the list a caller widens from. A closure folds a\n * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and\n * one of these kinds leads nowhere any bytes are.\n */\nexport const RUNTIME_EDGES: readonly EdgeKind[] = EDGE_KINDS.filter((kind) => kind !== 'type');\n\nexport interface Node {\n readonly kind: NodeKind;\n /**\n * Repository-relative for a file, the identifier for a component, and for a\n * package the name source code imports it by — `@mui/material`, never a\n * version and never a resolution. Which copy a resolver handed any one\n * importer is unanswerable without running that resolver, and the question\n * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).\n */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. Kept for whoever reports on the scan. No walk reads it:\n * the edge a reader could not see is one a recorded run sees load.\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n\n /**\n * Per file, the files its run never reaches at any depth: what a mock\n * replaces for the whole of a test's run.\n *\n * Carried with the graph rather than handed to each walk, because the graph\n * crosses every seam a walk does and a table left behind at one of them is a\n * selection that quietly widened. Empty for a graph nobody tainted. A walk\n * against the arrows consults it: a change affects a file only when some\n * trail from the change arrives without crossing one of that file's shadows.\n */\n readonly shadows: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of a few\n * fixed words and none contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\n/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\n/** The node an id names: its kind and name, or `undefined` past the end. */\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
@@ -110,7 +110,8 @@ export declare function driftedBetween(before: Closure, after: Closure): Drift;
110
110
  * a decision with a cost, and it should read like one.
111
111
  *
112
112
  * `type` is not in it: a type-only import is erased before a render, so a change
113
- * behind one is not an input the render folded.
113
+ * behind one is not an input the render folded. `depends` is: the module's author
114
+ * declared that it reads the file, which makes the file's bytes an input.
114
115
  *
115
116
  * `depends-on` is not in it either, for the opposite reason. It runs between two
116
117
  * packages, and a package node is a name rather than a content digest — an
@@ -226,7 +226,8 @@ function condense(relations, allowed) {
226
226
  * a decision with a cost, and it should read like one.
227
227
  *
228
228
  * `type` is not in it: a type-only import is erased before a render, so a change
229
- * behind one is not an input the render folded.
229
+ * behind one is not an input the render folded. `depends` is: the module's author
230
+ * declared that it reads the file, which makes the file's bytes an input.
230
231
  *
231
232
  * `depends-on` is not in it either, for the opposite reason. It runs between two
232
233
  * packages, and a package node is a name rather than a content digest — an
@@ -240,6 +241,7 @@ export const CLOSURE_EDGES = [
240
241
  'dynamic',
241
242
  'asset',
242
243
  'declared-in',
244
+ 'depends',
243
245
  ];
244
246
  /** Edge kinds as a lookup, so the inner loop tests a byte. */
245
247
  function allowedKinds(through) {
@@ -1 +1 @@
1
- {"version":3,"file":"merkle.js","sourceRoot":"","sources":["../../src/relate/merkle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The graph, hashed the way a build system hashes an action's inputs.\n *\n * Reachability answers *what could this diff affect*. It needs a diff, which\n * means it needs `git`, a ref that exists, a checkout deep enough to contain it,\n * and the assumption that the ref is where this branch actually diverged. Every\n * one of those is a thing CI gets wrong, and each failure narrows a run.\n *\n * A Merkle digest asks a different question and needs none of them: **is this\n * subject's entire input closure byte-identical to the one that produced the\n * baseline?** One digest per node, over the node's own content and the digests of\n * everything it rests on — the same construction `bazel` uses to key an action,\n * and the same idea as this project's own content addressing, applied one level\n * out from the document to the source that produced it.\n *\n * What the difference buys:\n *\n * | situation | reachability | closure digest |\n * |---|---|---|\n * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |\n * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |\n * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |\n * | a dependency changed two hops away | affected, and observed | different digest, and observed |\n * | *why* a subject is being observed | a chain of files | a digest that differs |\n *\n * Neither replaces the other. The trail is the explanation, and the digest is the\n * proof; a run wants the digest to decide and the trail to justify.\n *\n * ## Cycles\n *\n * A dependency graph has them and a Merkle tree cannot. Strongly connected\n * components are condensed and hashed as a unit, so every file in a cycle carries\n * the same digest — which is exactly the truth about a cycle: no member of one can\n * be called unchanged while another changed.\n *\n * ## The digest that must never lie\n *\n * A digest says *these inputs are the same*. A file whose content was not supplied\n * breaks that claim — its bytes may have changed with no digest in this structure\n * changing. Those nodes are marked **volatile**, the mark propagates to everything\n * that rests on them, and a volatile node is treated as changed however its digest\n * compares. A cache that cannot be trusted must not be silently trusted.\n *\n * A file whose imports could not all be read is not volatile. Its bytes are\n * hashed, and the edges that were read are folded; the one nobody could read is\n * left to a recorded run, which sees the module load whatever named it.\n */\n\nimport { digestCombine, digestString, type Digest } from '../format/hash.js';\nimport {\n EDGE_KINDS,\n NODE_KINDS,\n keyOf,\n type EdgeKind,\n type NodeId,\n type Relations,\n} from './graph.js';\n\nexport interface ClosureInput {\n readonly relations: Relations;\n\n /**\n * Content digest per file, keyed by the file's path.\n *\n * A file with no entry is volatile: nothing here can tell whether it changed.\n * Component nodes take no entry — a component has no bytes of its own, and its\n * content is the file that declares it, which it already depends on.\n */\n readonly content: ReadonlyMap<string, Digest>;\n\n /** Restrict the closure to these edge kinds. Every kind by default. */\n readonly through?: Iterable<EdgeKind>;\n}\n\nexport interface Closure {\n /** `keyOf(kind, name)` → the digest of everything that node rests on. */\n readonly digests: ReadonlyMap<string, Digest>;\n\n /**\n * Nodes whose content was not supplied, and everything resting on them.\n *\n * Not an error. It is the honest half of the answer, and a caller that ignores\n * it converts a missing input into a subject nobody observed.\n */\n readonly volatile: ReadonlySet<string>;\n}\n\n/**\n * Hash every node over its own content and its whole dependency closure.\n *\n * One pass of Tarjan's algorithm and one pass over the components it emits, which\n * is `O(n + m)` — the same cost as the traversal next door, for an answer that\n * survives a rebase.\n */\nexport function closureOf(input: ClosureInput): Closure {\n const { relations, content } = input;\n const allowed = allowedKinds(input.through);\n const count = relations.names.length;\n\n const { component, order } = condense(relations, allowed);\n const digests = new Map<string, Digest>();\n const unstable = new Set<string>();\n\n // One digest per component, so every member of a cycle carries the same one.\n const byComponent: Digest[] = [];\n const shakyComponent: boolean[] = [];\n\n // Tarjan emits a component only after everything it can reach, so a dependency\n // digest is always already computed by the time it is read here.\n for (const [id, members] of order.entries()) {\n const terms: string[] = [];\n const outward = new Set<string>();\n let shaky = false;\n\n for (const node of members) {\n const kind = NODE_KINDS[relations.kinds[node]!]!;\n const name = relations.names[node]!;\n const own = kind === 'file' ? content.get(name) : SETTLED;\n\n if (own === undefined) shaky = true;\n terms.push(`${kind}\\u0000${name}\\u0000${own ?? UNREAD}`);\n\n const { offset, target, kind: edgeKind } = relations.depends;\n for (let at = offset[node]!; at < offset[node + 1]!; at += 1) {\n if (allowed[edgeKind[at]!] !== 1) continue;\n\n const other = target[at]!;\n if (component[other] === id) continue;\n\n outward.add(`${EDGE_KINDS[edgeKind[at]!]!}\\u0000${byComponent[component[other]!]!}`);\n if (shakyComponent[component[other]!] === true) shaky = true;\n }\n }\n\n byComponent.push(\n digestCombine('relate.closure', [\n digestString([...terms].sort(byCodeUnit).join('\u0001')),\n digestString([...outward].sort(byCodeUnit).join('\u0001')),\n ]),\n );\n shakyComponent.push(shaky);\n }\n\n for (let node = 0; node < count; node += 1) {\n const key = keyOf(NODE_KINDS[relations.kinds[node]!]!, relations.names[node]!);\n digests.set(key, byComponent[component[node]!]!);\n if (shakyComponent[component[node]!] === true) unstable.add(key);\n }\n\n return { digests, volatile: unstable };\n}\n\n/** The term a node with content contributes when the content is not known. */\nconst UNREAD = '(unread)';\n\n/** The term a node that has no content of its own contributes. */\nconst SETTLED = '(declared)';\n\nexport interface Drift {\n /** Node keys whose closure is not provably the one it was. */\n readonly changed: readonly string[];\n /** The file paths among them. */\n readonly files: readonly string[];\n /** The component names among them. */\n readonly components: readonly string[];\n /** Node keys the earlier closure held and this one does not. */\n readonly gone: readonly string[];\n}\n\n/**\n * What changed between two closures, without consulting a repository.\n *\n * Three ways a node lands in `changed`, and only the first is a difference: its\n * digest differs, it is **new**, or it is **volatile** — the last because a node\n * whose inputs were not fully read has a digest that can match by accident, and a\n * selector that believed it would skip a subject on the strength of a hash over\n * bytes nobody hashed.\n */\nexport function driftedBetween(before: Closure, after: Closure): Drift {\n const changed: string[] = [];\n\n for (const [key, digest] of after.digests) {\n const was = before.digests.get(key);\n if (was === digest && !after.volatile.has(key) && !before.volatile.has(key)) continue;\n changed.push(key);\n }\n\n const gone = [...before.digests.keys()].filter((key) => !after.digests.has(key));\n\n return {\n changed: changed.sort(byCodeUnit),\n files: named(changed, 'file'),\n components: named(changed, 'component'),\n gone: gone.sort(byCodeUnit),\n };\n}\n\nfunction named(keys: readonly string[], kind: 'file' | 'component'): readonly string[] {\n const prefix = `${kind}:`;\n\n return keys\n .filter((key) => key.startsWith(prefix))\n .map((key) => key.slice(prefix.length))\n .sort(byCodeUnit);\n}\n\ninterface Condensation {\n /** Component index per node. */\n readonly component: Int32Array;\n /** Components in emission order — every one after everything it reaches. */\n readonly order: readonly (readonly NodeId[])[];\n}\n\n/**\n * Tarjan's strongly connected components, iteratively.\n *\n * Iterative because the recursion depth is the longest dependency chain in the\n * repository, and a deep monorepo would overflow a call stack on a structure whose\n * whole point is to be cheap. The emission order is the useful half: a component\n * appears only once everything it can reach has, which is the order a Merkle fold\n * needs and costs nothing extra to get.\n */\nfunction condense(relations: Relations, allowed: Uint8Array): Condensation {\n const count = relations.names.length;\n const { offset, target, kind } = relations.depends;\n\n const index = new Int32Array(count).fill(-1);\n const low = new Int32Array(count);\n const onStack = new Uint8Array(count);\n const component = new Int32Array(count).fill(-1);\n\n const pending: NodeId[] = [];\n const order: NodeId[][] = [];\n\n // Frames as two parallel arrays: the node, and how far through its edges it is.\n const frameNode: number[] = [];\n const frameEdge: number[] = [];\n let counter = 0;\n\n for (let root = 0; root < count; root += 1) {\n if (index[root] !== -1) continue;\n\n index[root] = counter;\n low[root] = counter;\n counter += 1;\n pending.push(root);\n onStack[root] = 1;\n frameNode.push(root);\n frameEdge.push(offset[root]!);\n\n while (frameNode.length > 0) {\n const node = frameNode[frameNode.length - 1]!;\n const at = frameEdge[frameEdge.length - 1]!;\n\n if (at < offset[node + 1]!) {\n frameEdge[frameEdge.length - 1] = at + 1;\n if (allowed[kind[at]!] !== 1) continue;\n\n const next = target[at]!;\n if (index[next] === -1) {\n index[next] = counter;\n low[next] = counter;\n counter += 1;\n pending.push(next);\n onStack[next] = 1;\n frameNode.push(next);\n frameEdge.push(offset[next]!);\n } else if (onStack[next] === 1) {\n if (index[next]! < low[node]!) low[node] = index[next]!;\n }\n continue;\n }\n\n frameNode.pop();\n frameEdge.pop();\n\n const parent = frameNode[frameNode.length - 1];\n if (parent !== undefined && low[node]! < low[parent]!) low[parent] = low[node]!;\n\n if (low[node] !== index[node]) continue;\n\n const members: NodeId[] = [];\n for (;;) {\n const member = pending.pop()!;\n onStack[member] = 0;\n component[member] = order.length;\n members.push(member);\n if (member === node) break;\n }\n order.push(members.sort((a, b) => a - b));\n }\n }\n\n return { component, order };\n}\n\n/**\n * The edge kinds a closure walks when the caller does not say.\n *\n * Listed rather than derived from `EDGE_KINDS`, and the difference is the whole\n * point. A digest is a claim about *which inputs* were folded, so changing the\n * fold silently changes every digest that exists — one edge kind added or\n * removed, and the next run finds nothing it can prove unchanged and re-observes\n * the entire suite while reporting a successful narrowing. Changing this list is\n * a decision with a cost, and it should read like one.\n *\n * `type` is not in it: a type-only import is erased before a render, so a change\n * behind one is not an input the render folded.\n *\n * `depends-on` is not in it either, for the opposite reason. It runs between two\n * packages, and a package node is a name rather than a content digest — an\n * install that changed is a fact the diff carries, never one a hash of names could\n * reveal. Folding it would add a term that cannot change and a walk that cannot\n * answer.\n */\nexport const CLOSURE_EDGES: readonly EdgeKind[] = [\n 'imports',\n 'reexports',\n 'dynamic',\n 'asset',\n 'declared-in',\n];\n\n/** Edge kinds as a lookup, so the inner loop tests a byte. */\nfunction allowedKinds(through: Iterable<EdgeKind> | undefined): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n\n for (const kind of through ?? CLOSURE_EDGES) {\n const at = EDGE_KINDS.indexOf(kind);\n if (at !== -1) allowed[at] = 1;\n }\n\n return allowed;\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
1
+ {"version":3,"file":"merkle.js","sourceRoot":"","sources":["../../src/relate/merkle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The graph, hashed the way a build system hashes an action's inputs.\n *\n * Reachability answers *what could this diff affect*. It needs a diff, which\n * means it needs `git`, a ref that exists, a checkout deep enough to contain it,\n * and the assumption that the ref is where this branch actually diverged. Every\n * one of those is a thing CI gets wrong, and each failure narrows a run.\n *\n * A Merkle digest asks a different question and needs none of them: **is this\n * subject's entire input closure byte-identical to the one that produced the\n * baseline?** One digest per node, over the node's own content and the digests of\n * everything it rests on — the same construction `bazel` uses to key an action,\n * and the same idea as this project's own content addressing, applied one level\n * out from the document to the source that produced it.\n *\n * What the difference buys:\n *\n * | situation | reachability | closure digest |\n * |---|---|---|\n * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |\n * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |\n * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |\n * | a dependency changed two hops away | affected, and observed | different digest, and observed |\n * | *why* a subject is being observed | a chain of files | a digest that differs |\n *\n * Neither replaces the other. The trail is the explanation, and the digest is the\n * proof; a run wants the digest to decide and the trail to justify.\n *\n * ## Cycles\n *\n * A dependency graph has them and a Merkle tree cannot. Strongly connected\n * components are condensed and hashed as a unit, so every file in a cycle carries\n * the same digest — which is exactly the truth about a cycle: no member of one can\n * be called unchanged while another changed.\n *\n * ## The digest that must never lie\n *\n * A digest says *these inputs are the same*. A file whose content was not supplied\n * breaks that claim — its bytes may have changed with no digest in this structure\n * changing. Those nodes are marked **volatile**, the mark propagates to everything\n * that rests on them, and a volatile node is treated as changed however its digest\n * compares. A cache that cannot be trusted must not be silently trusted.\n *\n * A file whose imports could not all be read is not volatile. Its bytes are\n * hashed, and the edges that were read are folded; the one nobody could read is\n * left to a recorded run, which sees the module load whatever named it.\n */\n\nimport { digestCombine, digestString, type Digest } from '../format/hash.js';\nimport {\n EDGE_KINDS,\n NODE_KINDS,\n keyOf,\n type EdgeKind,\n type NodeId,\n type Relations,\n} from './graph.js';\n\nexport interface ClosureInput {\n readonly relations: Relations;\n\n /**\n * Content digest per file, keyed by the file's path.\n *\n * A file with no entry is volatile: nothing here can tell whether it changed.\n * Component nodes take no entry — a component has no bytes of its own, and its\n * content is the file that declares it, which it already depends on.\n */\n readonly content: ReadonlyMap<string, Digest>;\n\n /** Restrict the closure to these edge kinds. Every kind by default. */\n readonly through?: Iterable<EdgeKind>;\n}\n\nexport interface Closure {\n /** `keyOf(kind, name)` → the digest of everything that node rests on. */\n readonly digests: ReadonlyMap<string, Digest>;\n\n /**\n * Nodes whose content was not supplied, and everything resting on them.\n *\n * Not an error. It is the honest half of the answer, and a caller that ignores\n * it converts a missing input into a subject nobody observed.\n */\n readonly volatile: ReadonlySet<string>;\n}\n\n/**\n * Hash every node over its own content and its whole dependency closure.\n *\n * One pass of Tarjan's algorithm and one pass over the components it emits, which\n * is `O(n + m)` — the same cost as the traversal next door, for an answer that\n * survives a rebase.\n */\nexport function closureOf(input: ClosureInput): Closure {\n const { relations, content } = input;\n const allowed = allowedKinds(input.through);\n const count = relations.names.length;\n\n const { component, order } = condense(relations, allowed);\n const digests = new Map<string, Digest>();\n const unstable = new Set<string>();\n\n // One digest per component, so every member of a cycle carries the same one.\n const byComponent: Digest[] = [];\n const shakyComponent: boolean[] = [];\n\n // Tarjan emits a component only after everything it can reach, so a dependency\n // digest is always already computed by the time it is read here.\n for (const [id, members] of order.entries()) {\n const terms: string[] = [];\n const outward = new Set<string>();\n let shaky = false;\n\n for (const node of members) {\n const kind = NODE_KINDS[relations.kinds[node]!]!;\n const name = relations.names[node]!;\n const own = kind === 'file' ? content.get(name) : SETTLED;\n\n if (own === undefined) shaky = true;\n terms.push(`${kind}\\u0000${name}\\u0000${own ?? UNREAD}`);\n\n const { offset, target, kind: edgeKind } = relations.depends;\n for (let at = offset[node]!; at < offset[node + 1]!; at += 1) {\n if (allowed[edgeKind[at]!] !== 1) continue;\n\n const other = target[at]!;\n if (component[other] === id) continue;\n\n outward.add(`${EDGE_KINDS[edgeKind[at]!]!}\\u0000${byComponent[component[other]!]!}`);\n if (shakyComponent[component[other]!] === true) shaky = true;\n }\n }\n\n byComponent.push(\n digestCombine('relate.closure', [\n digestString([...terms].sort(byCodeUnit).join('\u0001')),\n digestString([...outward].sort(byCodeUnit).join('\u0001')),\n ]),\n );\n shakyComponent.push(shaky);\n }\n\n for (let node = 0; node < count; node += 1) {\n const key = keyOf(NODE_KINDS[relations.kinds[node]!]!, relations.names[node]!);\n digests.set(key, byComponent[component[node]!]!);\n if (shakyComponent[component[node]!] === true) unstable.add(key);\n }\n\n return { digests, volatile: unstable };\n}\n\n/** The term a node with content contributes when the content is not known. */\nconst UNREAD = '(unread)';\n\n/** The term a node that has no content of its own contributes. */\nconst SETTLED = '(declared)';\n\nexport interface Drift {\n /** Node keys whose closure is not provably the one it was. */\n readonly changed: readonly string[];\n /** The file paths among them. */\n readonly files: readonly string[];\n /** The component names among them. */\n readonly components: readonly string[];\n /** Node keys the earlier closure held and this one does not. */\n readonly gone: readonly string[];\n}\n\n/**\n * What changed between two closures, without consulting a repository.\n *\n * Three ways a node lands in `changed`, and only the first is a difference: its\n * digest differs, it is **new**, or it is **volatile** — the last because a node\n * whose inputs were not fully read has a digest that can match by accident, and a\n * selector that believed it would skip a subject on the strength of a hash over\n * bytes nobody hashed.\n */\nexport function driftedBetween(before: Closure, after: Closure): Drift {\n const changed: string[] = [];\n\n for (const [key, digest] of after.digests) {\n const was = before.digests.get(key);\n if (was === digest && !after.volatile.has(key) && !before.volatile.has(key)) continue;\n changed.push(key);\n }\n\n const gone = [...before.digests.keys()].filter((key) => !after.digests.has(key));\n\n return {\n changed: changed.sort(byCodeUnit),\n files: named(changed, 'file'),\n components: named(changed, 'component'),\n gone: gone.sort(byCodeUnit),\n };\n}\n\nfunction named(keys: readonly string[], kind: 'file' | 'component'): readonly string[] {\n const prefix = `${kind}:`;\n\n return keys\n .filter((key) => key.startsWith(prefix))\n .map((key) => key.slice(prefix.length))\n .sort(byCodeUnit);\n}\n\ninterface Condensation {\n /** Component index per node. */\n readonly component: Int32Array;\n /** Components in emission order — every one after everything it reaches. */\n readonly order: readonly (readonly NodeId[])[];\n}\n\n/**\n * Tarjan's strongly connected components, iteratively.\n *\n * Iterative because the recursion depth is the longest dependency chain in the\n * repository, and a deep monorepo would overflow a call stack on a structure whose\n * whole point is to be cheap. The emission order is the useful half: a component\n * appears only once everything it can reach has, which is the order a Merkle fold\n * needs and costs nothing extra to get.\n */\nfunction condense(relations: Relations, allowed: Uint8Array): Condensation {\n const count = relations.names.length;\n const { offset, target, kind } = relations.depends;\n\n const index = new Int32Array(count).fill(-1);\n const low = new Int32Array(count);\n const onStack = new Uint8Array(count);\n const component = new Int32Array(count).fill(-1);\n\n const pending: NodeId[] = [];\n const order: NodeId[][] = [];\n\n // Frames as two parallel arrays: the node, and how far through its edges it is.\n const frameNode: number[] = [];\n const frameEdge: number[] = [];\n let counter = 0;\n\n for (let root = 0; root < count; root += 1) {\n if (index[root] !== -1) continue;\n\n index[root] = counter;\n low[root] = counter;\n counter += 1;\n pending.push(root);\n onStack[root] = 1;\n frameNode.push(root);\n frameEdge.push(offset[root]!);\n\n while (frameNode.length > 0) {\n const node = frameNode[frameNode.length - 1]!;\n const at = frameEdge[frameEdge.length - 1]!;\n\n if (at < offset[node + 1]!) {\n frameEdge[frameEdge.length - 1] = at + 1;\n if (allowed[kind[at]!] !== 1) continue;\n\n const next = target[at]!;\n if (index[next] === -1) {\n index[next] = counter;\n low[next] = counter;\n counter += 1;\n pending.push(next);\n onStack[next] = 1;\n frameNode.push(next);\n frameEdge.push(offset[next]!);\n } else if (onStack[next] === 1) {\n if (index[next]! < low[node]!) low[node] = index[next]!;\n }\n continue;\n }\n\n frameNode.pop();\n frameEdge.pop();\n\n const parent = frameNode[frameNode.length - 1];\n if (parent !== undefined && low[node]! < low[parent]!) low[parent] = low[node]!;\n\n if (low[node] !== index[node]) continue;\n\n const members: NodeId[] = [];\n for (;;) {\n const member = pending.pop()!;\n onStack[member] = 0;\n component[member] = order.length;\n members.push(member);\n if (member === node) break;\n }\n order.push(members.sort((a, b) => a - b));\n }\n }\n\n return { component, order };\n}\n\n/**\n * The edge kinds a closure walks when the caller does not say.\n *\n * Listed rather than derived from `EDGE_KINDS`, and the difference is the whole\n * point. A digest is a claim about *which inputs* were folded, so changing the\n * fold silently changes every digest that exists — one edge kind added or\n * removed, and the next run finds nothing it can prove unchanged and re-observes\n * the entire suite while reporting a successful narrowing. Changing this list is\n * a decision with a cost, and it should read like one.\n *\n * `type` is not in it: a type-only import is erased before a render, so a change\n * behind one is not an input the render folded. `depends` is: the module's author\n * declared that it reads the file, which makes the file's bytes an input.\n *\n * `depends-on` is not in it either, for the opposite reason. It runs between two\n * packages, and a package node is a name rather than a content digest — an\n * install that changed is a fact the diff carries, never one a hash of names could\n * reveal. Folding it would add a term that cannot change and a walk that cannot\n * answer.\n */\nexport const CLOSURE_EDGES: readonly EdgeKind[] = [\n 'imports',\n 'reexports',\n 'dynamic',\n 'asset',\n 'declared-in',\n 'depends',\n];\n\n/** Edge kinds as a lookup, so the inner loop tests a byte. */\nfunction allowedKinds(through: Iterable<EdgeKind> | undefined): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n\n for (const kind of through ?? CLOSURE_EDGES) {\n const at = EDGE_KINDS.indexOf(kind);\n if (at !== -1) allowed[at] = 1;\n }\n\n return allowed;\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@variance-authority/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.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",