@modernrelay/orbit-core 0.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.
- package/LICENSE +21 -0
- package/dist/chunk-Z7FOEASL.js +141 -0
- package/dist/chunk-Z7FOEASL.js.map +1 -0
- package/dist/clusters-ExqnvobT.d.ts +123 -0
- package/dist/engine.d.ts +1 -0
- package/dist/engine.js +3 -0
- package/dist/engine.js.map +1 -0
- package/dist/index-BPjuELfY.d.ts +1198 -0
- package/dist/index.d.ts +2995 -0
- package/dist/index.js +11938 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +163 -0
- package/dist/testing.js +384 -0
- package/dist/testing.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error taxonomy (spec §6.5, v0.2 subset of call sites).
|
|
3
|
+
*
|
|
4
|
+
* Structural fatality rule: a *transient* context loss never mints a
|
|
5
|
+
* GraphError — it is a status transition ('lost') plus a 'context-lost'
|
|
6
|
+
* warning diagnostic, and the 'error' event does not fire. A GraphError with
|
|
7
|
+
* code 'context-lost' is constructed ONLY on terminal recovery failure, where
|
|
8
|
+
* it is always fatal. This keeps fatality structural rather than conditional.
|
|
9
|
+
*/
|
|
10
|
+
/** Placeholder until resource admission control lands (plan S4-T06). */
|
|
11
|
+
interface ResourceAdmissionReport {
|
|
12
|
+
reason: string;
|
|
13
|
+
estimatedBytes?: number;
|
|
14
|
+
limitBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Instance-level faults, delivered via the 'error' event's `detail`. */
|
|
17
|
+
type GraphError = {
|
|
18
|
+
code: 'engine-unsupported';
|
|
19
|
+
detail: string;
|
|
20
|
+
} | {
|
|
21
|
+
code: 'resource-limit';
|
|
22
|
+
detail: ResourceAdmissionReport;
|
|
23
|
+
fatal: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Minted only on terminal context-recovery failure — always fatal. */
|
|
26
|
+
| {
|
|
27
|
+
code: 'context-lost';
|
|
28
|
+
} | {
|
|
29
|
+
code: 'service-failed';
|
|
30
|
+
service: 'search' | 'expansion' | 'metrics' | 'path' | 'crossfilter';
|
|
31
|
+
cause: unknown;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Operation-scoped failures (spec §6.5): rejected promises / thrown calls.
|
|
35
|
+
* All ten codes are typed now even where their subsystems are post-v0.2 —
|
|
36
|
+
* the union is the public contract, the wiring arrives with each subsystem.
|
|
37
|
+
*/
|
|
38
|
+
type GraphOperationError = {
|
|
39
|
+
code: 'export-too-large';
|
|
40
|
+
elementCount: number;
|
|
41
|
+
limit: number;
|
|
42
|
+
} | {
|
|
43
|
+
code: 'export-materialization-too-large';
|
|
44
|
+
rowCount: number;
|
|
45
|
+
limit: number;
|
|
46
|
+
} | {
|
|
47
|
+
code: 'queue-overflow';
|
|
48
|
+
queuedBytes: number;
|
|
49
|
+
limit: number;
|
|
50
|
+
} | {
|
|
51
|
+
code: 'invalid-view-state';
|
|
52
|
+
detail: string;
|
|
53
|
+
} | {
|
|
54
|
+
code: 'invalid-ingest-sequence';
|
|
55
|
+
expected: number;
|
|
56
|
+
received: number;
|
|
57
|
+
conflict: boolean;
|
|
58
|
+
} | {
|
|
59
|
+
code: 'ingest-session-closed';
|
|
60
|
+
} | {
|
|
61
|
+
code: 'stale-revision';
|
|
62
|
+
expected: number;
|
|
63
|
+
actual: number;
|
|
64
|
+
} | {
|
|
65
|
+
code: 'overlay-id-conflict';
|
|
66
|
+
overlayId: string;
|
|
67
|
+
} | {
|
|
68
|
+
code: 'resource-limit';
|
|
69
|
+
detail: ResourceAdmissionReport;
|
|
70
|
+
} | {
|
|
71
|
+
code: 'aborted';
|
|
72
|
+
cause?: unknown;
|
|
73
|
+
};
|
|
74
|
+
/** Lifecycle phase an instance-level error occurred in. */
|
|
75
|
+
type ErrorPhase = 'mount' | 'running' | 'recovery';
|
|
76
|
+
/** Spec §6.5 fatality matrix as a pure decision function. */
|
|
77
|
+
declare function isFatalGraphError(error: GraphError, phase: ErrorPhase): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Spec §6.5 rule for a resource-limit producer deciding `fatal`: rejection is
|
|
80
|
+
* fatal before the first accepted scene or during terminal recovery; a later
|
|
81
|
+
* inadmissible declarative update keeps the previous scene (non-fatal).
|
|
82
|
+
*/
|
|
83
|
+
declare function resourceLimitFatal(phase: ErrorPhase, hasAcceptedScene: boolean): boolean;
|
|
84
|
+
/** Error class carried by rejected/thrown operations; `.detail` is typed. */
|
|
85
|
+
declare class OrbitOperationError extends Error {
|
|
86
|
+
readonly name = "OrbitOperationError";
|
|
87
|
+
readonly detail: GraphOperationError;
|
|
88
|
+
constructor(detail: GraphOperationError, message?: string);
|
|
89
|
+
}
|
|
90
|
+
/** Human-readable Error for a GraphError (used as the 'error' event payload). */
|
|
91
|
+
declare function graphErrorToError(graphError: GraphError, cause?: Error): Error;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* orbit-core public data model (spec §5, v0.1 subset).
|
|
95
|
+
*
|
|
96
|
+
* The public model is object-based, id-keyed, and generic over caller attribute
|
|
97
|
+
* types. A `GraphSnapshot` is the declarative source of truth; the core keeps a
|
|
98
|
+
* derived index model and drives the engine imperatively (§4).
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
type NodeId = string;
|
|
102
|
+
type EdgeId = string;
|
|
103
|
+
/** Plain JSON value — the shape `dataRef` and other verbatim host payloads
|
|
104
|
+
* must fit (§16.14: stored, round-tripped, compared canonically, NEVER
|
|
105
|
+
* interpreted). */
|
|
106
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
107
|
+
[key: string]: JsonValue;
|
|
108
|
+
};
|
|
109
|
+
interface GraphNode<N = Record<string, unknown>> {
|
|
110
|
+
id: NodeId;
|
|
111
|
+
attrs?: N;
|
|
112
|
+
/** Optional fixed/persisted position (layout 'fixed' honors these; §10). */
|
|
113
|
+
x?: number;
|
|
114
|
+
y?: number;
|
|
115
|
+
}
|
|
116
|
+
interface GraphEdge<E = Record<string, unknown>> {
|
|
117
|
+
/**
|
|
118
|
+
* Optional stable id. When absent, the core synthesizes a deterministic id
|
|
119
|
+
* `${escapedSource}→${escapedTarget}#${k}` where `\\`, `→`, and `#` are
|
|
120
|
+
* backslash-escaped inside endpoint ids, and k disambiguates parallel edges
|
|
121
|
+
* in first-occurrence order (§5). Simple endpoint ids retain the familiar
|
|
122
|
+
* `${source}→${target}#${k}` form.
|
|
123
|
+
*/
|
|
124
|
+
id?: EdgeId;
|
|
125
|
+
source: NodeId;
|
|
126
|
+
target: NodeId;
|
|
127
|
+
attrs?: E;
|
|
128
|
+
}
|
|
129
|
+
/** Versioned snapshot — the declarative source of truth (§4, §5). */
|
|
130
|
+
interface GraphSnapshot<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
131
|
+
/** Identity of the dataset; changing it clears all per-dataset state (§5). */
|
|
132
|
+
datasetKey: string;
|
|
133
|
+
/** Caller-owned revision; same {datasetKey, sourceRevision} replays are idempotent (§5). */
|
|
134
|
+
sourceRevision: number | string;
|
|
135
|
+
nodes: readonly GraphNode<N>[];
|
|
136
|
+
edges: readonly GraphEdge<E>[];
|
|
137
|
+
}
|
|
138
|
+
type DiagnosticSeverity = 'info' | 'warning' | 'error';
|
|
139
|
+
type DiagnosticCode = 'duplicate-node-id' | 'duplicate-edge-id' | 'dangling-edge-endpoint' | 'invalid-node' | 'invalid-edge' | 'self-loop-retained'
|
|
140
|
+
/** A filter predicate threw or an expr referenced bad data; aggregated (§9.1). */
|
|
141
|
+
| 'filter-error'
|
|
142
|
+
/** Async metric column rejected (misaligned/duplicate/unknown ids; §12). */
|
|
143
|
+
| 'metric-column-error'
|
|
144
|
+
/** A channel reprojected repeatedly with identical outputs (§8 dev). */
|
|
145
|
+
| 'accessor-churn'
|
|
146
|
+
/** Image atlas resolve/decoding failures, cadence-batched (§8). */
|
|
147
|
+
| 'image-resolve-failed' | 'source-revision-reused'
|
|
148
|
+
/** A host config lane was rejected at the boundary (§5.1/§16.3 — e.g. a
|
|
149
|
+
* groups array whose containment is cyclic or multiply parented); the
|
|
150
|
+
* previous config stays live. */
|
|
151
|
+
| 'config-error'
|
|
152
|
+
/** §16.14: a setViewState payload failed structural validation or carries
|
|
153
|
+
* a version newer than this library; NOTHING was applied. */
|
|
154
|
+
| 'invalid-view-state' | 'engine-error' | 'accessor-error'
|
|
155
|
+
/** A user event listener threw; isolated per §15 (the chain continues). */
|
|
156
|
+
| 'listener-error'
|
|
157
|
+
/** showLabelsFor exceeded tracked-label capacity; omissions counted (§14). */
|
|
158
|
+
| 'label-overload'
|
|
159
|
+
/** A same-id row from an earlier overlay won in admission order (§7.5). */
|
|
160
|
+
| 'overlay-node-shadowed'
|
|
161
|
+
/** A service call was aborted/discarded before admission (§9.2; info). */
|
|
162
|
+
| 'service-aborted'
|
|
163
|
+
/** A service call failed (§9.2; error). */
|
|
164
|
+
| 'service-error' | 'context-lost' | 'operation-rejected'
|
|
165
|
+
/** Adapter-defined codes are namespaced (spec §6.5 routing note). */
|
|
166
|
+
| `engine:${string}`;
|
|
167
|
+
declare const DIAGNOSTIC_SAMPLE_CAP = 10;
|
|
168
|
+
interface GraphDiagnostic {
|
|
169
|
+
code: DiagnosticCode;
|
|
170
|
+
severity: DiagnosticSeverity;
|
|
171
|
+
/** Total occurrences in the pass this diagnostic summarizes. */
|
|
172
|
+
count: number;
|
|
173
|
+
/** At most DIAGNOSTIC_SAMPLE_CAP offending ids. */
|
|
174
|
+
sampleIds: readonly string[];
|
|
175
|
+
message: string;
|
|
176
|
+
}
|
|
177
|
+
interface Revisions {
|
|
178
|
+
/** Last accepted caller sourceRevision (null before first accept). */
|
|
179
|
+
source: number | string | null;
|
|
180
|
+
/** Monotonic counter advanced on every accepted model change. */
|
|
181
|
+
model: number;
|
|
182
|
+
/** Filtering/subgraph scope revision (§9). Advances with every accepted
|
|
183
|
+
* model change AND on every hard-scope (subgraph) change; a SCOPE-ONLY
|
|
184
|
+
* change advances `scope` and `render` but NOT `model` — the first genuine
|
|
185
|
+
* scope/model split (v0.5, §9.2). */
|
|
186
|
+
scope: number;
|
|
187
|
+
/** Monotonic counter advanced on every desired-render publication. */
|
|
188
|
+
render: number;
|
|
189
|
+
/** Highest render revision the engine has visibly applied (null pre-mount). */
|
|
190
|
+
appliedRender: number | null;
|
|
191
|
+
}
|
|
192
|
+
interface AcceptedEdge<E = Record<string, unknown>> extends GraphEdge<E> {
|
|
193
|
+
id: EdgeId;
|
|
194
|
+
}
|
|
195
|
+
interface AcceptedGraph<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
196
|
+
datasetKey: string;
|
|
197
|
+
sourceRevision: number | string;
|
|
198
|
+
/** Deduplicated (first-wins), in accepted-base order (§5.1, §16.2). */
|
|
199
|
+
nodes: readonly GraphNode<N>[];
|
|
200
|
+
/** Dangling endpoints dropped; ids present (synthesized when needed). */
|
|
201
|
+
edges: readonly AcceptedEdge<E>[];
|
|
202
|
+
/** id → position in `nodes` (accepted-base order). */
|
|
203
|
+
nodeIndex: ReadonlyMap<NodeId, number>;
|
|
204
|
+
diagnostics: readonly GraphDiagnostic[];
|
|
205
|
+
}
|
|
206
|
+
interface RenderScene {
|
|
207
|
+
count: number;
|
|
208
|
+
linkCount: number;
|
|
209
|
+
/** engine index → node id. */
|
|
210
|
+
idByIndex: readonly NodeId[];
|
|
211
|
+
/** node id → engine index. */
|
|
212
|
+
indexById: ReadonlyMap<NodeId, number>;
|
|
213
|
+
/** engine link index → edge id. */
|
|
214
|
+
edgeIdByIndex: readonly EdgeId[];
|
|
215
|
+
/**
|
|
216
|
+
* 2*count floats. NaN pairs mean "no known position" — the engine seeds
|
|
217
|
+
* them (§7.3); known positions come from the position cache.
|
|
218
|
+
*/
|
|
219
|
+
positions: Float32Array;
|
|
220
|
+
/** 2*linkCount uint32 endpoint indices into the point set. */
|
|
221
|
+
links: Uint32Array;
|
|
222
|
+
/**
|
|
223
|
+
* §16.3 stage-3 synthetic suffix (S12). Present iff the scene was rewritten
|
|
224
|
+
* by collapsed groups: point slots >= physicalPointCount are super-nodes
|
|
225
|
+
* and link slots >= physicalLinkCount are meta-edges (synthetics are always
|
|
226
|
+
* a contiguous suffix). For those slots, idByIndex/edgeIdByIndex hold
|
|
227
|
+
* INTERNAL scene keys that never escape public payloads (§7.4) — consumers
|
|
228
|
+
* resolve slots through the discriminated ScenePointRef/SceneLinkRef
|
|
229
|
+
* helpers instead.
|
|
230
|
+
*/
|
|
231
|
+
groups?: SceneGroups;
|
|
232
|
+
}
|
|
233
|
+
/** §16.3 compact synthetic-suffix descriptor attached to a rewritten scene. */
|
|
234
|
+
interface SceneGroups {
|
|
235
|
+
physicalPointCount: number;
|
|
236
|
+
physicalLinkCount: number;
|
|
237
|
+
/** Aligned to point slots physicalPointCount..count-1. */
|
|
238
|
+
superNodes: readonly ResolvedGroup[];
|
|
239
|
+
/** Aligned to link slots physicalLinkCount..linkCount-1. */
|
|
240
|
+
metaEdges: readonly MetaEdge[];
|
|
241
|
+
/**
|
|
242
|
+
* §16.3 node folds: representatives that are REAL nodes, so they carry no
|
|
243
|
+
* synthetic slot and never appear in `superNodes`. A folded anchor keeps
|
|
244
|
+
* its physical row (and its own caller-driven styling, R-6.1-14) — this
|
|
245
|
+
* list only reports how many descendants it currently stands for, for
|
|
246
|
+
* badge rendering. Empty when nothing is folded.
|
|
247
|
+
*/
|
|
248
|
+
folds: readonly SceneFold[];
|
|
249
|
+
}
|
|
250
|
+
/** One drawn fold anchor and the descendant count it currently hides. */
|
|
251
|
+
interface SceneFold {
|
|
252
|
+
anchorId: NodeId;
|
|
253
|
+
hiddenCount: number;
|
|
254
|
+
}
|
|
255
|
+
/** §7.4 discriminated point ref: a physical node id or a resolved group —
|
|
256
|
+
* public namespaces only, never internal scene keys. */
|
|
257
|
+
type ScenePointRef = {
|
|
258
|
+
kind: 'node';
|
|
259
|
+
id: NodeId;
|
|
260
|
+
} | {
|
|
261
|
+
kind: 'group';
|
|
262
|
+
group: ResolvedGroup;
|
|
263
|
+
};
|
|
264
|
+
/** §7.4 discriminated link ref: a physical edge id or a meta-edge record. */
|
|
265
|
+
type SceneLinkRef = {
|
|
266
|
+
kind: 'edge';
|
|
267
|
+
id: EdgeId;
|
|
268
|
+
} | {
|
|
269
|
+
kind: 'meta-edge';
|
|
270
|
+
metaEdge: MetaEdge;
|
|
271
|
+
};
|
|
272
|
+
type Accessor<T, V> = V | ((item: T) => V);
|
|
273
|
+
type LayoutKind = 'force' | 'fixed';
|
|
274
|
+
/**
|
|
275
|
+
* §10 force tunables under stable, engine-neutral names — orbit maps them onto
|
|
276
|
+
* the active engine's parameters through atomic config-only commits (§13), so
|
|
277
|
+
* a value here never resets positions or restarts the layout.
|
|
278
|
+
*
|
|
279
|
+
* Every field is optional and OMISSION MEANS "leave the engine's default
|
|
280
|
+
* alone" — it is never written as an explicit value. The defaults quoted below
|
|
281
|
+
* are cosmos 3.3.0's (`defaultConfigValues`), listed so a host knows what it is
|
|
282
|
+
* overriding; an engine without a given force ignores that field.
|
|
283
|
+
*
|
|
284
|
+
* NOT here: `spaceSize` is a construction option on the adapter, not a runtime
|
|
285
|
+
* tunable (cosmos documents that large values crash some devices, and the
|
|
286
|
+
* seeding ring is derived from it).
|
|
287
|
+
*/
|
|
288
|
+
interface SimulationConfig {
|
|
289
|
+
/** Pull toward the layout centre. Default 0.25. */
|
|
290
|
+
gravity?: number;
|
|
291
|
+
/** How hard every node pushes every other away — the spread. Default 1. */
|
|
292
|
+
repulsion?: number;
|
|
293
|
+
/** Velocity retained per tick: lower settles sooner, higher keeps drifting.
|
|
294
|
+
* Default 0.85. */
|
|
295
|
+
friction?: number;
|
|
296
|
+
/** Rest length of an edge spring. Default 10. */
|
|
297
|
+
linkDistance?: number;
|
|
298
|
+
/** Edge spring stiffness. Default 1. */
|
|
299
|
+
linkSpring?: number;
|
|
300
|
+
/**
|
|
301
|
+
* Cool-down coefficient — how fast the run loses energy and comes to rest.
|
|
302
|
+
* SMALLER cools slower (a longer, more thorough settle); larger snaps to a
|
|
303
|
+
* stop. Default 5000.
|
|
304
|
+
*/
|
|
305
|
+
decay?: number;
|
|
306
|
+
/**
|
|
307
|
+
* Overlap resolution: above 0, nodes push apart when their circles
|
|
308
|
+
* intersect. Default 0 (OFF) — the reason dense clusters render as solid
|
|
309
|
+
* blobs until you turn it on.
|
|
310
|
+
*/
|
|
311
|
+
collision?: number;
|
|
312
|
+
/** Collision circle radius. Default: derived from the point size. */
|
|
313
|
+
collisionRadius?: number;
|
|
314
|
+
/** Extra spacing added around each collision circle. Default 0. */
|
|
315
|
+
collisionPadding?: number;
|
|
316
|
+
/**
|
|
317
|
+
* Barnes-Hut opening angle θ for the many-body approximation: larger is
|
|
318
|
+
* coarser and faster, smaller is more exact and slower. Default 1.15.
|
|
319
|
+
*/
|
|
320
|
+
repulsionTheta?: number;
|
|
321
|
+
/** Attraction toward the scene's centre of mass. Default 0 (OFF). */
|
|
322
|
+
center?: number;
|
|
323
|
+
/** How strongly nodes shy away from the cursor. Default 2. */
|
|
324
|
+
repulsionFromMouse?: number;
|
|
325
|
+
}
|
|
326
|
+
interface GraphHostUpdate<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
327
|
+
data?: GraphSnapshot<N, E>;
|
|
328
|
+
nodeColor?: Accessor<GraphNode<N>, string> | Scale<string, N>;
|
|
329
|
+
nodeSize?: Accessor<GraphNode<N>, number> | Scale<number, N>;
|
|
330
|
+
linkColor?: Accessor<AcceptedEdge<E>, string>;
|
|
331
|
+
linkWidth?: Accessor<AcceptedEdge<E>, number>;
|
|
332
|
+
/** §12 async metric columns, joined once with revision-gated admission. */
|
|
333
|
+
metrics?: readonly MetricColumn[];
|
|
334
|
+
/**
|
|
335
|
+
* §8 image sprites: synchronous, string-valued ref accessor (URL/blob
|
|
336
|
+
* ref/cache key — opaque to orbit). Refs feed the image-atlas pipeline when
|
|
337
|
+
* the engine declares `pointImages`; otherwise refs are retained and the
|
|
338
|
+
* placeholder shape renders (§13 capability policy).
|
|
339
|
+
*/
|
|
340
|
+
/** §8 image refs; `null` CLEARS the accessor and evicts the atlas back to
|
|
341
|
+
* placeholders (D2 explicit reset — omission stays "no change"). */
|
|
342
|
+
nodeImage?: ((node: GraphNode<N>) => string | null) | null;
|
|
343
|
+
/** §16.12 instanced arrowheads (capability-gated; inert when unsupported). */
|
|
344
|
+
edgeArrows?: boolean;
|
|
345
|
+
/** §16.14 durable source coordinate for view states — stored VERBATIM,
|
|
346
|
+
* never interpreted; serialized by getViewState and canonically compared
|
|
347
|
+
* on setViewState. Stash-only lane: no publish, no commit. Omission means
|
|
348
|
+
* no change (there is no clear form in v1 — set `{}` for emptiness). */
|
|
349
|
+
dataRef?: JsonValue;
|
|
350
|
+
/** §16.13 runtime toggles — atomic config-only commits, no reprojection. */
|
|
351
|
+
showLinks?: boolean;
|
|
352
|
+
layout?: LayoutKind;
|
|
353
|
+
simulation?: SimulationConfig;
|
|
354
|
+
/** Controlled selection (uncontrolled when never provided; §6.4 subset). */
|
|
355
|
+
selection?: readonly NodeId[];
|
|
356
|
+
theme?: ThemeInput;
|
|
357
|
+
/** DOM label lane configuration (§14; strategy 'dom' only in v0.4). */
|
|
358
|
+
labels?: LabelConfig<N>;
|
|
359
|
+
/** §15.1 accessibility runtime options. */
|
|
360
|
+
accessibility?: AccessibilityConfig<N>;
|
|
361
|
+
/** §9.2 hard scope: feed ONLY the resolved subset through the reconciler;
|
|
362
|
+
* null restores full scope. Positions come from the cache; reflow default
|
|
363
|
+
* true restarts the layout around the remainder. */
|
|
364
|
+
subgraph?: SubgraphSpec | null;
|
|
365
|
+
/** §9.1 soft filter: mask (hide/dim) with ZERO relayout; null clears. */
|
|
366
|
+
filter?: FilterSpec<N, E> | null;
|
|
367
|
+
/** §16.6 crossfilter dimensions (declarative; brushes live on the session). */
|
|
368
|
+
crossfilter?: readonly DimensionSpec<N>[];
|
|
369
|
+
/** §16.3 manual groups; null clears (D2). Config-error with groupBy. */
|
|
370
|
+
groups?: readonly GroupSpec[] | null;
|
|
371
|
+
/** §16.3 derived grouping; null clears (D2). Config-error with groups. */
|
|
372
|
+
groupBy?: GroupBySpec<N> | null;
|
|
373
|
+
/** §16.3 stage-4 non-collapsing layout clusters; null clears (D2). Clusters
|
|
374
|
+
* COEXIST with groups — they preserve every node and edge. */
|
|
375
|
+
clusters?: ClusterSpec<N> | null;
|
|
376
|
+
/** §16.3 persistent pins (independent of transient drag pinning); null
|
|
377
|
+
* clears (D2). Departed ids prune through ownership. */
|
|
378
|
+
pinnedNodeIds?: readonly NodeId[] | null;
|
|
379
|
+
/** §16.3 parallel-edge grouping toggle: same-pair edges collapse into one
|
|
380
|
+
* count-weighted meta-edge. */
|
|
381
|
+
parallelEdgeGrouping?: boolean;
|
|
382
|
+
}
|
|
383
|
+
/** Built-in synchronous metrics plus caller-supplied async column names. */
|
|
384
|
+
type MetricName = 'degree' | 'inDegree' | 'outDegree' | (string & {});
|
|
385
|
+
interface DomainPolicy {
|
|
386
|
+
/** Domain population. Default 'dataset' (frozen per dataset revision —
|
|
387
|
+
* masking/isolation never change what a color means). */
|
|
388
|
+
scope?: 'dataset' | 'hard-scope' | 'visible';
|
|
389
|
+
/** Streaming behavior. Default 'freeze-per-revision'; 'expand' permits
|
|
390
|
+
* monotonic growth as batches arrive. */
|
|
391
|
+
streaming?: 'freeze-per-revision' | 'expand';
|
|
392
|
+
}
|
|
393
|
+
type Scale<T, N = Record<string, unknown>> = {
|
|
394
|
+
kind: 'sequential';
|
|
395
|
+
metric: MetricName;
|
|
396
|
+
range: readonly [T, T];
|
|
397
|
+
domain?: readonly [number, number] | DomainPolicy;
|
|
398
|
+
} | {
|
|
399
|
+
kind: 'categorical';
|
|
400
|
+
by: string | ((node: GraphNode<N>) => string | null);
|
|
401
|
+
palette?: readonly T[];
|
|
402
|
+
/** Fixed category order → stable colors and stable legend rows,
|
|
403
|
+
* including empty categories; out-of-domain values hash stably. */
|
|
404
|
+
domain?: readonly string[];
|
|
405
|
+
domainPolicy?: DomainPolicy;
|
|
406
|
+
} | {
|
|
407
|
+
kind: 'diverging';
|
|
408
|
+
metric: MetricName;
|
|
409
|
+
mid: number;
|
|
410
|
+
range: readonly [T, T, T];
|
|
411
|
+
};
|
|
412
|
+
/** Async metric column joined against the accepted model (§12). */
|
|
413
|
+
interface MetricColumn {
|
|
414
|
+
metric: string;
|
|
415
|
+
/** 'ids' joins by the ids array; 'index' is accepted-base positional. */
|
|
416
|
+
align: 'ids' | 'index';
|
|
417
|
+
values: readonly (number | null)[];
|
|
418
|
+
ids?: readonly NodeId[];
|
|
419
|
+
/**
|
|
420
|
+
* §12/I1 issue-time stamp: the `getRevisions().model` value CURRENT WHEN
|
|
421
|
+
* THE UPDATE CARRYING THIS COLUMN WAS BUILT. Capture it before starting an
|
|
422
|
+
* async computation and deliver it with the result — admission rejects the
|
|
423
|
+
* column (info diagnostic) when the model has moved since, so stale async
|
|
424
|
+
* work can never join a newer roster. Columns delivered atomically with
|
|
425
|
+
* their matching `data` in one update stamp the revision current at build
|
|
426
|
+
* time (the pre-update revision): the transaction is atomic, so that stamp
|
|
427
|
+
* uniquely names the roster the columns were derived from.
|
|
428
|
+
*/
|
|
429
|
+
forModelRevision: number;
|
|
430
|
+
}
|
|
431
|
+
interface GraphTheme {
|
|
432
|
+
background: string;
|
|
433
|
+
nodeDefault: string;
|
|
434
|
+
edgeDefault: string;
|
|
435
|
+
labelFg: string;
|
|
436
|
+
accent: string;
|
|
437
|
+
mutedAlpha: number;
|
|
438
|
+
}
|
|
439
|
+
type ThemeInput = (Partial<GraphTheme> & {
|
|
440
|
+
base?: 'light' | 'dark';
|
|
441
|
+
}) | GraphTheme;
|
|
442
|
+
type FilterMode = 'hide' | 'dim';
|
|
443
|
+
type FilterValue = string | number | boolean | null;
|
|
444
|
+
type FilterExpr = {
|
|
445
|
+
op: 'eq' | 'neq';
|
|
446
|
+
field: string;
|
|
447
|
+
value: FilterValue;
|
|
448
|
+
} | {
|
|
449
|
+
op: 'in';
|
|
450
|
+
field: string;
|
|
451
|
+
values: readonly FilterValue[];
|
|
452
|
+
} | {
|
|
453
|
+
op: 'range';
|
|
454
|
+
field: string;
|
|
455
|
+
min?: number;
|
|
456
|
+
max?: number;
|
|
457
|
+
/** Default true. */
|
|
458
|
+
includeMin?: boolean;
|
|
459
|
+
/** Default true. */
|
|
460
|
+
includeMax?: boolean;
|
|
461
|
+
} | {
|
|
462
|
+
op: 'is-null';
|
|
463
|
+
field: string;
|
|
464
|
+
} | {
|
|
465
|
+
op: 'not';
|
|
466
|
+
expr: FilterExpr;
|
|
467
|
+
} | {
|
|
468
|
+
op: 'and' | 'or';
|
|
469
|
+
exprs: readonly FilterExpr[];
|
|
470
|
+
};
|
|
471
|
+
interface FilterSpec<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
472
|
+
nodes?: FilterExpr | ((node: GraphNode<N>) => boolean);
|
|
473
|
+
edges?: FilterExpr | ((edge: AcceptedEdge<E>) => boolean);
|
|
474
|
+
/** 'hide' removes from view (alpha 0 + picking); 'dim' mutes. Default 'hide'. */
|
|
475
|
+
mode?: FilterMode;
|
|
476
|
+
}
|
|
477
|
+
type DimensionKind = 'numeric' | 'temporal' | 'categorical';
|
|
478
|
+
interface DimensionSpec<N = Record<string, unknown>> {
|
|
479
|
+
/** Stable dimension key (brushes rebase by this key across data updates). */
|
|
480
|
+
key: string;
|
|
481
|
+
kind: DimensionKind;
|
|
482
|
+
/** Raw value accessor; §8 hygiene applies (non-finite → excluded from bins).
|
|
483
|
+
* Temporal accepts epoch-ms numbers, ISO strings, or 'YYYY-MM-DD'. */
|
|
484
|
+
get: (node: GraphNode<N>) => unknown;
|
|
485
|
+
/** Histogram bin count for numeric/temporal (default 24). */
|
|
486
|
+
bins?: number;
|
|
487
|
+
}
|
|
488
|
+
/** Numeric/temporal brush (coordinates in the dimension's units — epoch ms
|
|
489
|
+
* for temporal), or categorical EXCLUSIONS, or null = no brush. */
|
|
490
|
+
type BrushState = {
|
|
491
|
+
min: number;
|
|
492
|
+
max: number;
|
|
493
|
+
} | {
|
|
494
|
+
excluded: readonly string[];
|
|
495
|
+
} | null;
|
|
496
|
+
interface HistogramBin {
|
|
497
|
+
x0: number;
|
|
498
|
+
x1: number;
|
|
499
|
+
/** Rows in this bin regardless of any mask. */
|
|
500
|
+
total: number;
|
|
501
|
+
/** Rows in this bin passing every OTHER dimension's brush + the filter
|
|
502
|
+
* prop's node mask (the §16.6 joint "filtered" second layer). */
|
|
503
|
+
filtered: number;
|
|
504
|
+
}
|
|
505
|
+
interface CategoryBin {
|
|
506
|
+
key: string;
|
|
507
|
+
total: number;
|
|
508
|
+
filtered: number;
|
|
509
|
+
excluded: boolean;
|
|
510
|
+
}
|
|
511
|
+
interface DimensionSummary {
|
|
512
|
+
key: string;
|
|
513
|
+
kind: DimensionKind;
|
|
514
|
+
/** Numeric/temporal domain (finite rows only); undefined when empty. */
|
|
515
|
+
domain?: {
|
|
516
|
+
min: number;
|
|
517
|
+
max: number;
|
|
518
|
+
};
|
|
519
|
+
bins: readonly HistogramBin[];
|
|
520
|
+
categories: readonly CategoryBin[];
|
|
521
|
+
/** Rows excluded by §8 hygiene (non-finite / unparseable). */
|
|
522
|
+
excludedRows: number;
|
|
523
|
+
}
|
|
524
|
+
interface CrossfilterSession {
|
|
525
|
+
/** Monotonic from 0; advances exactly once per observable selection change. */
|
|
526
|
+
readonly selectionRevision: number;
|
|
527
|
+
/** Latest-call-wins coalescing per dimension; resolves once observable. */
|
|
528
|
+
setBrush(key: string, brush: BrushState): Promise<void>;
|
|
529
|
+
getBrush(key: string): BrushState;
|
|
530
|
+
summarize(key: string): DimensionSummary;
|
|
531
|
+
/** Fires once per observable selection/summary change. */
|
|
532
|
+
subscribe(cb: () => void): () => void;
|
|
533
|
+
}
|
|
534
|
+
interface TimelinePlayback {
|
|
535
|
+
/** 'sliding' plays a fixed window; 'cumulative' grows from the domain start. */
|
|
536
|
+
mode: 'sliding' | 'cumulative';
|
|
537
|
+
/** Window width in dimension units (sliding; default domain/10). */
|
|
538
|
+
window?: number;
|
|
539
|
+
/** Tick interval in ms (default 100). */
|
|
540
|
+
tickMs?: number;
|
|
541
|
+
/** Fraction of the domain traversed per tick (default 0.01). */
|
|
542
|
+
step?: number;
|
|
543
|
+
loop?: boolean;
|
|
544
|
+
}
|
|
545
|
+
interface SubgraphSpec {
|
|
546
|
+
seedIds: readonly NodeId[];
|
|
547
|
+
/** Expand N hops from the seeds via the expansion service (default 0). */
|
|
548
|
+
hops?: number;
|
|
549
|
+
/** Restart the layout around the subset (default true). */
|
|
550
|
+
reflow?: boolean;
|
|
551
|
+
}
|
|
552
|
+
interface SearchResult<N = Record<string, unknown>> {
|
|
553
|
+
id: string;
|
|
554
|
+
score?: number;
|
|
555
|
+
label?: string;
|
|
556
|
+
node?: GraphNode<N>;
|
|
557
|
+
}
|
|
558
|
+
/** Why an activated result could not be focused (§16.5 result contract). */
|
|
559
|
+
type SearchUnavailableReason = 'not-loaded' | 'out-of-scope' | 'filtered';
|
|
560
|
+
type SearchActivation = {
|
|
561
|
+
status: 'focused';
|
|
562
|
+
id: NodeId;
|
|
563
|
+
} | {
|
|
564
|
+
status: 'unavailable';
|
|
565
|
+
reason: SearchUnavailableReason;
|
|
566
|
+
result: SearchResult;
|
|
567
|
+
};
|
|
568
|
+
/** Context every async service call receives (§9.2 sequencing rule). */
|
|
569
|
+
interface RequestContext {
|
|
570
|
+
datasetKey: string;
|
|
571
|
+
sourceRevision: number | string | null;
|
|
572
|
+
modelRevision: number;
|
|
573
|
+
scopeRevision: number;
|
|
574
|
+
requestId: string;
|
|
575
|
+
/** Abort is an optimization; admission is the correctness gate. */
|
|
576
|
+
signal: AbortSignal;
|
|
577
|
+
}
|
|
578
|
+
type RevisionDimension = 'source' | 'model' | 'scope';
|
|
579
|
+
/** A service declares exactly the revision dimensions it consumes (§9.2). */
|
|
580
|
+
interface RevisionAwareService {
|
|
581
|
+
readonly revisionDependencies: readonly RevisionDimension[];
|
|
582
|
+
}
|
|
583
|
+
interface ExpansionBatch<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
584
|
+
nodes?: readonly GraphNode<N>[];
|
|
585
|
+
edges?: readonly GraphEdge<E>[];
|
|
586
|
+
}
|
|
587
|
+
type ExpansionResponse<N = Record<string, unknown>, E = Record<string, unknown>> = (ExpansionBatch<N, E> & {
|
|
588
|
+
provenance?: unknown;
|
|
589
|
+
}) | {
|
|
590
|
+
batches: AsyncIterable<ExpansionBatch<N, E>>;
|
|
591
|
+
provenance?: unknown;
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* §16.2 path resolver seam (S12-T08). `find` resolves the node/edge id path
|
|
595
|
+
* between two loaded nodes or null when unreachable (null is a RESULT, not
|
|
596
|
+
* an error). Extends the §9.2 revision-aware contract: abort is advisory,
|
|
597
|
+
* revision admission at delivery is authoritative.
|
|
598
|
+
*/
|
|
599
|
+
interface PathService extends RevisionAwareService {
|
|
600
|
+
find(sourceId: NodeId, targetId: NodeId, options: PathOptions, ctx: RequestContext): Promise<PathResult | null>;
|
|
601
|
+
}
|
|
602
|
+
interface ExpansionService<N = Record<string, unknown>, E = Record<string, unknown>> extends RevisionAwareService {
|
|
603
|
+
neighbors(seedIds: readonly NodeId[], hops: number, ctx: RequestContext): Promise<ExpansionResponse<N, E>>;
|
|
604
|
+
}
|
|
605
|
+
interface BeginIngestOptions {
|
|
606
|
+
/** 'replace' commits a new source coordinate atomically; 'overlay' advances
|
|
607
|
+
* only modelRevision and may be progressive (§7.5). */
|
|
608
|
+
purpose: 'replace' | 'overlay';
|
|
609
|
+
datasetKey: string;
|
|
610
|
+
/** Required for 'replace': the source coordinate the commit establishes. */
|
|
611
|
+
sourceRevision?: number | string;
|
|
612
|
+
/** CAS precondition: the model revision current when the session begins
|
|
613
|
+
* (zero on an empty instance). Mismatch rejects with 'stale-revision'. */
|
|
614
|
+
baseModelRevision: number;
|
|
615
|
+
/** Overlays only (replace is always atomic). Default true. */
|
|
616
|
+
atomic?: boolean;
|
|
617
|
+
/** Caller-supplied stable overlay id; generated when omitted. */
|
|
618
|
+
overlayId?: string;
|
|
619
|
+
/** Progressive overlays: flush no later than this while running (default 50). */
|
|
620
|
+
maxFlushLatencyMs?: number;
|
|
621
|
+
/** Byte backpressure budget. Progressive receipts await drainage past this;
|
|
622
|
+
* atomic sessions terminally reject an append that would exceed it because
|
|
623
|
+
* atomic staging cannot drain before commit. */
|
|
624
|
+
maxPendingBytes?: number;
|
|
625
|
+
}
|
|
626
|
+
interface IngestBatch<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
627
|
+
/** Consecutive, strictly monotonic from zero (§7.5). */
|
|
628
|
+
sequence: number;
|
|
629
|
+
/** Idempotency key: an admitted {sequence, batchId} replay returns its
|
|
630
|
+
* original receipt; same sequence + different batchId rejects. */
|
|
631
|
+
batchId: string;
|
|
632
|
+
nodes?: readonly GraphNode<N>[];
|
|
633
|
+
edges?: readonly GraphEdge<E>[];
|
|
634
|
+
/** Caller-declared payload size; estimated when omitted. */
|
|
635
|
+
bytes?: number;
|
|
636
|
+
}
|
|
637
|
+
interface AppendReceipt {
|
|
638
|
+
sequence: number;
|
|
639
|
+
batchId: string;
|
|
640
|
+
admittedNodes: number;
|
|
641
|
+
admittedEdges: number;
|
|
642
|
+
/** Present once the flush containing this batch became public (progressive
|
|
643
|
+
* overlays resolve only then, so exact replays return complete receipts). */
|
|
644
|
+
publishedModelRevision?: number;
|
|
645
|
+
/** Bytes admitted but not yet flushed (the backpressure signal). */
|
|
646
|
+
pendingBytes: number;
|
|
647
|
+
}
|
|
648
|
+
interface IngestCommitReceipt {
|
|
649
|
+
overlayId?: string;
|
|
650
|
+
modelRevision: number;
|
|
651
|
+
sourceRevision?: number | string;
|
|
652
|
+
admittedNodes: number;
|
|
653
|
+
admittedEdges: number;
|
|
654
|
+
/** Dangling edges dropped at commit (diagnostics emitted only then; §7.5). */
|
|
655
|
+
danglingEdges: number;
|
|
656
|
+
}
|
|
657
|
+
type IngestSessionState = 'open' | 'committing' | 'committed' | 'aborted';
|
|
658
|
+
interface IngestSession<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
659
|
+
readonly state: IngestSessionState;
|
|
660
|
+
readonly overlayId: string | undefined;
|
|
661
|
+
append(batch: IngestBatch<N, E>): Promise<AppendReceipt>;
|
|
662
|
+
commit(): Promise<IngestCommitReceipt>;
|
|
663
|
+
abort(reason?: unknown): Promise<void>;
|
|
664
|
+
}
|
|
665
|
+
/** §14 label lane configuration (zoom-LOD, ranking, forced ids). */
|
|
666
|
+
interface LabelConfig<N = Record<string, unknown>> {
|
|
667
|
+
enabled?: boolean;
|
|
668
|
+
/** Labels appear only at/above this zoom (LOD threshold). Default 1. */
|
|
669
|
+
minZoom?: number;
|
|
670
|
+
/**
|
|
671
|
+
* §16.3 cluster-label LOD ceiling. At or BELOW this zoom the active
|
|
672
|
+
* `clusters` spec's labels render and NODE labels are suppressed; above it
|
|
673
|
+
* cluster labels stop and node-label LOD (`minZoom`) takes over. Absent ⇒
|
|
674
|
+
* no LOD hand-off: cluster labels (when a spec is active) and node labels
|
|
675
|
+
* coexist, each on its own gate.
|
|
676
|
+
*/
|
|
677
|
+
maxZoom?: number;
|
|
678
|
+
/** Ranked-candidate cap k (viewport-culled). Default 64, policy max 1024. */
|
|
679
|
+
maxVisible?: number;
|
|
680
|
+
/** Ids that claim capacity FIRST, bypassing ranking (§14 showLabelsFor). */
|
|
681
|
+
showFor?: readonly NodeId[];
|
|
682
|
+
/** Label text; default attrs.label ?? id. Rendered as a TEXT NODE (§14). */
|
|
683
|
+
getText?: (node: GraphNode<N>) => string;
|
|
684
|
+
/** Ranking weight; default nodeSize result order, else degree. */
|
|
685
|
+
getWeight?: (node: GraphNode<N>) => number;
|
|
686
|
+
}
|
|
687
|
+
/** §15.1 accessibility runtime options. */
|
|
688
|
+
interface AccessibilityConfig<N = Record<string, unknown>> {
|
|
689
|
+
/** Canvas aria-label. Default 'Graph visualization'. */
|
|
690
|
+
label?: string;
|
|
691
|
+
description?: string;
|
|
692
|
+
/** Max items per navigator relationship page. Default 50. */
|
|
693
|
+
navigatorWindow?: number;
|
|
694
|
+
/** Gate live-region announcements (default true). */
|
|
695
|
+
announcements?: boolean;
|
|
696
|
+
/** Text name for a node in the navigator/live region; default label/id. */
|
|
697
|
+
getAccessibleLabel?: (node: GraphNode<N>) => string;
|
|
698
|
+
/**
|
|
699
|
+
* Reduced-motion override: true forces reduced, false forces full motion,
|
|
700
|
+
* undefined follows the host binding's media-query detection (§15.1).
|
|
701
|
+
*/
|
|
702
|
+
reducedMotion?: boolean;
|
|
703
|
+
}
|
|
704
|
+
/** One positioned label emitted to the overlay lane per scheduler tick (§14). */
|
|
705
|
+
interface LabelPlacement {
|
|
706
|
+
/** Node id — or, for `kind: 'cluster'`, the §16.3 CLUSTER KEY. */
|
|
707
|
+
id: NodeId;
|
|
708
|
+
text: string;
|
|
709
|
+
/** Screen coordinates (CSS px, container-relative). */
|
|
710
|
+
x: number;
|
|
711
|
+
y: number;
|
|
712
|
+
forced: boolean;
|
|
713
|
+
/**
|
|
714
|
+
* §16.3 placement kind. 'node' (default) anchors to the node's cached
|
|
715
|
+
* position; 'cluster' anchors to the cluster's force center while the
|
|
716
|
+
* simulation is hot and to its settled centroid afterwards, and selects its
|
|
717
|
+
* MEMBER node ids when activated (R-16.3-18/21). Ids are drawn from
|
|
718
|
+
* different namespaces, so consumers must key on `(kind, id)`.
|
|
719
|
+
*/
|
|
720
|
+
kind?: 'node' | 'cluster';
|
|
721
|
+
}
|
|
722
|
+
interface ViewportState {
|
|
723
|
+
x: number;
|
|
724
|
+
y: number;
|
|
725
|
+
zoom: number;
|
|
726
|
+
}
|
|
727
|
+
type InstanceStatus = 'idle' | 'mounting' | 'ready'
|
|
728
|
+
/** WebGL context lost; engine frozen, CPU model stays live (§13.1). */
|
|
729
|
+
| 'lost'
|
|
730
|
+
/** Context restored; the full-scene replay commit is in flight (§13.1). */
|
|
731
|
+
| 'recovering' | 'destroyed' | 'error';
|
|
732
|
+
/**
|
|
733
|
+
* Namespaced selection (§16.2). Namespaces are independent: node-set algebra
|
|
734
|
+
* never mutates edge selection. `groupIds` is reserved (populated from S12).
|
|
735
|
+
*/
|
|
736
|
+
interface SelectionState {
|
|
737
|
+
nodeIds: readonly NodeId[];
|
|
738
|
+
edgeIds: readonly EdgeId[];
|
|
739
|
+
groupIds: readonly string[];
|
|
740
|
+
}
|
|
741
|
+
/** §16.3 manual group definition. Flat and disjoint: membership may not
|
|
742
|
+
* nest, overlap, duplicate, self-reference, or name unknown ids — violations
|
|
743
|
+
* are §5.1 config-error diagnostics BEFORE any scene rewrite. */
|
|
744
|
+
interface GroupSpec {
|
|
745
|
+
/** Public group id — its own namespace, never colliding with node ids. */
|
|
746
|
+
id: string;
|
|
747
|
+
memberIds: readonly NodeId[];
|
|
748
|
+
label?: string;
|
|
749
|
+
/** Collapsed groups rewrite to super-nodes with meta-edges (stage 3). */
|
|
750
|
+
collapsed?: boolean;
|
|
751
|
+
color?: string;
|
|
752
|
+
}
|
|
753
|
+
/** §16.3 derived grouping: one group per distinct accessor key (null =
|
|
754
|
+
* ungrouped). Membership is derived and READ-ONLY; collapsed defaults false
|
|
755
|
+
* so adding groupBy alone changes no rendering. */
|
|
756
|
+
interface GroupBySpec<N = Record<string, unknown>> {
|
|
757
|
+
by: (node: GraphNode<N>) => string | null;
|
|
758
|
+
/** Hysteresis semantic zoom: crossing below collapseBelow collapses all
|
|
759
|
+
* derived groups; crossing above expandAbove expands only groups
|
|
760
|
+
* intersecting the viewport; between the thresholds the band holds.
|
|
761
|
+
* expandAbove must be strictly greater than collapseBelow. */
|
|
762
|
+
semanticZoom?: {
|
|
763
|
+
collapseBelow: number;
|
|
764
|
+
expandAbove: number;
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* §16.3 stage-4 non-collapsing layout clusters: a categorical `by` accessor
|
|
769
|
+
* partitions the PHYSICAL scene (`null` ⇒ unclustered) into force-clustered,
|
|
770
|
+
* centroid-labelled sets. Clusters preserve every node and edge and therefore
|
|
771
|
+
* NEVER synthesize super-nodes or meta-edges (R-16.3-17/19); they coexist with
|
|
772
|
+
* groups and re-derive over the post-group-rewrite physical scene.
|
|
773
|
+
*/
|
|
774
|
+
interface ClusterSpec<N = Record<string, unknown>> {
|
|
775
|
+
/** Membership accessor, compared by function REFERENCE (a new inline lambda
|
|
776
|
+
* re-derives — the groupBy convention). */
|
|
777
|
+
by: (node: GraphNode<N>) => string | null;
|
|
778
|
+
/** Cluster-force strength handed to the engine. Inert (with ONE loud
|
|
779
|
+
* degradation diagnostic) on engines that do not declare `clusterForce`;
|
|
780
|
+
* membership, labels, and centroids still work (R-13-39). */
|
|
781
|
+
strength?: number;
|
|
782
|
+
/** Explicit force centers per key, in SPACE coordinates. Keys omitted here
|
|
783
|
+
* generate deterministically from the ordered keys + layout seed
|
|
784
|
+
* (R-16.3-20 — see `resolveClusterCenters`). */
|
|
785
|
+
centers?: ReadonlyMap<string, readonly [number, number]>;
|
|
786
|
+
}
|
|
787
|
+
/** Resolved cluster surface for overlays/selection (public ids only). */
|
|
788
|
+
interface ResolvedCluster {
|
|
789
|
+
/** The categorical key — also the cluster label's text and overlay id. */
|
|
790
|
+
key: string;
|
|
791
|
+
/** Member PHYSICAL node ids in scene order. */
|
|
792
|
+
memberIds: readonly NodeId[];
|
|
793
|
+
/** The force center labels anchor to while the simulation is HOT. */
|
|
794
|
+
forceCenter: readonly [number, number];
|
|
795
|
+
/** Settled centroid from the last permitted §7.1 readback (or the commit
|
|
796
|
+
* under a fixed layout); null until one has landed. */
|
|
797
|
+
centroid: readonly [number, number] | null;
|
|
798
|
+
}
|
|
799
|
+
/** Resolved group surface for events/selection/store (public namespace). */
|
|
800
|
+
interface ResolvedGroup {
|
|
801
|
+
id: string;
|
|
802
|
+
label?: string;
|
|
803
|
+
memberIds: readonly NodeId[];
|
|
804
|
+
collapsed: boolean;
|
|
805
|
+
/** True for groupBy-derived groups (membership read-only). */
|
|
806
|
+
derived: boolean;
|
|
807
|
+
color?: string;
|
|
808
|
+
}
|
|
809
|
+
/** §16.3 rerouted member edge on a collapsed group (stage 3), or a grouped
|
|
810
|
+
* parallel-edge bundle (§16.3 R-24). Count is the badge datum. */
|
|
811
|
+
interface MetaEdge {
|
|
812
|
+
id: string;
|
|
813
|
+
/** Node id OR group id endpoint (public namespaces). */
|
|
814
|
+
source: string;
|
|
815
|
+
target: string;
|
|
816
|
+
/** Underlying (rerouted / collapsed-parallel) edge count. */
|
|
817
|
+
count: number;
|
|
818
|
+
}
|
|
819
|
+
/** §16.2 path query options (PathService). */
|
|
820
|
+
interface PathOptions {
|
|
821
|
+
/** Edge-direction rule for traversal. Default 'outgoing'. */
|
|
822
|
+
direction?: 'outgoing' | 'incoming' | 'either';
|
|
823
|
+
}
|
|
824
|
+
/** A resolved path: node ids in order plus the edge ids walked. */
|
|
825
|
+
interface PathResult {
|
|
826
|
+
nodeIds: readonly NodeId[];
|
|
827
|
+
edgeIds: readonly EdgeId[];
|
|
828
|
+
}
|
|
829
|
+
interface GraphStoreState {
|
|
830
|
+
status: InstanceStatus;
|
|
831
|
+
revisions: Revisions;
|
|
832
|
+
nodeCount: number;
|
|
833
|
+
edgeCount: number;
|
|
834
|
+
selection: SelectionState;
|
|
835
|
+
hover: {
|
|
836
|
+
nodeId: NodeId | null;
|
|
837
|
+
edgeId: EdgeId | null;
|
|
838
|
+
};
|
|
839
|
+
/** id → pinned space position (§16.3 pin slice; drag-pinning writes here). */
|
|
840
|
+
pins: ReadonlyMap<NodeId, readonly [number, number]>;
|
|
841
|
+
/** §16.3 PERSISTENT pins (S12-T09): ids held at their CURRENT position via
|
|
842
|
+
* engine.setPinnedIndices. Independent lifecycle from the transient
|
|
843
|
+
* drag-pin `pins` slice — the engine receives the UNION; releasing a drag
|
|
844
|
+
* pin on a persistently-pinned node leaves it pinned. No position payload
|
|
845
|
+
* in v0.10: a persistent pin freezes the node wherever it currently is. */
|
|
846
|
+
pinnedNodeIds: ReadonlySet<NodeId>;
|
|
847
|
+
hiddenNodeIds: ReadonlySet<NodeId>;
|
|
848
|
+
/** Active hard scope (§9.2); null = full scope. */
|
|
849
|
+
scope: SubgraphSpec | null;
|
|
850
|
+
/** Soft-mask visibility counts (§9.1): RENDERED SCENE entities with zero
|
|
851
|
+
* hide-failures — the §16.3 synthetic suffix INCLUDED, so a collapsed
|
|
852
|
+
* group contributes its one drawn super-node. Equals nodeCount/edgeCount
|
|
853
|
+
* when nothing masks, scopes, or groups.
|
|
854
|
+
*
|
|
855
|
+
* NOT the same question as `getVisibleNodeIds()`, which lists PUBLIC
|
|
856
|
+
* physical ids only (§7.4). Pair a count with that list via
|
|
857
|
+
* `getVisibleNodeIds().length`; use `visible` for "how much is on screen". */
|
|
858
|
+
visible: {
|
|
859
|
+
nodes: number;
|
|
860
|
+
edges: number;
|
|
861
|
+
};
|
|
862
|
+
/** Timeline playback state (§16.6): at most one playing dimension. */
|
|
863
|
+
timeline: {
|
|
864
|
+
playingKey: string | null;
|
|
865
|
+
};
|
|
866
|
+
/** §16.14 history kernel depths (S9-T20; full walk semantics in S15). */
|
|
867
|
+
history: {
|
|
868
|
+
undoDepth: number;
|
|
869
|
+
redoDepth: number;
|
|
870
|
+
};
|
|
871
|
+
/** Node ids with an expansion in flight (§9.2 loading affordance). */
|
|
872
|
+
pendingExpansions: ReadonlySet<NodeId>;
|
|
873
|
+
/**
|
|
874
|
+
* §16.3 node folds: anchor id → how many members it stands for. Empty when
|
|
875
|
+
* nothing is folded.
|
|
876
|
+
*
|
|
877
|
+
* Published so folds are OBSERVABLE. A fold changes neither an anchor's id
|
|
878
|
+
* nor its label text, so the §14 label lane — which re-renders content only
|
|
879
|
+
* when the candidate SET changes — would otherwise never re-render a badge
|
|
880
|
+
* that depends on fold state. Subscribing to this slice is how a host keeps
|
|
881
|
+
* fold-derived chrome (badges, affordances) in step.
|
|
882
|
+
*/
|
|
883
|
+
folds: ReadonlyMap<NodeId, number>;
|
|
884
|
+
/** Committed overlay ids for the current dataset (§7.5). */
|
|
885
|
+
overlayIds: readonly string[];
|
|
886
|
+
/** §16.3 resolved groups (manual or groupBy-derived); [] when ungrouped.
|
|
887
|
+
* Path highlight is deliberately NOT here: session-local, never
|
|
888
|
+
* serialized (§16.2). */
|
|
889
|
+
groups: readonly ResolvedGroup[];
|
|
890
|
+
/** Last completed search (§16.5): feeds <GraphSearch> and the §15.1
|
|
891
|
+
* navigator's search-results section. Cleared on datasetKey change. */
|
|
892
|
+
search: {
|
|
893
|
+
query: string;
|
|
894
|
+
results: readonly SearchResult[];
|
|
895
|
+
} | null;
|
|
896
|
+
viewport: ViewportState | null;
|
|
897
|
+
/** §14/§16.1: live force-simulation activity — true after a commit with
|
|
898
|
+
* restart or resumeSimulation(); false on settle or pauseSimulation(). */
|
|
899
|
+
simulationRunning: boolean;
|
|
900
|
+
/** §8 resolved theme tokens (S10): the merged GraphTheme currently driving
|
|
901
|
+
* engine config, projection fallbacks, and mask dim alpha. Published on
|
|
902
|
+
* change; defaults to the dark base. */
|
|
903
|
+
theme: GraphTheme;
|
|
904
|
+
diagnostics: readonly GraphDiagnostic[];
|
|
905
|
+
}
|
|
906
|
+
interface GraphListenerControl {
|
|
907
|
+
preventDefault(): void;
|
|
908
|
+
}
|
|
909
|
+
interface NodeEventPayload<N = Record<string, unknown>> {
|
|
910
|
+
node: GraphNode<N>;
|
|
911
|
+
}
|
|
912
|
+
interface GraphEventMap<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
913
|
+
nodeClick: NodeEventPayload<N> & {
|
|
914
|
+
metaKey?: boolean;
|
|
915
|
+
};
|
|
916
|
+
backgroundClick: Record<string, never>;
|
|
917
|
+
nodeHover: {
|
|
918
|
+
node: GraphNode<N> | null;
|
|
919
|
+
};
|
|
920
|
+
edgeClick: {
|
|
921
|
+
edge: AcceptedEdge<E>;
|
|
922
|
+
};
|
|
923
|
+
edgeHover: {
|
|
924
|
+
edge: AcceptedEdge<E> | null;
|
|
925
|
+
};
|
|
926
|
+
nodeDragStart: NodeEventPayload<N>;
|
|
927
|
+
/** Fired on drag release with the final space position; the built-in
|
|
928
|
+
* follow-up pins the node there (preventDefault cancels the pin). */
|
|
929
|
+
nodeDragEnd: NodeEventPayload<N> & {
|
|
930
|
+
x: number;
|
|
931
|
+
y: number;
|
|
932
|
+
};
|
|
933
|
+
/** §16.14: a setViewState dataRef mismatch — fired INSTEAD of applying.
|
|
934
|
+
* Restoration proceeds only when the caller re-invokes with the opt-in. */
|
|
935
|
+
viewStateMismatch: {
|
|
936
|
+
stored: JsonValue | undefined;
|
|
937
|
+
current: JsonValue | undefined;
|
|
938
|
+
};
|
|
939
|
+
/** §16.14 aggregate restore intent: fired ONCE per restore/history
|
|
940
|
+
* transaction touching any §6.4 controlled slice or serialized styling —
|
|
941
|
+
* never fanned out per lane. The host reflects every participating prop in
|
|
942
|
+
* one commit; the transaction commits when the reflected values match, and
|
|
943
|
+
* times out / diverges / supersedes as typed results otherwise. `next` is
|
|
944
|
+
* the full target view state. */
|
|
945
|
+
viewStateRestore: {
|
|
946
|
+
transactionId: string;
|
|
947
|
+
source: 'setViewState' | 'undo' | 'redo';
|
|
948
|
+
next: unknown;
|
|
949
|
+
};
|
|
950
|
+
/** Right-click / long-press; built-in follow-up opens <GraphContextMenu>. */
|
|
951
|
+
contextMenu: {
|
|
952
|
+
target: {
|
|
953
|
+
kind: 'node';
|
|
954
|
+
node: GraphNode<N>;
|
|
955
|
+
} | {
|
|
956
|
+
kind: 'background';
|
|
957
|
+
};
|
|
958
|
+
/** Container-relative CSS px. */
|
|
959
|
+
screen: readonly [number, number];
|
|
960
|
+
};
|
|
961
|
+
viewportChange: ViewportState;
|
|
962
|
+
selectionChange: SelectionState;
|
|
963
|
+
/** §16.3/§7.4 (R-16.3-12): a super-node hit carries the resolved GROUP —
|
|
964
|
+
* never a GraphNode, never an internal scene key. Built-in follow-up
|
|
965
|
+
* selects the group id into SelectionState.groupIds (preventDefault
|
|
966
|
+
* cancels it, mirroring nodeClick). */
|
|
967
|
+
groupClick: {
|
|
968
|
+
group: ResolvedGroup;
|
|
969
|
+
metaKey?: boolean;
|
|
970
|
+
};
|
|
971
|
+
/** §16.3/§7.4: a meta-edge hit carries the MetaEdge record (public
|
|
972
|
+
* endpoint ids + the underlying count badge datum). No built-in follow-up. */
|
|
973
|
+
metaEdgeClick: {
|
|
974
|
+
metaEdge: MetaEdge;
|
|
975
|
+
};
|
|
976
|
+
/** §6.4 groups slice change: op results (uncontrolled), op intents
|
|
977
|
+
* (controlled — the host reflects the array back through the `groups`
|
|
978
|
+
* prop), and groupBy re-derivations (notification; groupBy is always
|
|
979
|
+
* instance-derived, R-16.3-16). Host `groups` prop writes and manual
|
|
980
|
+
* model-drift re-resolutions are store-only and do NOT fire this. */
|
|
981
|
+
groupsChange: {
|
|
982
|
+
groups: readonly ResolvedGroup[];
|
|
983
|
+
};
|
|
984
|
+
/** §6.4 persistent-pin slice change (S12-T09), the groups-latch mirror:
|
|
985
|
+
* op results (uncontrolled) and op INTENTS (controlled — the host
|
|
986
|
+
* reflects the array back through the `pinnedNodeIds` prop). Host prop
|
|
987
|
+
* writes and model-drift prunes are store-only and do NOT fire this. */
|
|
988
|
+
pinnedChange: {
|
|
989
|
+
pinnedNodeIds: readonly NodeId[];
|
|
990
|
+
};
|
|
991
|
+
/** §16.3 effective-set reporting seam (S12-T03): retractExpansion fires this
|
|
992
|
+
* with the NEXT effective set as a SubgraphSpec whenever a collapse
|
|
993
|
+
* changed what is displayed. v0.10 keeps `subgraph` UNCONTROLLED-ONLY, so
|
|
994
|
+
* this is a notification today; a future controlled subgraph mode turns
|
|
995
|
+
* it into the §6.4 intent without changing the payload shape. */
|
|
996
|
+
subgraphChange: {
|
|
997
|
+
subgraph: SubgraphSpec;
|
|
998
|
+
};
|
|
999
|
+
ready: Record<string, never>;
|
|
1000
|
+
error: {
|
|
1001
|
+
error: Error;
|
|
1002
|
+
detail?: GraphError;
|
|
1003
|
+
};
|
|
1004
|
+
simulationEnd: Record<string, never>;
|
|
1005
|
+
}
|
|
1006
|
+
type GraphEventName = keyof GraphEventMap;
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* GraphEngine adapter contract (spec §13, v0.1 subset).
|
|
1010
|
+
*
|
|
1011
|
+
* The core drives any engine exclusively through this interface; cosmos.gl is
|
|
1012
|
+
* the first implementation and lives in @modernrelay/orbit-engine-cosmos — the
|
|
1013
|
+
* only package allowed to import it. FakeEngine (core /testing) implements the
|
|
1014
|
+
* same contract headlessly.
|
|
1015
|
+
*
|
|
1016
|
+
* v0.1 subset decisions:
|
|
1017
|
+
* - Buffer updates are full-channel replaces (`rangeUpdates` reserved).
|
|
1018
|
+
* - One visibly atomic update per EngineCommit (§13): the adapter applies all
|
|
1019
|
+
* channels/config of a commit before the next drawn frame.
|
|
1020
|
+
* - Position readback (`getPositions`) is per-event only, never per-tick (§7.1/§17).
|
|
1021
|
+
*/
|
|
1022
|
+
|
|
1023
|
+
type EngineBufferChannel = 'pointPosition' | 'link' | 'pointColor' | 'pointSize' | 'linkColor' | 'linkWidth';
|
|
1024
|
+
interface EngineCapabilities {
|
|
1025
|
+
/** Native edge hover/click picking. */
|
|
1026
|
+
linkPicking: boolean;
|
|
1027
|
+
/** Channels supporting ranged (partial) updates. Empty in v0.1 for cosmos. */
|
|
1028
|
+
rangeUpdates: readonly EngineBufferChannel[];
|
|
1029
|
+
/** O(k) tracked-subset position readback per frame (§13). */
|
|
1030
|
+
trackedPositions: boolean;
|
|
1031
|
+
/** Engine runs a live GPU force simulation. */
|
|
1032
|
+
simulation: boolean;
|
|
1033
|
+
/** Instanced directional arrowheads on links (§16.12). */
|
|
1034
|
+
edgeArrows?: boolean;
|
|
1035
|
+
/** Per-point image sprites via a texture atlas (§8). */
|
|
1036
|
+
pointImages?: boolean;
|
|
1037
|
+
/** §16.3 cluster force: per-point cluster assignment with optional
|
|
1038
|
+
* strength/centers (S12). Engines without it degrade loudly — membership,
|
|
1039
|
+
* labels, and centroids still work (R-13-39). */
|
|
1040
|
+
clusterForce?: boolean;
|
|
1041
|
+
}
|
|
1042
|
+
interface EngineConfigUpdate {
|
|
1043
|
+
/** §16.3 stage-4 cluster force (capability `clusterForce`; inert
|
|
1044
|
+
* otherwise). null clears. `pointClusters` maps point index → cluster
|
|
1045
|
+
* ordinal (aligned to `centers` pairs). */
|
|
1046
|
+
cluster?: {
|
|
1047
|
+
pointClusters: Float32Array;
|
|
1048
|
+
centers?: Float32Array;
|
|
1049
|
+
strength?: number;
|
|
1050
|
+
} | null;
|
|
1051
|
+
backgroundColor?: string;
|
|
1052
|
+
simulation?: SimulationConfig;
|
|
1053
|
+
/** Space-coordinate defaults used when seeding unknown (NaN) positions. */
|
|
1054
|
+
seedRadius?: number;
|
|
1055
|
+
/** §16.12 arrowheads on/off (capability edgeArrows; inert otherwise). */
|
|
1056
|
+
linkArrows?: boolean;
|
|
1057
|
+
/** §16.13 link visibility toggle — config-only, never a buffer rebuild. */
|
|
1058
|
+
renderLinks?: boolean;
|
|
1059
|
+
/** Engine-relevant §8 theme tokens beyond background. */
|
|
1060
|
+
defaultPointColor?: string;
|
|
1061
|
+
defaultLinkColor?: string;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* One atomic engine update. `revision` is the desired-render revision the
|
|
1065
|
+
* commit realizes; adapters must report it back via `appliedRevision` once
|
|
1066
|
+
* visible. Buffers are full-channel replaces sized to the current structure;
|
|
1067
|
+
* when `structure` is present it replaces point/link structure and MUST be
|
|
1068
|
+
* applied together with any buffers in the same commit.
|
|
1069
|
+
*/
|
|
1070
|
+
interface EngineCommit {
|
|
1071
|
+
revision: number;
|
|
1072
|
+
structure?: {
|
|
1073
|
+
pointCount: number;
|
|
1074
|
+
/** 2*pointCount floats; NaN pairs = engine seeds the position. */
|
|
1075
|
+
positions: Float32Array;
|
|
1076
|
+
/** 2*linkCount uint32 endpoint indices. */
|
|
1077
|
+
links: Uint32Array;
|
|
1078
|
+
};
|
|
1079
|
+
buffers?: Partial<{
|
|
1080
|
+
/** 4*pointCount RGBA floats in [0,1]. */
|
|
1081
|
+
pointColor: Float32Array;
|
|
1082
|
+
/** pointCount floats (px). */
|
|
1083
|
+
pointSize: Float32Array;
|
|
1084
|
+
/** 4*linkCount RGBA floats in [0,1]. */
|
|
1085
|
+
linkColor: Float32Array;
|
|
1086
|
+
/** linkCount floats (px). */
|
|
1087
|
+
linkWidth: Float32Array;
|
|
1088
|
+
}>;
|
|
1089
|
+
config?: EngineConfigUpdate;
|
|
1090
|
+
/** §8 image-atlas resource updates (capability pointImages); applied
|
|
1091
|
+
* atomically with the same commit's buffers. */
|
|
1092
|
+
resources?: {
|
|
1093
|
+
imageAtlas?: {
|
|
1094
|
+
upserts?: readonly {
|
|
1095
|
+
slot: number;
|
|
1096
|
+
bitmap: ImageBitmap;
|
|
1097
|
+
}[];
|
|
1098
|
+
removeSlots?: readonly number[];
|
|
1099
|
+
};
|
|
1100
|
+
/** Per-point atlas slot indices (-1 = placeholder shape). */
|
|
1101
|
+
pointImageIndex?: Float32Array;
|
|
1102
|
+
};
|
|
1103
|
+
/** Restart/reheat the simulation after applying; false/absent = keep state. */
|
|
1104
|
+
restart?: {
|
|
1105
|
+
alpha: number;
|
|
1106
|
+
} | false;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* WebGL context lifecycle events (§13.1). `restored` means the adapter has a
|
|
1110
|
+
* fresh, empty, commit-ready GL machine in the same container — the core then
|
|
1111
|
+
* re-commits the scene. `failed` is terminal reinitialization failure.
|
|
1112
|
+
*/
|
|
1113
|
+
type EngineContextEvent = {
|
|
1114
|
+
type: 'lost';
|
|
1115
|
+
} | {
|
|
1116
|
+
type: 'restored';
|
|
1117
|
+
} | {
|
|
1118
|
+
type: 'failed';
|
|
1119
|
+
error: Error;
|
|
1120
|
+
};
|
|
1121
|
+
/** Adapter observability channel; codes are namespaced `engine:*` (§6.5). */
|
|
1122
|
+
interface EngineDiagnostic {
|
|
1123
|
+
code: `engine:${string}`;
|
|
1124
|
+
severity: DiagnosticSeverity;
|
|
1125
|
+
message: string;
|
|
1126
|
+
}
|
|
1127
|
+
/** Events the engine reports up to the core (indices are engine-local; the
|
|
1128
|
+
* core maps them back to typed objects — §7.4). */
|
|
1129
|
+
interface EngineHostEvents {
|
|
1130
|
+
onPointClick?(index: number | null, modifiers?: {
|
|
1131
|
+
metaKey: boolean;
|
|
1132
|
+
shiftKey: boolean;
|
|
1133
|
+
}): void;
|
|
1134
|
+
onPointHover?(index: number | null): void;
|
|
1135
|
+
/** Native link picking (capability `linkPicking`); indices are link-local. */
|
|
1136
|
+
onLinkClick?(linkIndex: number): void;
|
|
1137
|
+
onLinkHover?(linkIndex: number | null): void;
|
|
1138
|
+
/** Native point drag (space coords). The core owns pin semantics (§16.3). */
|
|
1139
|
+
onDragStart?(index: number): void;
|
|
1140
|
+
onDragEnd?(index: number, x: number, y: number): void;
|
|
1141
|
+
/** Context-menu gesture (right-click / touch long-press); null = background. */
|
|
1142
|
+
onContextMenu?(index: number | null, screen: readonly [number, number]): void;
|
|
1143
|
+
/**
|
|
1144
|
+
* Overlay/activity clock (§13/§17): the adapter's per-frame callback for the
|
|
1145
|
+
* core scheduler's fan-out (DOM labels, tooltips). Under
|
|
1146
|
+
* `postDrawFrames:false` engines this is an activity clock — overlays may
|
|
1147
|
+
* lag the canvas by one sample; the adapter reports that degradation once.
|
|
1148
|
+
*/
|
|
1149
|
+
onFrame?(timeMs: number): void;
|
|
1150
|
+
onViewportChange?(v: ViewportState): void;
|
|
1151
|
+
onSimulationEnd?(): void;
|
|
1152
|
+
onError?(error: Error): void;
|
|
1153
|
+
onContextEvent?(ev: EngineContextEvent): void;
|
|
1154
|
+
onDiagnostic?(d: EngineDiagnostic): void;
|
|
1155
|
+
}
|
|
1156
|
+
interface FitViewOptions {
|
|
1157
|
+
durationMs?: number;
|
|
1158
|
+
padding?: number;
|
|
1159
|
+
}
|
|
1160
|
+
interface GraphEngine {
|
|
1161
|
+
readonly capabilities: EngineCapabilities;
|
|
1162
|
+
/** Mount into a container. Resolve when the first frame can be produced. */
|
|
1163
|
+
mount(container: HTMLElement, events: EngineHostEvents): Promise<void>;
|
|
1164
|
+
/** Apply one atomic update (§13). Throws only on programmer error. */
|
|
1165
|
+
commit(update: EngineCommit): void;
|
|
1166
|
+
/** Highest commit revision that is visibly applied. */
|
|
1167
|
+
appliedRevision(): number | null;
|
|
1168
|
+
fitView(opts?: FitViewOptions): void;
|
|
1169
|
+
zoom(factor: number, durationMs?: number): void;
|
|
1170
|
+
setViewport(v: Partial<ViewportState>, opts?: {
|
|
1171
|
+
durationMs?: number;
|
|
1172
|
+
}): void;
|
|
1173
|
+
getViewport(): ViewportState | null;
|
|
1174
|
+
/** Optional: animate the camera to a point (used by focusNode). */
|
|
1175
|
+
zoomToIndex?(index: number, durationMs?: number): void;
|
|
1176
|
+
start(alpha?: number): void;
|
|
1177
|
+
pause(): void;
|
|
1178
|
+
setSelectedIndices(indices: readonly number[] | null): void;
|
|
1179
|
+
setFocusedIndex(index: number | null): void;
|
|
1180
|
+
/** Point indices inside a SCREEN-coordinate polygon (lasso/rectangle). */
|
|
1181
|
+
pointsInPolygon?(screenPolygon: readonly [number, number][]): number[];
|
|
1182
|
+
/** Point indices inside a SCREEN-coordinate rect [x0,y0,x1,y1] (label cull). */
|
|
1183
|
+
pointsInRect?(screenRect: readonly [number, number, number, number]): number[];
|
|
1184
|
+
/** Capture the current frame as an image (toolbar screenshot; §16.1). */
|
|
1185
|
+
captureScreenshot?(): Promise<Blob | null>;
|
|
1186
|
+
/** 1-hop neighbor point indices (engine adjacency, if available). */
|
|
1187
|
+
neighborIndices?(index: number): number[];
|
|
1188
|
+
screenToSpace?(p: readonly [number, number]): [number, number] | null;
|
|
1189
|
+
spaceToScreen?(p: readonly [number, number]): [number, number] | null;
|
|
1190
|
+
/** Replace the full pinned set; null/[] unpins all (idempotent). */
|
|
1191
|
+
setPinnedIndices?(indices: readonly number[] | null): void;
|
|
1192
|
+
getPositions(): Float32Array | null;
|
|
1193
|
+
destroy(): void;
|
|
1194
|
+
}
|
|
1195
|
+
/** Engine factory the host passes in; called once per instance mount. */
|
|
1196
|
+
type EngineFactory = () => GraphEngine;
|
|
1197
|
+
|
|
1198
|
+
export { type BrushState as $, type AcceptedGraph as A, type BeginIngestOptions as B, type SearchActivation as C, type CrossfilterSession as D, type EngineFactory as E, type Scale as F, type GraphSnapshot as G, type MetricName as H, type IngestSession as I, type JsonValue as J, type Revisions as K, type LabelPlacement as L, type MetaEdge as M, type NodeId as N, type AccessibilityConfig as O, type PathService as P, type ThemeInput as Q, type ResolvedGroup as R, type SceneFold as S, type TimelinePlayback as T, type LabelConfig as U, type ViewportState as V, type IngestBatch as W, type RevisionDimension as X, type FilterSpec as Y, type FilterExpr as Z, type DimensionSpec as _, type GraphDiagnostic as a, type DimensionSummary as a0, type DomainPolicy as a1, type EngineBufferChannel as a2, type GraphEngine as a3, type EngineCommit as a4, type EngineCapabilities as a5, type MetricColumn as a6, type AppendReceipt as a7, type CategoryBin as a8, type ClusterSpec as a9, type FitViewOptions as aA, type EngineDiagnostic as aB, type EngineConfigUpdate as aC, type EngineContextEvent as aD, DIAGNOSTIC_SAMPLE_CAP as aa, type DiagnosticCode as ab, type DiagnosticSeverity as ac, type DimensionKind as ad, type ErrorPhase as ae, type ExpansionBatch as af, type ExpansionResponse as ag, type FilterMode as ah, type FilterValue as ai, type GraphEdge as aj, type GraphError as ak, type GraphOperationError as al, type HistogramBin as am, type IngestCommitReceipt as an, type IngestSessionState as ao, type InstanceStatus as ap, type LayoutKind as aq, type NodeEventPayload as ar, OrbitOperationError as as, type ResourceAdmissionReport as at, type SearchUnavailableReason as au, type SimulationConfig as av, graphErrorToError as aw, isFatalGraphError as ax, resourceLimitFatal as ay, type EngineHostEvents as az, type GraphNode as b, type AcceptedEdge as c, type GroupSpec as d, type GroupBySpec as e, type SceneGroups as f, type RenderScene as g, type SceneLinkRef as h, type ScenePointRef as i, type Accessor as j, type RevisionAwareService as k, type RequestContext as l, type SearchResult as m, type SelectionState as n, type SubgraphSpec as o, type ExpansionService as p, type GraphTheme as q, type GraphStoreState as r, type GraphHostUpdate as s, type GraphEventName as t, type GraphEventMap as u, type GraphListenerControl as v, type EdgeId as w, type ResolvedCluster as x, type PathOptions as y, type PathResult as z };
|