@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2995 @@
|
|
|
1
|
+
import { G as GraphSnapshot, A as AcceptedGraph, R as ResolvedGroup, a as GraphDiagnostic, b as GraphNode, c as AcceptedEdge, N as NodeId, M as MetaEdge, S as SceneFold, d as GroupSpec, e as GroupBySpec, f as SceneGroups, g as RenderScene, h as SceneLinkRef, i as ScenePointRef, j as Accessor, k as RevisionAwareService, l as RequestContext, m as SearchResult, V as ViewportState, n as SelectionState, o as SubgraphSpec, J as JsonValue, E as EngineFactory, p as ExpansionService, P as PathService, q as GraphTheme, r as GraphStoreState, L as LabelPlacement, s as GraphHostUpdate, B as BeginIngestOptions, I as IngestSession, t as GraphEventName, u as GraphEventMap, v as GraphListenerControl, w as EdgeId, x as ResolvedCluster, y as PathOptions, z as PathResult, C as SearchActivation, D as CrossfilterSession, T as TimelinePlayback, F as Scale, H as MetricName, K as Revisions, O as AccessibilityConfig, Q as ThemeInput, U as LabelConfig, W as IngestBatch, X as RevisionDimension, Y as FilterSpec, Z as FilterExpr, _ as DimensionSpec, $ as BrushState, a0 as DimensionSummary, a1 as DomainPolicy, a2 as EngineBufferChannel, a3 as GraphEngine, a4 as EngineCommit, a5 as EngineCapabilities, a6 as MetricColumn } from './index-BPjuELfY.js';
|
|
2
|
+
export { a7 as AppendReceipt, a8 as CategoryBin, a9 as ClusterSpec, aa as DIAGNOSTIC_SAMPLE_CAP, ab as DiagnosticCode, ac as DiagnosticSeverity, ad as DimensionKind, ae as ErrorPhase, af as ExpansionBatch, ag as ExpansionResponse, ah as FilterMode, ai as FilterValue, aj as GraphEdge, ak as GraphError, al as GraphOperationError, am as HistogramBin, an as IngestCommitReceipt, ao as IngestSessionState, ap as InstanceStatus, aq as LayoutKind, ar as NodeEventPayload, as as OrbitOperationError, at as ResourceAdmissionReport, au as SearchUnavailableReason, av as SimulationConfig, aw as graphErrorToError, ax as isFatalGraphError, ay as resourceLimitFatal } from './index-BPjuELfY.js';
|
|
3
|
+
export { C as ClusterDerivation, D as DEFAULT_CLUSTER_CENTER_RADIUS, a as DEFAULT_LAYOUT_SEED, c as clusterCentroids, d as deriveClusters, g as generateClusterCenters, r as resolveClusterCenters } from './clusters-ExqnvobT.js';
|
|
4
|
+
import { StoreApi } from 'zustand/vanilla';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* §5.1 snapshot validation — deterministic malformed-input resolution.
|
|
8
|
+
*
|
|
9
|
+
* Pure function: same input → structurally identical output; never throws on
|
|
10
|
+
* malformed input. Diagnostics are batched — at most one GraphDiagnostic per
|
|
11
|
+
* code per pass, with a total count and at most DIAGNOSTIC_SAMPLE_CAP samples
|
|
12
|
+
* (O(categories) work/allocation, never O(bad rows)).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare function validateSnapshot<N = Record<string, unknown>, E = Record<string, unknown>>(snapshot: GraphSnapshot<N, E>): AcceptedGraph<N, E>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* §16.3 stage-3 containment rewrite (S12-T01) — pure derivation, no engine,
|
|
19
|
+
* no DOM.
|
|
20
|
+
*
|
|
21
|
+
* ## The representative forest
|
|
22
|
+
*
|
|
23
|
+
* ONE structure drives both group collapse and node fold. Every entity — a
|
|
24
|
+
* physical node id, or a synthetic group scene key — has AT MOST ONE
|
|
25
|
+
* representative parent, and:
|
|
26
|
+
*
|
|
27
|
+
* > An entity is drawn iff no ancestor is collapsed. Each edge endpoint
|
|
28
|
+
* > reroutes to its OUTERMOST COLLAPSED ancestor; edges whose endpoints
|
|
29
|
+
* > rewrite to the same key are dropped; the rest merge per directed pair
|
|
30
|
+
* > into one meta-edge carrying the underlying count. A SYNTHETIC
|
|
31
|
+
* > representative materializes only while collapsed; a REAL one is always
|
|
32
|
+
* > drawn.
|
|
33
|
+
*
|
|
34
|
+
* `collapsed` is a property of the REPRESENTATIVE, not of the node — which
|
|
35
|
+
* is exactly why a folded anchor stays visible while its children hide, and
|
|
36
|
+
* why nesting needs no special case (a fold inside a collapsed group hides
|
|
37
|
+
* with it, and its members' edges route to the group).
|
|
38
|
+
*
|
|
39
|
+
* Contract summary (spec §16.3/§7.6 stage 3, invariants E5):
|
|
40
|
+
* - Groups are HIERARCHICAL but singly-parented: a member id may name another
|
|
41
|
+
* group (nesting), but no entity has two parents. `validateGroupSpecs`
|
|
42
|
+
* rejects a violating array with ONE batched §5.1 'config-error' diagnostic
|
|
43
|
+
* BEFORE any scene rewrite — a rejected array changes nothing.
|
|
44
|
+
* - `rewriteGroups` runs over the HARD-SCOPED model (§7.6 stage 2 output):
|
|
45
|
+
* collapsed representatives replace their in-scope descendants with one row
|
|
46
|
+
* (a synthetic super-node, or the fold anchor's existing physical row);
|
|
47
|
+
* descendant edges re-route into meta-edge rows carrying the underlying-edge
|
|
48
|
+
* count (the badge datum). The post-rewrite graph feeds the EXISTING §7.2
|
|
49
|
+
* structural diff, so collapse/expand is a diff, never a reload.
|
|
50
|
+
* - Group ids occupy a distinct PUBLIC namespace (§7.4): synthetic rows carry
|
|
51
|
+
* INTERNAL scene keys (NUL-prefixed — outside the documented caller id
|
|
52
|
+
* contract) that never escape public payloads. A group id equal to a node
|
|
53
|
+
* id coexists without collision. A FOLD anchor is a real node, so its
|
|
54
|
+
* entity key IS its node id and it needs no synthetic slot at all.
|
|
55
|
+
* - Synthetic rows are never cast to N/E: they carry only `id`, and consumers
|
|
56
|
+
* identify them by slot position — synthetics are always a CONTIGUOUS
|
|
57
|
+
* SUFFIX of the node/edge lists (physical prefix ordering is preserved), so
|
|
58
|
+
* `slot >= physicalCount` is the discrimination rule.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** Internal scene key of a collapsed group's super-node row. NEVER public. */
|
|
62
|
+
declare function groupSceneKey(groupId: string): string;
|
|
63
|
+
/** Internal scene key of a meta-edge row (directed rewritten endpoint pair). */
|
|
64
|
+
declare function metaEdgeSceneKey(sourceKey: string, targetKey: string): string;
|
|
65
|
+
/** §16.3 PUBLIC meta-edge id: the collision-safe ordered endpoint tuple
|
|
66
|
+
* `JSON.stringify(['meta-edge', source.kind, source.id, target.kind,
|
|
67
|
+
* target.id])` — shared with the parallel-edge grouping toggle (R-16.3-24). */
|
|
68
|
+
declare function metaEdgePublicId(sourceKind: 'node' | 'group', sourceId: string, targetKind: 'node' | 'group', targetId: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Entity key: a physical node id, or a synthetic group scene key. A fold
|
|
71
|
+
* anchor IS its node id — that is the whole point of a real representative.
|
|
72
|
+
*/
|
|
73
|
+
type EntityKey = string;
|
|
74
|
+
interface RepForest {
|
|
75
|
+
/** entity → its representative parent (at most one, by construction). */
|
|
76
|
+
readonly parent: ReadonlyMap<EntityKey, EntityKey>;
|
|
77
|
+
/** Representatives whose CHILDREN are hidden — never themselves. */
|
|
78
|
+
readonly collapsed: ReadonlySet<EntityKey>;
|
|
79
|
+
/** Synthetic representatives: scene key → the group it stands for. */
|
|
80
|
+
readonly groupOf: ReadonlyMap<EntityKey, ResolvedGroup>;
|
|
81
|
+
/** Fold anchors in declaration order (deterministic scene output). */
|
|
82
|
+
readonly anchors: readonly NodeId[];
|
|
83
|
+
/** Groups in array order (deterministic super-node suffix order). */
|
|
84
|
+
readonly groups: readonly ResolvedGroup[];
|
|
85
|
+
}
|
|
86
|
+
/** Physical default size when no nodeSize accessor is configured but the
|
|
87
|
+
* scene needs a full buffer for the synthetic suffix. */
|
|
88
|
+
declare const PHYSICAL_DEFAULT_POINT_SIZE = 4;
|
|
89
|
+
/** Physical default width when no linkWidth accessor is configured. */
|
|
90
|
+
declare const PHYSICAL_DEFAULT_LINK_WIDTH = 1;
|
|
91
|
+
declare const SUPER_NODE_MAX_SIZE = 36;
|
|
92
|
+
declare const META_EDGE_MAX_WIDTH = 8;
|
|
93
|
+
/** Aggregate super-node size: sublinear in member count, bounded. */
|
|
94
|
+
declare function superNodeSizeFor(memberCount: number): number;
|
|
95
|
+
/** Aggregate meta-edge width: sublinear in underlying-edge count, bounded. */
|
|
96
|
+
declare function metaEdgeWidthFor(count: number): number;
|
|
97
|
+
interface GroupValidationResult {
|
|
98
|
+
/** Null when the array is valid; otherwise ONE batched 'config-error'. */
|
|
99
|
+
diagnostic: GraphDiagnostic | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validates a manual `groups` array against the FULL accepted model (members
|
|
103
|
+
* may live outside the current hard scope). Violations, all collected into
|
|
104
|
+
* one diagnostic (§5.1 delivery contract — never one event per bad row):
|
|
105
|
+
* - duplicate group id in the array;
|
|
106
|
+
* - self-membership (a group naming its own id — rejected even when a node
|
|
107
|
+
* with that id exists: the ambiguity itself is the error);
|
|
108
|
+
* - duplicate membership (the same node twice in one group);
|
|
109
|
+
* - overlapping membership (the same node in two groups);
|
|
110
|
+
* - CYCLIC nesting (groups that contain each other, directly or through a
|
|
111
|
+
* chain — the containment forest must stay acyclic);
|
|
112
|
+
* - unknown members (ids absent from the accepted model AND not naming
|
|
113
|
+
* another group; with no accepted model every non-group member is unknown).
|
|
114
|
+
*
|
|
115
|
+
* NESTING IS LEGAL: a member id naming another group id nests that group.
|
|
116
|
+
* Containment is a forest, not a partition — but it stays SINGLY PARENTED,
|
|
117
|
+
* so overlap remains an error.
|
|
118
|
+
*/
|
|
119
|
+
declare function validateGroupSpecs(specs: readonly GroupSpec[], nodeIndex: ReadonlyMap<NodeId, number> | null): GroupValidationResult;
|
|
120
|
+
/**
|
|
121
|
+
* Resolves a VALIDATED manual groups array against the accepted model.
|
|
122
|
+
* Tolerant by design: members that later departed the model drop out of the
|
|
123
|
+
* resolved membership (model drift is data, not a config error — mirrors
|
|
124
|
+
* selection pruning). Spec order and member order are preserved.
|
|
125
|
+
*/
|
|
126
|
+
declare function resolveManualGroups(specs: readonly GroupSpec[], nodeIndex: ReadonlyMap<NodeId, number>): ResolvedGroup[];
|
|
127
|
+
/** Canonical structural equality for the groups HOST LANE (GroupSpec is a
|
|
128
|
+
* plain descriptor like Scale — equal literals never re-rewrite, §6.1). */
|
|
129
|
+
declare function sameGroupSpecArrays(a: readonly GroupSpec[] | null, b: readonly GroupSpec[] | null): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* R-16.3-14 collision-safe derived-group id codec: `JSON.stringify(['group',
|
|
132
|
+
* key])`. Injective over keys (JSON string encoding), never equal to the raw
|
|
133
|
+
* key itself (every output starts with `["group",`), and round-trippable via
|
|
134
|
+
* JSON.parse. Labels stay separate — the derived group's `label` is the raw
|
|
135
|
+
* key. Group ids remain a distinct public namespace (§7.4), so a NODE id that
|
|
136
|
+
* happens to equal a derived id still coexists without collision.
|
|
137
|
+
*/
|
|
138
|
+
declare function groupByDerivedId(key: string): string;
|
|
139
|
+
/** Canonical identity for the groupBy HOST LANE: `by` compares by function
|
|
140
|
+
* reference (a new inline lambda re-derives), semanticZoom structurally. */
|
|
141
|
+
declare function sameGroupBySpec<N>(a: GroupBySpec<N> | null, b: GroupBySpec<N> | null): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* D4 boundary validation for a groupBy spec: `by` must be a function and
|
|
144
|
+
* `semanticZoom.expandAbove` must be STRICTLY greater than `collapseBelow`
|
|
145
|
+
* (the §16.3 hysteresis contract T05 builds on). Returns ONE batched
|
|
146
|
+
* 'config-error' diagnostic (the rejected spec never lands — the previous
|
|
147
|
+
* groupBy configuration stays active) or null when valid.
|
|
148
|
+
*/
|
|
149
|
+
declare function validateGroupBySpec<N>(spec: GroupBySpec<N>): GraphDiagnostic | null;
|
|
150
|
+
interface GroupByDerivation {
|
|
151
|
+
/** One derived group per distinct key, first-encounter order over the
|
|
152
|
+
* model's node list. `collapsed` comes from the caller's residue lookup. */
|
|
153
|
+
groups: readonly ResolvedGroup[];
|
|
154
|
+
/** Derived PUBLIC id → derived key (the setGroupCollapsed reverse map —
|
|
155
|
+
* the residue is keyed by KEY, never by id, R-16.3-16). */
|
|
156
|
+
keyById: ReadonlyMap<string, string>;
|
|
157
|
+
/** ONE aggregated 'accessor-error' warning when `by` threw (the affected
|
|
158
|
+
* nodes derive as ungrouped — never silent loss, I3), else null. */
|
|
159
|
+
diagnostic: GraphDiagnostic | null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* §16.3 groupBy derivation: one GroupSpec-shaped ResolvedGroup per distinct
|
|
163
|
+
* string key returned by `by` (first-encounter order, matching §11
|
|
164
|
+
* categorical-domain conventions); `null` — and any non-string value — means
|
|
165
|
+
* ungrouped. Derived ids use {@link groupByDerivedId}; `label` is the raw
|
|
166
|
+
* key; `derived: true` marks membership read-only. `collapsed` defaults to
|
|
167
|
+
* whatever `isCollapsedKey` reports — with an empty residue everything is
|
|
168
|
+
* expanded, so adding groupBy alone changes no rendering (R-16.3-13).
|
|
169
|
+
*/
|
|
170
|
+
declare function deriveGroupsByKey<N>(nodes: readonly GraphNode<N>[], by: (node: GraphNode<N>) => string | null, isCollapsedKey: (key: string) => boolean): GroupByDerivation;
|
|
171
|
+
interface SuperNodeRecord {
|
|
172
|
+
/** Internal scene key of the super-node row (never public). */
|
|
173
|
+
sceneKey: string;
|
|
174
|
+
group: ResolvedGroup;
|
|
175
|
+
/** TRANSITIVE descendants present in the REWRITTEN model's input
|
|
176
|
+
* (hard-scoped set) — the stage-5 mask and aggregate size read this, not
|
|
177
|
+
* the full declared membership. Transitive and direct coincide unless
|
|
178
|
+
* something is nested underneath. */
|
|
179
|
+
presentMemberIds: readonly NodeId[];
|
|
180
|
+
}
|
|
181
|
+
interface MetaEdgeRecord {
|
|
182
|
+
/** Internal scene key of the meta-edge row (never public). */
|
|
183
|
+
sceneKey: string;
|
|
184
|
+
/** Public record (§7.4 payload surface): endpoints are PUBLIC ids — a
|
|
185
|
+
* group id or a node id — and `count` is the badge datum. */
|
|
186
|
+
metaEdge: MetaEdge;
|
|
187
|
+
/** Indices of the rerouted edges in the PRE-rewrite model's edge list —
|
|
188
|
+
* the stage-5 "any underlying edge passes" rule evaluates these. */
|
|
189
|
+
underlying: readonly number[];
|
|
190
|
+
}
|
|
191
|
+
interface GroupRewrite<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
192
|
+
/** The post-rewrite scene model — what feeds the §7.2 structural diff. */
|
|
193
|
+
graph: AcceptedGraph<N, E>;
|
|
194
|
+
/** Physical rows (same objects as the input model's, members removed). */
|
|
195
|
+
physicalNodes: readonly GraphNode<N>[];
|
|
196
|
+
physicalEdges: readonly AcceptedEdge<E>[];
|
|
197
|
+
/** Scene slots >= these counts are synthetic (contiguous-suffix rule). */
|
|
198
|
+
physicalNodeCount: number;
|
|
199
|
+
physicalEdgeCount: number;
|
|
200
|
+
/** Aligned to point slots physicalNodeCount..count-1, in groups order. */
|
|
201
|
+
superNodes: readonly SuperNodeRecord[];
|
|
202
|
+
/** Aligned to link slots physicalEdgeCount..linkCount-1, first-encounter
|
|
203
|
+
* order over the input edge scan. */
|
|
204
|
+
metaEdges: readonly MetaEdgeRecord[];
|
|
205
|
+
/** Drawn fold anchors and the descendant count each currently hides. These
|
|
206
|
+
* are PHYSICAL rows — they occupy no synthetic slot. */
|
|
207
|
+
folds: readonly SceneFold[];
|
|
208
|
+
/** hidden node id → the entity key of the row standing for it (a super-node
|
|
209
|
+
* scene key, or a fold anchor's node id). */
|
|
210
|
+
hiddenOwner: ReadonlyMap<NodeId, EntityKey>;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Rewrites the collapsed representatives of `forest` over the hard-scoped
|
|
214
|
+
* model (§7.6 stage 3). Returns null when nothing collapsed intersects the
|
|
215
|
+
* model — an uncollapsed group exists only in store.groups and never
|
|
216
|
+
* rewrites the scene.
|
|
217
|
+
*
|
|
218
|
+
* Deterministic: physical rows keep model order; super-nodes append in
|
|
219
|
+
* groups-array order; meta-edges append in first-encounter order. Edges whose
|
|
220
|
+
* endpoints land on the SAME representative drop — that covers both a
|
|
221
|
+
* same-group internal edge and an anchor's edge to its own folded member,
|
|
222
|
+
* which would otherwise emit a self-loop. Dropped edges are internal state,
|
|
223
|
+
* cached by construction: they re-derive from the unchanged accepted model on
|
|
224
|
+
* expand. Synthetic rows carry no positions; the reconciler's departed cache
|
|
225
|
+
* restores a re-collapsed super-node (stable scene key) and returning members
|
|
226
|
+
* exactly like any other leave-and-return (§7.3).
|
|
227
|
+
*/
|
|
228
|
+
declare function rewriteGroups<N, E>(model: AcceptedGraph<N, E>, forest: RepForest): GroupRewrite<N, E> | null;
|
|
229
|
+
/**
|
|
230
|
+
* Collapses same-DIRECTED-endpoint-pair physical edges into ONE meta-edge
|
|
231
|
+
* per pair, composing with an existing group rewrite (or synthesizing a
|
|
232
|
+
* groups-empty rewrite when none is active). Returns `base` unchanged when
|
|
233
|
+
* no pair has multiplicity > 1 — the toggle is then a scene no-op.
|
|
234
|
+
*
|
|
235
|
+
* Meta-edge identity reuses the T01 codecs: public id
|
|
236
|
+
* `metaEdgePublicId('node', source, 'node', target)` (the R-16.3-24 tuple),
|
|
237
|
+
* internal scene key `metaEdgeSceneKey(source, target)`. `count` (the badge
|
|
238
|
+
* datum) is the collapsed multiplicity and drives the T01 aggregate width
|
|
239
|
+
* channel; `underlying` indexes the PRE-rewrite model's edge list so the
|
|
240
|
+
* stage-5 "any underlying edge passes" mask rule applies unchanged.
|
|
241
|
+
*/
|
|
242
|
+
declare function collapseParallelEdges<N, E>(model: AcceptedGraph<N, E>, base: GroupRewrite<N, E> | null): GroupRewrite<N, E> | null;
|
|
243
|
+
/** The RenderScene.groups descriptor for a rewrite. */
|
|
244
|
+
declare function sceneGroupsOf(rewrite: GroupRewrite<unknown, unknown>): SceneGroups;
|
|
245
|
+
/** Discriminated point ref: physical node id or the ResolvedGroup — never an
|
|
246
|
+
* internal scene key. Null when out of range. */
|
|
247
|
+
declare function scenePointRefAt(scene: RenderScene, index: number): ScenePointRef | null;
|
|
248
|
+
/** Discriminated link ref: physical edge id or the MetaEdge record. */
|
|
249
|
+
declare function sceneLinkRefAt(scene: RenderScene, linkIndex: number): SceneLinkRef | null;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* §7 reconciler — id↔index model, structural diff, position cache.
|
|
253
|
+
*
|
|
254
|
+
* v0.1 implements the 'rebuild' index policy only (§7.3): every reconcile
|
|
255
|
+
* rebuilds the flat buffers from scratch in accepted-base order, preserving
|
|
256
|
+
* positions by id via the live/departed caches. Pure data-structure work: no
|
|
257
|
+
* DOM, no engine import.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
interface ReconcileResult {
|
|
261
|
+
scene: RenderScene;
|
|
262
|
+
/**
|
|
263
|
+
* True when the node id sequence OR the edge endpoint/id sequence differs
|
|
264
|
+
* from the previous reconcile. The first reconcile is always structural.
|
|
265
|
+
*/
|
|
266
|
+
structuralChange: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* True when the resolved position buffer differs from the previous live
|
|
269
|
+
* position mirror. NaN pairs compare equal so attr-only updates on
|
|
270
|
+
* unseeded nodes do not spuriously dirty the engine structure.
|
|
271
|
+
*/
|
|
272
|
+
positionChange: boolean;
|
|
273
|
+
/** Nodes whose position was restored from the live or departed cache. */
|
|
274
|
+
reusedPositions: number;
|
|
275
|
+
}
|
|
276
|
+
declare class Reconciler {
|
|
277
|
+
private datasetKey;
|
|
278
|
+
private hasScene;
|
|
279
|
+
private prevIdByIndex;
|
|
280
|
+
private prevIndexById;
|
|
281
|
+
private prevEdgeIdByIndex;
|
|
282
|
+
private prevLinks;
|
|
283
|
+
/**
|
|
284
|
+
* Declared node.x/y as of the previous reconcile, for nodes that declared
|
|
285
|
+
* them. Lets Priority 1 distinguish a CHANGED declaration (caller intent —
|
|
286
|
+
* wins over any cache) from an unchanged one (defers to live drift). Fresh
|
|
287
|
+
* per pass, so departed ids drop and a re-add treats its declaration as new.
|
|
288
|
+
*/
|
|
289
|
+
private prevDeclared;
|
|
290
|
+
/**
|
|
291
|
+
* CPU position mirror for the CURRENT scene, indexed by slot (§7.1 posBuf).
|
|
292
|
+
* Owned copy — never aliases a published scene's positions array, so
|
|
293
|
+
* noteEnginePositions never mutates an already-published RenderScene.
|
|
294
|
+
*/
|
|
295
|
+
private livePositions;
|
|
296
|
+
/**
|
|
297
|
+
* Ids removed from the scene → last known finite position (§7.1/§7.3
|
|
298
|
+
* leave-and-return guarantee). Map insertion order doubles as LRU order;
|
|
299
|
+
* invariant: never overlaps the current scene's id set.
|
|
300
|
+
*/
|
|
301
|
+
private readonly departed;
|
|
302
|
+
reconcile<N, E>(accepted: AcceptedGraph<N, E>): ReconcileResult;
|
|
303
|
+
/**
|
|
304
|
+
* Copy engine-read positions into the live cache for the CURRENT scene's
|
|
305
|
+
* slots (§7.1 per-event readback — simulation end, pre-structural-swap).
|
|
306
|
+
* Extra trailing floats are ignored; a short buffer updates a prefix.
|
|
307
|
+
*/
|
|
308
|
+
noteEnginePositions(positions: Float32Array): void;
|
|
309
|
+
private resetDatasetState;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* §8 projection (v0.1 subset): styling accessors → engine-ready typed buffers.
|
|
314
|
+
*
|
|
315
|
+
* Pure and DOM-free. Color parsing covers the CSS subset orbit documents
|
|
316
|
+
* (hex, rgb()/rgba(), hsl()/hsla(), small named map); anything else is a
|
|
317
|
+
* caller error surfaced as a batched 'accessor-error' diagnostic, never a
|
|
318
|
+
* throw and never a NaN in a GPU buffer (§8 numeric hygiene).
|
|
319
|
+
*/
|
|
320
|
+
|
|
321
|
+
type RGBA = [number, number, number, number];
|
|
322
|
+
/**
|
|
323
|
+
* Pure, DOM-free CSS color parse → RGBA floats in [0,1], or null when the
|
|
324
|
+
* string is not a recognized color. Successful parses are memoized (cache
|
|
325
|
+
* capped at 4096 entries; cleared wholesale on overflow). The returned tuple
|
|
326
|
+
* is shared across calls — treat it as immutable.
|
|
327
|
+
*/
|
|
328
|
+
declare function parseColor(css: string): RGBA | null;
|
|
329
|
+
declare function projectColors<T>(items: readonly T[], accessor: Accessor<T, string>, fallback?: [number, number, number, number]): {
|
|
330
|
+
buffer: Float32Array;
|
|
331
|
+
diagnostics: GraphDiagnostic[];
|
|
332
|
+
};
|
|
333
|
+
declare function projectSizes<T>(items: readonly T[], accessor: Accessor<T, number>, fallbackSize?: number): {
|
|
334
|
+
buffer: Float32Array;
|
|
335
|
+
diagnostics: GraphDiagnostic[];
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* §16.5 search — the SearchService contract plus the built-in LOCAL indexed
|
|
340
|
+
* service (S11-T06).
|
|
341
|
+
*
|
|
342
|
+
* The default service is client-side and field-scoped: it indexes the node id
|
|
343
|
+
* ALWAYS plus `attrs[field]` (String()-coerced) for each field the host
|
|
344
|
+
* declared via `searchIndex` — it never guesses privileged attr names, so a
|
|
345
|
+
* missing declaration leaves the service id-only. Matching is a
|
|
346
|
+
* case-insensitive SUBSTRING scan over ONE precomputed lowercase haystack per
|
|
347
|
+
* node (id + indexed field values joined with unit separators) — NOT a
|
|
348
|
+
* per-keystroke re-tokenization: the index builds once per model revision
|
|
349
|
+
* (lazily, on the first search that sees the new revision) and every query
|
|
350
|
+
* against that revision reuses it.
|
|
351
|
+
*
|
|
352
|
+
* Scoring: exact-id match {@link SEARCH_SCORE_EXACT_ID}, id-prefix
|
|
353
|
+
* {@link SEARCH_SCORE_ID_PREFIX}, field/id substring
|
|
354
|
+
* {@link SEARCH_SCORE_SUBSTRING} (+{@link SEARCH_SCORE_TOKEN_START_BONUS}
|
|
355
|
+
* when the match starts a token). Results sort score-desc then accepted-base
|
|
356
|
+
* order; `label` is the first matching indexed field's ORIGINAL value (the id
|
|
357
|
+
* when only the id matched).
|
|
358
|
+
*
|
|
359
|
+
* `ctx.signal` is honored between scan chunks (an awaited microtask every
|
|
360
|
+
* {@link SEARCH_SCAN_CHUNK} nodes) — abort is an optimization; the instance's
|
|
361
|
+
* admission gate is the correctness gate (§9.2). Nothing here touches the
|
|
362
|
+
* engine, the store, or the DOM.
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/** §16.5 search resolver: custom services plug in server-side search
|
|
366
|
+
* (Omnigraph B.7); the instance owns RequestContext creation, revision-keyed
|
|
367
|
+
* caching, supersede cancellation, and stale-result rejection at admission. */
|
|
368
|
+
interface SearchService<N = Record<string, unknown>> extends RevisionAwareService {
|
|
369
|
+
search(q: string, options: {
|
|
370
|
+
limit: number;
|
|
371
|
+
}, ctx: RequestContext): Promise<readonly SearchResult<N>[]>;
|
|
372
|
+
}
|
|
373
|
+
/** Accepted-base view the local service indexes (thunked for lazy wiring —
|
|
374
|
+
* the instance re-reads it on every call, so the service can be constructed
|
|
375
|
+
* before any data arrives). */
|
|
376
|
+
interface LocalSearchBase<N = Record<string, unknown>> {
|
|
377
|
+
/** Accepted-model nodes in accepted-base order (the tie-break order). */
|
|
378
|
+
nodes: readonly GraphNode<N>[];
|
|
379
|
+
/** §16.5 declared attr fields; undefined = id-only search (the service
|
|
380
|
+
* never guesses attr names). */
|
|
381
|
+
searchIndex: readonly string[] | undefined;
|
|
382
|
+
}
|
|
383
|
+
/** The built-in service plus its test seam: how many times the index was
|
|
384
|
+
* (re)built — pins "one build per model revision, not one per keystroke". */
|
|
385
|
+
interface LocalSearchService<N = Record<string, unknown>> extends SearchService<N> {
|
|
386
|
+
readonly buildCount: number;
|
|
387
|
+
}
|
|
388
|
+
declare const SEARCH_SCORE_EXACT_ID = 3;
|
|
389
|
+
declare const SEARCH_SCORE_ID_PREFIX = 2;
|
|
390
|
+
declare const SEARCH_SCORE_SUBSTRING = 1;
|
|
391
|
+
declare const SEARCH_SCORE_TOKEN_START_BONUS = 0.25;
|
|
392
|
+
/** Nodes scanned between cooperative yields (awaited microtask + signal check). */
|
|
393
|
+
declare const SEARCH_SCAN_CHUNK = 4096;
|
|
394
|
+
/**
|
|
395
|
+
* Creates the §16.5 default indexed search service over the accepted model.
|
|
396
|
+
* Declares `revisionDependencies: ['source', 'model']` — the index keys on
|
|
397
|
+
* `ctx.modelRevision` (plus the declared field list), so it builds at most
|
|
398
|
+
* once per model revision and a stale-model result is discarded by the
|
|
399
|
+
* instance's admission gate. `getBase` is a thunk re-read on every call so
|
|
400
|
+
* the instance can wire the service before data arrives.
|
|
401
|
+
*/
|
|
402
|
+
declare function createLocalSearchService<N = Record<string, unknown>>(getBase: () => LocalSearchBase<N>): LocalSearchService<N>;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* §8 image-atlas pipeline (S10-T09) — pure core side.
|
|
406
|
+
*
|
|
407
|
+
* Turns per-point image refs (stable strings from the synchronous `nodeImage`
|
|
408
|
+
* accessor) into engine-ready atlas resources: resolved ImageBitmaps with
|
|
409
|
+
* slot assignments plus an index-aligned per-point slot buffer. The engine
|
|
410
|
+
* only ever sees the output shape (`EngineCommit.resources`); everything
|
|
411
|
+
* async — resolve, fetch, decode, retry, abort — stays here.
|
|
412
|
+
*
|
|
413
|
+
* Invariants (spec §8):
|
|
414
|
+
* - Loads are deduplicated by ref: one in-flight resolve per unique ref.
|
|
415
|
+
* - Slots are allocated per unique ref from a free-list capped at
|
|
416
|
+
* `maxEntries`; over-cap refs stay placeholder and are diagnosed once per
|
|
417
|
+
* generation.
|
|
418
|
+
* - Results are admission-checked against the current request state before
|
|
419
|
+
* atlas admission; the abort signal is an optimization, admission is the
|
|
420
|
+
* gate (a resolver that ignores its signal cannot poison a newer
|
|
421
|
+
* generation).
|
|
422
|
+
* - Transient resolver/fetch failures retry up to `maxRetries`; decode
|
|
423
|
+
* failures are final.
|
|
424
|
+
* - Failures are cadence-batched: ONE `image-resolve-failed` diagnostic per
|
|
425
|
+
* flush with a count and sampled refs, each ref counted once per
|
|
426
|
+
* generation. Failures never poison the engine commit.
|
|
427
|
+
* - A resolver-returned string is a final URL/data URI fetched + decoded
|
|
428
|
+
* exactly once — never recursively passed back to the resolver.
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Owns authenticated fetch / caching / redirect handling for one ref
|
|
433
|
+
* (spec §8, Appendix B.6). Returning a Blob hands the bytes straight to
|
|
434
|
+
* decode; returning a string names a FINAL URL or data URI that the pipeline
|
|
435
|
+
* fetches + decodes once (it is never re-resolved).
|
|
436
|
+
*/
|
|
437
|
+
type ImageResolver = (ref: string, signal: AbortSignal) => Promise<Blob | string>;
|
|
438
|
+
/** Minimal structural slice of `fetch` the pipeline needs (Node-test injectable). */
|
|
439
|
+
type FetchLike = (url: string, init: {
|
|
440
|
+
signal: AbortSignal;
|
|
441
|
+
}) => Promise<{
|
|
442
|
+
ok: boolean;
|
|
443
|
+
status: number;
|
|
444
|
+
blob(): Promise<Blob>;
|
|
445
|
+
}>;
|
|
446
|
+
/** Blob → ImageBitmap. Injectable so Node tests never touch createImageBitmap. */
|
|
447
|
+
type ImageDecode = (blob: Blob) => Promise<ImageBitmap>;
|
|
448
|
+
/** One coalesced flush of atlas work — mirrors `EngineCommit.resources` (§13). */
|
|
449
|
+
interface ImageAtlasBatch {
|
|
450
|
+
/** Newly resolved bitmaps with their slot assignments. */
|
|
451
|
+
upserts: readonly {
|
|
452
|
+
slot: number;
|
|
453
|
+
bitmap: ImageBitmap;
|
|
454
|
+
}[];
|
|
455
|
+
/** Slots freed by eviction that the engine previously received. */
|
|
456
|
+
removeSlots: readonly number[];
|
|
457
|
+
/** Per-point atlas slot indices, aligned to the last requestRefs call
|
|
458
|
+
* (-1 = placeholder shape). */
|
|
459
|
+
pointImageIndex: Float32Array;
|
|
460
|
+
/** At most one batched `image-resolve-failed` diagnostic per flush. */
|
|
461
|
+
diagnostics: readonly GraphDiagnostic[];
|
|
462
|
+
}
|
|
463
|
+
interface ImageAtlasPipelineOptions {
|
|
464
|
+
/** Ref → bytes. Default: plain `fetch(ref)` → blob for public URLs. */
|
|
465
|
+
resolver?: ImageResolver;
|
|
466
|
+
/** Concurrent resolve bound. Default 4. */
|
|
467
|
+
maxConcurrent?: number;
|
|
468
|
+
/** Transient-failure retries per ref lifecycle (decode failures are final).
|
|
469
|
+
* Default 2 (three attempts total). */
|
|
470
|
+
maxRetries?: number;
|
|
471
|
+
/** Atlas slot capacity; refs beyond it stay placeholder. Default 512. */
|
|
472
|
+
maxEntries?: number;
|
|
473
|
+
/** Blob → ImageBitmap. Default `createImageBitmap`. */
|
|
474
|
+
decode?: ImageDecode;
|
|
475
|
+
/** Fetch used by the default resolver AND for resolver-returned strings. */
|
|
476
|
+
fetchImpl?: FetchLike;
|
|
477
|
+
/** Batch scheduler: called with a flush thunk when work is pending; each
|
|
478
|
+
* scheduled thunk must run at most once. Default: queueMicrotask. */
|
|
479
|
+
schedule?: (flush: () => void) => void;
|
|
480
|
+
}
|
|
481
|
+
declare const ATLAS_MAX_CONCURRENT_DEFAULT = 4;
|
|
482
|
+
declare const ATLAS_MAX_RETRIES_DEFAULT = 2;
|
|
483
|
+
declare const ATLAS_MAX_ENTRIES_DEFAULT = 512;
|
|
484
|
+
declare class ImageAtlasPipeline {
|
|
485
|
+
private readonly resolver;
|
|
486
|
+
private readonly maxConcurrent;
|
|
487
|
+
private readonly maxRetries;
|
|
488
|
+
private readonly maxEntries;
|
|
489
|
+
private readonly decode;
|
|
490
|
+
private readonly fetchImpl;
|
|
491
|
+
private readonly schedule;
|
|
492
|
+
/** ref → live entry. Identity of the mapped entry is the admission gate. */
|
|
493
|
+
private readonly entries;
|
|
494
|
+
private readonly freeSlots;
|
|
495
|
+
private nextSlot;
|
|
496
|
+
private readonly queue;
|
|
497
|
+
private active;
|
|
498
|
+
/** Slots the engine has received via a flushed upsert. */
|
|
499
|
+
private readonly deliveredSlots;
|
|
500
|
+
private readonly pendingUpserts;
|
|
501
|
+
private readonly pendingRemoveSlots;
|
|
502
|
+
/** Failed refs awaiting the next flush's single batched diagnostic. */
|
|
503
|
+
private pendingFailureRefs;
|
|
504
|
+
/** ref → generation it was last counted in (once-per-generation gate). */
|
|
505
|
+
private readonly countedFailures;
|
|
506
|
+
private lastRefs;
|
|
507
|
+
private generation;
|
|
508
|
+
private flushScheduled;
|
|
509
|
+
private disposed;
|
|
510
|
+
/** D5/F11-06: evicted-entry bitmaps awaiting close — closed AFTER the
|
|
511
|
+
* flush that carries their removeSlots, so the instance's recovery-replay
|
|
512
|
+
* map (pruned synchronously in the batch callback) can never re-send a
|
|
513
|
+
* closed bitmap. */
|
|
514
|
+
private pendingCloseBitmaps;
|
|
515
|
+
private readonly batchCallbacks;
|
|
516
|
+
constructor(options?: ImageAtlasPipelineOptions);
|
|
517
|
+
/** Subscribe to coalesced flushes. Returns an unsubscribe thunk. */
|
|
518
|
+
onBatch(cb: (batch: ImageAtlasBatch) => void): () => void;
|
|
519
|
+
/**
|
|
520
|
+
* Declare the full index-aligned per-point ref list for `generation`.
|
|
521
|
+
* Refs that drop out are evicted (slot freed → removeSlots); new unique
|
|
522
|
+
* refs are resolved once each; calls with a stale (lower) generation are
|
|
523
|
+
* discarded.
|
|
524
|
+
*/
|
|
525
|
+
requestRefs(refs: readonly (string | null)[], generation: number): void;
|
|
526
|
+
/** Abort all in-flight work and drop all state. Terminal. */
|
|
527
|
+
dispose(): void;
|
|
528
|
+
private allocSlot;
|
|
529
|
+
private evict;
|
|
530
|
+
/** Admission gate: entry must still be the live entry for its ref. */
|
|
531
|
+
private isCurrent;
|
|
532
|
+
private pump;
|
|
533
|
+
/** One attempt: resolve → (fetch if string) → decode. Never throws. */
|
|
534
|
+
private runAttempt;
|
|
535
|
+
private fetchBlob;
|
|
536
|
+
private finalFailure;
|
|
537
|
+
/** Queue a ref for the next flush's single diagnostic, once per generation. */
|
|
538
|
+
private recordFailure;
|
|
539
|
+
private scheduleFlush;
|
|
540
|
+
private flush;
|
|
541
|
+
/** -1 (placeholder) until the ref's bitmap is delivered or in this batch. */
|
|
542
|
+
private buildPointIndex;
|
|
543
|
+
/**
|
|
544
|
+
* I2 (roster-atomic resource mappings, F11-03): the SYNCHRONOUS point→slot
|
|
545
|
+
* mapping for the last requested roster — a slot appears only when its ref
|
|
546
|
+
* is resolved AND the engine has already received the bitmap (delivered);
|
|
547
|
+
* pending, failed, evicted, and reused-but-undelivered refs are the −1
|
|
548
|
+
* placeholder. The instance pairs every STRUCTURAL commit with this index
|
|
549
|
+
* in the same commit, so the engine never renders a new roster against the
|
|
550
|
+
* previous roster's (wrong-length, stale-slot) mapping; the scheduled
|
|
551
|
+
* async flush then only ever PROMOTES placeholders to resolved slots.
|
|
552
|
+
*/
|
|
553
|
+
currentPointIndex(): Float32Array | null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* §16.14 view state (S15 view-state lane) — pure module: the serialized
|
|
558
|
+
* schema, the canonical-JSON encoder, the structural validator, and the
|
|
559
|
+
* version gate. No engine, no DOM, no store access; the instance wires
|
|
560
|
+
* `getViewState`/`setViewState` on top.
|
|
561
|
+
*
|
|
562
|
+
* ## Compatibility contract
|
|
563
|
+
*
|
|
564
|
+
* Deep-links outlive library versions, so everything in this file is a
|
|
565
|
+
* COMMITMENT, not an internal:
|
|
566
|
+
* - The v1 wire shape below never changes incompatibly; breaking shape
|
|
567
|
+
* changes bump `v` and ship an in-code migration from every prior version
|
|
568
|
+
* in the same release (the registry seam is here from day one).
|
|
569
|
+
* - Additive fields never bump `v` — an equal-`v` payload with unknown
|
|
570
|
+
* fields applies cleanly with the unknowns ignored.
|
|
571
|
+
* - A payload with `v` HIGHER than this library knows, or failing structural
|
|
572
|
+
* validation (hand-edited URL, truncated paste), is rejected whole: a
|
|
573
|
+
* half-restored view misrepresents what the sender saw (§16.14).
|
|
574
|
+
*
|
|
575
|
+
* ## v1 shape notes
|
|
576
|
+
*
|
|
577
|
+
* - `folds` extends the spec's v1 schema (amendment logged with this slice):
|
|
578
|
+
* §16.3 node folds are exploration state over stable public ids — a
|
|
579
|
+
* deep-link that silently dropped them would misrepresent the sender's
|
|
580
|
+
* view, the exact failure this feature exists to prevent. Session-local
|
|
581
|
+
* expansion records, by contrast, are NEVER serialized (R-16.3-08).
|
|
582
|
+
* - Brushes are TAGGED on the wire (`kind`) even though the runtime
|
|
583
|
+
* BrushState is untagged: the tag comes from `DimensionSpec.kind` at
|
|
584
|
+
* serialize time and lets restore validate shape-vs-dimension without
|
|
585
|
+
* consulting the live spec first. Categorical stores EXCLUSIONS, so
|
|
586
|
+
* categories that appear after the view was saved stay visible.
|
|
587
|
+
* - `layout` is the normalized OBJECT form (`{ kind }`) even though the
|
|
588
|
+
* runtime today carries only the bare kind string — the object is what
|
|
589
|
+
* makes a future static seed or a layouts package additive.
|
|
590
|
+
*/
|
|
591
|
+
|
|
592
|
+
/** Tagged wire form of a crossfilter brush (§16.6 → §16.14). */
|
|
593
|
+
type ViewBrushState = {
|
|
594
|
+
kind: 'numeric' | 'temporal';
|
|
595
|
+
range: readonly [number, number];
|
|
596
|
+
} | {
|
|
597
|
+
kind: 'categorical';
|
|
598
|
+
excluded: readonly string[];
|
|
599
|
+
};
|
|
600
|
+
/** Normalized layout descriptor — object form reserves room for a static
|
|
601
|
+
* seed / structured layouts without a `v` bump. */
|
|
602
|
+
interface ViewLayoutSpec {
|
|
603
|
+
kind: 'force' | 'fixed';
|
|
604
|
+
}
|
|
605
|
+
/** §11 Scale subset that is data by construction (no functions). */
|
|
606
|
+
type SerializableScale = {
|
|
607
|
+
kind: 'sequential' | 'diverging';
|
|
608
|
+
metric: string;
|
|
609
|
+
range: readonly string[] | readonly number[];
|
|
610
|
+
domain?: readonly number[];
|
|
611
|
+
mid?: number;
|
|
612
|
+
} | {
|
|
613
|
+
kind: 'categorical';
|
|
614
|
+
/** Field-descriptor form only — a function `by` is omitted upstream. */
|
|
615
|
+
by: string;
|
|
616
|
+
palette?: readonly string[] | readonly number[];
|
|
617
|
+
domain?: readonly string[];
|
|
618
|
+
};
|
|
619
|
+
interface ViewStyling {
|
|
620
|
+
nodeColor?: SerializableScale;
|
|
621
|
+
nodeSize?: SerializableScale;
|
|
622
|
+
showLinks?: boolean;
|
|
623
|
+
edgeArrows?: boolean;
|
|
624
|
+
/** Named themes only; a custom GraphTheme object is omitted upstream. */
|
|
625
|
+
theme?: 'light' | 'dark';
|
|
626
|
+
}
|
|
627
|
+
interface GraphViewState {
|
|
628
|
+
v: 1;
|
|
629
|
+
camera: ViewportState | null;
|
|
630
|
+
selection: SelectionState;
|
|
631
|
+
hiddenNodeIds: readonly NodeId[];
|
|
632
|
+
/** §9.2 isolation; null = full scope. */
|
|
633
|
+
subgraph: SubgraphSpec | null;
|
|
634
|
+
/** Manual groups verbatim, or — under `groupBy` — `{key, collapsed}` pairs
|
|
635
|
+
* only (membership recomputes from current data on restore, R-16.3-16). */
|
|
636
|
+
groups: readonly GroupSpec[] | ReadonlyArray<{
|
|
637
|
+
key: string;
|
|
638
|
+
collapsed: boolean;
|
|
639
|
+
}>;
|
|
640
|
+
pinnedNodeIds: readonly NodeId[];
|
|
641
|
+
/** §16.3 node folds: anchor → declared members (v1 extension, see header). */
|
|
642
|
+
folds?: ReadonlyArray<readonly [NodeId, readonly NodeId[]]>;
|
|
643
|
+
layout: ViewLayoutSpec;
|
|
644
|
+
/** Declaration order; a key absent here has no brush. */
|
|
645
|
+
crossfilter: ReadonlyArray<{
|
|
646
|
+
key: string;
|
|
647
|
+
state: ViewBrushState;
|
|
648
|
+
}>;
|
|
649
|
+
/** Opt-in frozen layout (quantized, visible set); tuple form keeps opaque
|
|
650
|
+
* ids safe. Restores as a fixed-equivalent regardless of engine
|
|
651
|
+
* nondeterminism. */
|
|
652
|
+
positions?: ReadonlyArray<readonly [string, number, number]>;
|
|
653
|
+
styling?: ViewStyling;
|
|
654
|
+
/** Host-owned durable source coordinate — stored verbatim, NEVER
|
|
655
|
+
* interpreted; compared canonically on restore (§16.14). */
|
|
656
|
+
dataRef?: JsonValue;
|
|
657
|
+
}
|
|
658
|
+
declare const VIEW_STATE_VERSION: 1;
|
|
659
|
+
/**
|
|
660
|
+
* Canonical encoding of a JSON value: object keys recursively sorted, arrays
|
|
661
|
+
* order-preserving, JSON semantics for scalars (so `undefined` members are
|
|
662
|
+
* dropped exactly as JSON.stringify drops them). Two values compare equal iff
|
|
663
|
+
* their canonical strings are identical.
|
|
664
|
+
*/
|
|
665
|
+
declare function canonicalJson(value: JsonValue | undefined): string | undefined;
|
|
666
|
+
/** Canonical equality for dataRef values (§16.14): key order never matters.
|
|
667
|
+
* A non-JSON value (cycles included) is never equal to anything — the §5
|
|
668
|
+
* "compared, never interpreted" rule extended to malformed input. */
|
|
669
|
+
declare function sameDataRef(a: JsonValue | undefined, b: JsonValue | undefined): boolean;
|
|
670
|
+
type ViewStateVerdict = {
|
|
671
|
+
ok: true;
|
|
672
|
+
state: GraphViewState;
|
|
673
|
+
} | {
|
|
674
|
+
ok: false;
|
|
675
|
+
code: 'invalid-view-state' | 'unsupported-version';
|
|
676
|
+
problems: readonly string[];
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* Full structural validation + version gate. Order per §16.14: version first
|
|
680
|
+
* (higher-than-known and non-numeric reject as `unsupported-version` /
|
|
681
|
+
* `invalid-view-state` BEFORE field checks), then per-field structure. Lower
|
|
682
|
+
* versions run the migration registry, then re-validate at the current shape.
|
|
683
|
+
*/
|
|
684
|
+
declare function validateViewState(raw: unknown): ViewStateVerdict;
|
|
685
|
+
type SetViewStateResult = {
|
|
686
|
+
status: 'applied';
|
|
687
|
+
}
|
|
688
|
+
/** dataRef differed: `viewStateMismatch` fired INSTEAD of applying.
|
|
689
|
+
* Re-call with `ignoreMismatch: true` to opt in. */
|
|
690
|
+
| {
|
|
691
|
+
status: 'mismatch';
|
|
692
|
+
} | {
|
|
693
|
+
status: 'rejected';
|
|
694
|
+
code: 'invalid-view-state' | 'unsupported-version'
|
|
695
|
+
/** The state touches a §6.4 controlled slice or carries styling the
|
|
696
|
+
* host must reflect, and no aggregate restore callback exists. */
|
|
697
|
+
| 'missing-restore-callback'
|
|
698
|
+
/** Another restore/history transaction is awaiting acknowledgement. */
|
|
699
|
+
| 'restore-pending'
|
|
700
|
+
/** The host never reflected the intent within the window. */
|
|
701
|
+
| 'restore-timeout'
|
|
702
|
+
/** The host reflected DIFFERENT values than the intent asked for. */
|
|
703
|
+
| 'restore-diverged';
|
|
704
|
+
problems: readonly string[];
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* §6 GraphInstance — the public headless core instance (v0.3 subset).
|
|
709
|
+
*
|
|
710
|
+
* One `applyHostUpdate` call is the atomic host boundary: it validates,
|
|
711
|
+
* reconciles, re-projects only dirty channels, and publishes EXACTLY ONE store
|
|
712
|
+
* `set()` and AT MOST ONE engine commit, so a simultaneous data + style +
|
|
713
|
+
* controlled-state change can never tear across frames (§6).
|
|
714
|
+
*
|
|
715
|
+
* Vanilla zustand only — no React, no DOM access at module scope (§18).
|
|
716
|
+
*/
|
|
717
|
+
|
|
718
|
+
/** §8 dark base theme (the default when no base is named). */
|
|
719
|
+
declare const GRAPH_THEME_DARK: GraphTheme;
|
|
720
|
+
/** §8 light base theme. */
|
|
721
|
+
declare const GRAPH_THEME_LIGHT: GraphTheme;
|
|
722
|
+
/**
|
|
723
|
+
* Resolve a ThemeInput to a full GraphTheme (§8): pick the named base
|
|
724
|
+
* (default dark), then merge every defined token over it. A full GraphTheme
|
|
725
|
+
* input resolves to exactly its own tokens; `undefined` resolves to the dark
|
|
726
|
+
* base; the v0.1 `{background}` compat shorthand merges as a partial.
|
|
727
|
+
*/
|
|
728
|
+
declare function resolveTheme(input?: ThemeInput): GraphTheme;
|
|
729
|
+
/** One categorical legend row (§11): declared-domain rows first (including
|
|
730
|
+
* currently-empty categories), then extra seen values sorted. */
|
|
731
|
+
interface ScaleInfoRow {
|
|
732
|
+
value: string;
|
|
733
|
+
/** TOTAL occurrences over the accepted model (v0.8 tier: not mask-aware). */
|
|
734
|
+
count: number;
|
|
735
|
+
/** Palette slot from `categoricalIndex` (-1 = empty palette). */
|
|
736
|
+
colorIndex: number;
|
|
737
|
+
}
|
|
738
|
+
/** `getScaleInfo` payload: the active Scale descriptor plus its resolved
|
|
739
|
+
* numeric domain (sequential/diverging) or legend rows (categorical). */
|
|
740
|
+
interface ScaleChannelInfo<N = Record<string, unknown>> {
|
|
741
|
+
scale: Scale<string, N> | Scale<number, N>;
|
|
742
|
+
/** Resolved numeric domain; omitted while unresolvable (no data/metric). */
|
|
743
|
+
domain?: readonly [number, number];
|
|
744
|
+
/** Categorical rows; omitted for sequential/diverging scales. */
|
|
745
|
+
rows?: readonly ScaleInfoRow[];
|
|
746
|
+
}
|
|
747
|
+
/** §9.2 revision-aware service seam (expansion since v0.5, search since S11). */
|
|
748
|
+
interface GraphServices<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
749
|
+
/**
|
|
750
|
+
* Ego-expansion resolver for `expandNode` / `SubgraphSpec.hops`. Default:
|
|
751
|
+
* the built-in LOCAL service — it walks the core's §7.1 adjacency over the
|
|
752
|
+
* accepted model, INCLUDING currently out-of-scope nodes (zero config,
|
|
753
|
+
* zero network; the core still never fetches).
|
|
754
|
+
*/
|
|
755
|
+
expansion?: ExpansionService<N, E>;
|
|
756
|
+
/**
|
|
757
|
+
* §16.2 path resolver for `findPath`. Default: the built-in LOCAL
|
|
758
|
+
* unweighted BFS over the loaded VISIBLE edge list, respecting
|
|
759
|
+
* PathOptions.direction. Revision-aware: a result arriving after a
|
|
760
|
+
* dataset replacement is discarded at admission (S12-T08).
|
|
761
|
+
*/
|
|
762
|
+
path?: PathService;
|
|
763
|
+
/**
|
|
764
|
+
* §16.5 search resolver for `instance.search`. Default: the built-in LOCAL
|
|
765
|
+
* indexed service over the accepted model plus the host's declared
|
|
766
|
+
* `searchIndex` fields (id-only when never declared — it never guesses
|
|
767
|
+
* attr names; zero config, zero network). Custom services plug in
|
|
768
|
+
* server-side search (Omnigraph B.7) with the same instance-side
|
|
769
|
+
* correctness: RequestContext, revision-keyed caching, supersede
|
|
770
|
+
* cancellation, stale rejection at admission.
|
|
771
|
+
*/
|
|
772
|
+
search?: SearchService<N>;
|
|
773
|
+
}
|
|
774
|
+
interface CreateGraphInstanceOptions<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
775
|
+
/** Called once per mount; a re-attach constructs a fresh engine (§6). */
|
|
776
|
+
engine: EngineFactory;
|
|
777
|
+
/** Fit the camera once when the first data-bearing commit reaches a fresh engine. Default true. */
|
|
778
|
+
fitViewOnFirstData?: boolean;
|
|
779
|
+
/** §9.2 revision-aware services (expansion + §16.5 search). */
|
|
780
|
+
services?: GraphServices<N, E>;
|
|
781
|
+
/**
|
|
782
|
+
* §16.5 node attr fields the DEFAULT search service indexes (ids always;
|
|
783
|
+
* absent = id-only — the service never guesses attr names). CONSTRUCTION-
|
|
784
|
+
* ONLY per the spec's host construction options (D7): read once here;
|
|
785
|
+
* changing it requires a keyed remount / replacement instance. A runtime
|
|
786
|
+
* `applyHostUpdate` attempt is ignored with a one-shot warning.
|
|
787
|
+
*/
|
|
788
|
+
searchIndex?: readonly string[];
|
|
789
|
+
/**
|
|
790
|
+
* §16.14 undo/redo (S9-T20). Default true. `false` makes the history
|
|
791
|
+
* surface inert (record/undo/redo no-ops, depths stay 0); an object sets
|
|
792
|
+
* the stack bound (default {@link HISTORY_LIMIT_DEFAULT} entries).
|
|
793
|
+
*/
|
|
794
|
+
history?: boolean | {
|
|
795
|
+
limit?: number;
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* §8 image-atlas resolver seam (S10): owns authenticated fetch/caching for
|
|
799
|
+
* one `nodeImage` ref. Default: plain `fetch(ref)` for public URLs.
|
|
800
|
+
* Injectable for tests and authenticated hosts.
|
|
801
|
+
*/
|
|
802
|
+
imageResolver?: ImageResolver;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* `expandNode` outcome (§9.2/§16.3):
|
|
806
|
+
* - `{ added }` — the admitted result merged; `added` counts the nodes it
|
|
807
|
+
* made newly visible in the current scope.
|
|
808
|
+
* - `{ noop: true }` — every returned neighbor was already visible in the
|
|
809
|
+
* current scope (T17); no session was opened, nothing changed.
|
|
810
|
+
* - `{ coalesced: true }` — reserved. v0.5 same-id coalescing hands the
|
|
811
|
+
* SECOND caller the IDENTICAL in-flight promise, so both callers observe
|
|
812
|
+
* the primary call's `{added}`/`{noop}` result instead of this marker.
|
|
813
|
+
*/
|
|
814
|
+
type ExpandNodeResult = {
|
|
815
|
+
added: number;
|
|
816
|
+
} | {
|
|
817
|
+
coalesced: true;
|
|
818
|
+
} | {
|
|
819
|
+
noop: true;
|
|
820
|
+
};
|
|
821
|
+
/**
|
|
822
|
+
* §9.2 expansion bookkeeping: one committed expansion overlay. Data-merging
|
|
823
|
+
* batches carry the request id (overlayId + batch ids) and provenance into
|
|
824
|
+
* ingestion so abort, rollback, and removeOverlay remove the exact
|
|
825
|
+
* contribution they own.
|
|
826
|
+
*/
|
|
827
|
+
interface ExpansionOverlayRecord {
|
|
828
|
+
overlayId: string;
|
|
829
|
+
requestId: string;
|
|
830
|
+
/** Node ids this expansion revealed into the visible scope. */
|
|
831
|
+
revealedIds: readonly NodeId[];
|
|
832
|
+
/** Service-supplied provenance (single-response or stream header). */
|
|
833
|
+
provenance?: unknown;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* §14 overlay label lane subscriptions. Two channels with distinct cadences:
|
|
837
|
+
* - `subscribeCandidates` fires ONLY when the candidate SET (ids/text/forced)
|
|
838
|
+
* changes — the throttled re-rank. React re-renders label content here.
|
|
839
|
+
* - `subscribePositions` fires on scheduler ticks (host `onFrame`) with fresh
|
|
840
|
+
* x/y for the SAME set — imperative transform writes, NO React re-render.
|
|
841
|
+
* Both replay the current state synchronously on subscribe. The emitted array
|
|
842
|
+
* and its placement objects are REUSED across position ticks — copy if you
|
|
843
|
+
* need a snapshot.
|
|
844
|
+
*/
|
|
845
|
+
interface LabelSubscriptions {
|
|
846
|
+
subscribeCandidates(cb: (list: readonly LabelPlacement[]) => void): () => void;
|
|
847
|
+
subscribePositions(cb: (list: readonly LabelPlacement[]) => void): () => void;
|
|
848
|
+
}
|
|
849
|
+
interface GraphInstance<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
850
|
+
/** Vanilla zustand store — the single observable state surface (§6.3). */
|
|
851
|
+
readonly store: StoreApi<GraphStoreState>;
|
|
852
|
+
/** §14 DOM label lane (overlay scheduler output). */
|
|
853
|
+
readonly labels: LabelSubscriptions;
|
|
854
|
+
/** Atomic host transaction: one store publication, at most one engine commit (§6). */
|
|
855
|
+
applyHostUpdate(update: GraphHostUpdate<N, E>): void;
|
|
856
|
+
/**
|
|
857
|
+
* §7.5 revisioned ingestion: begin a bounded, cancellable session against an
|
|
858
|
+
* explicit `datasetKey` and `baseModelRevision` (compare-and-set; mismatch
|
|
859
|
+
* throws 'stale-revision'). Overlay sessions must name the CURRENT
|
|
860
|
+
* datasetKey; replace sessions may establish a new one and are always
|
|
861
|
+
* atomic.
|
|
862
|
+
*
|
|
863
|
+
* T16 rule: while a declarative data source is actively driving (a snapshot
|
|
864
|
+
* was applied through `applyHostUpdate` and has not been superseded by a
|
|
865
|
+
* committed replace session), `purpose:'replace'` is rejected at begin with
|
|
866
|
+
* a TypeError — two writers may not race for the base. Overlay ingestion
|
|
867
|
+
* stays allowed alongside a declarative base.
|
|
868
|
+
*
|
|
869
|
+
* Every session admission/publication is serialized through the
|
|
870
|
+
* instance-local acceptance queue; arrival there is the global admission
|
|
871
|
+
* order (§7.5).
|
|
872
|
+
*/
|
|
873
|
+
beginIngest(opts: BeginIngestOptions): IngestSession<N, E>;
|
|
874
|
+
/**
|
|
875
|
+
* §7.5: atomically remove exactly one committed overlay — re-runs collision
|
|
876
|
+
* and endpoint resolution, promotes formerly shadowed rows from surviving
|
|
877
|
+
* overlays, advances model/render revisions, and releases the overlayId for
|
|
878
|
+
* deliberate reuse. Unknown ids are an idempotent `{ removed: false }`.
|
|
879
|
+
*/
|
|
880
|
+
removeOverlay(overlayId: string): {
|
|
881
|
+
removed: boolean;
|
|
882
|
+
};
|
|
883
|
+
/** Committed overlay ids for the current dataset (§7.5). */
|
|
884
|
+
getOverlayIds(): readonly string[];
|
|
885
|
+
attach(container: HTMLElement): Promise<void>;
|
|
886
|
+
detach(): void;
|
|
887
|
+
destroy(): void;
|
|
888
|
+
/**
|
|
889
|
+
* §15 typed events: listeners run SYNCHRONOUSLY in registration order; the
|
|
890
|
+
* control's preventDefault() cancels ONLY the built-in follow-up (click
|
|
891
|
+
* selection, drag pin), never other listeners.
|
|
892
|
+
*/
|
|
893
|
+
on<K extends GraphEventName>(name: K, cb: (payload: GraphEventMap<N, E>[K], control: GraphListenerControl) => void): () => void;
|
|
894
|
+
fitView(): void;
|
|
895
|
+
zoomIn(): void;
|
|
896
|
+
zoomOut(): void;
|
|
897
|
+
setViewport(v: Partial<ViewportState>): void;
|
|
898
|
+
/**
|
|
899
|
+
* §16 focus neighborhood (T10): keep the v0.1 camera behavior
|
|
900
|
+
* (setFocusedIndex + zoomToIndex) and RETURN the 1-hop neighbor ids
|
|
901
|
+
* (engine adjacency when available, else the core CSR adjacency).
|
|
902
|
+
*
|
|
903
|
+
* Documented compromise: the engine exposes ONE highlight channel until S10
|
|
904
|
+
* styling lands, so the neighbor ring is pushed through setSelectedIndices
|
|
905
|
+
* ONLY when that cannot lie about real selection state — selection empty
|
|
906
|
+
* and uncontrolled. The ring is a visual, never a store write; the next
|
|
907
|
+
* selection push overwrites it. Opt out via `highlightNeighbors: false`.
|
|
908
|
+
* `hops` is reserved at 1 until multi-hop lands.
|
|
909
|
+
*/
|
|
910
|
+
focusNode(id: NodeId, opts?: {
|
|
911
|
+
highlightNeighbors?: boolean;
|
|
912
|
+
hops?: 1;
|
|
913
|
+
}): readonly NodeId[];
|
|
914
|
+
/**
|
|
915
|
+
* §14/§15 typed context-menu channel, opened from a DOM presenter. Label
|
|
916
|
+
* divs are `pointerEvents: 'auto'` by design (click-to-focus), so a
|
|
917
|
+
* right-click on one never reaches the engine canvas — without this seam
|
|
918
|
+
* the nodes prominent enough to carry labels are exactly the ones whose
|
|
919
|
+
* right-click falls through to the browser's native menu. Emits the SAME
|
|
920
|
+
* 'contextMenu' event the canvas gesture produces; `screen` is
|
|
921
|
+
* container-relative CSS px (the §15 payload contract). Unknown ids are a
|
|
922
|
+
* silent no-op (a stale label racing a model swap is data, not an error).
|
|
923
|
+
*/
|
|
924
|
+
requestNodeContextMenu(id: NodeId, screen: readonly [number, number]): void;
|
|
925
|
+
setSelection(ids: readonly NodeId[] | SelectionState): void;
|
|
926
|
+
selectNodes(ids: readonly NodeId[]): void;
|
|
927
|
+
selectEdges(ids: readonly EdgeId[]): void;
|
|
928
|
+
/** §16.2 group namespace (S12-T02): validate against the CURRENT resolved
|
|
929
|
+
* groups (unknown ids dropped, duplicates collapse) and store in
|
|
930
|
+
* groups-array order — the group analog of accepted-base ordering. Never
|
|
931
|
+
* touches the node/edge namespaces; the group namespace is always
|
|
932
|
+
* instance-owned (§6.4 controlled selection covers nodes only). */
|
|
933
|
+
selectGroups(ids: readonly string[]): void;
|
|
934
|
+
/** Expand to the 1-hop neighborhood of `id` (or of the current selection). */
|
|
935
|
+
selectNeighbors(id?: NodeId): void;
|
|
936
|
+
selectAll(): void;
|
|
937
|
+
invertSelection(): void;
|
|
938
|
+
clearSelection(): void;
|
|
939
|
+
/**
|
|
940
|
+
* §16.2 lasso (S6-T05): resolve the SCREEN-coordinate polygon to node ids
|
|
941
|
+
* via `engine.pointsInPolygon`, drop hidden ids, then replace (default) or
|
|
942
|
+
* union (`additive`) the node selection through the same §6.4 ownership
|
|
943
|
+
* path as every other mutator (controlled → intent only). Returns the
|
|
944
|
+
* resolved lasso ids (accepted-base order) regardless of ownership; empty
|
|
945
|
+
* when the engine is not ready or lacks `pointsInPolygon`.
|
|
946
|
+
*/
|
|
947
|
+
selectWithinPolygon(screenPolygon: readonly [number, number][], opts?: {
|
|
948
|
+
additive?: boolean;
|
|
949
|
+
}): readonly NodeId[];
|
|
950
|
+
/** Pure fallback-route query: the accepted edge within pick tolerance of a
|
|
951
|
+
* SCREEN point. Null on the native route, while picking is disarmed (sim
|
|
952
|
+
* hot), or when nothing is in range. */
|
|
953
|
+
pickEdgeAt(screen: readonly [number, number]): AcceptedEdge<E> | null;
|
|
954
|
+
/** Fallback-route hover sample: writes `hover.edgeId` and emits 'edgeHover'
|
|
955
|
+
* on transitions (identical payloads to the native route). No-op (null) on
|
|
956
|
+
* the native route. */
|
|
957
|
+
sampleEdgeHover(screen: readonly [number, number]): AcceptedEdge<E> | null;
|
|
958
|
+
/** Fallback-route click resolution: emits 'edgeClick' when a link is within
|
|
959
|
+
* tolerance. No-op (null) on the native route. */
|
|
960
|
+
sampleEdgeClick(screen: readonly [number, number]): AcceptedEdge<E> | null;
|
|
961
|
+
hideNodes(ids: readonly NodeId[]): void;
|
|
962
|
+
showNodes(ids: readonly NodeId[]): void;
|
|
963
|
+
showAll(): void;
|
|
964
|
+
pinNode(id: NodeId, xy?: readonly [number, number]): void;
|
|
965
|
+
unpinNode(id: NodeId): void;
|
|
966
|
+
clearPins(): void;
|
|
967
|
+
/** §16.3 PERSISTENT pins (S12-T09): pin ids AT THEIR CURRENT POSITION via
|
|
968
|
+
* engine.setPinnedIndices — no position payload in v0.10. Independent of
|
|
969
|
+
* transient drag pinning (`pins`): the engine receives the UNION of both
|
|
970
|
+
* slices, so releasing a drag pin leaves a persistent pin held. §6.4
|
|
971
|
+
* ownership mirrors groups: once the host supplies `pinnedNodeIds` (null
|
|
972
|
+
* included) the ops fire the 'pinnedChange' intent instead of writing.
|
|
973
|
+
* Unknown ids drop; departed ids prune through the ownership path on
|
|
974
|
+
* model changes (R-16.3-25). */
|
|
975
|
+
pinNodes(ids: readonly NodeId[]): void;
|
|
976
|
+
/** Release persistent pins (see {@link pinNodes}); unpinned ids no-op. */
|
|
977
|
+
unpinNodes(ids: readonly NodeId[]): void;
|
|
978
|
+
/** Add one group definition (same §16.3 acyclic/singly-parented validation as the
|
|
979
|
+
* `groups` prop — a violating spec is ONE 'config-error' and a no-op). */
|
|
980
|
+
groupNodes(spec: GroupSpec): void;
|
|
981
|
+
/** Remove one group definition; its id prunes from SelectionState.groupIds
|
|
982
|
+
* through the ownership path. Unknown ids no-op (dev-mode warning). */
|
|
983
|
+
ungroup(groupId: string): void;
|
|
984
|
+
/** Collapse/expand one group as a §7.2 structural diff. Works on manual
|
|
985
|
+
* groups AND groupBy-derived groups (the residue toggle). Same-value
|
|
986
|
+
* calls are exact no-ops (zero publishes, zero commits). */
|
|
987
|
+
setGroupCollapsed(groupId: string, collapsed: boolean): void;
|
|
988
|
+
/**
|
|
989
|
+
* Folds `id`'s neighbourhood into `id`. Members default to the anchor's
|
|
990
|
+
* neighbours in the CURRENT render model that no representative has
|
|
991
|
+
* claimed yet — first fold wins, so folding two adjacent hubs never fights
|
|
992
|
+
* over a shared neighbour and never needs a leaf-only restriction. Pass
|
|
993
|
+
* `memberIds` to fold an explicit set instead (unknown ids and ids already
|
|
994
|
+
* claimed elsewhere drop; an id that is an ANCESTOR of the anchor is
|
|
995
|
+
* rejected, since that would close a containment cycle).
|
|
996
|
+
*
|
|
997
|
+
* One publish and at most one structural commit (E1). A no-member fold is
|
|
998
|
+
* an exact no-op. Records a §16.14 'folds' history step.
|
|
999
|
+
*/
|
|
1000
|
+
foldNode(id: NodeId, opts?: {
|
|
1001
|
+
memberIds?: readonly NodeId[];
|
|
1002
|
+
}): void;
|
|
1003
|
+
/** Unfolds `id`, returning its members to the scene as a §7.2 structural
|
|
1004
|
+
* diff. Unknown or unfolded ids are exact no-ops. */
|
|
1005
|
+
unfoldNode(id: NodeId): void;
|
|
1006
|
+
/** The members `id` currently stands for, or null when it is not folded.
|
|
1007
|
+
* Membership is the DECLARED set — members that have since left the model
|
|
1008
|
+
* are reported but simply do not draw. */
|
|
1009
|
+
getFold(id: NodeId): {
|
|
1010
|
+
memberIds: readonly NodeId[];
|
|
1011
|
+
} | null;
|
|
1012
|
+
/** Current stage-4 clusters over the physical scene: ordered keys, member
|
|
1013
|
+
* ids, the force center labels anchor to while hot, and the settled
|
|
1014
|
+
* centroid (null until a §7.1 readback or a fixed-layout commit). Empty
|
|
1015
|
+
* when no `clusters` spec is active. Clusters synthesize nothing — the
|
|
1016
|
+
* scene is byte-identical with and without a spec (R-16.3-17/19). */
|
|
1017
|
+
getClusters(): readonly ResolvedCluster[];
|
|
1018
|
+
/** R-16.3-18: resolve a cluster (by key) to its MEMBER node ids and write
|
|
1019
|
+
* them into SelectionState.nodeIds through the standard §6.4 ownership
|
|
1020
|
+
* path — clusters have no id namespace of their own in selection.
|
|
1021
|
+
* `additive` unions with the current node selection. Unknown keys no-op. */
|
|
1022
|
+
selectCluster(key: string, opts?: {
|
|
1023
|
+
additive?: boolean;
|
|
1024
|
+
}): void;
|
|
1025
|
+
/**
|
|
1026
|
+
* §9.2 isolate: hard-scope the graph to the CURRENT node selection —
|
|
1027
|
+
* `subgraph: { seedIds: selection.nodeIds }` through the SAME path as the
|
|
1028
|
+
* host-update prop. No-op when nothing is selected. Ownership note (v0.5):
|
|
1029
|
+
* `subgraph` is UNCONTROLLED-ONLY — always instance-owned; the prop and
|
|
1030
|
+
* this method write the same state, last writer wins.
|
|
1031
|
+
*/
|
|
1032
|
+
isolateSelection(): void;
|
|
1033
|
+
/** §9.2: clear the hard scope (`subgraph: null`) — the full accepted model
|
|
1034
|
+
* returns with cached positions (§9.4-style identity for kept ids). */
|
|
1035
|
+
resetIsolation(): void;
|
|
1036
|
+
/**
|
|
1037
|
+
* §9.2/§16.3 ego-expansion of `id` (default 1 hop) through the configured
|
|
1038
|
+
* ExpansionService. The result is gated by admission (declared revision
|
|
1039
|
+
* dependencies + dataset lineage + seed existence — abort is only an
|
|
1040
|
+
* optimization) and merges through ONE awaited atomic overlay
|
|
1041
|
+
* IngestSession carrying the request id; a discard/rejection leaves the
|
|
1042
|
+
* graph untouched ('service-aborted' info / 'service-error' error
|
|
1043
|
+
* diagnostic; the promise rejects, the 'error' event never fires). Within
|
|
1044
|
+
* one valid scope revision a second same-id call while one is in flight
|
|
1045
|
+
* returns the IDENTICAL promise (one service call serves both); distinct
|
|
1046
|
+
* ids run concurrently. Under an active hard scope, revealed neighbors
|
|
1047
|
+
* join the resolved scope (accretion) in the same commit.
|
|
1048
|
+
*/
|
|
1049
|
+
expandNode(id: NodeId, opts?: {
|
|
1050
|
+
hops?: number;
|
|
1051
|
+
}): Promise<ExpandNodeResult>;
|
|
1052
|
+
/**
|
|
1053
|
+
* Undoes `id`'s own expansions — the navigation Back button, NOT a
|
|
1054
|
+
* containment operation. Aborts `id`'s pending expansion AND explicitly
|
|
1055
|
+
* removes the overlays its past expansions committed (plus their scope
|
|
1056
|
+
* accretion). Committed overlay DATA otherwise persists per §7.5 until
|
|
1057
|
+
* removeOverlay / a replacing snapshot; this IS that explicit removal for
|
|
1058
|
+
* expansion overlays.
|
|
1059
|
+
*
|
|
1060
|
+
* On a node that was never expanded from, this does nothing — there is no
|
|
1061
|
+
* record to pop. To hide a node's neighbourhood behind it on a freshly
|
|
1062
|
+
* loaded graph, that is {@link foldNode}: one word for containment, a
|
|
1063
|
+
* different word for navigation history.
|
|
1064
|
+
*/
|
|
1065
|
+
retractExpansion(id: NodeId): void;
|
|
1066
|
+
/** §9.2 expansion bookkeeping for `id`: committed overlay records with
|
|
1067
|
+
* request id, provenance, and the ids each expansion revealed. */
|
|
1068
|
+
getExpansionOverlays(id: NodeId): readonly ExpansionOverlayRecord[];
|
|
1069
|
+
/**
|
|
1070
|
+
* Run the configured SearchService (default: the built-in local indexed
|
|
1071
|
+
* service). The instance creates the `RequestContext`, caches results by
|
|
1072
|
+
* `serviceCacheKey` over EXACTLY the service's declared revision
|
|
1073
|
+
* dimensions ({@link SEARCH_CACHE_LIMIT}-entry LRU; a second call with an
|
|
1074
|
+
* equal key while one is in flight coalesces onto the same service call),
|
|
1075
|
+
* cancels superseded work (a NEWER query aborts the older in-flight call —
|
|
1076
|
+
* the older promise rejects `OrbitOperationError {code:'aborted'}`), and
|
|
1077
|
+
* rejects stale results at admission (declared revision drift or dataset
|
|
1078
|
+
* lineage change → the same typed 'aborted' rejection with a distinct
|
|
1079
|
+
* staleness message; the store is untouched). A successful search
|
|
1080
|
+
* publishes `store.search = {query, results}` — `node` populated for
|
|
1081
|
+
* in-model ids — in ONE store publication. Search NEVER changes
|
|
1082
|
+
* scope/filter semantics and never fetches graph data (§16.5).
|
|
1083
|
+
*/
|
|
1084
|
+
search(query: string, opts?: {
|
|
1085
|
+
limit?: number;
|
|
1086
|
+
}): Promise<readonly SearchResult<N>[]>;
|
|
1087
|
+
/** Clear `store.search` to null (e.g. the <GraphSearch> input emptied). */
|
|
1088
|
+
clearSearch(): void;
|
|
1089
|
+
/** §16.2 path query + atomic emphasis (S12-T08): resolves via the path
|
|
1090
|
+
* service (local BFS default); null = unreachable (a RESULT). Emphasis is
|
|
1091
|
+
* session-local — released by clearPath, any selection mutation, undo/
|
|
1092
|
+
* redo, or a scene rebuild; never a history step; never serialized. */
|
|
1093
|
+
findPath(sourceId: NodeId, targetId: NodeId, options?: PathOptions): Promise<PathResult | null>;
|
|
1094
|
+
clearPath(): void;
|
|
1095
|
+
getActivePath(): PathResult | null;
|
|
1096
|
+
/**
|
|
1097
|
+
* §16.5 result contract (T07): a result id in the current rendered scene
|
|
1098
|
+
* AND §9.1 mask-visible is focused (`focusNode`) → `{status:'focused'}`.
|
|
1099
|
+
* Otherwise classification ONLY — 'not-loaded' (absent from the accepted
|
|
1100
|
+
* model), 'out-of-scope' (in the model but outside the hard scope),
|
|
1101
|
+
* 'filtered' (in the scene but mask-hidden). Never mutates scope or
|
|
1102
|
+
* filters — the host reacts explicitly (§16.5).
|
|
1103
|
+
*/
|
|
1104
|
+
activateSearchResult(result: SearchResult<N>): SearchActivation;
|
|
1105
|
+
pauseSimulation(): void;
|
|
1106
|
+
resumeSimulation(): void;
|
|
1107
|
+
isSimulationRunning(): boolean;
|
|
1108
|
+
/** §16.1 screenshot: delegates to the engine; null when unsupported/not ready. */
|
|
1109
|
+
captureScreenshot(): Promise<Blob | null>;
|
|
1110
|
+
/**
|
|
1111
|
+
* §15.1 binding-detected reduced-motion media preference. The EFFECTIVE
|
|
1112
|
+
* value is `accessibility.reducedMotion ?? v` — when reduced, camera
|
|
1113
|
+
* durations (fitView/setViewport/focusNode) coerce to 0.
|
|
1114
|
+
*/
|
|
1115
|
+
setReducedMotion(v: boolean | undefined): void;
|
|
1116
|
+
/**
|
|
1117
|
+
* The crossfilter session facade, or null until the `crossfilter` prop has
|
|
1118
|
+
* configured dimensions over an accepted base. Delegates to the
|
|
1119
|
+
* typed-column engine; `setBrush` routes visibility deltas into the §9.1
|
|
1120
|
+
* soft mask (buffers-only commit, zero relayout) and resolves after the
|
|
1121
|
+
* publish. Brush slot deltas are BASE indices; under a hard scope
|
|
1122
|
+
* out-of-scope rows have no scene slot and simply do not mask anything.
|
|
1123
|
+
*/
|
|
1124
|
+
getCrossfilterSession(): CrossfilterSession | null;
|
|
1125
|
+
/**
|
|
1126
|
+
* Play a brush window across a numeric/temporal dimension's domain through
|
|
1127
|
+
* the crossfilter mask fast path (zero relayout). One playing dimension at
|
|
1128
|
+
* a time — a second play supersedes; a USER `setBrush` on the playing key
|
|
1129
|
+
* pauses playback. The whole play session coalesces into ONE history entry.
|
|
1130
|
+
*/
|
|
1131
|
+
playTimeline(key: string, playback?: Partial<TimelinePlayback>): void;
|
|
1132
|
+
pauseTimeline(): void;
|
|
1133
|
+
/** Undo the most recent uncontrolled mutation entry (selection / hidden /
|
|
1134
|
+
* pins / scope / brushes). Returns false when there is nothing to undo. */
|
|
1135
|
+
undo(): boolean;
|
|
1136
|
+
/** Re-apply the most recently undone entry. False when nothing to redo. */
|
|
1137
|
+
redo(): boolean;
|
|
1138
|
+
/**
|
|
1139
|
+
* Serialize the exploration state (§16.14): camera, selection, hidden ids,
|
|
1140
|
+
* isolation, groups (manual specs verbatim; under `groupBy` only collapsed
|
|
1141
|
+
* `{key, collapsed}` pairs — membership recomputes on restore), pins,
|
|
1142
|
+
* folds, layout, crossfilter brushes in declaration order, the Scale-valued
|
|
1143
|
+
* styling subset, and the host's `dataRef` verbatim. The predicate `filter`
|
|
1144
|
+
* and §16.3 expansion records are never serialized. The sync form carries
|
|
1145
|
+
* no positions: reproduction is best-effort via the layout descriptor.
|
|
1146
|
+
*/
|
|
1147
|
+
getViewState(opts?: {
|
|
1148
|
+
includePositions?: false;
|
|
1149
|
+
}): GraphViewState;
|
|
1150
|
+
/**
|
|
1151
|
+
* Async form: additionally embeds quantized coordinates for the VISIBLE
|
|
1152
|
+
* (post-mask) set, read once from the engine (per-event readback, ADR-001).
|
|
1153
|
+
* Restores as a frozen fixed-equivalent — pixel-faithful regardless of
|
|
1154
|
+
* engine nondeterminism. Past `maxPositions` (default 100 000) the call
|
|
1155
|
+
* rejects `export-materialization-too-large`; persist the layout through
|
|
1156
|
+
* the export lane and reference it from `dataRef` instead of inlining.
|
|
1157
|
+
*/
|
|
1158
|
+
getViewState(opts: {
|
|
1159
|
+
includePositions: true;
|
|
1160
|
+
maxPositions?: number;
|
|
1161
|
+
}): Promise<GraphViewState>;
|
|
1162
|
+
/**
|
|
1163
|
+
* Atomically restore a serialized view (§16.14). NEVER partially applies:
|
|
1164
|
+
* structural validation and the version gate run first (reject whole with
|
|
1165
|
+
* one 'invalid-view-state' diagnostic); then the dataRef canonical
|
|
1166
|
+
* comparison — a mismatch fires the 'viewStateMismatch' event INSTEAD of
|
|
1167
|
+
* applying, and restoration proceeds only on an `ignoreMismatch` re-call.
|
|
1168
|
+
* The apply is ONE history transaction through the same command appliers
|
|
1169
|
+
* undo/redo uses, so a restore is itself undoable. Embedded positions
|
|
1170
|
+
* apply as a frozen fixed-equivalent (one replay-style commit, then the
|
|
1171
|
+
* simulation pauses — a later explicit layout change or reheat unfreezes).
|
|
1172
|
+
* A state touching a §6.4 controlled slice (or styling, once a restore
|
|
1173
|
+
* callback exists) resolves 'missing-restore-callback' until the aggregate
|
|
1174
|
+
* protocol is registered.
|
|
1175
|
+
*/
|
|
1176
|
+
setViewState(raw: unknown, opts?: {
|
|
1177
|
+
ignoreMismatch?: boolean;
|
|
1178
|
+
isDataRefEqual?: (stored: JsonValue | undefined, current: JsonValue | undefined) => boolean;
|
|
1179
|
+
}): Promise<SetViewStateResult>;
|
|
1180
|
+
/**
|
|
1181
|
+
* Picture exports. 'png' delegates to {@link captureScreenshot} (typed
|
|
1182
|
+
* rejection when the engine lacks the capability). 'svg' renders the
|
|
1183
|
+
* VISIBLE (post-mask) set through the engine-free exporter: one per-event
|
|
1184
|
+
* position readback, colors/sizes from the same projectors the commits
|
|
1185
|
+
* use, labels from the current candidate set, in space coordinates with a
|
|
1186
|
+
* padded viewBox. Above `maxSvgElements` (default 50 000) it rejects
|
|
1187
|
+
* `export-too-large` — pass `fallback: 'raster-hybrid'` to instead receive
|
|
1188
|
+
* a PNG base layer with a vector label overlay.
|
|
1189
|
+
*/
|
|
1190
|
+
exportImage(format: 'png'): Promise<Blob>;
|
|
1191
|
+
exportImage(format: 'svg', opts?: {
|
|
1192
|
+
maxSvgElements?: number;
|
|
1193
|
+
fallback?: 'raster-hybrid';
|
|
1194
|
+
}): Promise<string>;
|
|
1195
|
+
/**
|
|
1196
|
+
* Bounded object export of the pinned model: 'visible' (default) is the
|
|
1197
|
+
* mask-visible roster with both-endpoint-visible edges; 'accepted' the
|
|
1198
|
+
* full model. Rejects `export-materialization-too-large` past `limit`
|
|
1199
|
+
* (default 100 000 rows) BEFORE allocating — the stream is the remedy.
|
|
1200
|
+
*/
|
|
1201
|
+
exportData(scope?: 'visible' | 'accepted', opts?: {
|
|
1202
|
+
limit?: number;
|
|
1203
|
+
}): Promise<{
|
|
1204
|
+
nodes: readonly GraphNode<N>[];
|
|
1205
|
+
edges: readonly AcceptedEdge<E>[];
|
|
1206
|
+
}>;
|
|
1207
|
+
/** Memory-bounded JSONL: one `{"kind":"node"|"edge","value":…}` line per
|
|
1208
|
+
* entity over ONE pinned revision — a mid-stream commit never mixes
|
|
1209
|
+
* epochs. Closing the generator releases the pin. */
|
|
1210
|
+
exportDataStream(scope?: 'visible' | 'accepted'): AsyncGenerator<string, void, undefined>;
|
|
1211
|
+
/** Bounded id → [x, y] map from one position readback (§7.1 per-event). */
|
|
1212
|
+
exportLayout(opts?: {
|
|
1213
|
+
limit?: number;
|
|
1214
|
+
}): Promise<ReadonlyMap<NodeId, readonly [number, number]>>;
|
|
1215
|
+
/** Memory-bounded `{"id","x","y"}` JSONL over one pinned readback. */
|
|
1216
|
+
exportLayoutStream(): AsyncGenerator<string, void, undefined>;
|
|
1217
|
+
/**
|
|
1218
|
+
* §11 legend surface: the active Scale on a styling channel plus its
|
|
1219
|
+
* resolved domain (sequential/diverging — resolved through the SAME frozen
|
|
1220
|
+
* DomainStore coordinate the projection uses) or categorical legend rows
|
|
1221
|
+
* (declared-domain order first including empty categories, then extra seen
|
|
1222
|
+
* values sorted; counts are v0.8-tier TOTALS over the accepted model, not
|
|
1223
|
+
* mask-aware). Null when the channel is not scale-valued.
|
|
1224
|
+
*/
|
|
1225
|
+
getScaleInfo(channel: 'nodeColor' | 'nodeSize'): ScaleChannelInfo<N> | null;
|
|
1226
|
+
/**
|
|
1227
|
+
* §12 metric read for one node id via the core id→index map (never a
|
|
1228
|
+
* public million-entry Map). Null for unknown ids/metrics and §8-null
|
|
1229
|
+
* values; lazily computes the degree family on first use.
|
|
1230
|
+
*/
|
|
1231
|
+
getMetricValue(metric: MetricName, id: NodeId): number | null;
|
|
1232
|
+
getRevisions(): Revisions;
|
|
1233
|
+
getDiagnostics(): readonly GraphDiagnostic[];
|
|
1234
|
+
getNode(id: NodeId): GraphNode<N> | undefined;
|
|
1235
|
+
/** Accepted edge by id — the symmetric partner of {@link getNode}, and the
|
|
1236
|
+
* way a hover/selection consumer resolves `store.hover.edgeId` or
|
|
1237
|
+
* `selection.edgeIds` to real records. Undefined for unknown ids. */
|
|
1238
|
+
getEdge(id: EdgeId): AcceptedEdge<E> | undefined;
|
|
1239
|
+
/** Scene ids that are §9.1 mask-visible (scope ∧ mask), scene order. */
|
|
1240
|
+
getVisibleNodeIds(): readonly NodeId[];
|
|
1241
|
+
/** Scene roster: scope applied, mask NOT applied — the §15.1 navigator's
|
|
1242
|
+
* entry list, which must still LIST masked/hidden nodes and expose their
|
|
1243
|
+
* state in text rather than dropping them. */
|
|
1244
|
+
getSceneNodeIds(): readonly NodeId[];
|
|
1245
|
+
/** §15.1 accessibility config stash (navigator/live-region consumers). */
|
|
1246
|
+
getAccessibility(): AccessibilityConfig<N> | undefined;
|
|
1247
|
+
}
|
|
1248
|
+
/** §16.14: consecutive same-dimension brush moves within this window merge
|
|
1249
|
+
* into one history entry (scrub/drag coalescing). */
|
|
1250
|
+
declare const BRUSH_HISTORY_COALESCE_MS = 500;
|
|
1251
|
+
/** §16.6 timeline defaults (S9-T10). */
|
|
1252
|
+
declare const TIMELINE_TICK_MS_DEFAULT = 100;
|
|
1253
|
+
declare const TIMELINE_STEP_DEFAULT = 0.01;
|
|
1254
|
+
/** §16.5 search defaults (S11-T06). */
|
|
1255
|
+
declare const SEARCH_LIMIT_DEFAULT = 20;
|
|
1256
|
+
declare const SEARCH_CACHE_LIMIT = 32;
|
|
1257
|
+
declare function createGraphInstance<N = Record<string, unknown>, E = Record<string, unknown>>(opts: CreateGraphInstanceOptions<N, E>): GraphInstance<N, E>;
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* §14 DOM label lane — pure candidate selection (S7-T01/T16).
|
|
1261
|
+
*
|
|
1262
|
+
* `selectLabelCandidates` is a pure, deterministic ranking function: no engine,
|
|
1263
|
+
* no DOM, no store. The instance-side overlay scheduler calls it on THROTTLED
|
|
1264
|
+
* re-rank triggers only (viewport idle, model change, config change, settle) —
|
|
1265
|
+
* NEVER per frame. Positions are the reconciler's CPU cache (space coords);
|
|
1266
|
+
* per-frame work elsewhere is a pure O(k) projection of the winners.
|
|
1267
|
+
*
|
|
1268
|
+
* Selection rules (§14):
|
|
1269
|
+
* - Zoom-LOD: below `minZoom` the lane is empty EXCEPT `showFor` ids, which
|
|
1270
|
+
* bypass the zoom gate but stay viewport-culled.
|
|
1271
|
+
* - `showFor` claims capacity FIRST in accepted-base order. When the
|
|
1272
|
+
* in-viewport `showFor` set alone exceeds capacity k, accepted-base order
|
|
1273
|
+
* wins deterministically and `overloadCount` reports the omissions (one
|
|
1274
|
+
* `label-overload` diagnostic upstream — no winner churn).
|
|
1275
|
+
* - Remaining capacity fills with viewport-visible nodes ranked by
|
|
1276
|
+
* `getWeight` (else degree), ties broken by accepted-base order.
|
|
1277
|
+
* - Visibility comes from the engine's `pointsInRect` when available, else a
|
|
1278
|
+
* CPU cull of cached positions through the viewport transform. Nodes with
|
|
1279
|
+
* unknown (NaN) cached positions are unplaceable on the CPU path.
|
|
1280
|
+
*/
|
|
1281
|
+
|
|
1282
|
+
/** A capacity winner before per-frame projection assigns screen coordinates. */
|
|
1283
|
+
type LabelCandidate = Omit<LabelPlacement, 'x' | 'y'>;
|
|
1284
|
+
/** §14 default ranked-candidate cap. */
|
|
1285
|
+
declare const LABEL_MAX_VISIBLE_DEFAULT = 64;
|
|
1286
|
+
/** §14 policy maximum for `maxVisible`. */
|
|
1287
|
+
declare const LABEL_MAX_VISIBLE_CAP = 1024;
|
|
1288
|
+
interface LabelCandidateViewport {
|
|
1289
|
+
zoom: number;
|
|
1290
|
+
/** Visible screen rect `[x0, y0, x1, y1]` (CSS px). Omit = size unknown → no viewport cull. */
|
|
1291
|
+
screenRect?: readonly [number, number, number, number];
|
|
1292
|
+
/** space → screen projection for the CPU cull path (null = not projectable). */
|
|
1293
|
+
spaceToScreen?: (p: readonly [number, number]) => readonly [number, number] | null;
|
|
1294
|
+
}
|
|
1295
|
+
interface SelectLabelCandidatesArgs<N = Record<string, unknown>> {
|
|
1296
|
+
scene: RenderScene;
|
|
1297
|
+
/**
|
|
1298
|
+
* Accepted nodes in accepted-base order. Under the 'rebuild' index policy
|
|
1299
|
+
* (§7.3) scene index i IS accepted-base position i, so `nodes[i]` is the
|
|
1300
|
+
* node behind `scene.idByIndex[i]`.
|
|
1301
|
+
*/
|
|
1302
|
+
nodes: readonly GraphNode<N>[];
|
|
1303
|
+
/** Space positions from the CPU cache (2*count floats; NaN pair = unknown). */
|
|
1304
|
+
positions: Float32Array;
|
|
1305
|
+
viewport: LabelCandidateViewport;
|
|
1306
|
+
config: LabelConfig<N>;
|
|
1307
|
+
/** Degree of scene index i — the default ranking weight. */
|
|
1308
|
+
degreeOf?: (index: number) => number;
|
|
1309
|
+
/**
|
|
1310
|
+
* Engine-accelerated visibility: point indices inside a screen rect. A null
|
|
1311
|
+
* return (engine not ready) falls back to the CPU cull.
|
|
1312
|
+
*/
|
|
1313
|
+
pointsInRect?: (rect: readonly [number, number, number, number]) => number[] | null;
|
|
1314
|
+
}
|
|
1315
|
+
interface LabelCandidateResult {
|
|
1316
|
+
/** Capacity-ordered winners: forced (accepted-base order) then ranked fills. */
|
|
1317
|
+
placements: readonly LabelCandidate[];
|
|
1318
|
+
/** In-viewport `showFor` ids omitted because they alone exceed capacity. */
|
|
1319
|
+
overloadCount: number;
|
|
1320
|
+
}
|
|
1321
|
+
declare function selectLabelCandidates<N = Record<string, unknown>>(args: SelectLabelCandidatesArgs<N>): LabelCandidateResult;
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* §7.1/§13 CSR adjacency over columnar link buffers.
|
|
1325
|
+
*
|
|
1326
|
+
* Pure, engine-free helpers shared by local expansion (§9.2), incident-edge
|
|
1327
|
+
* dirtying (§9.1), and the engine-facing `neighborIndices` interaction helper
|
|
1328
|
+
* (§13). Build is O(L) via two counting passes over the flat
|
|
1329
|
+
* `[src0, tgt0, src1, tgt1, …]` link buffer — no comparison sort and no
|
|
1330
|
+
* per-link allocation (the only allocations are the CSR arrays plus one
|
|
1331
|
+
* point-sized cursor array).
|
|
1332
|
+
*
|
|
1333
|
+
* The adjacency is UNDIRECTED: every link contributes one neighbor entry per
|
|
1334
|
+
* endpoint slot, so parallel links repeat, and a self-loop (a, a) lists `a`
|
|
1335
|
+
* twice under point a — once per endpoint slot.
|
|
1336
|
+
*/
|
|
1337
|
+
interface Adjacency {
|
|
1338
|
+
/**
|
|
1339
|
+
* Length `pointCount + 1`; the neighbors of point `i` live at
|
|
1340
|
+
* `neighbors[offsets[i] … offsets[i + 1])`.
|
|
1341
|
+
*/
|
|
1342
|
+
readonly offsets: Uint32Array;
|
|
1343
|
+
/** Length `links.length` (two entries per link — one per direction). */
|
|
1344
|
+
readonly neighbors: Uint32Array;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Builds a CSR adjacency from a flat `[src, tgt]` pair buffer.
|
|
1348
|
+
*
|
|
1349
|
+
* @param links flat `[src0, tgt0, src1, tgt1, …]` point-index pairs
|
|
1350
|
+
* @param pointCount number of points; every endpoint must be `< pointCount`
|
|
1351
|
+
*/
|
|
1352
|
+
declare function buildAdjacency(links: Uint32Array, pointCount: number): Adjacency;
|
|
1353
|
+
/**
|
|
1354
|
+
* Zero-copy neighbor list of one point: a `subarray` VIEW into
|
|
1355
|
+
* `adj.neighbors` — do not mutate, and do not hold across a rebuild.
|
|
1356
|
+
*/
|
|
1357
|
+
declare function neighborsOf(adj: Adjacency, index: number): Uint32Array;
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* §13/§15 CPU link-pick fallback: a uniform grid over ALL links in SPACE
|
|
1361
|
+
* coordinates (cell ≈ median link length), used when the engine lacks native
|
|
1362
|
+
* `linkAt` (`capabilities.linkPicking === false`). Backs
|
|
1363
|
+
* `onEdgeClick`/`onEdgeHover` via the §7.4 index→edge mapping.
|
|
1364
|
+
*
|
|
1365
|
+
* Coordinate contract: everything here is SPACE coordinates. Callers invert
|
|
1366
|
+
* the affine `Viewport` (space = (screen − center)/zoom + [x, y]) and pass a
|
|
1367
|
+
* space-unit tolerance (`tolerancePx / zoom`).
|
|
1368
|
+
*
|
|
1369
|
+
* Invalidation contract (normative — wave 2 enforces arming):
|
|
1370
|
+
* - The grid indexes a POSITION SNAPSHOT (the §7.1 `posBuf` CPU mirror as of
|
|
1371
|
+
* the build call). It NEVER observes live simulation positions: while the
|
|
1372
|
+
* simulation is hot (alpha above threshold) or an animated `setLayout()`
|
|
1373
|
+
* transition runs, picking is DISARMED by the caller; on settle one
|
|
1374
|
+
* per-event `getPositions` refreshes the mirror and the grid is rebuilt.
|
|
1375
|
+
* - Rebuild ONLY on structural change (link buffer changed) or position
|
|
1376
|
+
* sync. The grid is invariant under camera moves and visibility-mask
|
|
1377
|
+
* changes: the §9.1 mask is applied per candidate at QUERY time via the
|
|
1378
|
+
* optional `visible` callback (pure pass-through until S9 wires the real
|
|
1379
|
+
* mask through it).
|
|
1380
|
+
*
|
|
1381
|
+
* Build uses two counting-sort passes into CSR grid arrays (`cellOffsets`,
|
|
1382
|
+
* `cellLinkIds`). Each segment is inserted with a supercover grid traversal,
|
|
1383
|
+
* so a link contributes O(cols + rows) cells in the worst case instead of
|
|
1384
|
+
* every cell in its O(cols * rows) axis-aligned bounding box. There is no
|
|
1385
|
+
* comparison sort or per-link allocation beyond the CSR arrays (plus
|
|
1386
|
+
* O(cells) cursor and O(links) query stamps). Links with a non-finite endpoint
|
|
1387
|
+
* (NaN-tombstoned points, §13) are excluded and can never be returned. A hard
|
|
1388
|
+
* CSR-entry cap deterministically degrades pathological builds to exact O(L)
|
|
1389
|
+
* query scans with no cell-link payload.
|
|
1390
|
+
*/
|
|
1391
|
+
/** §9.1 visibility mask applied per candidate at query time. */
|
|
1392
|
+
type LinkVisibilityMask = (linkIndex: number) => boolean;
|
|
1393
|
+
/**
|
|
1394
|
+
* Read-only view of the built grid (diagnostics/tests). The typed arrays are
|
|
1395
|
+
* the LIVE internal buffers — never mutate them.
|
|
1396
|
+
*/
|
|
1397
|
+
interface LinkPickGridSnapshot {
|
|
1398
|
+
/** `scan` means the CSR cap tripped and queries use an exact full scan. */
|
|
1399
|
+
readonly mode: 'grid' | 'scan';
|
|
1400
|
+
readonly cellSize: number;
|
|
1401
|
+
readonly cols: number;
|
|
1402
|
+
readonly rows: number;
|
|
1403
|
+
readonly minX: number;
|
|
1404
|
+
readonly minY: number;
|
|
1405
|
+
/** CSR: length `cols * rows + 1`; `[0, 0]` in `scan` mode. */
|
|
1406
|
+
readonly cellOffsets: Uint32Array;
|
|
1407
|
+
/** CSR segment-supercover pairs; empty in `scan` mode. */
|
|
1408
|
+
readonly cellLinkIds: Uint32Array;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Exact point→segment squared distance (shared with the query path so the
|
|
1412
|
+
* grid and any external oracle agree bit-for-bit).
|
|
1413
|
+
*/
|
|
1414
|
+
declare function pointSegmentDistanceSquared(px: number, py: number, ax: number, ay: number, bx: number, by: number): number;
|
|
1415
|
+
declare class LinkPickIndex {
|
|
1416
|
+
private grid;
|
|
1417
|
+
/** Per-link visited stamps: dedupe across cells within one query. */
|
|
1418
|
+
private stamps;
|
|
1419
|
+
private queryStamp;
|
|
1420
|
+
get isBuilt(): boolean;
|
|
1421
|
+
/** Read-only grid/scan index view for diagnostics/tests; null before build. */
|
|
1422
|
+
gridSnapshot(): LinkPickGridSnapshot | null;
|
|
1423
|
+
/**
|
|
1424
|
+
* Drops the grid: `nearestLink` reports null until the next build. Wave 2
|
|
1425
|
+
* calls this on structural change / sim-hot disarm.
|
|
1426
|
+
*/
|
|
1427
|
+
invalidate(): void;
|
|
1428
|
+
/**
|
|
1429
|
+
* One-shot output-sensitive build over a position snapshot: O(L + I), where
|
|
1430
|
+
* I is the emitted segment-cell pairs up to `MAX_CELL_LINK_ENTRIES`; above
|
|
1431
|
+
* that cap it commits exact-scan mode. Drains the chunked generator with an
|
|
1432
|
+
* infinite budget so both entry points share one code path (chunked and
|
|
1433
|
+
* one-shot builds are bit-identical).
|
|
1434
|
+
*/
|
|
1435
|
+
build(positions: Float32Array, links: Uint32Array): void;
|
|
1436
|
+
/**
|
|
1437
|
+
* Incremental build: yields whenever `now() − sliceStart ≥ budgetMs` so a
|
|
1438
|
+
* scheduler can spread the work across idle frames (§17 long-task budget).
|
|
1439
|
+
* Pure generator — no rAF/timers here; the caller owns scheduling.
|
|
1440
|
+
*
|
|
1441
|
+
* The previous grid stays armed and queryable until the new one commits on
|
|
1442
|
+
* generator completion; abandoning the iterator leaves the old grid
|
|
1443
|
+
* intact. At most one build should be in flight per index — the wave-2
|
|
1444
|
+
* scheduler serializes rebuilds.
|
|
1445
|
+
*/
|
|
1446
|
+
buildChunked(positions: Float32Array, links: Uint32Array, budgetMs: number, now: () => number): IterableIterator<void>;
|
|
1447
|
+
/**
|
|
1448
|
+
* Nearest link within `tolerance` (space units) of the space point
|
|
1449
|
+
* `(x, y)`, or null. Scans the candidate cells covering the tolerance
|
|
1450
|
+
* disc's bounding box (typically the 3×3 neighborhood; more when the
|
|
1451
|
+
* tolerance exceeds the cell size), applies the optional §9.1 visibility
|
|
1452
|
+
* mask per candidate, then runs the exact point→segment distance test.
|
|
1453
|
+
* Nearest wins; exact-distance ties break toward the LOWER link index.
|
|
1454
|
+
* Returns null while unbuilt/invalidated (picking disarmed).
|
|
1455
|
+
*/
|
|
1456
|
+
nearestLink(x: number, y: number, tolerance: number, visible?: LinkVisibilityMask): number | null;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* §13/§15 edge-picking facade (S6-T07).
|
|
1461
|
+
*
|
|
1462
|
+
* The core commits to ONE route per mount session, read from
|
|
1463
|
+
* `engine.capabilities.linkPicking` at engine-ready time — never method
|
|
1464
|
+
* sniffing, and never re-evaluated (a capability record mutated after ready
|
|
1465
|
+
* changes nothing):
|
|
1466
|
+
*
|
|
1467
|
+
* - 'native': the adapter delivers `onLinkClick`/`onLinkHover` host events
|
|
1468
|
+
* itself; the facade is inert (arm/disarm/queries no-op and
|
|
1469
|
+
* `pickLinkAt` returns null) and the instance maps link indices
|
|
1470
|
+
* to typed edges straight off the host events.
|
|
1471
|
+
* - 'fallback': the instance samples the pointer on the shared §15 throttle
|
|
1472
|
+
* cadence and resolves hits through a `LinkPickIndex` uniform
|
|
1473
|
+
* grid that is armed ONLY while the simulation is settled.
|
|
1474
|
+
*
|
|
1475
|
+
* Arming protocol (fallback route; §13):
|
|
1476
|
+
* - `arm(positions, links)` runs on simulation settle (fed by one per-event
|
|
1477
|
+
* `getPositions` readback) and on structural commits that do NOT restart
|
|
1478
|
+
* the simulation (fixed layout). The grid builds CHUNKED via
|
|
1479
|
+
* `LinkPickIndex.buildChunked` under a per-slice time budget so large link
|
|
1480
|
+
* sets never block (§17); small builds complete synchronously in the first
|
|
1481
|
+
* slice. While an initial build is in flight queries return null; a
|
|
1482
|
+
* position-sync REBUILD keeps answering from the previous grid until the
|
|
1483
|
+
* new one commits.
|
|
1484
|
+
* - `disarm()` runs on any commit that restarts the simulation: targets are
|
|
1485
|
+
* moving and the position mirror is stale, so queries return null until
|
|
1486
|
+
* the next settle re-arms.
|
|
1487
|
+
* - Rebuilds happen ONLY on structural change or position sync — the grid is
|
|
1488
|
+
* invariant under camera moves and mask changes (the §9.1 visibility mask
|
|
1489
|
+
* is applied per candidate at query time; pass-through stub until S9).
|
|
1490
|
+
*
|
|
1491
|
+
* Tolerance (§13): a query converts screen px to space units via
|
|
1492
|
+
* `screenToSpace` of two points `EDGE_PICK_TOLERANCE_PX` apart, then uses
|
|
1493
|
+
* `max(4px, half the median link width)` scaled by that factor.
|
|
1494
|
+
*/
|
|
1495
|
+
|
|
1496
|
+
type EdgePickRoute = 'native' | 'fallback';
|
|
1497
|
+
/** Screen-space pick tolerance floor, and the probe distance used to measure
|
|
1498
|
+
* the screen→space scale (§13). */
|
|
1499
|
+
declare const EDGE_PICK_TOLERANCE_PX = 4;
|
|
1500
|
+
/**
|
|
1501
|
+
* Median link width in px from a projected linkWidth buffer (deterministic
|
|
1502
|
+
* stride sample, cap 1024). Returns 0 for null/empty/non-finite input — the
|
|
1503
|
+
* 4px floor then wins in the tolerance formula.
|
|
1504
|
+
*/
|
|
1505
|
+
declare function medianLinkWidthPx(widths: Float32Array | null): number;
|
|
1506
|
+
interface EdgePickingFacadeOptions {
|
|
1507
|
+
/** Fixed for the facade's lifetime; read from capabilities at ready. */
|
|
1508
|
+
route: EdgePickRoute;
|
|
1509
|
+
/** Affine screen→space conversion; null = conversion unavailable. */
|
|
1510
|
+
screenToSpace: (p: readonly [number, number]) => readonly [number, number] | null;
|
|
1511
|
+
/** Median link width in px for the tolerance floor; default () => 0. */
|
|
1512
|
+
medianLinkWidthPx?: () => number;
|
|
1513
|
+
/** §9.1 visibility mask pass-through stub (mutable via the setter). */
|
|
1514
|
+
linkVisible?: LinkVisibilityMask;
|
|
1515
|
+
/** Chunked-build slice budget in ms. */
|
|
1516
|
+
buildBudgetMs?: number;
|
|
1517
|
+
/** Scheduler for build continuation slices (test seam). */
|
|
1518
|
+
schedule?: (continueBuild: () => void) => void;
|
|
1519
|
+
/** Clock for the build budget (test seam). */
|
|
1520
|
+
now?: () => number;
|
|
1521
|
+
}
|
|
1522
|
+
declare class EdgePickingFacade {
|
|
1523
|
+
/** The route this mount session committed to at ready. */
|
|
1524
|
+
readonly route: EdgePickRoute;
|
|
1525
|
+
private readonly screenToSpace;
|
|
1526
|
+
private readonly medianWidthPx;
|
|
1527
|
+
private readonly budgetMs;
|
|
1528
|
+
private readonly schedule;
|
|
1529
|
+
private readonly now;
|
|
1530
|
+
private readonly index;
|
|
1531
|
+
private mask;
|
|
1532
|
+
/** Bumped by disarm/arm/destroy; abandons any in-flight chunked build. */
|
|
1533
|
+
private generation;
|
|
1534
|
+
constructor(opts: EdgePickingFacadeOptions);
|
|
1535
|
+
/** True when the fallback grid is built and answering queries. */
|
|
1536
|
+
get armed(): boolean;
|
|
1537
|
+
/**
|
|
1538
|
+
* (Re-)arm from a settled position snapshot. `positions`/`links` must be
|
|
1539
|
+
* stable snapshots — the grid references them until the next rebuild.
|
|
1540
|
+
* No-op on the native route.
|
|
1541
|
+
*/
|
|
1542
|
+
arm(positions: Float32Array, links: Uint32Array): void;
|
|
1543
|
+
/** Simulation went hot: drop the grid; queries null until the next settle. */
|
|
1544
|
+
disarm(): void;
|
|
1545
|
+
/** §9.1 mask pass-through stub: applied per candidate at query time. */
|
|
1546
|
+
setLinkVisibilityMask(mask: LinkVisibilityMask | null): void;
|
|
1547
|
+
/**
|
|
1548
|
+
* Nearest link index within tolerance of a SCREEN point, or null when: on
|
|
1549
|
+
* the native route, disarmed (sim hot / not yet settled), no conversion,
|
|
1550
|
+
* or nothing in range.
|
|
1551
|
+
*/
|
|
1552
|
+
pickLinkAt(screen: readonly [number, number]): number | null;
|
|
1553
|
+
/** Abandon any in-flight build and drop the grid (session teardown). */
|
|
1554
|
+
destroy(): void;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Collision-safe identity helpers for synthesized edges.
|
|
1559
|
+
*
|
|
1560
|
+
* Simple ids retain the documented `source→target#k` shape. The three
|
|
1561
|
+
* framing characters are backslash-escaped inside endpoint components, making
|
|
1562
|
+
* the concatenation injective for arbitrary JavaScript strings (including
|
|
1563
|
+
* strings that themselves contain backslashes, arrows, hashes, or NULs).
|
|
1564
|
+
*/
|
|
1565
|
+
|
|
1566
|
+
type EdgePairCounters = Map<NodeId, Map<NodeId, number>>;
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* §7.5 revisioned ingestion — pure session/overlay bookkeeping plus the
|
|
1570
|
+
* instance-local acceptance queue (S8-T01..T07).
|
|
1571
|
+
*
|
|
1572
|
+
* Everything here is data-structure work: no store, no engine, no DOM. The
|
|
1573
|
+
* GraphInstance wires these helpers into its publication path (instance.ts).
|
|
1574
|
+
*
|
|
1575
|
+
* Ordering model: every admission takes a monotonically increasing ticket from
|
|
1576
|
+
* the instance's AcceptanceQueue. Overlay rows are stamped with their
|
|
1577
|
+
* admission ticket, and the merge folds base + overlays strictly in ticket
|
|
1578
|
+
* order — arrival at the queue IS the global admission order (§7.5).
|
|
1579
|
+
*/
|
|
1580
|
+
|
|
1581
|
+
/** Byte backpressure budget default: 8 MiB of admitted-but-unflushed payload. */
|
|
1582
|
+
declare const INGEST_MAX_PENDING_BYTES_DEFAULT = 8388608;
|
|
1583
|
+
/** Progressive-overlay coalesced flush latency cap default (ms). */
|
|
1584
|
+
declare const INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT = 50;
|
|
1585
|
+
/** A single batch larger than `factor × maxPendingBytes` rejects outright. */
|
|
1586
|
+
declare const INGEST_OVERFLOW_FACTOR = 4;
|
|
1587
|
+
declare class AcceptanceQueue {
|
|
1588
|
+
private ticket;
|
|
1589
|
+
/** Depth guard: admissions made from inside a job run nested — arrival
|
|
1590
|
+
* order still equals call order under synchronous execution. */
|
|
1591
|
+
private depth;
|
|
1592
|
+
/** Total admissions so far (the global admission-order clock). */
|
|
1593
|
+
get admissions(): number;
|
|
1594
|
+
/** True while a job admitted by this queue is executing. */
|
|
1595
|
+
get active(): boolean;
|
|
1596
|
+
/** Take the next admission-order ticket without running a job (row stamps). */
|
|
1597
|
+
nextTicket(): number;
|
|
1598
|
+
/**
|
|
1599
|
+
* Admit a job at the tail of the global order and execute it synchronously.
|
|
1600
|
+
* Exceptions propagate to the admitter (v0.5 synchronous execution).
|
|
1601
|
+
*/
|
|
1602
|
+
admit<T>(job: () => T): T;
|
|
1603
|
+
}
|
|
1604
|
+
/** `batch.bytes` when declared, else a rough JSON estimate of the payload. */
|
|
1605
|
+
declare function estimateBatchBytes(batch: IngestBatch<unknown, unknown>): number;
|
|
1606
|
+
interface RowTally {
|
|
1607
|
+
count: number;
|
|
1608
|
+
samples: string[];
|
|
1609
|
+
}
|
|
1610
|
+
/** Per-session staging tallies; surfaced as diagnostics ONLY at commit (§7.5). */
|
|
1611
|
+
interface StagingTallies {
|
|
1612
|
+
invalidNodes: RowTally;
|
|
1613
|
+
duplicateNodes: RowTally;
|
|
1614
|
+
invalidEdges: RowTally;
|
|
1615
|
+
duplicateEdges: RowTally;
|
|
1616
|
+
}
|
|
1617
|
+
declare function newStagingTallies(): StagingTallies;
|
|
1618
|
+
interface StampedNode<N> {
|
|
1619
|
+
/** Global admission-order ticket (queue arrival). */
|
|
1620
|
+
readonly order: number;
|
|
1621
|
+
readonly node: GraphNode<N>;
|
|
1622
|
+
}
|
|
1623
|
+
interface StampedEdge<E> {
|
|
1624
|
+
readonly order: number;
|
|
1625
|
+
readonly edge: AcceptedEdge<E>;
|
|
1626
|
+
}
|
|
1627
|
+
interface SessionContribution<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
1628
|
+
readonly overlayId: string;
|
|
1629
|
+
/** Session-deduped admitted node rows in admission order. */
|
|
1630
|
+
readonly nodes: StampedNode<N>[];
|
|
1631
|
+
/** Session-deduped admitted edge records (ids resolved) in admission order. */
|
|
1632
|
+
readonly edges: StampedEdge<E>[];
|
|
1633
|
+
readonly nodeIds: Set<NodeId>;
|
|
1634
|
+
readonly edgeIds: Set<string>;
|
|
1635
|
+
/** Exact ordered endpoint tuple → next synthesized-parallel-edge k (§5). */
|
|
1636
|
+
readonly pairCounters: EdgePairCounters;
|
|
1637
|
+
/** Public prefix lengths: rows before these counts have been published
|
|
1638
|
+
* (progressive flush / commit); appends only grow the arrays, so a prefix
|
|
1639
|
+
* is sufficient. Atomic sessions stay at 0 until commit (§7.5). */
|
|
1640
|
+
publicNodeCount: number;
|
|
1641
|
+
publicEdgeCount: number;
|
|
1642
|
+
}
|
|
1643
|
+
declare function newContribution<N, E>(overlayId: string): SessionContribution<N, E>;
|
|
1644
|
+
interface StageResult {
|
|
1645
|
+
admittedNodes: number;
|
|
1646
|
+
admittedEdges: number;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Validate and stage one batch's rows into a session contribution.
|
|
1650
|
+
*
|
|
1651
|
+
* §5.1-equivalent row validation WITHOUT endpoint checks — edges may arrive
|
|
1652
|
+
* before nodes (§7.5): they occupy their eventual edge records here; endpoint
|
|
1653
|
+
* resolution happens at merge. Within a session, duplicate ids are
|
|
1654
|
+
* first-wins-dropped (tallied for commit diagnostics); cross-session
|
|
1655
|
+
* collisions are the merge's shadowing concern, not staging's.
|
|
1656
|
+
*/
|
|
1657
|
+
declare function stageBatch<N, E>(contribution: SessionContribution<N, E>, batch: IngestBatch<N, E>, tallies: StagingTallies, nextOrder: () => number): StageResult;
|
|
1658
|
+
/** The accepted base (declarative snapshot or committed replace session). */
|
|
1659
|
+
interface MergeBase<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
1660
|
+
datasetKey: string;
|
|
1661
|
+
sourceRevision: number | string;
|
|
1662
|
+
nodes: readonly GraphNode<N>[];
|
|
1663
|
+
/** id → position in `nodes`. */
|
|
1664
|
+
nodeIndex: ReadonlyMap<NodeId, number>;
|
|
1665
|
+
/** Endpoint-resolved base edges. */
|
|
1666
|
+
edges: readonly AcceptedEdge<E>[];
|
|
1667
|
+
/** Replace-session bases retain edge records whose endpoints were missing
|
|
1668
|
+
* at commit; a later overlay node can still resolve them (§7.5). */
|
|
1669
|
+
pendingEdges: readonly StampedEdge<E>[];
|
|
1670
|
+
diagnostics: readonly GraphDiagnostic[];
|
|
1671
|
+
}
|
|
1672
|
+
/** Sentinel owner key for base pending edges in `pendingBySource`. */
|
|
1673
|
+
declare const BASE_PENDING_KEY = "";
|
|
1674
|
+
interface MergeResult<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
1675
|
+
accepted: AcceptedGraph<N, E>;
|
|
1676
|
+
/** Overlay node rows shadowed by an earlier admission (base included). */
|
|
1677
|
+
shadowedCount: number;
|
|
1678
|
+
shadowedSamples: readonly string[];
|
|
1679
|
+
/** Later same-id edge records dropped (first admission wins). */
|
|
1680
|
+
duplicateEdgeCount: number;
|
|
1681
|
+
duplicateEdgeSamples: readonly string[];
|
|
1682
|
+
/** overlayId (or BASE_PENDING_KEY) → edges still awaiting an endpoint. The
|
|
1683
|
+
* pending-endpoint index: these occupy edge records but never enter the
|
|
1684
|
+
* engine link buffer (§7.5). */
|
|
1685
|
+
pendingBySource: ReadonlyMap<string, number>;
|
|
1686
|
+
pendingEdgeCount: number;
|
|
1687
|
+
}
|
|
1688
|
+
declare function baseFromAccepted<N, E>(accepted: AcceptedGraph<N, E>): MergeBase<N, E>;
|
|
1689
|
+
/**
|
|
1690
|
+
* Build a MergeBase from a replace session's staged contribution. Edges whose
|
|
1691
|
+
* endpoints exist in the session's own node set resolve immediately; the rest
|
|
1692
|
+
* stay pending (they may resolve through later overlays).
|
|
1693
|
+
*/
|
|
1694
|
+
declare function baseFromContribution<N, E>(datasetKey: string, sourceRevision: number | string, contribution: SessionContribution<N, E>, diagnostics: readonly GraphDiagnostic[]): MergeBase<N, E>;
|
|
1695
|
+
/**
|
|
1696
|
+
* Merge the accepted base with every overlay contribution's PUBLIC rows.
|
|
1697
|
+
*
|
|
1698
|
+
* - Node collisions: earliest admission (ticket order; base first) wins.
|
|
1699
|
+
* Later rows stay retained-but-shadowed in their contribution and are
|
|
1700
|
+
* tallied for the single count-aggregated 'overlay-node-shadowed' info
|
|
1701
|
+
* diagnostic (§7.5).
|
|
1702
|
+
* - Edge ids: first admission wins; later same-id records are dropped
|
|
1703
|
+
* (tallied). An edge whose endpoints are not all present in the merged
|
|
1704
|
+
* node set is pending — it occupies its record and the pending-endpoint
|
|
1705
|
+
* accounting but is excluded from `accepted.edges` (and therefore from the
|
|
1706
|
+
* engine link buffer). Endpoints arriving later resolve it on the next
|
|
1707
|
+
* merge (same or different batch/overlay).
|
|
1708
|
+
*
|
|
1709
|
+
* Deterministic: output depends only on the base and the stamped rows.
|
|
1710
|
+
*/
|
|
1711
|
+
declare function mergeModel<N, E>(base: MergeBase<N, E>, overlays: readonly SessionContribution<N, E>[]): MergeResult<N, E>;
|
|
1712
|
+
/** Recomputed per merge: ONE count-aggregated shadow info + edge-dedupe note. */
|
|
1713
|
+
declare function mergeDiagnostics(merge: MergeResult<unknown, unknown>): GraphDiagnostic[];
|
|
1714
|
+
/**
|
|
1715
|
+
* Commit-time diagnostics for one session: staging tallies plus the dangling
|
|
1716
|
+
* (still-pending-endpoint) count — emitted ONLY at session commit (§7.5).
|
|
1717
|
+
*/
|
|
1718
|
+
declare function sessionCommitDiagnostics(tallies: StagingTallies, danglingCount: number, danglingSamples?: readonly string[]): GraphDiagnostic[];
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* §9.2 hard subgraph scope — pure resolution over the accepted base.
|
|
1722
|
+
*
|
|
1723
|
+
* `resolveScope` turns a `SubgraphSpec` into the exact node/edge subset the
|
|
1724
|
+
* reconciler should be fed: seeds validated against the accepted base
|
|
1725
|
+
* (unknown ids dropped, duplicates collapsed), optional `hops` expansion via
|
|
1726
|
+
* BFS over the §7.1 CSR adjacency of the ACCEPTED BASE (not the scene — the
|
|
1727
|
+
* default expansion service walks this same index, §9.2), and edges cascaded
|
|
1728
|
+
* through `cascadeEdges`, the single §9 edge-survival primitive (an edge
|
|
1729
|
+
* survives iff BOTH endpoints survive) that S9's soft masks reuse.
|
|
1730
|
+
*
|
|
1731
|
+
* Everything here is synchronous and engine-free. The async
|
|
1732
|
+
* `ExpansionService` seam (./services) exists for `expandNode`; `hops`
|
|
1733
|
+
* resolution inside a host update takes this local path directly.
|
|
1734
|
+
*/
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* THE edge-cascade primitive (§9): an edge survives iff BOTH of its
|
|
1738
|
+
* endpoints survive. O(E); the only allocation is the output array, which
|
|
1739
|
+
* holds references to the input edge objects (never copies).
|
|
1740
|
+
*
|
|
1741
|
+
* Exported for reuse by §9.1 soft masks (S9) and any other subset producer.
|
|
1742
|
+
*/
|
|
1743
|
+
declare function cascadeEdges<E>(edges: readonly AcceptedEdge<E>[], survives: (id: NodeId) => boolean): AcceptedEdge<E>[];
|
|
1744
|
+
/**
|
|
1745
|
+
* Builds the §7.1 CSR adjacency of an accepted base: endpoints are positions
|
|
1746
|
+
* in `accepted.nodes` (accepted-base order). Every accepted edge has resolved
|
|
1747
|
+
* endpoints by contract (§5.1 drops dangling edges), so this cannot throw on
|
|
1748
|
+
* a well-formed `AcceptedGraph`.
|
|
1749
|
+
*/
|
|
1750
|
+
declare function buildAcceptedAdjacency(accepted: AcceptedGraph<unknown, unknown>): Adjacency;
|
|
1751
|
+
/** Output of `resolveScope`: the exact subset to feed the reconciler (§9.2). */
|
|
1752
|
+
interface ResolvedScope<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
1753
|
+
/** Surviving node ids (seeds + hop expansion), membership-query form. */
|
|
1754
|
+
nodeIds: ReadonlySet<NodeId>;
|
|
1755
|
+
/** Surviving nodes in accepted-base order (same objects, never copies). */
|
|
1756
|
+
nodes: readonly GraphNode<N>[];
|
|
1757
|
+
/** Edges whose BOTH endpoints survive, in accepted-base order. */
|
|
1758
|
+
edges: readonly AcceptedEdge<E>[];
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Resolves a hard-scope spec against the accepted base (§9.2).
|
|
1762
|
+
*
|
|
1763
|
+
* - `spec.seedIds` are validated against `accepted.nodeIndex`: ids unknown to
|
|
1764
|
+
* the accepted base are dropped, duplicates collapse to one seed.
|
|
1765
|
+
* - `spec.hops` (default 0; negative/non-finite values clamp to 0, fractions
|
|
1766
|
+
* floor) expands the survivor set by BFS over the accepted-base adjacency.
|
|
1767
|
+
* This is the synchronous local path — the §9.2 default `ExpansionService`
|
|
1768
|
+
* walks the same index for `expandNode`.
|
|
1769
|
+
* - `adjacency` may be a caller-cached `buildAcceptedAdjacency(accepted)`
|
|
1770
|
+
* result; pass `null` to have one built on demand (only when `hops > 0`
|
|
1771
|
+
* and there are surviving seeds — hop-0 resolution never builds it).
|
|
1772
|
+
* - Output preserves accepted-base order for both nodes and edges; edges are
|
|
1773
|
+
* cascaded through {@link cascadeEdges}.
|
|
1774
|
+
*/
|
|
1775
|
+
declare function resolveScope<N, E>(accepted: AcceptedGraph<N, E>, spec: SubgraphSpec, adjacency: Adjacency | null): ResolvedScope<N, E>;
|
|
1776
|
+
|
|
1777
|
+
/**
|
|
1778
|
+
* §9.2 revision-aware services — pure sequencing/admission/caching helpers
|
|
1779
|
+
* plus the built-in local expansion service.
|
|
1780
|
+
*
|
|
1781
|
+
* The correctness gate is `admitServiceResult`: a result is admitted only if
|
|
1782
|
+
* every revision dimension the service DECLARED is unchanged since the call
|
|
1783
|
+
* was issued; undeclared dimensions never invalidate (so e.g. the local
|
|
1784
|
+
* expansion service, declaring only 'source', survives unrelated model/scope
|
|
1785
|
+
* advances from other overlay publications). Abort is an optimization —
|
|
1786
|
+
* admission is the gate.
|
|
1787
|
+
*
|
|
1788
|
+
* Cache keys include service identity, canonical-JSON request parameters,
|
|
1789
|
+
* `datasetKey`, and EXACTLY the declared revision dimensions' current values.
|
|
1790
|
+
*
|
|
1791
|
+
* Nothing here touches the engine, the store, or the DOM; the instance wires
|
|
1792
|
+
* these primitives to its acceptance queue.
|
|
1793
|
+
*/
|
|
1794
|
+
|
|
1795
|
+
/** Current value of each §9.2 revision dimension (a Revisions subset). */
|
|
1796
|
+
interface RevisionSnapshot {
|
|
1797
|
+
source: number | string | null;
|
|
1798
|
+
model: number;
|
|
1799
|
+
scope: number;
|
|
1800
|
+
}
|
|
1801
|
+
/** Monotonic instance-process-unique request id (deterministic prefix for tests). */
|
|
1802
|
+
declare function nextRequestId(prefix?: string): string;
|
|
1803
|
+
interface CreateRequestContextArgs {
|
|
1804
|
+
datasetKey: string;
|
|
1805
|
+
/** Revision values current at issue time (snapshotted into the context). */
|
|
1806
|
+
revisions: RevisionSnapshot;
|
|
1807
|
+
/** Explicit id (e.g. for coalescing bookkeeping); generated when omitted. */
|
|
1808
|
+
requestId?: string;
|
|
1809
|
+
/** Chain an upstream signal (e.g. instance teardown) into this request. */
|
|
1810
|
+
parentSignal?: AbortSignal;
|
|
1811
|
+
}
|
|
1812
|
+
/** A `RequestContext` plus its owning abort handle. */
|
|
1813
|
+
interface RequestContextHandle {
|
|
1814
|
+
readonly context: RequestContext;
|
|
1815
|
+
readonly controller: AbortController;
|
|
1816
|
+
abort(reason?: unknown): void;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Builds the `RequestContext` a service call receives (§9.2): dataset,
|
|
1820
|
+
* the three revision dimensions at issue time, a request id, and a
|
|
1821
|
+
* cancellation signal owned by the returned controller.
|
|
1822
|
+
*/
|
|
1823
|
+
declare function createRequestContext(args: CreateRequestContextArgs): RequestContextHandle;
|
|
1824
|
+
interface AdmitServiceResultArgs {
|
|
1825
|
+
/** The service's `revisionDependencies` (§9.2). */
|
|
1826
|
+
declared: readonly RevisionDimension[];
|
|
1827
|
+
/** Revision values when the request was issued. */
|
|
1828
|
+
at: RevisionSnapshot;
|
|
1829
|
+
/** Revision values at admission time. */
|
|
1830
|
+
now: RevisionSnapshot;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* §9.2 stale-result rule: admit a service result iff EVERY declared revision
|
|
1834
|
+
* dimension is unchanged between issue and admission. Undeclared dimensions
|
|
1835
|
+
* never invalidate. Declaring nothing means the result is admissible under
|
|
1836
|
+
* any drift; declaring all three restores strict point-in-model semantics.
|
|
1837
|
+
*/
|
|
1838
|
+
declare function admitServiceResult(args: AdmitServiceResultArgs): boolean;
|
|
1839
|
+
interface ServiceCacheKeyArgs {
|
|
1840
|
+
serviceId: string;
|
|
1841
|
+
/** Request parameters (JSON-shaped); key order is canonicalized away. */
|
|
1842
|
+
params: unknown;
|
|
1843
|
+
datasetKey: string;
|
|
1844
|
+
/** The service's declared revision dependencies (§9.2). */
|
|
1845
|
+
declared: readonly RevisionDimension[];
|
|
1846
|
+
/** Current revision values; only declared dimensions enter the key. */
|
|
1847
|
+
revisions: RevisionSnapshot;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* §9.2 cache-key rule: service identity + canonical-JSON params +
|
|
1851
|
+
* `datasetKey` + EXACTLY the declared revision dimensions' current values.
|
|
1852
|
+
* Declaration-list order and params key order do not affect the key;
|
|
1853
|
+
* undeclared revision drift never changes it.
|
|
1854
|
+
*/
|
|
1855
|
+
declare function serviceCacheKey(args: ServiceCacheKeyArgs): string;
|
|
1856
|
+
/** Accepted-base view the local service walks (thunked for lazy wiring). */
|
|
1857
|
+
interface LocalExpansionBase<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
1858
|
+
accepted: AcceptedGraph<N, E>;
|
|
1859
|
+
/** The §7.1 CSR adjacency of `accepted` (see buildAcceptedAdjacency). */
|
|
1860
|
+
adjacency: Adjacency;
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* The built-in §9.2 expansion service: walks the core's accepted-base
|
|
1864
|
+
* adjacency — INCLUDING currently out-of-scope nodes (it reads the base, not
|
|
1865
|
+
* the scene) — with zero config and zero network. Declares
|
|
1866
|
+
* `revisionDependencies: ['source']`, so unrelated model/scope advances
|
|
1867
|
+
* (e.g. other overlay publications) never invalidate its results.
|
|
1868
|
+
*
|
|
1869
|
+
* `getBase` is a thunk so the instance can wire the service before data
|
|
1870
|
+
* arrives; it is re-read on every call. The returned promise is async but
|
|
1871
|
+
* effectively synchronous (resolves without I/O). `ctx.signal` is honored:
|
|
1872
|
+
* an aborted call rejects with `OrbitOperationError { code: 'aborted' }`
|
|
1873
|
+
* instead of returning a result (abort is still only an optimization — the
|
|
1874
|
+
* admission gate is authoritative).
|
|
1875
|
+
*
|
|
1876
|
+
* The response is the CLOSED N-hop neighborhood (seeds included, plus every
|
|
1877
|
+
* edge between returned nodes); per §9.2 the overlay merger dedupes rows
|
|
1878
|
+
* that already exist, so returning already-known seeds is correct.
|
|
1879
|
+
*/
|
|
1880
|
+
declare function createLocalExpansionService<N = Record<string, unknown>, E = Record<string, unknown>>(getBase: () => LocalExpansionBase<N, E>): ExpansionService<N, E>;
|
|
1881
|
+
type RegisterExpansionResult = {
|
|
1882
|
+
kind: 'new';
|
|
1883
|
+
}
|
|
1884
|
+
/** A same-id expansion is already in flight; its result serves both. */
|
|
1885
|
+
| {
|
|
1886
|
+
kind: 'coalesced';
|
|
1887
|
+
onto: string;
|
|
1888
|
+
};
|
|
1889
|
+
/**
|
|
1890
|
+
* Pure in-flight-expansion ledger (§9.2): within one valid scope revision a
|
|
1891
|
+
* second `expandNode(id)` while one is in flight coalesces into the pending
|
|
1892
|
+
* call; expansions of DISTINCT ids run (and complete) concurrently and
|
|
1893
|
+
* independently. `retractExpansion(id)` uses `abort(id)` to drop that id's
|
|
1894
|
+
* pending expansion. Holds no timers, promises, or engine state — the
|
|
1895
|
+
* instance owns the actual requests.
|
|
1896
|
+
*/
|
|
1897
|
+
declare class PendingExpansions {
|
|
1898
|
+
private readonly inFlight;
|
|
1899
|
+
/**
|
|
1900
|
+
* Registers an expansion of `id` under `requestId`. Returns
|
|
1901
|
+
* `{ kind: 'coalesced', onto }` when a same-id expansion is already in
|
|
1902
|
+
* flight (the caller must NOT issue a new request; `onto` is the request
|
|
1903
|
+
* id whose result serves both), else records the id and returns
|
|
1904
|
+
* `{ kind: 'new' }`.
|
|
1905
|
+
*/
|
|
1906
|
+
register(id: NodeId, requestId: string): RegisterExpansionResult;
|
|
1907
|
+
/**
|
|
1908
|
+
* Marks `id`'s expansion complete. When `requestId` is given, clears only
|
|
1909
|
+
* if it still owns the slot (a stale completion after abort + re-expand
|
|
1910
|
+
* must not clear the newer request). Returns whether the slot was cleared.
|
|
1911
|
+
*/
|
|
1912
|
+
resolve(id: NodeId, requestId?: string): boolean;
|
|
1913
|
+
/**
|
|
1914
|
+
* Drops `id`'s pending expansion (retractExpansion path). Returns the aborted
|
|
1915
|
+
* request id so the caller can cancel its controller, or null when nothing
|
|
1916
|
+
* was in flight.
|
|
1917
|
+
*/
|
|
1918
|
+
abort(id: NodeId): string | null;
|
|
1919
|
+
/** Request id currently serving `id`, if any. */
|
|
1920
|
+
requestIdFor(id: NodeId): string | undefined;
|
|
1921
|
+
has(id: NodeId): boolean;
|
|
1922
|
+
get size(): number;
|
|
1923
|
+
/** Snapshot of pending ids (store's `pendingExpansions` shape). */
|
|
1924
|
+
ids(): ReadonlySet<NodeId>;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/**
|
|
1928
|
+
* §16.14 history command-stack kernel (S9-T20; pulled forward from M7).
|
|
1929
|
+
*
|
|
1930
|
+
* v0.7 implements the KERNEL only: value-diff commands, transactions,
|
|
1931
|
+
* coalescing, and a bounded stack. The kernel never touches stores — it is a
|
|
1932
|
+
* pure application seam: `undo()`/`redo()` return the command list to apply
|
|
1933
|
+
* (already inverted, in application order) and move the cursor; the instance
|
|
1934
|
+
* (wired in S15) applies commands to its slices and publishes.
|
|
1935
|
+
* Ownership-mode acknowledgement walks and view-state integration are S15.
|
|
1936
|
+
*
|
|
1937
|
+
* Commands are `{ slice, before, after }` value diffs — never closures — so
|
|
1938
|
+
* they are serializable and invert by swapping. The kernel freezes and stores
|
|
1939
|
+
* exactly what it is given (no structuredClone): callers pass plain values /
|
|
1940
|
+
* arrays and must not mutate them afterward. A DEV-only serializability walk
|
|
1941
|
+
* (no functions, no Map/Set — callers convert) runs behind the `debug` flag.
|
|
1942
|
+
*/
|
|
1943
|
+
/** One history-worthy mutation: a serializable value diff over a named slice. */
|
|
1944
|
+
interface HistoryCommand {
|
|
1945
|
+
readonly slice: string;
|
|
1946
|
+
readonly before: unknown;
|
|
1947
|
+
readonly after: unknown;
|
|
1948
|
+
}
|
|
1949
|
+
/** Depth snapshot published to subscribers (mirrors GraphStoreState.history). */
|
|
1950
|
+
interface HistoryDepths {
|
|
1951
|
+
readonly undoDepth: number;
|
|
1952
|
+
readonly redoDepth: number;
|
|
1953
|
+
}
|
|
1954
|
+
interface HistoryKernelOptions {
|
|
1955
|
+
/** Stack bound; oldest entry evicted past it (§16.14; default 50). */
|
|
1956
|
+
limit?: number;
|
|
1957
|
+
/** `false` = the `history: false` prop: everything is a no-op, depths stay 0. */
|
|
1958
|
+
enabled?: boolean;
|
|
1959
|
+
/** DEV-only: walk recorded payloads and throw on non-serializable values. */
|
|
1960
|
+
debug?: boolean;
|
|
1961
|
+
}
|
|
1962
|
+
declare const HISTORY_LIMIT_DEFAULT = 50;
|
|
1963
|
+
/**
|
|
1964
|
+
* Bounded undo/redo command stack. Store-agnostic: see module doc for the
|
|
1965
|
+
* application seam. All methods are synchronous; there is no async state.
|
|
1966
|
+
*/
|
|
1967
|
+
declare class HistoryKernel {
|
|
1968
|
+
private readonly limit;
|
|
1969
|
+
private readonly enabled;
|
|
1970
|
+
private readonly debug;
|
|
1971
|
+
private readonly undoStack;
|
|
1972
|
+
private readonly redoStack;
|
|
1973
|
+
/** Nested begin() joins the outer transaction (depth-counted). */
|
|
1974
|
+
private txDepth;
|
|
1975
|
+
private txCommands;
|
|
1976
|
+
private txLabel;
|
|
1977
|
+
private txCoalesce;
|
|
1978
|
+
/**
|
|
1979
|
+
* Coalescing chain anchor: the tag of the most recently PUSHED entry, valid
|
|
1980
|
+
* only while that entry is still the top of the undo stack with nothing in
|
|
1981
|
+
* between — any undo/redo/clear or non-coalesced push breaks the chain.
|
|
1982
|
+
*/
|
|
1983
|
+
private lastCoalesce;
|
|
1984
|
+
private readonly listeners;
|
|
1985
|
+
private lastDepths;
|
|
1986
|
+
constructor(options?: HistoryKernelOptions);
|
|
1987
|
+
/** Open a transaction; nested calls join the outer one (depth-counted). */
|
|
1988
|
+
begin(label?: string): void;
|
|
1989
|
+
/**
|
|
1990
|
+
* Open a coalescing transaction: consecutive transactions with the same
|
|
1991
|
+
* `key` within `windowMs` MERGE into the prior stack entry (original before
|
|
1992
|
+
* kept, after replaced per slice) — camera drags, timeline play sessions.
|
|
1993
|
+
* Nested inside an open transaction it joins the outer one unchanged.
|
|
1994
|
+
* `now` is injectable for tests; each merge refreshes the window anchor.
|
|
1995
|
+
*/
|
|
1996
|
+
beginCoalesced(key: string, windowMs: number, now?: () => number): void;
|
|
1997
|
+
/**
|
|
1998
|
+
* Record one value-diff command. Inside a transaction it appends to the
|
|
1999
|
+
* pending entry; outside it wraps itself in an implicit single-command
|
|
2000
|
+
* transaction. The kernel freezes and stores what it is given.
|
|
2001
|
+
*/
|
|
2002
|
+
record(slice: string, before: unknown, after: unknown): void;
|
|
2003
|
+
/** Close a transaction; the outermost end() pushes one stack entry. */
|
|
2004
|
+
end(): void;
|
|
2005
|
+
/**
|
|
2006
|
+
* Move the cursor back one entry and return its commands, already inverted
|
|
2007
|
+
* (before/after swapped) and in application order (reverse of recorded).
|
|
2008
|
+
* Returns null at the bottom of the stack. Does not touch any store.
|
|
2009
|
+
*/
|
|
2010
|
+
undo(): readonly HistoryCommand[] | null;
|
|
2011
|
+
/**
|
|
2012
|
+
* Move the cursor forward one entry and return its commands in original
|
|
2013
|
+
* application order. Returns null when there is nothing to redo.
|
|
2014
|
+
*/
|
|
2015
|
+
redo(): readonly HistoryCommand[] | null;
|
|
2016
|
+
peekUndoDepth(): number;
|
|
2017
|
+
peekRedoDepth(): number;
|
|
2018
|
+
/** Empty both stacks and any pending transaction (datasetKey swaps). */
|
|
2019
|
+
clear(): void;
|
|
2020
|
+
/** Subscribe to depth CHANGES (not every call). Returns an unsubscriber. */
|
|
2021
|
+
subscribe(cb: (depths: HistoryDepths) => void): () => void;
|
|
2022
|
+
/**
|
|
2023
|
+
* Push one entry: any new entry clears the redo branch; a coalesce-tagged
|
|
2024
|
+
* entry within the window of a still-anchored same-key predecessor merges
|
|
2025
|
+
* into it instead (original before kept, after replaced per slice); pushing
|
|
2026
|
+
* past the limit evicts the oldest entry.
|
|
2027
|
+
*/
|
|
2028
|
+
private pushEntry;
|
|
2029
|
+
private notify;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/**
|
|
2033
|
+
* §9.1 soft filtering (S9-T01) — pure, engine-free expr evaluation,
|
|
2034
|
+
* validation, compilation, and canonical keying. The instance feeds compiled
|
|
2035
|
+
* filters into the SoftMask kernel (./mask); nothing here touches the store.
|
|
2036
|
+
*
|
|
2037
|
+
* Semantics (orbit-spec.md §9.1):
|
|
2038
|
+
* - `field` addresses `attrs[field]`; the literal field 'id' addresses the
|
|
2039
|
+
* entity id (it wins even when `attrs.id` exists).
|
|
2040
|
+
* - The serializable expr path NEVER throws: junk shapes and §8-hygiene
|
|
2041
|
+
* failures (non-numeric / non-finite values under 'range') simply fail the
|
|
2042
|
+
* item. `validateFilterExpr` is the reporting channel — the instance turns
|
|
2043
|
+
* its findings into validation errors at applyHostUpdate.
|
|
2044
|
+
* - eq/neq/in compare numbers with Object.is semantics EXCEPT that NaN is
|
|
2045
|
+
* never equal to anything (so ±0 are distinct and NaN ≠ NaN); everything
|
|
2046
|
+
* else compares with plain ===. No coercion, ever ('5' never equals 5).
|
|
2047
|
+
* - Function predicates are black boxes: throws are caught and aggregated
|
|
2048
|
+
* into ONE {count, samples} result the caller converts to a single
|
|
2049
|
+
* 'filter-error' diagnostic (§5.1 batching — O(categories), never O(bad
|
|
2050
|
+
* rows)). A throwing predicate FAILS OPEN: the item stays visible, so a
|
|
2051
|
+
* buggy predicate can never blank the graph.
|
|
2052
|
+
* - Structural specs compare by canonical key (`canonicalFilterKey`) so
|
|
2053
|
+
* identity churn with equal structure never re-evaluates; function
|
|
2054
|
+
* predicates key by reference identity via a WeakMap-issued token.
|
|
2055
|
+
*/
|
|
2056
|
+
|
|
2057
|
+
/**
|
|
2058
|
+
* Resolves a filter `field` against an entity: 'id' addresses the entity id;
|
|
2059
|
+
* any other field addresses `attrs[field]` (undefined when attrs are absent).
|
|
2060
|
+
*/
|
|
2061
|
+
declare function resolveFilterField(item: GraphNode<unknown> | AcceptedEdge<unknown>, field: string): unknown;
|
|
2062
|
+
/**
|
|
2063
|
+
* Evaluates one expr for one item via a field resolver. NEVER throws:
|
|
2064
|
+
* malformed shapes and §8-hygiene failures fail the item (return false);
|
|
2065
|
+
* a malformed operand under 'not' also fails (junk never passes by double
|
|
2066
|
+
* negation). `range` requires a finite number value; missing bounds are
|
|
2067
|
+
* unbounded; includeMin/includeMax default true. `is-null` matches null OR
|
|
2068
|
+
* undefined (absent attr).
|
|
2069
|
+
*/
|
|
2070
|
+
declare function evaluateFilterExpr(expr: FilterExpr, resolve: (field: string) => unknown): boolean;
|
|
2071
|
+
/**
|
|
2072
|
+
* Structural checker for serializable exprs: unknown ops and malformed
|
|
2073
|
+
* shapes are reported as `$`-rooted path strings ([] = valid). The instance
|
|
2074
|
+
* converts findings into validation errors at applyHostUpdate (§9.1) —
|
|
2075
|
+
* evaluation itself never throws on the same junk, it just fails the item.
|
|
2076
|
+
*/
|
|
2077
|
+
declare function validateFilterExpr(expr: FilterExpr): string[];
|
|
2078
|
+
/** ONE aggregated throw tally per compiled filter → one 'filter-error'
|
|
2079
|
+
* diagnostic (count + capped samples), never O(bad rows) diagnostics. */
|
|
2080
|
+
interface FilterErrorAggregate {
|
|
2081
|
+
count: number;
|
|
2082
|
+
/** At most DIAGNOSTIC_SAMPLE_CAP offending entity ids. */
|
|
2083
|
+
samples: string[];
|
|
2084
|
+
}
|
|
2085
|
+
interface CompiledFilter<T> {
|
|
2086
|
+
/** True = item passes (stays fully visible); false = item fails the mask. */
|
|
2087
|
+
test(item: T): boolean;
|
|
2088
|
+
/** Live tally of predicate throws (function specs only; the expr path
|
|
2089
|
+
* never throws). The caller converts a nonzero count to a 'filter-error'
|
|
2090
|
+
* diagnostic after the evaluation pass. */
|
|
2091
|
+
readonly errors: FilterErrorAggregate;
|
|
2092
|
+
}
|
|
2093
|
+
/** Compiles the node lane of a FilterSpec (absent selector = pass-all). */
|
|
2094
|
+
declare function compileNodeFilter<N = Record<string, unknown>, E = Record<string, unknown>>(spec: FilterSpec<N, E>): CompiledFilter<GraphNode<N>>;
|
|
2095
|
+
/** Compiles the edge lane of a FilterSpec (absent selector = pass-all). */
|
|
2096
|
+
declare function compileEdgeFilter<N = Record<string, unknown>, E = Record<string, unknown>>(spec: FilterSpec<N, E>): CompiledFilter<AcceptedEdge<E>>;
|
|
2097
|
+
/**
|
|
2098
|
+
* Canonical structural key for a FilterSpec or FilterExpr: object keys are
|
|
2099
|
+
* sorted, arrays preserve order, undefined-valued keys are omitted, and
|
|
2100
|
+
* function predicates map to a unique reference-identity token. Two inputs
|
|
2101
|
+
* with equal keys are §9.1-equivalent — the instance skips re-evaluation
|
|
2102
|
+
* when the key of an incoming filter matches the active one (identity churn
|
|
2103
|
+
* with equal structure never re-evaluates; swapping a function reference
|
|
2104
|
+
* always does).
|
|
2105
|
+
*/
|
|
2106
|
+
declare function canonicalFilterKey(specOrExpr: unknown): string;
|
|
2107
|
+
|
|
2108
|
+
/**
|
|
2109
|
+
* §9.1 soft-mask kernel (S9-T02/T03) — failure COUNTERS, never Sets and
|
|
2110
|
+
* never per-source bit positions (so there is no 32-dimension cap).
|
|
2111
|
+
*
|
|
2112
|
+
* The mask owns four Uint16 counter lanes: hideFailures and dimFailures for
|
|
2113
|
+
* nodes AND edges. Every acquired {@link MaskSource} contributes +1 to a
|
|
2114
|
+
* lane counter for each slot it currently fails and -1 when that slot
|
|
2115
|
+
* re-enters. A slot is visible iff hideFailures === 0 and dimmed iff visible
|
|
2116
|
+
* AND dimFailures > 0 (spec §9.1 mask mechanics).
|
|
2117
|
+
*
|
|
2118
|
+
* Delta discipline: each source keeps its previous failing membership as
|
|
2119
|
+
* per-source Uint8 flag columns plus a dense slot list, so replacing a
|
|
2120
|
+
* membership touches counters ONLY for slots whose membership actually
|
|
2121
|
+
* changed (O(1) detection per presented slot). Counter transitions across
|
|
2122
|
+
* zero append the slot to reusable dirty lists; {@link SoftMask.drainDirty}
|
|
2123
|
+
* compares against the state at the previous drain, so a fail-then-restore
|
|
2124
|
+
* inside one drain period nets to no emission.
|
|
2125
|
+
*
|
|
2126
|
+
* {@link SoftMask.applyNodeCascadeToEdges} implements §9's SINGLE edge-
|
|
2127
|
+
* survival rule over the mask lane: an edge hide-fails iff EITHER endpoint
|
|
2128
|
+
* hide-fails (equivalently: survives iff BOTH endpoints survive —
|
|
2129
|
+
* scope.ts `cascadeEdges` is the hard-scope variant of the same rule). It is
|
|
2130
|
+
* fed through one dedicated internal cascade source from the node hide lane:
|
|
2131
|
+
* O(E) recompute per call, but only edges whose state changed produce
|
|
2132
|
+
* counter deltas.
|
|
2133
|
+
*
|
|
2134
|
+
* Overflow guard: increments clamp at 0xFFFF and latch a one-time
|
|
2135
|
+
* {@link SoftMask.overflowed} flag the caller reports (an implementation may
|
|
2136
|
+
* widen to Uint32 without changing semantics, §9.1). Debug builds assert
|
|
2137
|
+
* balanced increments/decrements whenever a clear()/release() leaves zero
|
|
2138
|
+
* held memberships.
|
|
2139
|
+
*/
|
|
2140
|
+
/** Default muted alpha for dimmed entities (§9.1 'dim' mode). */
|
|
2141
|
+
declare const DIM_ALPHA_DEFAULT = 0.15;
|
|
2142
|
+
/**
|
|
2143
|
+
* A handle contributing failure memberships to the mask. Setting a lane
|
|
2144
|
+
* REPLACES that lane's previous membership for this source (the kernel
|
|
2145
|
+
* applies only the delta). `null` = empty membership for that lane; an
|
|
2146
|
+
* omitted `dimIdx` leaves the dim lane untouched.
|
|
2147
|
+
*/
|
|
2148
|
+
interface MaskSource {
|
|
2149
|
+
readonly name: string;
|
|
2150
|
+
setNodeFailures(hideIdx: Iterable<number> | null, dimIdx?: Iterable<number> | null): void;
|
|
2151
|
+
setEdgeFailures(hideIdx: Iterable<number> | null, dimIdx?: Iterable<number> | null): void;
|
|
2152
|
+
/** Empties all four lane memberships (the source stays usable). */
|
|
2153
|
+
clear(): void;
|
|
2154
|
+
/** clear() + permanently retires the handle (further set/clear throws;
|
|
2155
|
+
* release() itself is idempotent). */
|
|
2156
|
+
release(): void;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* One drain payload. The four index arrays are REUSED across drains — copy
|
|
2160
|
+
* before the next drainDirty() call if you need to keep them.
|
|
2161
|
+
*/
|
|
2162
|
+
interface MaskDrain {
|
|
2163
|
+
/** Node slots whose hide-visibility (hideFailures 0 ↔ nonzero) flipped
|
|
2164
|
+
* since the previous drain. */
|
|
2165
|
+
nodes: readonly number[];
|
|
2166
|
+
/** Edge slots whose hide-visibility flipped since the previous drain. */
|
|
2167
|
+
edges: readonly number[];
|
|
2168
|
+
/** Node slots whose dim state (dimFailures 0 ↔ nonzero) flipped — the
|
|
2169
|
+
* alpha lane. Consumers recompute alpha over `nodes ∪ nodesAlpha` (a hide
|
|
2170
|
+
* flip also changes the effective alpha; a slot may appear in both). */
|
|
2171
|
+
nodesAlpha: readonly number[];
|
|
2172
|
+
/** Edge slots whose dim state flipped. */
|
|
2173
|
+
edgesAlpha: readonly number[];
|
|
2174
|
+
nodeVisibleCount: number;
|
|
2175
|
+
edgeVisibleCount: number;
|
|
2176
|
+
}
|
|
2177
|
+
declare class SoftMask {
|
|
2178
|
+
private nodeCap;
|
|
2179
|
+
private edgeCap;
|
|
2180
|
+
private readonly nodeHideLane;
|
|
2181
|
+
private readonly nodeDimLane;
|
|
2182
|
+
private readonly edgeHideLane;
|
|
2183
|
+
private readonly edgeDimLane;
|
|
2184
|
+
private readonly sources;
|
|
2185
|
+
/** Dedicated internal source implementing the §9 node→edge cascade. */
|
|
2186
|
+
private cascadeSource;
|
|
2187
|
+
private overflowedFlag;
|
|
2188
|
+
/** Total memberships currently held across all sources and lanes. */
|
|
2189
|
+
private totalHeld;
|
|
2190
|
+
constructor(nodeCapacity: number, edgeCapacity: number);
|
|
2191
|
+
get nodeCapacity(): number;
|
|
2192
|
+
get edgeCapacity(): number;
|
|
2193
|
+
/** One-time latch: some counter hit 0xFFFF and an increment was dropped.
|
|
2194
|
+
* Counts may drift afterwards; the caller reports it (§9.1 overflow guard). */
|
|
2195
|
+
get overflowed(): boolean;
|
|
2196
|
+
get nodeHideFailures(): Uint16Array;
|
|
2197
|
+
get nodeDimFailures(): Uint16Array;
|
|
2198
|
+
get edgeHideFailures(): Uint16Array;
|
|
2199
|
+
get edgeDimFailures(): Uint16Array;
|
|
2200
|
+
/**
|
|
2201
|
+
* Grows capacities for structure changes (existing slot state is
|
|
2202
|
+
* preserved; new slots start fully visible). Capacities never shrink —
|
|
2203
|
+
* a smaller value is a no-op for that dimension.
|
|
2204
|
+
*/
|
|
2205
|
+
grow(nodeCapacity: number, edgeCapacity: number): void;
|
|
2206
|
+
/** Registers a new failure source. No cap on source count (§9.1). */
|
|
2207
|
+
acquire(name: string): MaskSource;
|
|
2208
|
+
/**
|
|
2209
|
+
* §9 edge cascade over the mask lane: recomputes, from the CURRENT node
|
|
2210
|
+
* hide lane, the set of edges with at least one hidden endpoint, and feeds
|
|
2211
|
+
* it to the dedicated internal cascade source (edge hide lane only).
|
|
2212
|
+
* `links` is the flat `[src0, tgt0, src1, tgt1, …]` node-slot pair buffer
|
|
2213
|
+
* (§7.1 CSR input shape); edge slot i has endpoints at links[2i]/[2i+1].
|
|
2214
|
+
* O(E) scan per call — typically once per drain — but only edges whose
|
|
2215
|
+
* cascade state changed produce counter deltas (and thus dirty entries).
|
|
2216
|
+
* Edges beyond `links.length / 2` are treated as having no hidden
|
|
2217
|
+
* endpoint (their previous cascade contribution, if any, is removed).
|
|
2218
|
+
*/
|
|
2219
|
+
applyNodeCascadeToEdges(links: Uint32Array): void;
|
|
2220
|
+
/**
|
|
2221
|
+
* Drains the zero-crossing dirty lists accumulated since the previous
|
|
2222
|
+
* drain. Only NET flips are emitted (state compared against the previous
|
|
2223
|
+
* drain). The returned index arrays are reused by the next drain.
|
|
2224
|
+
*/
|
|
2225
|
+
drainDirty(): MaskDrain;
|
|
2226
|
+
visibleNodeCount(): number;
|
|
2227
|
+
visibleEdgeCount(): number;
|
|
2228
|
+
/** Visible iff hideFailures === 0 (out-of-range slots read not-visible). */
|
|
2229
|
+
isNodeVisible(index: number): boolean;
|
|
2230
|
+
isEdgeVisible(index: number): boolean;
|
|
2231
|
+
/** Dimmed iff visible AND dimFailures > 0 (§9.1). */
|
|
2232
|
+
isNodeDimmed(index: number): boolean;
|
|
2233
|
+
isEdgeDimmed(index: number): boolean;
|
|
2234
|
+
/** 1 (fully visible) | dimAlpha (dimmed) | 0 (hidden). */
|
|
2235
|
+
nodeAlpha(index: number, dimAlpha?: number): number;
|
|
2236
|
+
edgeAlpha(index: number, dimAlpha?: number): number;
|
|
2237
|
+
/**
|
|
2238
|
+
* Replaces one source-lane membership with `incoming` (null = empty),
|
|
2239
|
+
* applying counter deltas ONLY for slots whose membership changed.
|
|
2240
|
+
* Incoming slots are validated up front so a RangeError never leaves the
|
|
2241
|
+
* membership partially applied; duplicates within `incoming` count once.
|
|
2242
|
+
*/
|
|
2243
|
+
private applyMembership;
|
|
2244
|
+
private increment;
|
|
2245
|
+
private decrement;
|
|
2246
|
+
/** Records the first zero-crossing of a slot per drain period, remembering
|
|
2247
|
+
* whether the counter was zero at the previous drain. */
|
|
2248
|
+
private markDirty;
|
|
2249
|
+
private drainLane;
|
|
2250
|
+
private clearSource;
|
|
2251
|
+
/** Debug balanced-increment assert: whenever no source holds any
|
|
2252
|
+
* membership, every counter must read zero (skipped once overflowed —
|
|
2253
|
+
* clamped increments legitimately drift the books). */
|
|
2254
|
+
private assertBalancedIfIdle;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
/**
|
|
2258
|
+
* §16.6 crossfilter — typed-column engine (v0.7 node-dimension subset).
|
|
2259
|
+
*
|
|
2260
|
+
* `TypedColumnCrossfilter` is the columnar backend the instance wraps into the
|
|
2261
|
+
* public `CrossfilterSession`. Design (crossfilter.js lineage):
|
|
2262
|
+
*
|
|
2263
|
+
* - **Parse once.** `build()` extracts every dimension exactly once into typed
|
|
2264
|
+
* columns (numeric/temporal → `Float64Array` epoch-ms/values, categorical →
|
|
2265
|
+
* dictionary codes). §8 hygiene: non-finite numerics, unparseable temporals,
|
|
2266
|
+
* and non-string/non-finite categorical values are excluded from the
|
|
2267
|
+
* dimension (tracked per dimension in `excludedRows`; the slot is marked
|
|
2268
|
+
* invalid). Temporal parsing: numbers are epoch ms verbatim; strings go
|
|
2269
|
+
* through `Date.parse` (ES2022 parses `'YYYY-MM-DD'` as UTC midnight);
|
|
2270
|
+
* `Date` instances use `getTime()`.
|
|
2271
|
+
* - **O(Δ) brushes.** Each range dimension keeps a one-time argsorted
|
|
2272
|
+
* permutation. A brush move performs two binary searches and walks ONLY the
|
|
2273
|
+
* symmetric difference between the old and new in-range windows; categorical
|
|
2274
|
+
* brushes walk only the per-code slot lists whose excluded flag changed. The
|
|
2275
|
+
* brush path never re-sorts. A per-slot, per-dimension pass flag plus a
|
|
2276
|
+
* global per-slot failure counter make a row selection-visible iff it fails
|
|
2277
|
+
* zero dimensions. Hygiene-excluded rows fail any non-null brush on that
|
|
2278
|
+
* dimension (they cannot be in range / in a category) and pass a null brush.
|
|
2279
|
+
* - **Deltas out.** `setBrush` returns the slots whose overall visibility
|
|
2280
|
+
* flipped (`hidden`/`shown`) for the instance's mask source.
|
|
2281
|
+
* - **Lazy dual layer.** `summarize()` returns immutable summaries whose
|
|
2282
|
+
* `filtered` layer counts rows passing every OTHER dimension's brush plus an
|
|
2283
|
+
* external node-mask predicate (`setExternalMask`). v0.7 recomputes a dirty
|
|
2284
|
+
* dimension's filtered layer lazily per `summarize()` call — O(rows) per
|
|
2285
|
+
* dirty summarize, documented and acceptable at this tier (§17 fixtures own
|
|
2286
|
+
* the perf claims; the O(Δ) guarantee covers the brush/visibility path).
|
|
2287
|
+
* - **Revisions & notify.** `selectionRevision` starts at 0 and advances
|
|
2288
|
+
* exactly once per observable `setBrush` (a brush state change is observable
|
|
2289
|
+
* via `getBrush` even when no row flips). v0.7 is synchronous, so
|
|
2290
|
+
* latest-call-wins degenerates to "every call applies immediately in call
|
|
2291
|
+
* order". Subscribers fire once per observable change after state is
|
|
2292
|
+
* consistent; synchronous re-entrancy (a subscriber mutating the engine) is
|
|
2293
|
+
* coalesced into one trailing notification pass. Model updates
|
|
2294
|
+
* (`appendRows`/`replaceAll`) keep the current `selectionRevision` (§16.6)
|
|
2295
|
+
* but do notify. External-mask changes notify (summaries changed) without
|
|
2296
|
+
* advancing the selection revision.
|
|
2297
|
+
* - **Incremental append.** `appendRows` extends columns/codes in place and
|
|
2298
|
+
* merges the pre-sorted old permutation with the sorted new block
|
|
2299
|
+
* (permutation merge — never a full re-argsort); bins extend incrementally
|
|
2300
|
+
* unless the domain grew (then a re-bin, still sort-free). Brushes persist
|
|
2301
|
+
* by key and are re-applied to the NEW slots only; the returned delta covers
|
|
2302
|
+
* only new slots (`shown` = new passing, `hidden` = new failing).
|
|
2303
|
+
* `replaceAll` rebuilds columns but preserves brushes by dimension key,
|
|
2304
|
+
* re-applying them as one combined delta against an all-visible baseline of
|
|
2305
|
+
* the new roster (`shown` is always empty). `replaceAll` clears the external
|
|
2306
|
+
* mask (slot indices changed meaning); `appendRows` keeps it and treats new
|
|
2307
|
+
* slots as passing until the instance re-supplies it.
|
|
2308
|
+
*/
|
|
2309
|
+
|
|
2310
|
+
/** Default histogram bin count for numeric/temporal dimensions (§16.6). */
|
|
2311
|
+
declare const DEFAULT_BIN_COUNT = 24;
|
|
2312
|
+
/** Slots whose overall selection-visibility flipped in one operation. */
|
|
2313
|
+
interface BrushDelta {
|
|
2314
|
+
/** Slots that flipped visible → hidden. */
|
|
2315
|
+
hidden: number[];
|
|
2316
|
+
/** Slots that flipped hidden → visible. */
|
|
2317
|
+
shown: number[];
|
|
2318
|
+
}
|
|
2319
|
+
/** Test instrumentation counters (see crossfilter.test.ts O(Δ) evidence). */
|
|
2320
|
+
interface CrossfilterStats {
|
|
2321
|
+
/** Slots touched by brush delta walks (the O(Δ) loop). */
|
|
2322
|
+
slotsWalked: number;
|
|
2323
|
+
/** Full-column argsorts (build/replaceAll only — never append or brush). */
|
|
2324
|
+
fullSorts: number;
|
|
2325
|
+
/** Permutation merges performed by appendRows. */
|
|
2326
|
+
permutationMerges: number;
|
|
2327
|
+
}
|
|
2328
|
+
declare class TypedColumnCrossfilter<N = Record<string, unknown>> {
|
|
2329
|
+
/** Test instrumentation; see CrossfilterStats. Reset with resetStats(). */
|
|
2330
|
+
readonly stats: CrossfilterStats;
|
|
2331
|
+
private dims;
|
|
2332
|
+
private byKey;
|
|
2333
|
+
private specs;
|
|
2334
|
+
private n;
|
|
2335
|
+
/** Per-slot count of dimensions the slot currently fails. */
|
|
2336
|
+
private failCount;
|
|
2337
|
+
private externalMask;
|
|
2338
|
+
private revision;
|
|
2339
|
+
private readonly subscribers;
|
|
2340
|
+
private built;
|
|
2341
|
+
private disposed;
|
|
2342
|
+
private notifying;
|
|
2343
|
+
private renotify;
|
|
2344
|
+
/** Monotonic from 0; advances exactly once per observable setBrush change. */
|
|
2345
|
+
get selectionRevision(): number;
|
|
2346
|
+
resetStats(): void;
|
|
2347
|
+
/**
|
|
2348
|
+
* (Re)initialize columns from scratch. Clears all brushes and the external
|
|
2349
|
+
* mask (use replaceAll to rebuild while preserving brushes by key). Does not
|
|
2350
|
+
* notify and does not touch selectionRevision.
|
|
2351
|
+
*/
|
|
2352
|
+
build(nodes: readonly GraphNode<N>[], specs: readonly DimensionSpec<N>[]): void;
|
|
2353
|
+
rowCount(): number;
|
|
2354
|
+
getBrush(key: string): BrushState;
|
|
2355
|
+
isSlotVisible(slot: number): boolean;
|
|
2356
|
+
/** Fresh array of selection-visible slots, ascending. */
|
|
2357
|
+
visibleSlots(): number[];
|
|
2358
|
+
/**
|
|
2359
|
+
* Apply a brush (latest-call-wins: v0.7 is synchronous, so each call applies
|
|
2360
|
+
* immediately in call order). Returns the slots whose overall
|
|
2361
|
+
* selection-visibility flipped. A no-op (deep-equal brush) returns empty
|
|
2362
|
+
* deltas without advancing selectionRevision or notifying.
|
|
2363
|
+
*/
|
|
2364
|
+
setBrush(key: string, brush: BrushState): BrushDelta;
|
|
2365
|
+
/**
|
|
2366
|
+
* External node-mask predicate for the joint "filtered" second layer (the
|
|
2367
|
+
* instance wires the §9.1 filter-prop node mask in). Affects summaries only,
|
|
2368
|
+
* never selection visibility. Length must equal rowCount(). Notifies on
|
|
2369
|
+
* observable change; does NOT advance selectionRevision.
|
|
2370
|
+
*/
|
|
2371
|
+
setExternalMask(passSlots: Uint8Array | null): void;
|
|
2372
|
+
/**
|
|
2373
|
+
* Immutable summary. The filtered layer is recomputed lazily when dirty —
|
|
2374
|
+
* O(rows) per dirty summarize (v0.7 tier; see module doc). Returned objects
|
|
2375
|
+
* are frozen and never mutated by later operations.
|
|
2376
|
+
*/
|
|
2377
|
+
summarize(key: string): DimensionSummary;
|
|
2378
|
+
/**
|
|
2379
|
+
* Incrementally extend columns with new rows (S9-T09 subset): no full
|
|
2380
|
+
* rebuild, no full re-argsort — the pre-sorted old permutation merges with
|
|
2381
|
+
* the sorted new block. Brushes stay by key and are applied to the NEW slots
|
|
2382
|
+
* only; the returned delta covers only new slots. Keeps selectionRevision;
|
|
2383
|
+
* notifies when rows were appended.
|
|
2384
|
+
*/
|
|
2385
|
+
appendRows(newNodes: readonly GraphNode<N>[]): BrushDelta;
|
|
2386
|
+
/**
|
|
2387
|
+
* Full rebuild from a new roster, PRESERVING brushes by dimension key and
|
|
2388
|
+
* re-applying them as one combined delta against an all-visible baseline of
|
|
2389
|
+
* the new roster (shown is always empty). Clears the external mask. Keeps
|
|
2390
|
+
* selectionRevision; notifies once.
|
|
2391
|
+
*/
|
|
2392
|
+
replaceAll(nodes: readonly GraphNode<N>[]): BrushDelta;
|
|
2393
|
+
/** Fires once per observable selection/summary change, state consistent. */
|
|
2394
|
+
subscribe(cb: () => void): () => void;
|
|
2395
|
+
/** Idempotent; every other method throws afterwards. */
|
|
2396
|
+
dispose(): void;
|
|
2397
|
+
private ensureLive;
|
|
2398
|
+
private ensureBuilt;
|
|
2399
|
+
private dim;
|
|
2400
|
+
private buildDims;
|
|
2401
|
+
private buildRangeDim;
|
|
2402
|
+
private buildCatDim;
|
|
2403
|
+
/** Recompute domain, bin edges, per-slot bin index, and totals. Sort-free. */
|
|
2404
|
+
private rebinRange;
|
|
2405
|
+
private binIndex;
|
|
2406
|
+
private flip;
|
|
2407
|
+
private walkSorted;
|
|
2408
|
+
private walkList;
|
|
2409
|
+
private applyRangeTransition;
|
|
2410
|
+
private applyCatTransition;
|
|
2411
|
+
private appendRange;
|
|
2412
|
+
private appendCat;
|
|
2413
|
+
private recomputeFiltered;
|
|
2414
|
+
/** One callback pass per observable change; synchronous re-entrancy coalesces. */
|
|
2415
|
+
private notify;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
/**
|
|
2419
|
+
* §11 scales & domains (S10-T01/T07) — canonical scale keying, default
|
|
2420
|
+
* palettes, sRGB color interpolation, domain-state machinery, and stable
|
|
2421
|
+
* categorical assignment. Pure and engine-free; the instance/projection
|
|
2422
|
+
* layers consume these primitives when a styling channel carries a `Scale`.
|
|
2423
|
+
*
|
|
2424
|
+
* Semantics (orbit-spec.md §8/§11):
|
|
2425
|
+
* - Scales are plain descriptors compared by CANONICAL STRUCTURAL VALUE —
|
|
2426
|
+
* equal inline literals produce equal keys and never reproject. A function
|
|
2427
|
+
* `by` keys by reference identity (WeakMap token), never by source text.
|
|
2428
|
+
* - Domains default to the whole dataset revision and stay FROZEN across
|
|
2429
|
+
* masking/brushing/isolation ('dataset' scope): the same metric value never
|
|
2430
|
+
* changes visual meaning because a user brushed. 'hard-scope'/'visible' are
|
|
2431
|
+
* explicit opt-ins that recompute when the caller's scope generation bumps;
|
|
2432
|
+
* streaming 'expand' permits monotonic domain growth on recompute.
|
|
2433
|
+
* - Explicit numeric domains always win verbatim (never computed, cached, or
|
|
2434
|
+
* unioned).
|
|
2435
|
+
* - Categorical values declared in `domain` take their declared position;
|
|
2436
|
+
* out-of-domain values take a stable fnv-1a hash slot — NEVER first-seen
|
|
2437
|
+
* order, so arrival order can never recolor a category.
|
|
2438
|
+
* - §8 numeric hygiene: null/non-finite metric values resolve to `null`
|
|
2439
|
+
* (caller falls back to the default style) and are excluded from domains.
|
|
2440
|
+
*/
|
|
2441
|
+
|
|
2442
|
+
type SequentialScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
|
|
2443
|
+
kind: 'sequential';
|
|
2444
|
+
}>;
|
|
2445
|
+
type CategoricalScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
|
|
2446
|
+
kind: 'categorical';
|
|
2447
|
+
}>;
|
|
2448
|
+
type DivergingScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
|
|
2449
|
+
kind: 'diverging';
|
|
2450
|
+
}>;
|
|
2451
|
+
/**
|
|
2452
|
+
* Canonical structural key for a Scale descriptor: object keys sorted, array
|
|
2453
|
+
* order preserved, undefined-valued keys omitted, and a function `by` mapped
|
|
2454
|
+
* to a unique reference-identity token (same WeakMap approach — and canonical
|
|
2455
|
+
* grammar — as `canonicalFilterKey`). Two scales with equal keys are
|
|
2456
|
+
* §11-equivalent: equal inline literals MUST and DO produce equal keys, so
|
|
2457
|
+
* identity churn never reprojects; swapping a function reference always does.
|
|
2458
|
+
*/
|
|
2459
|
+
declare function canonicalScaleKey<T, N>(scale: Scale<T, N>): string;
|
|
2460
|
+
/** 12 brand-neutral categorical hues distinguishable on light AND dark. */
|
|
2461
|
+
declare const CATEGORICAL_PALETTE: readonly string[];
|
|
2462
|
+
/** Default sequential ramp endpoints (low → high). */
|
|
2463
|
+
declare const SEQUENTIAL_RANGE_DEFAULT: readonly [string, string];
|
|
2464
|
+
/** Default diverging stops (low → mid → high). */
|
|
2465
|
+
declare const DIVERGING_RANGE_DEFAULT: readonly [string, string, string];
|
|
2466
|
+
/**
|
|
2467
|
+
* Interpolates between two CSS color strings in sRGB (component-wise,
|
|
2468
|
+
* including alpha) and returns an `rgba(r, g, b, a)` string. `t` is clamped
|
|
2469
|
+
* to [0,1] (non-finite t → 0). Unparseable endpoints fall back to the
|
|
2470
|
+
* projection lane's neutral gray — never a throw, never NaN output.
|
|
2471
|
+
*/
|
|
2472
|
+
declare function interpolateColor(a: string, b: string, t: number): string;
|
|
2473
|
+
/**
|
|
2474
|
+
* Sequential color: maps `value` across `domain` onto the two-stop ramp.
|
|
2475
|
+
* Returns null (caller renders the default style, §8 hygiene) when the value
|
|
2476
|
+
* is null/non-finite or the domain is null.
|
|
2477
|
+
*/
|
|
2478
|
+
declare function sequentialColor<N = Record<string, unknown>>(scale: SequentialScale<string, N>, value: number | null, domain: readonly [number, number] | null): string | null;
|
|
2479
|
+
/**
|
|
2480
|
+
* Diverging color: `mid` maps to the middle stop exactly; each half
|
|
2481
|
+
* interpolates independently ([domain[0]..mid] over range[0..1],
|
|
2482
|
+
* [mid..domain[1]] over range[1..2]). A degenerate half collapses to the
|
|
2483
|
+
* middle stop. Null/non-finite value or null domain → null.
|
|
2484
|
+
*/
|
|
2485
|
+
declare function divergingColor<N = Record<string, unknown>>(scale: DivergingScale<string, N>, value: number | null, domain: readonly [number, number] | null): string | null;
|
|
2486
|
+
/**
|
|
2487
|
+
* Sequential size: linear map of `value` across `domain` onto [lo,hi],
|
|
2488
|
+
* clamped at the range endpoints. Degenerate domains yield the range
|
|
2489
|
+
* midpoint; null/non-finite values and null domains yield null (default
|
|
2490
|
+
* style — NaN is never handed to a size buffer, §8).
|
|
2491
|
+
*/
|
|
2492
|
+
declare function sequentialSize(range: readonly [number, number], value: number | null, domain: readonly [number, number] | null): number | null;
|
|
2493
|
+
/**
|
|
2494
|
+
* [min,max] over the finite numbers in `values` (§8 hygiene: null and
|
|
2495
|
+
* non-finite entries are excluded), or null when nothing qualifies. A single
|
|
2496
|
+
* qualifying value yields a degenerate [v,v] domain.
|
|
2497
|
+
*/
|
|
2498
|
+
declare function computeNumericDomain(values: Iterable<number | null>): [number, number] | null;
|
|
2499
|
+
interface ResolveDomainArgs {
|
|
2500
|
+
/** Cache key — `canonicalScaleKey(scale)` (the metric name is part of the
|
|
2501
|
+
* descriptor, so the key already discriminates by metric). Opaque here. */
|
|
2502
|
+
key: string;
|
|
2503
|
+
/** Explicit caller domain — returned VERBATIM; never computed, cached, or
|
|
2504
|
+
* expand-unioned (§11: explicit always wins). */
|
|
2505
|
+
explicit?: readonly [number, number] | undefined;
|
|
2506
|
+
/** Defaults: scope 'dataset', streaming 'freeze-per-revision'. */
|
|
2507
|
+
policy?: DomainPolicy | undefined;
|
|
2508
|
+
/** The dataset revision the caller is resolving against. */
|
|
2509
|
+
datasetRevision: number | string;
|
|
2510
|
+
/** Caller-owned generation counter for 'hard-scope'/'visible' scopes
|
|
2511
|
+
* (bump = recompute). IGNORED under 'dataset' scope — masking/brushing
|
|
2512
|
+
* must never change what a color means. */
|
|
2513
|
+
scopeGeneration?: number | undefined;
|
|
2514
|
+
/** Source lineage this resolve belongs to (F11-02: datasetKey + source
|
|
2515
|
+
* revision). `streaming: 'expand'` unions only WITHIN one lineage — a
|
|
2516
|
+
* source replacement starts fresh instead of unioning dead extrema.
|
|
2517
|
+
* Callers that omit it keep the legacy always-union behavior. */
|
|
2518
|
+
lineage?: string | undefined;
|
|
2519
|
+
/** Domain producer (typically wraps computeNumericDomain over the metric
|
|
2520
|
+
* column). Called at most once per freeze coordinate. */
|
|
2521
|
+
compute: () => readonly [number, number] | null;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Per-instance domain freezer keyed by canonical scale key (§11).
|
|
2525
|
+
*
|
|
2526
|
+
* FREEZE-PER-REVISION: under scope 'dataset' the domain is computed ONCE per
|
|
2527
|
+
* {key, datasetRevision} — repeat resolves return the frozen value without
|
|
2528
|
+
* calling `compute`, no matter how the underlying data was masked or brushed
|
|
2529
|
+
* in between. 'hard-scope'/'visible' additionally recompute when the caller's
|
|
2530
|
+
* scopeGeneration bumps. Streaming 'expand' turns every recompute into a
|
|
2531
|
+
* monotonic union with the previous domain (the domain only ever grows);
|
|
2532
|
+
* 'freeze-per-revision' replaces it. Explicit domains bypass the store.
|
|
2533
|
+
*/
|
|
2534
|
+
declare class DomainStore {
|
|
2535
|
+
private readonly entries;
|
|
2536
|
+
resolveDomain(args: ResolveDomainArgs): readonly [number, number] | null;
|
|
2537
|
+
/**
|
|
2538
|
+
* Dataset replace: `revision` is the revision now current — every entry
|
|
2539
|
+
* frozen for any OTHER revision is dropped (including expand-union lineage,
|
|
2540
|
+
* so a replaced dataset never unions against dead data). Entries already
|
|
2541
|
+
* at `revision` survive.
|
|
2542
|
+
*/
|
|
2543
|
+
invalidateDataset(revision: number | string): void;
|
|
2544
|
+
/** Full reset (dataset identity change / instance teardown). */
|
|
2545
|
+
clear(): void;
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Palette slot for a categorical value: values declared in `domain` take
|
|
2549
|
+
* their declared position (mod palette length — fixed order → stable colors);
|
|
2550
|
+
* out-of-domain values take fnv-1a(value) mod palette length — stable across
|
|
2551
|
+
* sessions and arrival orders, NEVER first-seen order. Returns -1 for an
|
|
2552
|
+
* empty palette.
|
|
2553
|
+
*/
|
|
2554
|
+
declare function categoricalIndex(domain: readonly string[] | undefined, palette: readonly unknown[], value: string): number;
|
|
2555
|
+
/**
|
|
2556
|
+
* Stable legend row order: declared `domain` values FIRST in declared order
|
|
2557
|
+
* (including currently-empty categories), then extra seen values sorted
|
|
2558
|
+
* lexicographically. Duplicates (within `seen` or already declared) collapse.
|
|
2559
|
+
*/
|
|
2560
|
+
declare function categoricalRows(domain: readonly string[] | undefined, seen: Iterable<string>): string[];
|
|
2561
|
+
|
|
2562
|
+
/**
|
|
2563
|
+
* DEF2 capability-policy module (spec §13; plan S1-T08/S4-T05).
|
|
2564
|
+
*
|
|
2565
|
+
* §13 (normative): for capabilities where the core owns a fallback path, the
|
|
2566
|
+
* core selects native-vs-fallback ONCE at mount from the engine's declared
|
|
2567
|
+
* `capabilities` record — never by sniffing method presence — and unsupported
|
|
2568
|
+
* *requested* props degrade loudly (a §5.1 diagnostic), never as silent no-ops.
|
|
2569
|
+
*
|
|
2570
|
+
* This module is the single place those decisions are made:
|
|
2571
|
+
* - {@link resolveEnginePolicy} turns the capability record + the host's
|
|
2572
|
+
* requested features into one frozen {@link EnginePolicy} at mount.
|
|
2573
|
+
* - {@link assertCapabilityMethodParity} is the mount-time dev-mode record
|
|
2574
|
+
* vs. method-surface assertion, restricted to honestly checkable pairs.
|
|
2575
|
+
* - {@link normalizeCommitForCapabilities} strips commit payload an engine
|
|
2576
|
+
* declared it cannot honor, so incapable adapters never see it.
|
|
2577
|
+
*/
|
|
2578
|
+
|
|
2579
|
+
/** One loud degradation: a feature the host requested that the mounted engine
|
|
2580
|
+
* does not declare. Feeds the §5.1 dev diagnostic at mount. */
|
|
2581
|
+
interface EnginePolicyDegradation {
|
|
2582
|
+
readonly feature: string;
|
|
2583
|
+
readonly reason: string;
|
|
2584
|
+
}
|
|
2585
|
+
/** Host-requested capability-gated features, gathered from mount-time props
|
|
2586
|
+
* (`edgeArrows` prop → edgeArrows; image-bearing nodeStyle/atlas → images). */
|
|
2587
|
+
interface RequestedEngineFeatures {
|
|
2588
|
+
edgeArrows?: boolean;
|
|
2589
|
+
images?: boolean;
|
|
2590
|
+
/** §16.3 stage-4: a `clusters` spec is active this session. */
|
|
2591
|
+
clusters?: boolean;
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* The frozen mount-time native-vs-fallback record (§13). Evaluated exactly
|
|
2595
|
+
* once per mount from `EngineCapabilities` and never revisited — capability
|
|
2596
|
+
* records are static declarations fixed at engine construction, so the policy
|
|
2597
|
+
* must not drift even if a caller mutates the input record afterwards.
|
|
2598
|
+
*/
|
|
2599
|
+
interface EnginePolicy {
|
|
2600
|
+
/** §16.12 arrowheads: engine-drawn, or the prop is inert (+ dev warning). */
|
|
2601
|
+
readonly edgeArrows: 'native' | 'inert';
|
|
2602
|
+
/** §8 image sprites: atlas-backed, or the placeholder glyph with refs
|
|
2603
|
+
* retained for a future compatible engine. */
|
|
2604
|
+
readonly images: 'native' | 'placeholder';
|
|
2605
|
+
/** §13 link hover/click: engine events, or the core's CPU grid fallback. */
|
|
2606
|
+
readonly linkPicking: 'native' | 'cpu-fallback';
|
|
2607
|
+
/** §16.3 stage-4 cluster force: engine-applied, or inert (membership,
|
|
2608
|
+
* labels, and centroids are core-owned and unaffected). */
|
|
2609
|
+
readonly clusterForce: 'native' | 'inert';
|
|
2610
|
+
/** Channels eligible for ranged (partial) uploads; empty = full replaces. */
|
|
2611
|
+
readonly rangedChannels: ReadonlySet<EngineBufferChannel>;
|
|
2612
|
+
/** Exactly one entry per REQUESTED-but-unsupported feature; requested and
|
|
2613
|
+
* supported — or simply never requested — contributes nothing. */
|
|
2614
|
+
readonly degradations: readonly EnginePolicyDegradation[];
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Resolve the mount-time engine policy from the declared capability record.
|
|
2618
|
+
*
|
|
2619
|
+
* Decisions come from `capabilities` ONLY — method sniffing is forbidden by
|
|
2620
|
+
* §13. The result is deep-frozen and holds a defensive copy of
|
|
2621
|
+
* `rangeUpdates`, so mutating the input record afterwards changes nothing.
|
|
2622
|
+
* A degradation entry exists only for features the host actually requested
|
|
2623
|
+
* that the engine does not declare; unrequested gaps stay silent.
|
|
2624
|
+
*/
|
|
2625
|
+
declare function resolveEnginePolicy(capabilities: EngineCapabilities, requested: RequestedEngineFeatures): EnginePolicy;
|
|
2626
|
+
/**
|
|
2627
|
+
* Mount-time dev-mode assertion: does the declared capability record agree
|
|
2628
|
+
* with the engine's method surface? Returns a list of human-readable
|
|
2629
|
+
* mismatches (empty = consistent).
|
|
2630
|
+
*
|
|
2631
|
+
* Why so few checks? §13's illustrative parity assertion
|
|
2632
|
+
* (`capabilities.linkPicking === (typeof engine.linkAt === 'function')`)
|
|
2633
|
+
* presumes the spec's full method surface; the v0.1 adapter contract
|
|
2634
|
+
* deliberately narrows it, leaving most capabilities with NO honestly
|
|
2635
|
+
* checkable method pair. Those defer to the §19 conformance suite, which
|
|
2636
|
+
* validates declared records against observed behavior:
|
|
2637
|
+
*
|
|
2638
|
+
* - `linkPicking`: native picking arrives through mount-time host events
|
|
2639
|
+
* (`onLinkClick`/`onLinkHover`), not a probeable `linkAt` method — there
|
|
2640
|
+
* is nothing on the engine object to compare the record against.
|
|
2641
|
+
* - `edgeArrows` / `pointImages` / `rangeUpdates`: honored inside
|
|
2642
|
+
* `commit()` payload handling (`config.linkArrows`, `resources`, ranged
|
|
2643
|
+
* uploads) with no distinguishing method; only behavior can validate them.
|
|
2644
|
+
* - `simulation`: `start()`/`pause()` are mandatory on every engine (static
|
|
2645
|
+
* engines no-op them), so method presence carries no signal either way.
|
|
2646
|
+
* - `pointsInPolygon`-family (`pointsInRect`, `captureScreenshot`,
|
|
2647
|
+
* `neighborIndices`, `screenToSpace`/`spaceToScreen`, `setPinnedIndices`,
|
|
2648
|
+
* `zoomToIndex`): optional-by-contract with no declaring capability bit —
|
|
2649
|
+
* absence is legitimate, so no check is fabricated for them.
|
|
2650
|
+
*
|
|
2651
|
+
* The one honest pair on this surface: `trackedPositions` declares position
|
|
2652
|
+
* readback works, so `getPositions` must actually be present.
|
|
2653
|
+
*/
|
|
2654
|
+
declare function assertCapabilityMethodParity(engine: GraphEngine): string[];
|
|
2655
|
+
/**
|
|
2656
|
+
* Strip commit payload the engine's capability record says it cannot honor:
|
|
2657
|
+
* - `resources` (image atlas + per-point image index) unless `pointImages`;
|
|
2658
|
+
* - `config.linkArrows` unless `edgeArrows`;
|
|
2659
|
+
* - `config.cluster` unless `clusterForce` (§16.3 stage 4 — the core keeps
|
|
2660
|
+
* membership, labels, and centroids; only the FORCE is engine-side).
|
|
2661
|
+
* A `config` left empty by the strip is dropped entirely.
|
|
2662
|
+
*
|
|
2663
|
+
* IDENTITY-PRESERVING: when nothing needs stripping the SAME commit object
|
|
2664
|
+
* reference is returned, so downstream dirty/equality checks stay cheap. The
|
|
2665
|
+
* input commit is never mutated. `dropped` names each stripped payload path.
|
|
2666
|
+
*/
|
|
2667
|
+
declare function normalizeCommitForCapabilities(commit: EngineCommit, capabilities: EngineCapabilities): {
|
|
2668
|
+
commit: EngineCommit;
|
|
2669
|
+
dropped: readonly string[];
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
/**
|
|
2673
|
+
* §8 shared numeric hygiene (R-19-10) — THE single coercion layer for every
|
|
2674
|
+
* numeric consumer in orbit-core.
|
|
2675
|
+
*
|
|
2676
|
+
* Wherever a caller-supplied value feeds a numeric sink — size/width
|
|
2677
|
+
* projection buffers (§8), scale domains (§11), metric columns (§12),
|
|
2678
|
+
* crossfilter bins (§16.6), table lanes — the value is REQUIRED to route
|
|
2679
|
+
* through `coerceNumeric` / `coerceNumericInto`. Non-numeric and non-finite
|
|
2680
|
+
* inputs (including the string sentinels `"NaN"` / `"Infinity"` /
|
|
2681
|
+
* `"-Infinity"` that JSON transports smuggle through, §5) coerce to `null`:
|
|
2682
|
+
* the row falls back to the default style and is excluded from domain
|
|
2683
|
+
* computation. NaN NEVER escapes this module — not as a return value and not
|
|
2684
|
+
* into a GPU buffer.
|
|
2685
|
+
*
|
|
2686
|
+
* Coercion rules:
|
|
2687
|
+
* - numbers pass iff `Number.isFinite` (NaN / ±Infinity → null);
|
|
2688
|
+
* - strings are trimmed; empty → null; the case-insensitive sentinels
|
|
2689
|
+
* 'NaN' / 'Infinity' / '-Infinity' / '+Infinity' → null; anything else
|
|
2690
|
+
* parses via `Number(...)` and passes iff finite (so '1e3' → 1000 but
|
|
2691
|
+
* '12px' → null — `Number`, not `parseFloat`, so no partial prefixes);
|
|
2692
|
+
* - booleans, objects, arrays, functions, symbols, bigints, null, and
|
|
2693
|
+
* undefined → null (no `valueOf`/`toString` coercion side channels).
|
|
2694
|
+
*
|
|
2695
|
+
* FOLLOW-UP (do not fix here): `crossfilter.ts` predates this module and
|
|
2696
|
+
* carries its own inline finite checks; migrating it to route through
|
|
2697
|
+
* `coerceNumeric` is a tracked S10 follow-up so the sentinel-string rules
|
|
2698
|
+
* stay defined in exactly one place.
|
|
2699
|
+
*/
|
|
2700
|
+
declare function coerceNumeric(value: unknown): number | null;
|
|
2701
|
+
/**
|
|
2702
|
+
* Buffer-writer variant for hot projection loops: coerces `value` and writes
|
|
2703
|
+
* it at `target[index]`, falling back to `fallback` when coercion yields
|
|
2704
|
+
* null. Returns true iff the caller's value was admitted (false = fallback
|
|
2705
|
+
* was written).
|
|
2706
|
+
*
|
|
2707
|
+
* The fallback itself is hygiene-checked (a non-finite fallback writes 0) so
|
|
2708
|
+
* NaN cannot reach the buffer through EITHER argument. Allocation-free.
|
|
2709
|
+
*/
|
|
2710
|
+
declare function coerceNumericInto(target: Float32Array, index: number, value: unknown, fallback: number): boolean;
|
|
2711
|
+
|
|
2712
|
+
/**
|
|
2713
|
+
* §12 metrics (v0.8 subset): lazy degree-family primitives over the
|
|
2714
|
+
* maintained topology plus revision-gated admission of async metric columns.
|
|
2715
|
+
*
|
|
2716
|
+
* - The degree family (degree / inDegree / outDegree) is computed LAZILY on
|
|
2717
|
+
* first request per model revision, in ONE combined O(n + L) pass, and
|
|
2718
|
+
* cached as Float64Arrays until the model revision changes.
|
|
2719
|
+
* - SELF-LOOP SEMANTICS: a self-loop (a, a) contributes exactly 1 to each of
|
|
2720
|
+
* degree, inDegree, and outDegree of `a`. The CSR adjacency lists a
|
|
2721
|
+
* self-loop twice under its point (once per endpoint slot, see
|
|
2722
|
+
* adjacency.ts), so degree = CSR row length MINUS the point's self-loop
|
|
2723
|
+
* count; in/out come from a directed pass over the flat link pairs.
|
|
2724
|
+
* - Async columns join once against the accepted model (§12): 'index' align
|
|
2725
|
+
* is positional over accepted-base order; 'ids' align joins by id with
|
|
2726
|
+
* unknown ids counted+sampled, duplicate ids counted (last occurrence
|
|
2727
|
+
* wins), and absent rows null. Every value routes through `coerceNumeric`
|
|
2728
|
+
* (§8 hygiene, R-19-10). A column computed for a stale model revision is
|
|
2729
|
+
* DISCARDED with a diagnostic — admission is the correctness gate, abort
|
|
2730
|
+
* is only an optimization (§9.2).
|
|
2731
|
+
* - Storage encodes null as NaN inside Float64Arrays; `getMetricValue`
|
|
2732
|
+
* converts back at the boundary so NaN never escapes to callers.
|
|
2733
|
+
* - Admitted columns SHADOW the built-in degree family under the same name
|
|
2734
|
+
* (a precomputed/server 'degree' column wins; §12 precomputed path).
|
|
2735
|
+
*/
|
|
2736
|
+
|
|
2737
|
+
/** Topology snapshot the degree family is computed from (§12). */
|
|
2738
|
+
interface MetricModelInput<N = Record<string, unknown>> {
|
|
2739
|
+
/** Accepted nodes in accepted-base order (index i ↔ point i). */
|
|
2740
|
+
nodes: readonly GraphNode<N>[];
|
|
2741
|
+
/** CSR adjacency over `links` (self-loops listed twice; adjacency.ts). */
|
|
2742
|
+
adjacency: Adjacency;
|
|
2743
|
+
/** Flat `[src0, tgt0, src1, tgt1, …]` directed point-index pairs. */
|
|
2744
|
+
links: Uint32Array;
|
|
2745
|
+
datasetRevision: number | string;
|
|
2746
|
+
modelRevision: number;
|
|
2747
|
+
}
|
|
2748
|
+
interface AdmitColumnsOptions {
|
|
2749
|
+
/** Accepted id → accepted-base index (the core map — no public copy). */
|
|
2750
|
+
nodeIndex: ReadonlyMap<NodeId, number>;
|
|
2751
|
+
/** Accepted node count ('index' align must match exactly). */
|
|
2752
|
+
count: number;
|
|
2753
|
+
/** The model revision current when the update carrying the columns was
|
|
2754
|
+
* ISSUED (the admission gate compares each column's own
|
|
2755
|
+
* `forModelRevision` stamp against this — I1). */
|
|
2756
|
+
modelRevision: number;
|
|
2757
|
+
}
|
|
2758
|
+
interface AdmitColumnsResult {
|
|
2759
|
+
/** Metric names admitted by this call, in input order. */
|
|
2760
|
+
admitted: readonly string[];
|
|
2761
|
+
diagnostics: readonly GraphDiagnostic[];
|
|
2762
|
+
}
|
|
2763
|
+
declare class MetricStore<N = Record<string, unknown>> {
|
|
2764
|
+
private model;
|
|
2765
|
+
private degreeCache;
|
|
2766
|
+
/** Admitted async columns by metric name; NaN encodes null. */
|
|
2767
|
+
private readonly columns;
|
|
2768
|
+
private degreePasses;
|
|
2769
|
+
/** Number of combined degree-family compute passes (test observability). */
|
|
2770
|
+
get degreeComputePasses(): number;
|
|
2771
|
+
/**
|
|
2772
|
+
* Installs the topology the degree family derives from. A changed
|
|
2773
|
+
* {datasetRevision, modelRevision} coordinate invalidates the lazy degree
|
|
2774
|
+
* cache AND drops every admitted column (their accepted-base alignment is
|
|
2775
|
+
* meaningless against a different model); re-setting the identical
|
|
2776
|
+
* coordinate keeps both.
|
|
2777
|
+
*/
|
|
2778
|
+
setModel(model: MetricModelInput<N>): void;
|
|
2779
|
+
/**
|
|
2780
|
+
* Joins async metric columns against the accepted model (§12).
|
|
2781
|
+
* Revision-gated PER COLUMN (I1): a column whose issue-time
|
|
2782
|
+
* `forModelRevision` stamp differs from `opts.modelRevision` is discarded
|
|
2783
|
+
* (info diagnostic — a normal async race outcome, §9.2). A missing or
|
|
2784
|
+
* mismatched stamp is never defaulted to the current revision — that
|
|
2785
|
+
* would make the gate self-satisfying.
|
|
2786
|
+
* Structural rejections ('index' length mismatch, missing/mismatched ids)
|
|
2787
|
+
* emit ONE warning diagnostic per column; a joined column emits at most
|
|
2788
|
+
* one unknown-ids and one duplicate-ids diagnostic, each carrying a total
|
|
2789
|
+
* count and at most DIAGNOSTIC_SAMPLE_CAP sample ids.
|
|
2790
|
+
*/
|
|
2791
|
+
admitColumns(columns: readonly MetricColumn[], opts: AdmitColumnsOptions): AdmitColumnsResult;
|
|
2792
|
+
/**
|
|
2793
|
+
* Allocation-free hot path: one map lookup + one typed-array read.
|
|
2794
|
+
* NaN-encoded nulls convert back to `null` at this boundary; unknown
|
|
2795
|
+
* metrics and out-of-range indices are `null`, never NaN.
|
|
2796
|
+
*/
|
|
2797
|
+
getMetricValue(metric: MetricName, index: number): number | null;
|
|
2798
|
+
/**
|
|
2799
|
+
* Raw column in accepted-base order, or null when unavailable. NaN encodes
|
|
2800
|
+
* null (§12) — consumers exclude NaN slots from domains. Do NOT mutate:
|
|
2801
|
+
* this is the live cache, not a copy.
|
|
2802
|
+
*/
|
|
2803
|
+
metricValues(metric: MetricName): Float64Array | null;
|
|
2804
|
+
/** True when the metric would resolve; never triggers a compute pass. */
|
|
2805
|
+
hasMetric(name: MetricName): boolean;
|
|
2806
|
+
private resolve;
|
|
2807
|
+
/** One combined O(n + L) pass computes all three family members. */
|
|
2808
|
+
private ensureDegrees;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
/**
|
|
2812
|
+
* §16.8 minimap / overview — CPU fallback lane (S11-T09/T10).
|
|
2813
|
+
*
|
|
2814
|
+
* v0.9 trim: no engine exposes `capabilities.overviewPass` yet (cosmos has no
|
|
2815
|
+
* second draw pass — M0 matrix), so this controller IS the minimap thumbnail
|
|
2816
|
+
* path: an O(n) CPU rasterization of the §7.1 position mirror into a small
|
|
2817
|
+
* RGBA dot field. It is a pure controller — no DOM, no canvas — the React
|
|
2818
|
+
* component blits the returned `Uint8ClampedArray` into an `ImageData`/canvas
|
|
2819
|
+
* and draws the viewport rectangle on top (O(1) from `getViewport()`, fully
|
|
2820
|
+
* decoupled from thumbnail refresh cadence).
|
|
2821
|
+
*
|
|
2822
|
+
* Refresh cadence (spec §16.8): ≤ 2 Hz while the simulation is hot (positions
|
|
2823
|
+
* change continuously on the GPU, so the epoch is ignored), ≤ 1 Hz after a
|
|
2824
|
+
* change while idle, and ZERO work while idle with an unchanged positions
|
|
2825
|
+
* epoch — `shouldRefresh` is the single throttle gate and latches its clock /
|
|
2826
|
+
* epoch only when it answers true (a `true` must be followed by one
|
|
2827
|
+
* `rasterize()`).
|
|
2828
|
+
*
|
|
2829
|
+
* Orientation: minimap pixel y grows DOWNWARD while world (space) y grows
|
|
2830
|
+
* upward — the same flip cosmos applies in its space→screen scale (dist
|
|
2831
|
+
* `scalePointY.domain([S, 0])`), so the thumbnail visually matches the main
|
|
2832
|
+
* canvas. `worldToMinimap`/`minimapToWorld` are exact inverses over the last
|
|
2833
|
+
* rasterized bounds.
|
|
2834
|
+
*/
|
|
2835
|
+
/** Position mirror handed to the controller: interleaved x,y space coords. */
|
|
2836
|
+
interface OverviewScene {
|
|
2837
|
+
/** Interleaved `[x0, y0, x1, y1, …]`; may be longer than `count` pairs. */
|
|
2838
|
+
positions: Float32Array;
|
|
2839
|
+
/** Number of points (pairs) to read from `positions`. */
|
|
2840
|
+
count: number;
|
|
2841
|
+
}
|
|
2842
|
+
interface OverviewControllerOptions {
|
|
2843
|
+
/** Latest scene snapshot, or null when nothing is loaded yet. */
|
|
2844
|
+
getScene: () => OverviewScene | null;
|
|
2845
|
+
/**
|
|
2846
|
+
* Soft-filter visibility mask. Hidden points still rasterize (and still
|
|
2847
|
+
* contribute to bounds — the thumbnail must not re-frame when a filter
|
|
2848
|
+
* toggles) but at a dimmed alpha.
|
|
2849
|
+
*/
|
|
2850
|
+
getVisible?: (index: number) => boolean;
|
|
2851
|
+
/** Square thumbnail edge in pixels. Default {@link OVERVIEW_SIZE_DEFAULT}. */
|
|
2852
|
+
size?: number;
|
|
2853
|
+
}
|
|
2854
|
+
/** World-space extent of the last rasterization (finite positions only). */
|
|
2855
|
+
interface OverviewBounds {
|
|
2856
|
+
minX: number;
|
|
2857
|
+
minY: number;
|
|
2858
|
+
maxX: number;
|
|
2859
|
+
maxY: number;
|
|
2860
|
+
}
|
|
2861
|
+
interface OverviewRaster {
|
|
2862
|
+
/** RGBA, `size * size * 4` bytes — blit via `new ImageData(bitmap, size)`. */
|
|
2863
|
+
bitmap: Uint8ClampedArray;
|
|
2864
|
+
bounds: OverviewBounds;
|
|
2865
|
+
}
|
|
2866
|
+
/** Spec §16.8: the CPU fallback rasterizes into a 256² target. */
|
|
2867
|
+
declare const OVERVIEW_SIZE_DEFAULT = 256;
|
|
2868
|
+
/** Hot-simulation refresh floor: ≤ 2 Hz. */
|
|
2869
|
+
declare const OVERVIEW_HOT_INTERVAL_MS = 500;
|
|
2870
|
+
/** Idle-after-change refresh floor: ≤ 1 Hz. */
|
|
2871
|
+
declare const OVERVIEW_IDLE_INTERVAL_MS = 1000;
|
|
2872
|
+
declare class OverviewController {
|
|
2873
|
+
private readonly getScene;
|
|
2874
|
+
private readonly getVisible;
|
|
2875
|
+
readonly size: number;
|
|
2876
|
+
/** Transform of the LAST rasterization; null until rasterize() succeeds. */
|
|
2877
|
+
private frame;
|
|
2878
|
+
private lastRefreshMs;
|
|
2879
|
+
private lastEpoch;
|
|
2880
|
+
constructor(options: OverviewControllerOptions);
|
|
2881
|
+
/**
|
|
2882
|
+
* The single throttle gate (see module header). Latches `nowMs`/`epoch`
|
|
2883
|
+
* when it returns true, so each `true` accounts for exactly one refresh:
|
|
2884
|
+
*
|
|
2885
|
+
* - hot (`simulationRunning`): time-gated only (≤ 2 Hz) — the epoch is
|
|
2886
|
+
* ignored because positions change continuously without epoch advances;
|
|
2887
|
+
* - idle: refresh only when the epoch ADVANCED since the last refresh,
|
|
2888
|
+
* time-gated at ≤ 1 Hz;
|
|
2889
|
+
* - idle + unchanged epoch: always false — zero work, forever.
|
|
2890
|
+
*/
|
|
2891
|
+
shouldRefresh(nowMs: number, simulationRunning: boolean, epoch: number): boolean;
|
|
2892
|
+
/**
|
|
2893
|
+
* Rasterizes the current scene into a fresh `size²` RGBA dot field: 1 px
|
|
2894
|
+
* white dots whose alpha ACCUMULATES on overlap (heatmap-ish density),
|
|
2895
|
+
* dimmed for mask-hidden points; NaN pairs (§7.3 tombstones) are skipped.
|
|
2896
|
+
* World bounds map into the thumbnail with a 5 % edge padding at a UNIFORM
|
|
2897
|
+
* scale (aspect preserved, centered on the short axis) and a downward pixel
|
|
2898
|
+
* y (see module header). Returns null when there is no scene or no point.
|
|
2899
|
+
*/
|
|
2900
|
+
rasterize(): OverviewRaster | null;
|
|
2901
|
+
/**
|
|
2902
|
+
* World (space) → minimap pixel coords over the LAST rasterized frame
|
|
2903
|
+
* (fractional; callers round for pixel work). Null before any rasterize.
|
|
2904
|
+
*/
|
|
2905
|
+
worldToMinimap(x: number, y: number): [number, number] | null;
|
|
2906
|
+
/** Exact inverse of {@link worldToMinimap}. Null before any rasterize. */
|
|
2907
|
+
minimapToWorld(px: number, py: number): [number, number] | null;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
/**
|
|
2911
|
+
* §16.14 SVG export (S15 export lane) — the M7 pure module.
|
|
2912
|
+
*
|
|
2913
|
+
* ENGINE-INDEPENDENT BY CONSTRUCTION, not by discipline: positions and
|
|
2914
|
+
* projected styles come IN as a plain descriptor, vector markup goes OUT as a
|
|
2915
|
+
* string. No engine, no instance, no DOM — the post-v1 server snapshot path
|
|
2916
|
+
* (spec §13, R-13-53) reuses this verbatim under plain Node, and the test
|
|
2917
|
+
* suite runs it exactly that way. Keeping it engine-free is an M7
|
|
2918
|
+
* requirement, which is why it is a standalone module rather than instance
|
|
2919
|
+
* code that might one day be extracted.
|
|
2920
|
+
*
|
|
2921
|
+
* Output discipline (§16.14):
|
|
2922
|
+
* - One element per node/edge/label, assembled OFF-DOM by chunked
|
|
2923
|
+
* array-join — O(elements), never a live DOM node per element.
|
|
2924
|
+
* - Every label and attribute string is XML-escaped (§14's untrusted-content
|
|
2925
|
+
* rule in its XML form): a hostile label renders as literal text in every
|
|
2926
|
+
* downstream vector tool, never as markup.
|
|
2927
|
+
* - Bounded: above `maxElements` (default 50 000 — the practical ceiling for
|
|
2928
|
+
* downstream vector editors) rendering THROWS before assembling anything;
|
|
2929
|
+
* the instance wraps that into the typed `export-too-large` rejection, and
|
|
2930
|
+
* the raster-hybrid form (a PNG base layer plus a small vector overlay) is
|
|
2931
|
+
* the sanctioned way past it.
|
|
2932
|
+
*/
|
|
2933
|
+
interface SvgSceneNode {
|
|
2934
|
+
x: number;
|
|
2935
|
+
y: number;
|
|
2936
|
+
/** Radius in output px (the caller halves its size-buffer diameter). */
|
|
2937
|
+
r: number;
|
|
2938
|
+
/** Any CSS color string (the caller projects RGBA buffers to rgba()). */
|
|
2939
|
+
color: string;
|
|
2940
|
+
}
|
|
2941
|
+
interface SvgSceneEdge {
|
|
2942
|
+
x1: number;
|
|
2943
|
+
y1: number;
|
|
2944
|
+
x2: number;
|
|
2945
|
+
y2: number;
|
|
2946
|
+
color: string;
|
|
2947
|
+
width: number;
|
|
2948
|
+
}
|
|
2949
|
+
interface SvgSceneLabel {
|
|
2950
|
+
x: number;
|
|
2951
|
+
y: number;
|
|
2952
|
+
/** UNTRUSTED text — escaped here, never pre-escaped by callers (double
|
|
2953
|
+
* escaping is a rendering bug, missing escaping is an injection). */
|
|
2954
|
+
text: string;
|
|
2955
|
+
color: string;
|
|
2956
|
+
/** Font size in px. Default 11. */
|
|
2957
|
+
size?: number;
|
|
2958
|
+
}
|
|
2959
|
+
interface SvgScene {
|
|
2960
|
+
width: number;
|
|
2961
|
+
height: number;
|
|
2962
|
+
background: string;
|
|
2963
|
+
nodes: readonly SvgSceneNode[];
|
|
2964
|
+
edges: readonly SvgSceneEdge[];
|
|
2965
|
+
labels?: readonly SvgSceneLabel[];
|
|
2966
|
+
/**
|
|
2967
|
+
* Raster-hybrid base layer: a data URI (typically the engine screenshot).
|
|
2968
|
+
* When present, `nodes` and `edges` are expected to be EMPTY — the raster
|
|
2969
|
+
* carries them — and only labels/overlay chrome emit as vectors.
|
|
2970
|
+
*/
|
|
2971
|
+
rasterBase?: {
|
|
2972
|
+
href: string;
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
interface RenderSvgOptions {
|
|
2976
|
+
/** Element budget across nodes+edges+labels. Default 50 000. */
|
|
2977
|
+
maxElements?: number;
|
|
2978
|
+
}
|
|
2979
|
+
declare const SVG_MAX_ELEMENTS_DEFAULT = 50000;
|
|
2980
|
+
/** Thrown (not returned) on budget overflow — a pure module has no
|
|
2981
|
+
* diagnostics lane; the instance converts this into `export-too-large`. */
|
|
2982
|
+
declare class SvgBudgetError extends Error {
|
|
2983
|
+
readonly elementCount: number;
|
|
2984
|
+
readonly limit: number;
|
|
2985
|
+
constructor(elementCount: number, limit: number);
|
|
2986
|
+
}
|
|
2987
|
+
declare function escapeXml(text: string): string;
|
|
2988
|
+
/**
|
|
2989
|
+
* Render a scene to an SVG document string. Pure: same input, same output,
|
|
2990
|
+
* byte for byte. Throws {@link SvgBudgetError} when the element budget is
|
|
2991
|
+
* exceeded — BEFORE assembling any markup.
|
|
2992
|
+
*/
|
|
2993
|
+
declare function renderSvg(scene: SvgScene, opts?: RenderSvgOptions): string;
|
|
2994
|
+
|
|
2995
|
+
export { ATLAS_MAX_CONCURRENT_DEFAULT, ATLAS_MAX_ENTRIES_DEFAULT, ATLAS_MAX_RETRIES_DEFAULT, AcceptanceQueue, AcceptedEdge, AcceptedGraph, AccessibilityConfig, Accessor, type Adjacency, type AdmitColumnsOptions, type AdmitColumnsResult, type AdmitServiceResultArgs, BASE_PENDING_KEY, BRUSH_HISTORY_COALESCE_MS, BeginIngestOptions, type BrushDelta, BrushState, CATEGORICAL_PALETTE, type CategoricalScale, type CompiledFilter, type CreateGraphInstanceOptions, type CreateRequestContextArgs, CrossfilterSession, type CrossfilterStats, DEFAULT_BIN_COUNT, DIM_ALPHA_DEFAULT, DIVERGING_RANGE_DEFAULT, DimensionSpec, DimensionSummary, type DivergingScale, DomainPolicy, DomainStore, EDGE_PICK_TOLERANCE_PX, EdgeId, type EdgePickRoute, EdgePickingFacade, type EdgePickingFacadeOptions, type EnginePolicy, type EnginePolicyDegradation, type ExpandNodeResult, type ExpansionOverlayRecord, ExpansionService, type FetchLike, type FilterErrorAggregate, FilterExpr, FilterSpec, GRAPH_THEME_DARK, GRAPH_THEME_LIGHT, GraphDiagnostic, GraphEventMap, GraphEventName, GraphHostUpdate, type GraphInstance, GraphListenerControl, GraphNode, type GraphServices, GraphSnapshot, GraphStoreState, GraphTheme, type GraphViewState, type GroupByDerivation, GroupBySpec, type GroupRewrite, GroupSpec, type GroupValidationResult, HISTORY_LIMIT_DEFAULT, type HistoryCommand, type HistoryDepths, HistoryKernel, type HistoryKernelOptions, INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT, INGEST_MAX_PENDING_BYTES_DEFAULT, INGEST_OVERFLOW_FACTOR, type ImageAtlasBatch, ImageAtlasPipeline, type ImageAtlasPipelineOptions, type ImageDecode, type ImageResolver, IngestBatch, IngestSession, JsonValue, LABEL_MAX_VISIBLE_CAP, LABEL_MAX_VISIBLE_DEFAULT, type LabelCandidate, type LabelCandidateResult, type LabelCandidateViewport, LabelConfig, LabelPlacement, type LabelSubscriptions, type LinkPickGridSnapshot, LinkPickIndex, type LinkVisibilityMask, type LocalExpansionBase, type LocalSearchBase, type LocalSearchService, META_EDGE_MAX_WIDTH, type MaskDrain, type MaskSource, type MergeBase, type MergeResult, MetaEdge, type MetaEdgeRecord, MetricColumn, type MetricModelInput, MetricName, MetricStore, NodeId, OVERVIEW_HOT_INTERVAL_MS, OVERVIEW_IDLE_INTERVAL_MS, OVERVIEW_SIZE_DEFAULT, type OverviewBounds, OverviewController, type OverviewControllerOptions, type OverviewRaster, type OverviewScene, PHYSICAL_DEFAULT_LINK_WIDTH, PHYSICAL_DEFAULT_POINT_SIZE, PathOptions, PathResult, PathService, PendingExpansions, type ReconcileResult, Reconciler, type RegisterExpansionResult, RenderScene, type RenderSvgOptions, RequestContext, type RequestContextHandle, type RequestedEngineFeatures, type ResolveDomainArgs, ResolvedCluster, ResolvedGroup, type ResolvedScope, RevisionAwareService, RevisionDimension, type RevisionSnapshot, Revisions, type RowTally, SEARCH_CACHE_LIMIT, SEARCH_LIMIT_DEFAULT, SEARCH_SCAN_CHUNK, SEARCH_SCORE_EXACT_ID, SEARCH_SCORE_ID_PREFIX, SEARCH_SCORE_SUBSTRING, SEARCH_SCORE_TOKEN_START_BONUS, SEQUENTIAL_RANGE_DEFAULT, SUPER_NODE_MAX_SIZE, SVG_MAX_ELEMENTS_DEFAULT, Scale, type ScaleChannelInfo, type ScaleInfoRow, SceneGroups, SceneLinkRef, ScenePointRef, SearchActivation, SearchResult, type SearchService, type SelectLabelCandidatesArgs, SelectionState, type SequentialScale, type SerializableScale, type ServiceCacheKeyArgs, type SessionContribution, type SetViewStateResult, SoftMask, type StageResult, type StagingTallies, type StampedEdge, type StampedNode, SubgraphSpec, type SuperNodeRecord, SvgBudgetError, type SvgScene, type SvgSceneEdge, type SvgSceneLabel, type SvgSceneNode, TIMELINE_STEP_DEFAULT, TIMELINE_TICK_MS_DEFAULT, ThemeInput, TimelinePlayback, TypedColumnCrossfilter, VIEW_STATE_VERSION, type ViewBrushState, type ViewLayoutSpec, type ViewStateVerdict, type ViewStyling, ViewportState, admitServiceResult, assertCapabilityMethodParity, baseFromAccepted, baseFromContribution, buildAcceptedAdjacency, buildAdjacency, canonicalFilterKey, canonicalJson, canonicalScaleKey, cascadeEdges, categoricalIndex, categoricalRows, coerceNumeric, coerceNumericInto, collapseParallelEdges, compileEdgeFilter, compileNodeFilter, computeNumericDomain, createGraphInstance, createLocalExpansionService, createLocalSearchService, createRequestContext, deriveGroupsByKey, divergingColor, escapeXml, estimateBatchBytes, evaluateFilterExpr, groupByDerivedId, groupSceneKey, interpolateColor, medianLinkWidthPx, mergeDiagnostics, mergeModel, metaEdgePublicId, metaEdgeSceneKey, metaEdgeWidthFor, neighborsOf, newContribution, newStagingTallies, nextRequestId, normalizeCommitForCapabilities, parseColor, pointSegmentDistanceSquared, projectColors, projectSizes, renderSvg, resolveEnginePolicy, resolveFilterField, resolveManualGroups, resolveScope, resolveTheme, rewriteGroups, sameDataRef, sameGroupBySpec, sameGroupSpecArrays, sceneGroupsOf, sceneLinkRefAt, scenePointRefAt, selectLabelCandidates, sequentialColor, sequentialSize, serviceCacheKey, sessionCommitDiagnostics, stageBatch, superNodeSizeFor, validateFilterExpr, validateGroupBySpec, validateGroupSpecs, validateSnapshot, validateViewState };
|