@graphty/graphty-element 2.0.1 → 2.2.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 (76) hide show
  1. package/README.md +3 -3
  2. package/dist/ai.js +4 -4
  3. package/dist/catalog.js +5 -5
  4. package/dist/chunks/{AiManager-awS0MNr1.js → AiManager-DWwQiBQu.js} +5 -5
  5. package/dist/chunks/{Algorithm-BdcqHKps.js → Algorithm-RQ629NLb.js} +210 -92
  6. package/dist/chunks/{DataSource-DK4GZBKg.js → DataSource-DEg3igzS.js} +3 -3
  7. package/dist/chunks/{GraphSession-D87eymV8.js → GraphSession-CtqyVNsw.js} +2518 -2239
  8. package/dist/chunks/{GraphtyError-B3eKs4yg.js → GraphtyError-BwcnblTH.js} +10 -8
  9. package/dist/chunks/{GraphtyLogger-CK03SnJi.js → GraphtyLogger-5KEttFUo.js} +1 -1
  10. package/dist/chunks/{NodeStyle-CS7dj20m.js → NodeStyle-Cup9O1lu.js} +2 -1
  11. package/dist/chunks/{VoiceInputAdapter-CcnCmxo5.js → VoiceInputAdapter-mD0x3z7t.js} +1 -1
  12. package/dist/chunks/{XRPivotCameraController-UjMsbran.js → XRPivotCameraController-Clil9NhV.js} +2 -2
  13. package/dist/chunks/{cameras-BeIAlMWR.js → cameras-DFXNYBze.js} +2 -2
  14. package/dist/chunks/{capability-check-DcKFwS6q.js → capability-check-dXwQ_z5B.js} +1 -1
  15. package/dist/chunks/{detect-B-YbbP7i.js → detect-CqN0mC6u.js} +3 -3
  16. package/dist/chunks/{format-detection-DohSEZnb.js → format-detection-C8vbCSlS.js} +1 -1
  17. package/dist/chunks/{index-uohvGegX.js → index-2VIpMJZP.js} +1576 -1211
  18. package/dist/chunks/{paletteRegistry-B-oS4YxP.js → paletteRegistry-NrWOKT-A.js} +17 -17
  19. package/dist/chunks/{registry-DU-e49Y2.js → registry-CSba5QGJ.js} +1 -1
  20. package/dist/chunks/{scales-DvGid5Ln.js → scales-BUS7NWy2.js} +2206 -1357
  21. package/dist/custom-elements.json +1 -1
  22. package/dist/extend.js +26 -27
  23. package/dist/graphty-catalog.json +7 -4
  24. package/dist/graphty.bundle.js +36288 -34368
  25. package/dist/graphty.js +66 -64
  26. package/dist/index.d.ts +1 -1
  27. package/dist/logging.js +2 -2
  28. package/dist/schema.js +1 -1
  29. package/dist/session.d.ts +2 -1
  30. package/dist/session.js +37 -33
  31. package/dist/src/Edge.d.ts +1 -1
  32. package/dist/src/Graph.d.ts +41 -7
  33. package/dist/src/Node.d.ts +1 -1
  34. package/dist/src/acceleration/AccelerationController.d.ts +27 -0
  35. package/dist/src/acceleration/index.d.ts +1 -1
  36. package/dist/src/acceleration/narrow.d.ts +56 -0
  37. package/dist/src/acceleration/types.d.ts +88 -7
  38. package/dist/src/algorithms/Algorithm.d.ts +96 -2
  39. package/dist/src/algorithms/BFSAlgorithm.d.ts +9 -0
  40. package/dist/src/algorithms/DijkstraAlgorithm.d.ts +2 -8
  41. package/dist/src/algorithms/PageRankAlgorithm.d.ts +8 -0
  42. package/dist/src/algorithms/utils/graphUtils.d.ts +13 -0
  43. package/dist/src/cameras/OrbitCameraController.d.ts +1 -0
  44. package/dist/src/catalog/layouts.d.ts +13 -1
  45. package/dist/src/config/GraphBehavior.d.ts +13 -0
  46. package/dist/src/config/GraphStyle.d.ts +1 -1
  47. package/dist/src/config/StyleTemplate.d.ts +4 -0
  48. package/dist/src/data/CSVDataSource.d.ts +1 -0
  49. package/dist/src/data/csv-variant-detection.d.ts +11 -0
  50. package/dist/src/errors/codes.d.ts +14 -1
  51. package/dist/src/events.d.ts +17 -4
  52. package/dist/src/graphty-element.d.ts +48 -3
  53. package/dist/src/layout/ForceAtlas2LayoutEngine.d.ts +28 -45
  54. package/dist/src/layout/LayoutEngine.d.ts +7 -7
  55. package/dist/src/layout/SimulationLayoutEngine.d.ts +458 -0
  56. package/dist/src/layout/SpringElectricalLayoutEngine.d.ts +32 -0
  57. package/dist/src/layout/SpringLayoutEngine.d.ts +22 -32
  58. package/dist/src/managers/EventManager.d.ts +7 -0
  59. package/dist/src/managers/GraphContext.d.ts +6 -0
  60. package/dist/src/managers/LayoutManager.d.ts +98 -4
  61. package/dist/src/managers/RenderManager.d.ts +7 -0
  62. package/dist/src/managers/UpdateManager.d.ts +1 -1
  63. package/dist/src/session/catalog.d.ts +2 -2
  64. package/dist/src/session/query.d.ts +83 -0
  65. package/dist/src/session/runs/types.d.ts +10 -4
  66. package/dist/src/session/selection/targets.d.ts +20 -3
  67. package/dist/src/session/styles/StylesApi.d.ts +18 -0
  68. package/dist/src/session/styles/legend.d.ts +9 -0
  69. package/dist/src/session/styles/predicate.d.ts +11 -0
  70. package/dist/src/session/styles/sources.d.ts +16 -0
  71. package/dist/src/session/types.d.ts +38 -2
  72. package/dist/src/testing/fakeAccelerator.d.ts +323 -0
  73. package/dist/webgpu.d.ts +23 -1
  74. package/dist/webgpu.js +70 -32
  75. package/package.json +10 -8
  76. package/dist/chunks/types-Dwm9waL2.js +0 -7
@@ -1,42 +1,32 @@
1
- import { z } from "zod/v4";
1
+ /**
2
+ * @file The Spring layout: Fruchterman-Reingold, computed on the CPU or on an accelerator.
3
+ *
4
+ * It used to be a one-shot pass -- run `springLayout` over a node and edge list, publish the
5
+ * answer, stop -- which is why the catalogue called it a batch layout. It is now a steppable
6
+ * simulation from `@graphty/layout`, so it keeps running until the arrangement settles and reheats
7
+ * on a drag or a pin, and the element decides at every load whether the simulation is the CPU's or
8
+ * the accelerator's.
9
+ */
10
+ import type { SimulationType } from "@graphty/layout";
2
11
  import { type OptionsSchema } from "../config";
3
- import { SimpleLayoutEngine } from "./LayoutEngine";
4
- declare const SpringLayoutConfig: z.ZodObject<{
5
- k: z.ZodDefault<z.ZodUnion<[z.ZodNumber, z.ZodNull]>>;
6
- pos: z.ZodDefault<z.ZodUnion<[z.ZodRecord<z.ZodNumber, z.ZodArray<z.ZodNumber>>, z.ZodNull]>>;
7
- fixed: z.ZodDefault<z.ZodUnion<[z.ZodArray<z.ZodNumber>, z.ZodNull]>>;
8
- iterations: z.ZodDefault<z.ZodNumber>;
9
- scale: z.ZodDefault<z.ZodNumber>;
10
- center: z.ZodDefault<z.ZodUnion<[z.ZodArray<z.ZodNumber>, z.ZodNull]>>;
11
- dim: z.ZodDefault<z.ZodNumber>;
12
- seed: z.ZodDefault<z.ZodUnion<[z.ZodNumber, z.ZodNull]>>;
13
- scalingFactor: z.ZodDefault<z.ZodNumber>;
14
- }, z.core.$strict>;
15
- type SpringLayoutConfigType = z.infer<typeof SpringLayoutConfig>;
16
- type SpringLayoutOpts = Partial<SpringLayoutConfigType>;
12
+ import { SimulationLayoutEngine } from "./SimulationLayoutEngine";
17
13
  /**
18
- * Spring layout engine using Fruchterman-Reingold force-directed algorithm
14
+ * The Spring engine, as the element declares it.
15
+ *
16
+ * Every member is a static the element reads: `LayoutManager` builds the bridge itself, with the
17
+ * graph's acceleration controller, so this class never runs a layout of its own. It declares no
18
+ * `static descriptor` because its arrangement is authored in the layout catalogue, where it sits
19
+ * under `force`.
19
20
  */
20
- export declare class SpringLayout extends SimpleLayoutEngine {
21
+ export declare class SpringLayout extends SimulationLayoutEngine {
21
22
  static type: string;
23
+ static simulationType: SimulationType;
22
24
  static maxDimensions: number;
23
25
  static zodOptionsSchema: OptionsSchema;
24
- scalingFactor: number;
25
- config: SpringLayoutConfigType;
26
- /**
27
- * Create a spring layout engine
28
- * @param opts - Configuration options including spring constant and iterations
29
- */
30
- constructor(opts: SpringLayoutOpts);
31
26
  /**
32
- * Get dimension-specific options for spring layout
33
- * @param dimension - The desired dimension (2 or 3)
34
- * @returns Options object with dim parameter
27
+ * Get dimension-specific options for spring layout.
28
+ * @param dimension - The desired dimension (2 or 3).
29
+ * @returns Options object with dim parameter.
35
30
  */
36
31
  static getOptionsForDimension(dimension: 2 | 3): object;
37
- /**
38
- * Compute node positions using spring-based force simulation
39
- */
40
- doLayout(): void;
41
32
  }
42
- export {};
@@ -95,6 +95,13 @@ export declare class EventManager implements Manager {
95
95
  * @param report - freezeWithReport's report
96
96
  */
97
97
  emitSnapshotReplaced(graph: Graph | GraphContext, previous: GraphSnapshot | null, next: GraphSnapshot, report: FreezeReport): void;
98
+ /**
99
+ * Emit `snapshot-dropped`: the store was discarded and every snapshot it froze is gone.
100
+ *
101
+ * ELEMENT-INTERNAL, for the same reason `snapshot-replaced` is. Emitted while the outgoing
102
+ * store is still alive, so a listener releasing a snapshot can still derive from it.
103
+ */
104
+ emitSnapshotDropped(): void;
98
105
  /**
99
106
  * Emits a layout initialized event when a layout is ready
100
107
  * @param layoutType - Type of layout that was initialized
@@ -1,4 +1,5 @@
1
1
  import type { Scene } from "@babylonjs/core";
2
+ import type { AccelerationController } from "../acceleration";
2
3
  import type { XRConfig } from "../config/XRConfig";
3
4
  import type { MeshCache } from "../meshes/MeshCache";
4
5
  import type { Styles } from "../Styles";
@@ -92,6 +93,11 @@ export interface GraphContext {
92
93
  * @since 1.5.0
93
94
  */
94
95
  getEventManager?(): EventManager | undefined;
96
+ /**
97
+ * The acceleration controller the graph owns; absent on a context built without a graph.
98
+ * @since 2.0.0
99
+ */
100
+ getAcceleration?(): AccelerationController;
95
101
  }
96
102
  /**
97
103
  * Configuration options accessible through GraphContext
@@ -1,11 +1,37 @@
1
+ import type { SimulationType } from "@graphty/layout";
2
+ import type { GraphLayoutBehavior } from "../config/GraphBehavior";
1
3
  import type { Edge } from "../Edge";
2
4
  import { LayoutEngine } from "../layout/LayoutEngine";
5
+ import { type SimulationEngineOptions } from "../layout/SimulationLayoutEngine";
3
6
  import type { Node } from "../Node";
4
7
  import type { Styles } from "../Styles";
5
8
  import type { DataManager } from "./DataManager";
6
9
  import type { EventManager } from "./EventManager";
7
10
  import type { GraphContext } from "./GraphContext";
8
11
  import type { Manager } from "./interfaces";
12
+ /**
13
+ * Turn what the consumer asked a simulation layout for into what the bridge runs on.
14
+ *
15
+ * `scalingFactor` travels twice over, and deliberately. The simulation gets it as `scale`, where
16
+ * it sizes the seed and, in ForceAtlas2, the units the arrangement is computed in; the BRIDGE gets
17
+ * it as the radius it publishes that arrangement at, which is the only one of the two a reader
18
+ * sees in the picture. A layout that publishes no such control takes
19
+ * {@link DEFAULT_SCALING_FACTOR}. What the bridge is given is {@link publishedRadius}, every
20
+ * size multiplier the layout publishes rolled into one, because the refit fits the arrangement to
21
+ * that radius and leaves no other way for a reader to change how big the graph comes out.
22
+ *
23
+ * The four knobs the frame loop needs -- `iterationsPerStep`, `maxInFlight`, `settleThreshold` and
24
+ * `settleWindow` -- are NOT in any layout's published schema, because they are the same four for
25
+ * every simulation and belong to how the element drives one rather than to the arrangement it
26
+ * draws. They are read from the options a caller passed, where a per-layout value overrides the
27
+ * behaviour configuration, and `behavior.layout` is where a host sets them once for every layout.
28
+ * @param type - Which simulation is being configured.
29
+ * @param options - The merged layout options, as the consumer set them.
30
+ * @param behavior - `behavior.layout`, which the frame-loop knobs default from.
31
+ * @returns The resolved options.
32
+ * @throws A `ZodError` when an option is outside the range its published schema declares.
33
+ */
34
+ export declare function resolveSimulationOptions(type: SimulationType, options: Record<string, unknown>, behavior: GraphLayoutBehavior): SimulationEngineOptions;
9
35
  /**
10
36
  * Manages layout engines and their lifecycle
11
37
  * Coordinates layout updates and transitions
@@ -34,10 +60,27 @@ export declare class LayoutManager implements Manager {
34
60
  */
35
61
  get running(): boolean;
36
62
  /**
37
- * Sets the running state of the layout
63
+ * Sets the running state of the layout.
64
+ *
65
+ * Going false -> true is "play", and a simulation that had SETTLED does nothing when it is
66
+ * stepped, so a reader who pressed play -- or let go of a node they dragged -- would watch a
67
+ * still graph. Resuming therefore reheats a settled simulation: the settle count starts
68
+ * again and the next frame moves nodes. Going true -> false only stops `step()` being
69
+ * called; batches already in flight land in the position array by themselves, nothing is
70
+ * disposed and nothing is released.
38
71
  */
39
72
  set running(value: boolean);
40
73
  private graphContext;
74
+ /**
75
+ * The graph's acceleration controller, once a context has arrived. A manager built without a
76
+ * graph -- which is what the manager's own unit tests build -- never gets one, and a
77
+ * simulation layout refuses to start on one.
78
+ */
79
+ private acceleration;
80
+ /** How to stop listening to the controller. */
81
+ private unsubscribeAcceleration;
82
+ /** How to stop listening for a new snapshot. */
83
+ private unsubscribeSnapshot;
41
84
  /**
42
85
  * Creates an instance of LayoutManager
43
86
  * @param eventManager - Event manager for emitting layout events
@@ -47,9 +90,33 @@ export declare class LayoutManager implements Manager {
47
90
  constructor(eventManager: EventManager, dataManager: DataManager, styles: Styles);
48
91
  /**
49
92
  * Set the GraphContext for error reporting
93
+ *
94
+ * This is also where the acceleration controller first becomes reachable: the manager is
95
+ * built before the graph finishes constructing itself, so it cannot be handed one.
50
96
  * @param context - GraphContext instance
51
97
  */
52
98
  setGraphContext(context: GraphContext): void;
99
+ /**
100
+ * Rebuild a running simulation on whatever the controller holds now.
101
+ *
102
+ * Every transition reaches here: an accelerator attached, detached, lost or recovered. The
103
+ * try is load-bearing. Under the `required` policy a detach cannot reach a CPU simulation --
104
+ * the controller's `plan()` throws `E_NO_ACCELERATOR` instead, which is the point of the
105
+ * policy -- and the throw has to be REPORTED here rather than escaping into the controller's
106
+ * listener loop, whose own catch would log it and continue. The layout then stops, and the
107
+ * next transition that attaches an accelerator brings it back.
108
+ */
109
+ private onAccelerationChange;
110
+ /**
111
+ * Hand a running simulation the snapshot that has just replaced the one it was laying out.
112
+ * @param event - The freeze, carrying the snapshot every consumer must switch to.
113
+ */
114
+ private onSnapshotReplaced;
115
+ /**
116
+ * Report a failure that happened to a layout already running, on the element's error channel.
117
+ * @param error - Whatever was thrown.
118
+ */
119
+ private reportSimulationFailure;
53
120
  /**
54
121
  * Update the styles reference when a new style template is loaded
55
122
  * @param styles - New styles instance
@@ -103,6 +170,9 @@ export declare class LayoutManager implements Manager {
103
170
  * @param type - The layout that failed.
104
171
  * @param error - Whatever was thrown.
105
172
  * @param phase - What the element was doing, in a word that fits "the layout could not be ...".
173
+ * `"stepped"` is the one that happens to a layout already running: a simulation's batch
174
+ * rejected minutes after set-up finished, and calling that "initialised" reads as a failure
175
+ * to start.
106
176
  * @returns The error to throw.
107
177
  */
108
178
  private reportLayoutFailure;
@@ -120,7 +190,19 @@ export declare class LayoutManager implements Manager {
120
190
  * `preSteps` is what makes a screenshot of a physics layout the same picture twice: an
121
191
  * unstepped force layout is a graph in mid-flight, and how far it has flown depends on when
122
192
  * the picture was taken.
193
+ *
194
+ * A SIMULATION CANNOT BE STEPPED IN A TIGHT SYNCHRONOUS LOOP, which is why this is
195
+ * asynchronous and why both the path that spends the pre-steps when a layout is built and the
196
+ * path that pays owed ones later come through here rather than each writing their own loop. A
197
+ * simulation's step is a BATCH: an accelerated one is fire-and-forget, and a simulation that
198
+ * already has a few batches in flight returns the oldest of them instead of submitting
199
+ * another, so a loop that calls `step()` a thousand times without waiting has all but the
200
+ * first couple coalesced away and draws a graph the consumer asked to be far more arranged
201
+ * than it is -- silently, because a coalesced batch is not an error. So a simulation is
202
+ * awaited one chunk at a time, the chunk being the largest batch the GPU package takes, and
203
+ * every other engine keeps the plain loop, whose `step()` returns having done the work.
123
204
  * @param engine - the engine to settle.
205
+ * @returns A promise that resolves once every pre-step has been taken and published.
124
206
  */
125
207
  private spendPreSteps;
126
208
  /**
@@ -136,15 +218,27 @@ export declare class LayoutManager implements Manager {
136
218
  *
137
219
  * So the count is owed rather than spent, and paid here: on the first frame at which there is
138
220
  * a node to move. This runs inside the render loop's update, which is called before
139
- * `scene.render()`, so the steps really are taken before the frame is drawn -- and because it
140
- * waits for a node rather than for a particular call, it does not matter whether the data
141
- * arrived in one batch, in ten, or from a fetch that finished a second later.
221
+ * `scene.render()`, so an engine that steps on the spot really is stepped before the frame is
222
+ * drawn -- and because it waits for a node rather than for a particular call, it does not
223
+ * matter whether the data arrived in one batch, in ten, or from a fetch that finished a
224
+ * second later. A simulation's batches land over the next few frames instead, because no
225
+ * caller inside a render loop can wait for a device; what {@link LayoutManager.spendPreSteps}
226
+ * guarantees for one is that every owed iteration is computed rather than coalesced away.
142
227
  */
143
228
  private runPreStepsOnceThereIsSomethingToStep;
144
229
  /**
145
230
  * Step the layout engine forward
146
231
  */
147
232
  step(): void;
233
+ /**
234
+ * Step the layout ONCE, whatever the frame loop's multiplier is.
235
+ *
236
+ * The name is the difference a reader of `UpdateManager` needs: a simulation layout does its
237
+ * whole frame's work in one call -- the batch it submits computes `iterationsPerStep`
238
+ * iterations -- so stepping it `stepMultiplier` times a frame would queue work the device
239
+ * cannot retire.
240
+ */
241
+ stepBatch(): void;
148
242
  /**
149
243
  * Get node position from layout engine
150
244
  * @param node - Node to get position for
@@ -23,6 +23,13 @@ export declare class RenderManager implements Manager {
23
23
  private renderLoopActive;
24
24
  private updateCallback?;
25
25
  private resizeHandler;
26
+ /**
27
+ * Stands in for Babylon's own pointer handling, which calls preventDefault and then
28
+ * `canvas.focus()` on every pointer down and up. That focus call scrolls the host page to the
29
+ * canvas. This one does the same thing without scrolling.
30
+ * @param evt - The pointer down or up event on the canvas
31
+ */
32
+ private focusOnPointer;
26
33
  /**
27
34
  * Creates a new render manager for Babylon.js scene and rendering
28
35
  * @param canvas - HTML canvas element for rendering
@@ -330,7 +330,7 @@ export declare class UpdateManager implements Manager {
330
330
  /**
331
331
  * Update the graph for the current frame.
332
332
  *
333
- * The pass itself is {@link UpdateManager.runUpdatePass}; what is added here is the one
333
+ * The pass itself is the private `runUpdatePass()`; what is added here is the one
334
334
  * question a consumer cares about and the pass has several exits from -- whether the state it
335
335
  * leaves behind is a finished picture.
336
336
  */
@@ -29,8 +29,8 @@
29
29
  * already been run here. So a session's catalogue is the shared tables with its own `metrics()`
30
30
  * closed over its own graph.
31
31
  *
32
- * The rest of the graph-dependent half -- resolved option bounds, expression validation -- still
33
- * waits on the query engine.
32
+ * The rest of the graph-dependent half -- resolved option bounds, expression validation -- is not
33
+ * wired to the session's query engine yet.
34
34
  */
35
35
  import type { AlgorithmDescriptor } from "../catalog/types";
36
36
  import { type MetricsSource } from "./metrics";
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file The session's one query engine: `{ where }` predicates and text search.
3
+ *
4
+ * A scope, a selection, a visibility filter and an edge filter all accept an expression, and a
5
+ * selection also accepts typed text. Every one of them reaches THIS object, and this object
6
+ * evaluates an expression with the compiler the style layers use (`./styles/predicate`) over the
7
+ * selector source the style layers read (`./styles/sources`). That is the point of it: a layer
8
+ * selector and a scope with the same text match the same elements, because they are the same
9
+ * code reading the same values. A second evaluator would drift from the first in exactly the
10
+ * places nobody tests -- JMESPath truthiness, a column default, an edge's endpoints.
11
+ *
12
+ * A path nothing answers matches nothing and is REPORTED through `unresolvedPathsOf`, never
13
+ * swallowed: "0 matched" and "you misspelled the attribute" must not look the same.
14
+ *
15
+ * Nothing here reaches Babylon.js, Lit or the DOM.
16
+ */
17
+ import type { GraphSnapshot } from "@graphty/graph-format";
18
+ import type { EdgeId, NodeId, Path, Query } from "../catalog/types";
19
+ import type { SelectionMatch, SelectionSearchHit, SelectionTextMode } from "./selection";
20
+ import { type SelectorSource, type SelectorTarget } from "./styles/predicate";
21
+ /** Everything the engine reads. */
22
+ interface QueryEngineParts {
23
+ /**
24
+ * The snapshot the dense indices address, read on every query.
25
+ * @returns The snapshot as it stands now.
26
+ */
27
+ readonly snapshot: () => GraphSnapshot;
28
+ /** Where values are read: the same source the style layers read. */
29
+ readonly elements: Required<Pick<SelectorSource, "edgeIdOf" | "nodeIdOf">> & SelectorSource;
30
+ /**
31
+ * Whether this session answers a path for one kind of element. Absent, no path is reported.
32
+ * @param path - The path as the query spelled it.
33
+ * @param target - Which kind of element.
34
+ * @returns True when something in the session answers it.
35
+ */
36
+ readonly answers?: (path: Path, target: SelectorTarget) => boolean;
37
+ /**
38
+ * The node attribute paths a text search reads, beside the id.
39
+ * @returns The paths, such as `data.label`.
40
+ */
41
+ readonly searchPaths: () => readonly Path[];
42
+ }
43
+ /** The session's query engine. */
44
+ export interface QueryEngine {
45
+ /**
46
+ * The nodes an expression matches.
47
+ * @param where - The expression.
48
+ * @returns The node ids, in index order.
49
+ */
50
+ nodes(where: Query): NodeId[];
51
+ /**
52
+ * The edges an expression matches.
53
+ * @param where - The expression.
54
+ * @returns The edge ids, in index order.
55
+ */
56
+ edges(where: Query): EdgeId[];
57
+ /**
58
+ * Both halves an expression matches, and the paths it names that nothing answers.
59
+ * @param where - The expression.
60
+ * @returns The match.
61
+ */
62
+ select(where: Query): SelectionMatch;
63
+ /**
64
+ * The paths an expression names that no node and no edge in this session answers.
65
+ * @param where - The expression.
66
+ * @returns The unresolved paths.
67
+ */
68
+ unresolvedPathsOf(where: Query): readonly Path[];
69
+ /**
70
+ * The nodes a text search finds.
71
+ * @param text - What was typed.
72
+ * @param mode - How to match it.
73
+ * @returns The hits, in index order.
74
+ */
75
+ find(text: string, mode: SelectionTextMode): SelectionSearchHit[];
76
+ }
77
+ /**
78
+ * Build the query engine over a session's selector source.
79
+ * @param parts - The snapshot, the value source, the path directory and the searchable paths.
80
+ * @returns The engine.
81
+ */
82
+ export declare function createQueryEngine(parts: QueryEngineParts): QueryEngine;
83
+ export {};
@@ -465,10 +465,16 @@ export interface Run<T = RunResult> extends PromiseLike<T> {
465
465
  readonly shape: ResultShape;
466
466
  /** What qualifies the numbers. */
467
467
  readonly caveats: Caveats;
468
- /** The result, once there is one. Awaiting the run is the other way to get it. */
469
- readonly result?: T;
470
- /** Why it failed, when it failed. */
471
- readonly error?: GraphtyError;
468
+ /**
469
+ * The result, once there is one. Awaiting the run is the other way to get it.
470
+ *
471
+ * Spelled `?: T | undefined` rather than `?: T` because the implementation answers with a
472
+ * getter, and under a consumer's `exactOptionalPropertyTypes` a getter that can return
473
+ * undefined does not satisfy a property that can only be absent or present.
474
+ */
475
+ readonly result?: T | undefined;
476
+ /** Why it failed, when it failed. Optional-or-undefined for the reason {@link Run.result} gives. */
477
+ readonly error?: GraphtyError | undefined;
472
478
  /** The frozen, structured-cloneable snapshot of everything above. */
473
479
  readonly record: RunRecord;
474
480
  /** The journal entry this run's command wrote, or null until it lands. */
@@ -32,7 +32,15 @@ import type { EdgeId, NodeId, Path, Query, Scope } from "../../catalog/types";
32
32
  import type { ResultsApi, RunRef } from "../results/types";
33
33
  import { ElementMask, type MaskIdSpace } from "../scope/ElementMask";
34
34
  import type { ScopeResolver } from "../scope/ScopeApi";
35
- /** How a text target decides whether an element matched. */
35
+ /**
36
+ * How a text target decides whether an element matched.
37
+ *
38
+ * - `substring`: the id or an attribute value contains the text, ignoring case.
39
+ * - `exact`: the id or an attribute value is the text, exactly.
40
+ * - `regex`: the id or an attribute value matches the text as a regular expression.
41
+ * - `attribute`: the text is `key:value`, and the node's `key` (or its id, for `id`) is the value,
42
+ * ignoring case. A key no node carries is searched as plain substring text.
43
+ */
36
44
  export type SelectionTextMode = "substring" | "exact" | "regex" | "attribute";
37
45
  /** Which way a neighbourhood target follows an edge. */
38
46
  export type SelectionDirection = "in" | "out" | "all";
@@ -70,14 +78,23 @@ export interface NeighborhoodTarget {
70
78
  * second ranking that could disagree with the one on screen.
71
79
  */
72
80
  export type SelectionTarget = ElementIdTarget | NeighborhoodTarget
73
- /** Every element a predicate matches. */
81
+ /** Every element a predicate matches, narrowed to a scope when one is named. */
74
82
  | {
75
83
  readonly where: Query;
84
+ readonly scope?: Scope;
76
85
  }
77
- /** Every element a text search finds. */
86
+ /**
87
+ * Every node a text search finds, narrowed to a scope when one is named.
88
+ *
89
+ * With no `mode`, the text may carry one as a prefix: `exact:`, `regex:`, or `<attribute>:`
90
+ * (`id:a17`, `type:person`), and anything else is a case-insensitive substring search over
91
+ * the node's id and its attribute values. See {@link SelectionTextMode}. A leading `=` makes
92
+ * the rest an expression, exactly as `{ where }` reads it, which selects edges as well.
93
+ */
78
94
  | {
79
95
  readonly text: string;
80
96
  readonly mode?: SelectionTextMode;
97
+ readonly scope?: Scope;
81
98
  }
82
99
  /** A pasted list of ids, which may name nodes, edges, or nothing at all. */
83
100
  | {
@@ -494,6 +494,24 @@ export interface StylesSources {
494
494
  */
495
495
  readonly onChange?: (change: StyleChange) => void;
496
496
  }
497
+ /**
498
+ * What a highlight paints when its caller names no style.
499
+ *
500
+ * A highlight is drawn OVER the default indigo node, the default darkgrey edge and the whitesmoke
501
+ * background, so it is chosen against those three rather than on its own. Okabe-Ito vermilion is
502
+ * at least Delta E 16 from each of them at normal vision and under all three kinds of colour
503
+ * blindness, and 3.5:1 against the background. The blue this replaced was Delta E 13 from the
504
+ * default node and under 4 for tritanopia, so a route through default nodes disappeared into
505
+ * them. `test/catalog/default-palette-quality.test.ts` measures it.
506
+ *
507
+ * Colour alone is not enough for an edge: a thin line at the default width reads as a thin line
508
+ * whatever its colour, so a highlighted edge is also drawn three times as wide. A node gets no
509
+ * size, because a size here would flatten whatever size encoding the layers beneath it drew.
510
+ */
511
+ export declare const DEFAULT_HIGHLIGHT: {
512
+ readonly color: "#D55E00";
513
+ readonly edgeWidth: number;
514
+ };
497
515
  /**
498
516
  * Build the style stack one session holds.
499
517
  * @param sources - What a selector compiles against, the element's own layers, the scales, the
@@ -184,6 +184,15 @@ export interface LegendSources {
184
184
  * @returns The words, or undefined when nothing in the session names that path.
185
185
  */
186
186
  readonly field?: (path: Path, target: SelectorTarget) => FieldWords | undefined;
187
+ /**
188
+ * Whether a layer above selects every element a layer below it selects.
189
+ *
190
+ * Absent, only a layer that selects everything is known to cover one below it.
191
+ * @param above - The higher layer.
192
+ * @param below - The lower layer.
193
+ * @returns True only when every element `below` selects is also selected by `above`.
194
+ */
195
+ readonly covers?: (above: Layer, below: Layer) => boolean;
187
196
  }
188
197
  /**
189
198
  * Read the legend out of the encoding model.
@@ -104,6 +104,17 @@ export interface SelectorSource {
104
104
  * @returns The id at that row.
105
105
  */
106
106
  readonly edgeIdOf?: (index: number) => EdgeId;
107
+ /**
108
+ * The dense indices that carry a value for one column, ascending, when the source can list
109
+ * them without a walk over every element. A session answers it for a run's column.
110
+ *
111
+ * Absent, or answering undefined, nothing asks the question another way: the legend then
112
+ * reports a layer as painted over only by a layer that selects everything.
113
+ * @param path - The column path.
114
+ * @param target - Whether the asking layer paints nodes or edges.
115
+ * @returns The indices, or undefined when the column cannot be enumerated.
116
+ */
117
+ readonly measured?: (path: Path, target: SelectorTarget) => ArrayLike<number> | undefined;
107
118
  }
108
119
  /**
109
120
  * One target's half of a {@link SelectorSource}, resolved once so the predicate never chooses.
@@ -132,6 +132,22 @@ export interface SessionSelectorSource extends SelectorSource {
132
132
  */
133
133
  readonly measured: (path: Path, target: SelectorTarget) => ArrayLike<number> | undefined;
134
134
  }
135
+ /**
136
+ * The id of the node at one end of an edge, for the attribute keys that name an endpoint.
137
+ *
138
+ * An edge's endpoints are graph structure, so the importer removes the keys they arrived under
139
+ * (`src`/`dst`, or whatever the id paths name) from the record a selector reads, to keep the data
140
+ * table from showing them twice. Without this, `data.source == 'A'` matched no edge at all and
141
+ * said nothing -- the empty answer looked exactly like a correct zero. So `source` and `target`
142
+ * are read from the snapshot for an edge whose record holds nothing under that key; an edge that
143
+ * really carries a `source` attribute (a provenance field, with its endpoints under `src`/`dst`)
144
+ * keeps it.
145
+ * @param graph - The snapshot the index addresses.
146
+ * @param index - The dense (logical) edge index, already bounded by the caller.
147
+ * @param key - The attribute key, without the `data.` prefix.
148
+ * @returns The endpoint's node id, or undefined when the key names no endpoint.
149
+ */
150
+ export declare function edgeEndpointOf(graph: GraphSnapshot, index: number, key: string): NodeId | undefined;
135
151
  /**
136
152
  * Build the selector source for one session.
137
153
  *
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { DerivedGraph, GraphSnapshot, NodeId } from "@graphty/graph-format";
16
16
  import type { z } from "zod/v4";
17
- import type { AccelerationCapabilities, AccelerationPolicy } from "../acceleration";
17
+ import type { AccelerationCapabilities, AccelerationPolicy, AccelerationStatus, GraphAccelerator } from "../acceleration";
18
18
  import type { AlgorithmKey, AttributeDescriptor, CatalogApi, EdgeId, RunId, Scope } from "../catalog/types";
19
19
  import type { DataConfig } from "../config/DataConfig";
20
20
  import type { ElementPositions } from "../data/positions";
@@ -349,7 +349,7 @@ export interface SessionDataApi {
349
349
  *
350
350
  * The rest of the graph-dependent half of {@link CatalogApi} -- what an option's bounds resolve to
351
351
  * over a scope, whether an expression references anything real -- is still absent rather than
352
- * stubbed, because the query engine it reads does not exist yet. A consumer discovers that gap by
352
+ * stubbed: the session's query engine exists, and the catalogue is not wired to it yet. A consumer discovers that gap by
353
353
  * autocomplete finding nothing, not by a call that throws.
354
354
  */
355
355
  export type SessionCatalogApi = Pick<CatalogApi, "algorithms" | "cameras" | "formats" | "layouts" | "logSinks" | "metrics" | "palettes" | "scales">;
@@ -409,6 +409,10 @@ export interface SessionEventMap {
409
409
  * looking at an old picture that reads as an answer.
410
410
  */
411
411
  "style:problem": StyleProblem;
412
+ /** Every acceleration transition; the document is the one `capabilities` returns. */
413
+ "capabilities:changed": {
414
+ readonly capabilities: AccelerationCapabilities;
415
+ };
412
416
  }
413
417
  /**
414
418
  * A painting the element started for itself, and why it did not land.
@@ -500,6 +504,22 @@ export interface GraphSession {
500
504
  readonly config: SessionConfig;
501
505
  /** What this machine can do, measured rather than guessed at by the consumer. */
502
506
  readonly capabilities: AccelerationCapabilities;
507
+ /**
508
+ * What the consumer asks of the hardware: use an accelerator when there is one, never look,
509
+ * or refuse to run without one.
510
+ *
511
+ * Settable, and the set applies at once: the next piece of accelerated work is planned under
512
+ * the new policy, and `capabilities:changed` reports where that left the hardware.
513
+ */
514
+ acceleration: AccelerationPolicy;
515
+ /**
516
+ * Attach an accelerator the caller built, or detach the current one with `null`.
517
+ *
518
+ * For tests and third parties. An injected accelerator is never replaced by a probed one and
519
+ * is not disposed by the session -- whoever built it owns its lifetime.
520
+ * @param accelerator - The accelerator to attach, or null to detach.
521
+ */
522
+ setAccelerator(accelerator: GraphAccelerator | null): void;
503
523
  /**
504
524
  * The current snapshot, by reference: nothing is copied.
505
525
  * @returns the immutable graph-format snapshot
@@ -665,6 +685,22 @@ export interface AccelerationControllerLike {
665
685
  readonly policy: AccelerationPolicy;
666
686
  /** The node count at or above which accelerated work uses the accelerator. */
667
687
  readonly minNodes: number;
688
+ /**
689
+ * Changes what the consumer asks of the hardware.
690
+ * @param policy - The new policy.
691
+ */
692
+ setPolicy(policy: AccelerationPolicy): void;
693
+ /**
694
+ * Attaches an accelerator the caller built, or detaches the current one with `null`.
695
+ * @param accelerator - The accelerator, or null to detach.
696
+ */
697
+ setAccelerator(accelerator: GraphAccelerator | null): void;
698
+ /**
699
+ * Watches every transition.
700
+ * @param listener - Called with the new status.
701
+ * @returns A function that stops the subscription.
702
+ */
703
+ onChange(listener: (status: AccelerationStatus) => void): () => void;
668
704
  /** Releases the hardware. */
669
705
  dispose(): void;
670
706
  }