@fjader/svelte 0.2.3 → 0.2.5
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/README.md +3 -1
- package/dist/animation/EntranceGate.d.ts +52 -0
- package/dist/animation/EntranceGate.js +93 -0
- package/dist/animation/TransitionClock.d.ts +61 -30
- package/dist/animation/TransitionClock.js +67 -29
- package/dist/animation/transition-log.d.ts +41 -0
- package/dist/animation/transition-log.js +57 -0
- package/dist/components/axes/AxisBottom.svelte +27 -8
- package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
- package/dist/components/axes/AxisCenter.svelte +4 -2
- package/dist/components/axes/AxisLeft.svelte +81 -10
- package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
- package/dist/components/axes/BandAxisTicks.svelte +4 -3
- package/dist/components/axes/axis-title.d.ts +30 -0
- package/dist/components/axes/axis-title.js +77 -0
- package/dist/components/charts/ChartCell.svelte +33 -6
- package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
- package/dist/components/charts/ChartContainer.svelte +55 -5
- package/dist/components/charts/ChartContainer.svelte.d.ts +14 -1
- package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
- package/dist/components/charts/ChartHover.svelte.js +22 -0
- package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
- package/dist/components/charts/ChartVisibility.svelte.js +67 -0
- package/dist/components/guides/Grid.svelte +12 -1
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +7 -1
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +8 -5
- package/dist/components/marks/AreaMarkX.svelte +18 -4
- package/dist/components/marks/AreaMarkY.svelte +18 -4
- package/dist/components/marks/BarMarkX.svelte +20 -4
- package/dist/components/marks/BarMarkY.svelte +20 -4
- package/dist/components/marks/DotsMark.svelte +8 -3
- package/dist/components/marks/LineMark.svelte +13 -6
- package/dist/components/marks/ParallelCoordinatesMark.svelte +11 -2
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +4 -0
- package/dist/components/marks/RectMark.svelte +8 -3
- package/dist/components/marks/RectXMark.svelte +9 -3
- package/dist/components/marks/RectYMark.svelte +9 -3
- package/dist/components/marks/RuleMarkX.svelte +31 -25
- package/dist/components/marks/RuleMarkY.svelte +31 -25
- package/dist/components/marks/TextMark.svelte +52 -14
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +22 -1
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +579 -66
- package/dist/components/marks/lifecycle/domain-queries.d.ts +13 -2
- package/dist/components/marks/lifecycle/domain-queries.js +43 -11
- package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
- package/dist/components/marks/lifecycle/domain-registration.js +17 -20
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +112 -0
- package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
- package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
- package/dist/components/marks/lifecycle/other-split.js +112 -0
- package/dist/components/marks/lifecycle/query-builder.d.ts +53 -3
- package/dist/components/marks/lifecycle/query-builder.js +46 -23
- package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
- package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
- package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
- package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
- package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
- package/dist/components/marks/utils/reactive-config.d.ts +1 -1
- package/dist/components/marks/utils/reactive-config.js +9 -0
- package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
- package/dist/components/marks/utils/tooltip-helpers.js +2 -1
- package/dist/components/rendering/Areas.svelte +57 -19
- package/dist/components/rendering/Areas.svelte.d.ts +12 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
- package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -2
- package/dist/components/rendering/Dots.svelte +23 -3
- package/dist/components/rendering/Dots.svelte.d.ts +6 -0
- package/dist/components/rendering/GridLine.svelte +13 -1
- package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
- package/dist/components/rendering/Lines.svelte +48 -13
- package/dist/components/rendering/Lines.svelte.d.ts +12 -0
- package/dist/components/rendering/ParallelLines.svelte +5 -5
- package/dist/components/rendering/Rects.svelte +164 -30
- package/dist/components/rendering/Rects.svelte.d.ts +23 -2
- package/dist/components/rendering/RuleX.svelte +1 -1
- package/dist/components/rendering/RuleY.svelte +1 -1
- package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
- package/dist/components/rendering/activity-reads.d.ts +47 -12
- package/dist/components/rendering/activity-reads.js +61 -19
- package/dist/components/rendering/uniform-sizes.d.ts +1 -1
- package/dist/components/rendering/uniform-sizes.js +1 -1
- package/dist/config/ChartConfig.svelte.d.ts +10 -1
- package/dist/config/ChartConfig.svelte.js +17 -0
- package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
- package/dist/gpu/GPUCoordinator.svelte.js +164 -7
- package/dist/gpu/MorphedChannels.d.ts +11 -28
- package/dist/gpu/MorphedChannels.js +72 -8
- package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
- package/dist/gpu/ScaleTemplateSystem.js +83 -6
- package/dist/index.d.ts +19 -4
- package/dist/index.js +22 -4
- package/dist/interactors/Interval1D.svelte +6 -1
- package/dist/interactors/Interval2D.svelte +6 -1
- package/dist/interactors/ParallelAxisSource.svelte +10 -1
- package/dist/interactors/Toggle.svelte +32 -3
- package/dist/interactors/Tooltip.svelte +138 -41
- package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
- package/dist/interactors/TooltipManager.svelte.js +46 -12
- package/dist/interactors/other-tooltip.d.ts +16 -0
- package/dist/interactors/other-tooltip.js +22 -0
- package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
- package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
- package/dist/query-engine/FieldActivity.svelte.d.ts +49 -1
- package/dist/query-engine/FieldActivity.svelte.js +175 -5
- package/dist/query-engine/FilterGroup.svelte.d.ts +23 -1
- package/dist/query-engine/FilterGroup.svelte.js +43 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +31 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.js +62 -15
- package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
- package/dist/query-engine/MarkHandle.svelte.js +33 -1
- package/dist/query-engine/QueryEngine.d.ts +225 -5
- package/dist/query-engine/QueryEngine.js +697 -47
- package/dist/query-engine/categorical-identity.d.ts +41 -0
- package/dist/query-engine/categorical-identity.js +145 -65
- package/dist/query-engine/debug/query-log.d.ts +39 -0
- package/dist/query-engine/debug/query-log.js +90 -0
- package/dist/query-engine/duckdb.d.ts +55 -8
- package/dist/query-engine/duckdb.js +82 -30
- package/dist/query-engine/index.d.ts +7 -4
- package/dist/query-engine/index.js +3 -3
- package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
- package/dist/query-engine/preagg/BuildQueue.js +51 -15
- package/dist/query-engine/preagg/Preagg.d.ts +46 -3
- package/dist/query-engine/preagg/Preagg.js +159 -31
- package/dist/query-engine/preagg/plan.d.ts +33 -1
- package/dist/query-engine/preagg/plan.js +158 -14
- package/dist/query-engine/sliced-lane.d.ts +108 -0
- package/dist/query-engine/sliced-lane.js +299 -0
- package/dist/query-engine/source-routing.d.ts +106 -0
- package/dist/query-engine/source-routing.js +189 -0
- package/dist/query-engine/toggle-predicate.d.ts +21 -0
- package/dist/query-engine/toggle-predicate.js +29 -1
- package/dist/scales/ActivityTable.d.ts +5 -1
- package/dist/scales/ActivityTable.js +12 -3
- package/dist/scales/BandScaleState.svelte.d.ts +22 -9
- package/dist/scales/BandScaleState.svelte.js +39 -21
- package/dist/scales/ChartScales.svelte.d.ts +3 -0
- package/dist/scales/ChartScales.svelte.js +26 -8
- package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
- package/dist/scales/ColorScaleState.svelte.js +138 -26
- package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
- package/dist/scales/LinearScaleState.svelte.js +21 -39
- package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
- package/dist/scales/ScaleGeneration.svelte.js +42 -0
- package/dist/scales/ScaleRegistry.svelte.js +3 -1
- package/dist/scales/SlotTransition.d.ts +15 -1
- package/dist/scales/SlotTransition.js +33 -4
- package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
- package/dist/scales/TimeScaleState.svelte.js +7 -7
- package/dist/scales/color-utils.d.ts +0 -3
- package/dist/scales/color-utils.js +0 -3
- package/dist/scales/entered.d.ts +8 -0
- package/dist/scales/entered.js +10 -0
- package/dist/spec/LazyQueryEngine.js +2 -19
- package/dist/spec/MarkRegistry.d.ts +3 -1
- package/dist/spec/MarkRegistry.js +13 -1
- package/dist/spec/PlotFromSpec.svelte +1 -0
- package/dist/spec/export.js +11 -2
- package/dist/spec/types.d.ts +43 -13
- package/dist/sql/bin.d.ts +25 -0
- package/dist/sql/bin.js +108 -5
- package/dist/theme.css +5 -4
- package/dist/transforms/TransformCompiler.d.ts +47 -2
- package/dist/transforms/TransformCompiler.js +70 -1
- package/dist/transforms/index.js +176 -42
- package/dist/types.d.ts +177 -2
- package/package.json +4 -3
|
@@ -144,7 +144,8 @@ export class FilterGroup {
|
|
|
144
144
|
};
|
|
145
145
|
this.#afterUpdate(sourceId, selection.update(sourceId, next), next);
|
|
146
146
|
},
|
|
147
|
-
clear:
|
|
147
|
+
// The registration's own clear: it already reset what it drew.
|
|
148
|
+
clear: () => this.clearSource(sourceId, opts),
|
|
148
149
|
activate: () => this.#activate(sourceId),
|
|
149
150
|
destroy: () => {
|
|
150
151
|
// Once only: a second call must not remove a newer registration
|
|
@@ -158,7 +159,8 @@ export class FilterGroup {
|
|
|
158
159
|
// while the registration is still in place, so the removal is
|
|
159
160
|
// routed by it: the marks re-queried are those the clause filtered.
|
|
160
161
|
this.#engine?.forgetSource(this, sourceId);
|
|
161
|
-
|
|
162
|
+
// Quietly: the registration is going away, nothing of it to reset.
|
|
163
|
+
this.clearSource(sourceId, opts);
|
|
162
164
|
selection.removeRegistration(sourceId, opts);
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
@@ -275,6 +277,24 @@ export class FilterGroup {
|
|
|
275
277
|
write([...next.values()]);
|
|
276
278
|
},
|
|
277
279
|
set: (tuples) => write(tuples),
|
|
280
|
+
toggleOther: (otherId, facetId, multi) => {
|
|
281
|
+
if (list.length !== 1 || categoricalKey === null)
|
|
282
|
+
return;
|
|
283
|
+
const click = this.activity(categoricalKey).otherClick(this.index, otherId, facetId, keysOf(selection.get(sourceId)), multi);
|
|
284
|
+
if (!click)
|
|
285
|
+
return;
|
|
286
|
+
const next = new Map(tuplesOf(selection.get(sourceId)).map((t) => [tupleKey(t), t]));
|
|
287
|
+
if (click.action === 'replace')
|
|
288
|
+
next.clear();
|
|
289
|
+
for (const value of click.values) {
|
|
290
|
+
const tuple = [value];
|
|
291
|
+
if (click.action === 'remove')
|
|
292
|
+
next.delete(tupleKey(tuple));
|
|
293
|
+
else
|
|
294
|
+
next.set(tupleKey(tuple), tuple);
|
|
295
|
+
}
|
|
296
|
+
write([...next.values()]);
|
|
297
|
+
},
|
|
278
298
|
clear: () => write([]),
|
|
279
299
|
activate: source.activate,
|
|
280
300
|
destroy: source.destroy
|
|
@@ -322,18 +342,38 @@ export class FilterGroup {
|
|
|
322
342
|
/**
|
|
323
343
|
* Remove a source's clause. Re-queries only when there was a clause to
|
|
324
344
|
* remove: an unmount or a clear with nothing selected changes no mark's data.
|
|
345
|
+
*
|
|
346
|
+
* The source's registrations are told (`onCleared`) so a control that drew the
|
|
347
|
+
* selection resets it, all of them from outside, and all but `by` when a
|
|
348
|
+
* registration is the one clearing (it has reset itself).
|
|
325
349
|
*/
|
|
326
|
-
clearSource(sourceId) {
|
|
350
|
+
clearSource(sourceId, by) {
|
|
327
351
|
if (!this.selection.isActive(sourceId))
|
|
328
352
|
return;
|
|
329
353
|
this.#afterUpdate(sourceId, this.selection.update(sourceId, null), null);
|
|
354
|
+
this.#notifyCleared(sourceId, by);
|
|
330
355
|
}
|
|
356
|
+
/** Remove every clause, and tell the controls that drew them (`onCleared`). */
|
|
331
357
|
clearAll() {
|
|
358
|
+
const cleared = this.selection.items.map((item) => item.sourceId);
|
|
332
359
|
this.selection.clear();
|
|
333
360
|
for (const key of this.#selectedFields)
|
|
334
361
|
this.#activityOf(key).set(this.index, null);
|
|
335
362
|
this.#selectedFields.clear();
|
|
336
363
|
this.#engine?.onFilterChange(this);
|
|
364
|
+
for (const sourceId of cleared)
|
|
365
|
+
this.#notifyCleared(sourceId);
|
|
366
|
+
}
|
|
367
|
+
/** Tell a source's registrations, but `except`, that its clause is gone. */
|
|
368
|
+
#notifyCleared(sourceId, except) {
|
|
369
|
+
const registrations = this.selection.registrationsOf(sourceId);
|
|
370
|
+
if (!registrations)
|
|
371
|
+
return;
|
|
372
|
+
// A copy: a control resetting itself may register or destroy while this runs.
|
|
373
|
+
for (const opts of [...registrations]) {
|
|
374
|
+
if (opts !== except)
|
|
375
|
+
opts.onCleared?.();
|
|
376
|
+
}
|
|
337
377
|
}
|
|
338
378
|
// -----------------------------------------------------------------
|
|
339
379
|
// After a change
|
|
@@ -24,10 +24,38 @@ export declare class FilterGroupRegistry {
|
|
|
24
24
|
* at once.
|
|
25
25
|
*/
|
|
26
26
|
setAnimation(loop: ClockLoop, chartsFor: (key: string, out: string[]) => string[]): void;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Get or create a FilterGroup by name. Undefined/null → "default".
|
|
29
|
+
*
|
|
30
|
+
* Untracked: a reader does not re-run because it called `get`. For a reactive
|
|
31
|
+
* read use `find`, `has` or `isActive`, which create nothing: a group created
|
|
32
|
+
* inside the derived that reads it is state that derived cannot track, so
|
|
33
|
+
* `$derived(get('brush').activeCount)` before any chart made the group never
|
|
34
|
+
* updates. Create groups from init, effects and handlers.
|
|
35
|
+
*/
|
|
28
36
|
get(name?: string | null): FilterGroup;
|
|
29
|
-
/**
|
|
30
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The named group, or undefined if no chart has made it yet. Creates nothing.
|
|
39
|
+
* Reactive: a reader re-runs when the group is created.
|
|
40
|
+
*/
|
|
41
|
+
find(name?: string | null): FilterGroup | undefined;
|
|
42
|
+
/** Whether a named group exists, without creating it. Reactive, as `find`. */
|
|
43
|
+
has(name?: string | null): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a named group has a selection (a brush, a toggle, a legend), without
|
|
46
|
+
* creating it: false for a group no chart has made yet. Reactive in both the
|
|
47
|
+
* group appearing and its clauses changing, so `$derived(groups.isActive('brush'))`
|
|
48
|
+
* is safe before any chart exists.
|
|
49
|
+
*/
|
|
50
|
+
isActive(name?: string | null): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The names of the groups, in creation order. Reactive: a reader re-runs when a
|
|
53
|
+
* group is created. A new array on each read. For app code that must reach every group the page's marks
|
|
54
|
+
* use, such as a clause that applies to the whole page.
|
|
55
|
+
*/
|
|
56
|
+
get names(): readonly string[];
|
|
57
|
+
/** The groups, in the order of `names`. Reactive in the same way. */
|
|
58
|
+
get groups(): readonly FilterGroup[];
|
|
31
59
|
/** How many groups the page has: the packer writes one activity half per group. */
|
|
32
60
|
get groupCount(): number;
|
|
33
61
|
/**
|
|
@@ -9,10 +9,18 @@
|
|
|
9
9
|
* and one FieldActivity per categorical field, which holds every group's
|
|
10
10
|
* selection fade on that field.
|
|
11
11
|
*/
|
|
12
|
+
import { untrack } from 'svelte';
|
|
13
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
12
14
|
import { FilterGroup } from './FilterGroup.svelte';
|
|
13
15
|
import { FieldActivity } from './FieldActivity.svelte';
|
|
14
16
|
export class FilterGroupRegistry {
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The groups by name, in creation order. Reactive, so `has`, `find`, `isActive`,
|
|
19
|
+
* `names` and `groups` re-run when a group is created. `get` reads and writes it
|
|
20
|
+
* untracked: templates and deriveds call `get`, and a tracked write there throws
|
|
21
|
+
* (`state_unsafe_mutation`) or re-runs the reader on its own write.
|
|
22
|
+
*/
|
|
23
|
+
#groups = new SvelteMap();
|
|
16
24
|
#engine = null;
|
|
17
25
|
#fields = new Map();
|
|
18
26
|
/**
|
|
@@ -54,27 +62,66 @@ export class FilterGroupRegistry {
|
|
|
54
62
|
this.#loop = loop;
|
|
55
63
|
this.#chartsFor = chartsFor;
|
|
56
64
|
}
|
|
57
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Get or create a FilterGroup by name. Undefined/null → "default".
|
|
67
|
+
*
|
|
68
|
+
* Untracked: a reader does not re-run because it called `get`. For a reactive
|
|
69
|
+
* read use `find`, `has` or `isActive`, which create nothing: a group created
|
|
70
|
+
* inside the derived that reads it is state that derived cannot track, so
|
|
71
|
+
* `$derived(get('brush').activeCount)` before any chart made the group never
|
|
72
|
+
* updates. Create groups from init, effects and handlers.
|
|
73
|
+
*/
|
|
58
74
|
get(name) {
|
|
59
75
|
const key = name ?? 'default';
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
return untrack(() => {
|
|
77
|
+
let g = this.#groups.get(key);
|
|
78
|
+
if (!g) {
|
|
79
|
+
// Indices are never reused or compacted: a mark keeps its group's
|
|
80
|
+
// index from mount, so renumbering would point it at another group.
|
|
81
|
+
g = new FilterGroup(this.#groups.size, (field) => this.field(field));
|
|
82
|
+
if (this.#engine)
|
|
83
|
+
g.setEngine(this.#engine);
|
|
84
|
+
this.#groups.set(key, g);
|
|
85
|
+
}
|
|
86
|
+
return g;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The named group, or undefined if no chart has made it yet. Creates nothing.
|
|
91
|
+
* Reactive: a reader re-runs when the group is created.
|
|
92
|
+
*/
|
|
93
|
+
find(name) {
|
|
94
|
+
return this.#groups.get(name ?? 'default');
|
|
70
95
|
}
|
|
71
|
-
/**
|
|
96
|
+
/** Whether a named group exists, without creating it. Reactive, as `find`. */
|
|
72
97
|
has(name) {
|
|
73
|
-
return this.#groups.has(name);
|
|
98
|
+
return this.#groups.has(name ?? 'default');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether a named group has a selection (a brush, a toggle, a legend), without
|
|
102
|
+
* creating it: false for a group no chart has made yet. Reactive in both the
|
|
103
|
+
* group appearing and its clauses changing, so `$derived(groups.isActive('brush'))`
|
|
104
|
+
* is safe before any chart exists.
|
|
105
|
+
*/
|
|
106
|
+
isActive(name) {
|
|
107
|
+
const group = this.find(name);
|
|
108
|
+
return group ? group.activeCount > 0 : false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The names of the groups, in creation order. Reactive: a reader re-runs when a
|
|
112
|
+
* group is created. A new array on each read. For app code that must reach every group the page's marks
|
|
113
|
+
* use, such as a clause that applies to the whole page.
|
|
114
|
+
*/
|
|
115
|
+
get names() {
|
|
116
|
+
return [...this.#groups.keys()];
|
|
117
|
+
}
|
|
118
|
+
/** The groups, in the order of `names`. Reactive in the same way. */
|
|
119
|
+
get groups() {
|
|
120
|
+
return [...this.#groups.values()];
|
|
74
121
|
}
|
|
75
122
|
/** How many groups the page has: the packer writes one activity half per group. */
|
|
76
123
|
get groupCount() {
|
|
77
|
-
return this.#groups.size;
|
|
124
|
+
return untrack(() => this.#groups.size);
|
|
78
125
|
}
|
|
79
126
|
/**
|
|
80
127
|
* The selection state of a categorical field, by identity key. Created on
|
|
@@ -15,15 +15,42 @@ export interface QueryParams {
|
|
|
15
15
|
sort?: NormalizedSortConfig;
|
|
16
16
|
limit?: number;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Why a result was queried. `catchUp`: the mark came back into view stale (see
|
|
20
|
+
* `active`), so the result brings it up to date with changes it missed, which
|
|
21
|
+
* nobody just made; it lands without animating. `update`: anything else.
|
|
22
|
+
*/
|
|
23
|
+
export type ResultCause = 'update' | 'catchUp';
|
|
18
24
|
export declare class MarkHandle {
|
|
19
25
|
#private;
|
|
20
26
|
arrowTable: Table<import("@uwdata/flechette").TypeMap>;
|
|
21
27
|
dataVersion: number;
|
|
28
|
+
/**
|
|
29
|
+
* The cause of the result `dataVersion` counts, set with it. Plain, not
|
|
30
|
+
* $state: read by whatever reacts to `dataVersion`, never on its own.
|
|
31
|
+
*/
|
|
32
|
+
resultCause: ResultCause;
|
|
22
33
|
error: Error;
|
|
23
34
|
get params(): QueryParams;
|
|
24
35
|
/** Called by mark lifecycle when sort/limit change. */
|
|
25
36
|
updateParams(params: QueryParams): void;
|
|
26
37
|
_onParamsChange: (() => void) | null;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the mark is on screen (or about to be). The engine does not
|
|
40
|
+
* re-query an inactive mark that has its first result: a filter or params
|
|
41
|
+
* change only marks it stale, and it queries once, with whatever is current,
|
|
42
|
+
* when it becomes active again. Its first query always runs, since the
|
|
43
|
+
* first result can feed domains other charts share. Not reactive: the
|
|
44
|
+
* engine reads it when it would send.
|
|
45
|
+
*/
|
|
46
|
+
get active(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Returns whether this call sent a catch-up query: the mark came back
|
|
49
|
+
* stale. A return with no query means the data it holds is current, so
|
|
50
|
+
* the caller can act on its return now rather than after a result.
|
|
51
|
+
*/
|
|
52
|
+
setActive(active: boolean): boolean;
|
|
53
|
+
_onActivate: (() => boolean) | null;
|
|
27
54
|
/**
|
|
28
55
|
* Engine-internal: called to deliver query results.
|
|
29
56
|
*
|
|
@@ -33,7 +60,7 @@ export declare class MarkHandle {
|
|
|
33
60
|
* The error is cleared either way, so a success after a failure still
|
|
34
61
|
* clears it.
|
|
35
62
|
*/
|
|
36
|
-
_setResult(table: Table): void;
|
|
63
|
+
_setResult(table: Table, cause?: ResultCause): void;
|
|
37
64
|
/** Engine-internal: called on query error. */
|
|
38
65
|
_setError(error: Error): void;
|
|
39
66
|
/** Clean up registration. Set by engine during registerMark. */
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
export class MarkHandle {
|
|
9
9
|
arrowTable = $state(null);
|
|
10
10
|
dataVersion = $state(0);
|
|
11
|
+
/**
|
|
12
|
+
* The cause of the result `dataVersion` counts, set with it. Plain, not
|
|
13
|
+
* $state: read by whatever reacts to `dataVersion`, never on its own.
|
|
14
|
+
*/
|
|
15
|
+
resultCause = 'update';
|
|
11
16
|
error = $state(null);
|
|
12
17
|
#params = $state({ sort: undefined, limit: undefined });
|
|
13
18
|
get params() {
|
|
@@ -21,6 +26,32 @@ export class MarkHandle {
|
|
|
21
26
|
// Engine sets this callback during registration.
|
|
22
27
|
// Conventional underscore — engine needs external access.
|
|
23
28
|
_onParamsChange = null;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the mark is on screen (or about to be). The engine does not
|
|
31
|
+
* re-query an inactive mark that has its first result: a filter or params
|
|
32
|
+
* change only marks it stale, and it queries once, with whatever is current,
|
|
33
|
+
* when it becomes active again. Its first query always runs, since the
|
|
34
|
+
* first result can feed domains other charts share. Not reactive: the
|
|
35
|
+
* engine reads it when it would send.
|
|
36
|
+
*/
|
|
37
|
+
get active() {
|
|
38
|
+
return this.#active;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns whether this call sent a catch-up query: the mark came back
|
|
42
|
+
* stale. A return with no query means the data it holds is current, so
|
|
43
|
+
* the caller can act on its return now rather than after a result.
|
|
44
|
+
*/
|
|
45
|
+
setActive(active) {
|
|
46
|
+
if (active === this.#active)
|
|
47
|
+
return false;
|
|
48
|
+
this.#active = active;
|
|
49
|
+
return active ? (this._onActivate?.() ?? false) : false;
|
|
50
|
+
}
|
|
51
|
+
#active = true;
|
|
52
|
+
// Engine sets this during registration: query a mark that went stale, and
|
|
53
|
+
// say whether it did.
|
|
54
|
+
_onActivate = null;
|
|
24
55
|
/**
|
|
25
56
|
* Engine-internal: called to deliver query results.
|
|
26
57
|
*
|
|
@@ -30,10 +61,11 @@ export class MarkHandle {
|
|
|
30
61
|
* The error is cleared either way, so a success after a failure still
|
|
31
62
|
* clears it.
|
|
32
63
|
*/
|
|
33
|
-
_setResult(table) {
|
|
64
|
+
_setResult(table, cause = 'update') {
|
|
34
65
|
this.error = null;
|
|
35
66
|
if (table === this.arrowTable)
|
|
36
67
|
return;
|
|
68
|
+
this.resultCause = cause;
|
|
37
69
|
this.arrowTable = table;
|
|
38
70
|
this.dataVersion++;
|
|
39
71
|
}
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
import { type Table } from '@uwdata/flechette';
|
|
21
21
|
export type { BrushActivation } from './sufficient-statistics';
|
|
22
22
|
import { type QueryParts } from '../components/marks/lifecycle/query-builder';
|
|
23
|
+
import { type BuildEvent } from './preagg/Preagg';
|
|
23
24
|
import type { ActivationSource } from './preagg/plan';
|
|
24
25
|
export type { ActiveDim, ActivationSource } from './preagg/plan';
|
|
25
26
|
import { MarkHandle } from './MarkHandle.svelte';
|
|
27
|
+
import { type LaneHold, type SlicedLane } from './sliced-lane';
|
|
26
28
|
import { type DimValue, type FieldIdentity, type IdentityField, type SyntheticId } from './categorical-identity';
|
|
27
29
|
import type { FilterGroup } from './FilterGroup.svelte';
|
|
28
30
|
import { type Clause, type MarkRoute } from './CrossfilterSelection.svelte';
|
|
@@ -33,13 +35,141 @@ export interface QueryOpts {
|
|
|
33
35
|
callerId?: string;
|
|
34
36
|
/** AbortSignal for external cancellation. */
|
|
35
37
|
signal?: AbortSignal;
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
+
/**
|
|
39
|
+
* Result caching, and sharing an identical query already running.
|
|
40
|
+
*
|
|
41
|
+
* - Mark queries (with a `callerId`): on by default; `false` for queries
|
|
42
|
+
* against tables whose contents change without the SQL changing.
|
|
43
|
+
* - Ad-hoc queries (no `callerId`): off unless `true`, so an app re-reading
|
|
44
|
+
* a table it writes to sees its writes. fjader's own setup queries pass
|
|
45
|
+
* `true`: they read base tables, which change only on a data reload, after
|
|
46
|
+
* which `clearQueryCache()` is the contract.
|
|
47
|
+
*
|
|
48
|
+
* The key includes the dims' version (see #cacheKey).
|
|
49
|
+
*/
|
|
38
50
|
cache?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Who is waiting for it; see StatementClass. Default 'interaction'.
|
|
53
|
+
*
|
|
54
|
+
* `'background'` runs on another connection when the engine has one (a
|
|
55
|
+
* sliced lane on DuckDB-WASM), so a TEMP table created with `exec` on the
|
|
56
|
+
* main connection is invisible there and the query fails: pass
|
|
57
|
+
* `connectionLocal: true` for statements on connection-local state.
|
|
58
|
+
*/
|
|
59
|
+
class?: StatementClass;
|
|
60
|
+
/** Reads or writes connection-local state; see StatementPlacement. */
|
|
61
|
+
connectionLocal?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The table this read reads (base table, view or preagg table). An
|
|
64
|
+
* interaction read with one is routed by its source's cost (see
|
|
65
|
+
* SourceRouting): known-fast on the main connection, unknown or slow sliced
|
|
66
|
+
* on that source's lane. Without one it stays on the main connection.
|
|
67
|
+
*/
|
|
68
|
+
source?: string;
|
|
69
|
+
/** A mark's data read: its cost updates the source's record. */
|
|
70
|
+
data?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* This read is known to be slow (a heavy query on whatever table, as a
|
|
73
|
+
* mark with `holdsEntrance={false}` says). The engine reads it where a
|
|
74
|
+
* slow read cannot hold up others (on DuckDB-WASM, sliced on its source's
|
|
75
|
+
* lane) and does not learn the source's cost from it: the source's other
|
|
76
|
+
* reads may be quick. A backend with one connection can ignore it.
|
|
77
|
+
*/
|
|
78
|
+
slow?: boolean;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Where a statement may run, beside who waits for it.
|
|
82
|
+
*
|
|
83
|
+
* `connectionLocal`: it reads or writes state that belongs to one connection
|
|
84
|
+
* (a TEMP table, a session setting), so it stays on the main connection
|
|
85
|
+
* whatever its class. fjader's tooltip tables are TEMP: their writes, drops,
|
|
86
|
+
* hover lookups and the fits that read them pass it. Each table is written
|
|
87
|
+
* through one connection only, so statements on it keep their order. A
|
|
88
|
+
* backend with one connection, or truly shared state, can ignore it.
|
|
89
|
+
*/
|
|
90
|
+
export interface StatementPlacement {
|
|
91
|
+
class: StatementClass;
|
|
92
|
+
connectionLocal?: boolean;
|
|
93
|
+
/** See QueryOpts.source. */
|
|
94
|
+
source?: string;
|
|
95
|
+
/** See QueryOpts.data. */
|
|
96
|
+
data?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* When the read was asked for (query() called): for a mark's
|
|
99
|
+
* execute-latest read, the state it reflects. Recorded, for data age.
|
|
100
|
+
*/
|
|
101
|
+
requestedAt?: number;
|
|
102
|
+
/** The caller (a mark's id), for the log. */
|
|
103
|
+
callerId?: string;
|
|
104
|
+
/** See QueryOpts.slow. */
|
|
105
|
+
slow?: boolean;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Who is waiting for a statement. Callers state a class, never a connection:
|
|
109
|
+
* which connection (or later, which backend) serves it is the engine's
|
|
110
|
+
* business, decided in one place, `#send`.
|
|
111
|
+
*
|
|
112
|
+
* - `'interaction'`: someone is looking at the result or about to: mark data,
|
|
113
|
+
* hover lookups, a mark's setup (domains, dims). The default.
|
|
114
|
+
* - `'background'`: nobody waits for it directly: preagg builds, tooltip-table
|
|
115
|
+
* writes, cleanup. On DuckDB-WASM it runs on a sliced lane (another
|
|
116
|
+
* connection, see SlicedLane) unless `connectionLocal`, so a slow build no
|
|
117
|
+
* longer holds up interaction. Without a lane, preagg builds yield to
|
|
118
|
+
* interaction statements, not to each other's background work.
|
|
119
|
+
*/
|
|
120
|
+
export type StatementClass = 'interaction' | 'background';
|
|
121
|
+
/**
|
|
122
|
+
* For `observeIdentityRequests`: a mark asking for categorical identity
|
|
123
|
+
* columns, or a table's drain starting (when its dims actually begin to build,
|
|
124
|
+
* which is what a chart's first read waits on).
|
|
125
|
+
*/
|
|
126
|
+
export interface IdentityRequest {
|
|
127
|
+
/** 'request': a mark's ensureColumns call; 'drain': one pass of a table's drain started. */
|
|
128
|
+
kind: 'request' | 'drain';
|
|
129
|
+
table: string;
|
|
130
|
+
/** For a request, the fields asked for; for a drain pass, the fields it took. */
|
|
131
|
+
fields: string[];
|
|
132
|
+
/** performance.now() when asked. */
|
|
133
|
+
time: number;
|
|
134
|
+
}
|
|
135
|
+
/** One statement as it reached the connection, for `observeStatements`. */
|
|
136
|
+
export interface StatementRecord {
|
|
137
|
+
sql: string;
|
|
138
|
+
/** How it was sent: a query returning rows, a statement, a DESCRIBE, an Arrow insert. */
|
|
139
|
+
kind: 'query' | 'exec' | 'describe' | 'insert' | 'batch';
|
|
140
|
+
/** For a batch (execBatch): how many statements it ran as one message. */
|
|
141
|
+
count?: number;
|
|
142
|
+
class: StatementClass;
|
|
143
|
+
/** Which connection ran it: the main one, or a sliced lane. */
|
|
144
|
+
connection: 'main' | 'lane';
|
|
145
|
+
/** The source it was routed by, when it named one. */
|
|
146
|
+
source?: string;
|
|
147
|
+
/**
|
|
148
|
+
* When the read was asked for (StatementPlacement.requestedAt). With
|
|
149
|
+
* `end`, how old the state a result reflects is when it lands.
|
|
150
|
+
*/
|
|
151
|
+
requested?: number;
|
|
152
|
+
/** Who asked (a mark's id), when it said. */
|
|
153
|
+
caller?: string;
|
|
154
|
+
/** performance.now() when handed to the connection, and when it settled. */
|
|
155
|
+
start: number;
|
|
156
|
+
end: number;
|
|
157
|
+
error?: unknown;
|
|
39
158
|
}
|
|
40
159
|
export interface QueryEngineOptions {
|
|
41
160
|
/** Overrides BUILD_MAX_WAIT_MS; tests use it to make the queue deterministic. */
|
|
42
161
|
buildMaxWaitMs?: number;
|
|
162
|
+
/**
|
|
163
|
+
* Debug. Send an execBatch's statements one by one, for per-statement
|
|
164
|
+
* timing. It changes what it measures: each statement then re-enters the
|
|
165
|
+
* queue behind whatever was sent meanwhile, which a batch avoids.
|
|
166
|
+
*/
|
|
167
|
+
unbatch?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Advanced. While the main connection works, all sliced lanes together get
|
|
170
|
+
* one slice per this many ms (see SLICE_SHARE_MS, the default).
|
|
171
|
+
*/
|
|
172
|
+
sliceShareMs?: number;
|
|
43
173
|
}
|
|
44
174
|
export interface ColumnInfo {
|
|
45
175
|
name: string;
|
|
@@ -58,11 +188,15 @@ export declare function buildSchemaMap(columns: ColumnInfo[]): Map<string, Colum
|
|
|
58
188
|
/**
|
|
59
189
|
* Minimal connection interface.
|
|
60
190
|
* Supports two query paths:
|
|
61
|
-
* - queryIPC (fast): returns raw Arrow IPC bytes, decoded by flechette
|
|
191
|
+
* - queryIPC (fast): returns raw Arrow IPC bytes, decoded by flechette into typed arrays
|
|
62
192
|
* - query (fallback): returns a Table directly (for mocks/testing)
|
|
63
193
|
*/
|
|
64
194
|
export interface DuckDBConnection {
|
|
65
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* Fast path: raw Arrow IPC bytes → flechette tableFromIPC → typed arrays. Views on
|
|
197
|
+
* the bytes within one record batch (~2k rows from DuckDB-WASM); past that,
|
|
198
|
+
* flechette concatenates each column once.
|
|
199
|
+
*/
|
|
66
200
|
queryIPC?(sql: string): Promise<Uint8Array>;
|
|
67
201
|
/** Fallback query path. Used when queryIPC is not available (mocks, testing). */
|
|
68
202
|
query(sql: string): Promise<Table>;
|
|
@@ -75,6 +209,17 @@ export interface DuckDBConnection {
|
|
|
75
209
|
name: string;
|
|
76
210
|
create: boolean;
|
|
77
211
|
}): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* Open another connection on the same database whose statements run
|
|
214
|
+
* sliced (see SlicedLane): a slow read there no longer blocks this
|
|
215
|
+
* connection's queries. DuckDB-WASM via `connectionFrom`; a connection
|
|
216
|
+
* without it keeps everything on itself. The engine opens lanes itself and
|
|
217
|
+
* sends through them from `#send`, so every statement keeps its record,
|
|
218
|
+
* class and accounting; nothing hands a raw lane to an app.
|
|
219
|
+
*/
|
|
220
|
+
openSlicedLane?(options?: {
|
|
221
|
+
hold?: LaneHold;
|
|
222
|
+
}): Promise<SlicedLane>;
|
|
78
223
|
}
|
|
79
224
|
export interface QueryDescriptor {
|
|
80
225
|
/** The table the mark queries: its identity view when it has categorical channels. */
|
|
@@ -86,7 +231,29 @@ export interface QueryDescriptor {
|
|
|
86
231
|
transformCompiler: TransformCompiler;
|
|
87
232
|
/** What routing needs to know about the mark. Absent in tests that route nothing. */
|
|
88
233
|
route?: MarkRoute;
|
|
234
|
+
/**
|
|
235
|
+
* The mark is known to be slow (`holdsEntrance={false}`): its data reads
|
|
236
|
+
* go sliced from the first, whatever its source's cost. The mark, not its
|
|
237
|
+
* source: other marks on a fast table keep reading on the main connection.
|
|
238
|
+
*/
|
|
239
|
+
slow?: boolean;
|
|
89
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* How a mark's data read is placed. Routed by the table it reads
|
|
243
|
+
* (SourceRouting): the preagg table when one answers, else the mark's BASE
|
|
244
|
+
* table (its identity view reads the same rows, and its range and stats reads
|
|
245
|
+
* name the base table: one record per source). `slow` (holdsEntrance={false})
|
|
246
|
+
* applies to the base query only: a preagg read is ~1 ms, and forced onto a
|
|
247
|
+
* lane during a brush it would cost ~20 ms of round trips in exactly the case
|
|
248
|
+
* preagg exists to make fast.
|
|
249
|
+
*/
|
|
250
|
+
export declare function markReadOptions(markId: string, descriptor: Pick<QueryDescriptor, 'baseTable' | 'slow'>, preaggTable: string | null): QueryOpts;
|
|
251
|
+
/**
|
|
252
|
+
* DuckDB's catalog error for a table or view this connection cannot see.
|
|
253
|
+
* Matched by its wording, which an upgrade could change: the integration
|
|
254
|
+
* test missing-relation.integration.test.ts checks it against real DuckDB.
|
|
255
|
+
*/
|
|
256
|
+
export declare function isMissingRelation(e: unknown): boolean;
|
|
90
257
|
export declare class QueryEngine {
|
|
91
258
|
#private;
|
|
92
259
|
constructor(connection: DuckDBConnection | Promise<DuckDBConnection>, options?: QueryEngineOptions);
|
|
@@ -178,6 +345,29 @@ export declare class QueryEngine {
|
|
|
178
345
|
* dropped state still invalidates every id handed out before it.
|
|
179
346
|
*/
|
|
180
347
|
onIdentityReset(listener: () => void): () => void;
|
|
348
|
+
/**
|
|
349
|
+
* Debug: every categorical identity request (a mark's ensureColumns call),
|
|
350
|
+
* with its table, fields and time, before deduplication, and every drain
|
|
351
|
+
* start (a table's dims beginning to build, with the fields it found
|
|
352
|
+
* pending). Returns the unsubscribe; costs nothing while no one listens, and
|
|
353
|
+
* a listener that throws is logged without failing identity work.
|
|
354
|
+
*/
|
|
355
|
+
observeIdentityRequests(listener: (request: IdentityRequest) => void): () => void;
|
|
356
|
+
/**
|
|
357
|
+
* Debug: every step of every preagg build (BuildEvent): planned or
|
|
358
|
+
* skipped (with the size guard's numbers), queued (with its hover batch),
|
|
359
|
+
* started, then built, failed or dropped (with the check that failed and
|
|
360
|
+
* the values it compared). Returns the unsubscribe; costs nothing while no
|
|
361
|
+
* one listens, and a listener that throws is logged without failing the
|
|
362
|
+
* build.
|
|
363
|
+
*/
|
|
364
|
+
observeBuilds(listener: (event: BuildEvent) => void): () => void;
|
|
365
|
+
/**
|
|
366
|
+
* Listen to every statement sent to the connection, as it settles. For
|
|
367
|
+
* debugging and measurement; returns the unsubscribe. Costs nothing while
|
|
368
|
+
* no one listens.
|
|
369
|
+
*/
|
|
370
|
+
observeStatements(listener: (record: StatementRecord) => void): () => void;
|
|
181
371
|
/**
|
|
182
372
|
* Execute a query and return the Arrow result table.
|
|
183
373
|
* Accepts a SQL string or any object with toString() (e.g. mosaic-sql Query).
|
|
@@ -190,17 +380,47 @@ export declare class QueryEngine {
|
|
|
190
380
|
query(sql: string | {
|
|
191
381
|
toString(): string;
|
|
192
382
|
}, opts?: QueryOpts): Promise<Table>;
|
|
383
|
+
/**
|
|
384
|
+
* Run several statements as ONE message on the main connection, returning
|
|
385
|
+
* the last statement's rows. Each statement commits on its own and
|
|
386
|
+
* execution stops at the first failure, as when sent one by one (measured
|
|
387
|
+
* on DuckDB-WASM, /test/query-slicing §13); the error does not say which
|
|
388
|
+
* statement failed.
|
|
389
|
+
*
|
|
390
|
+
* For statements on tables the main connection owns (fjader's identity
|
|
391
|
+
* dims, present tables, views): one connection per table. Deliberately no
|
|
392
|
+
* class, source or routing options: it always runs on the main connection
|
|
393
|
+
* as interaction (so lanes hold their slices while it runs). What it saves
|
|
394
|
+
* is queue entries: a dependent chain sent one by one goes to the back of
|
|
395
|
+
* the queue at every step.
|
|
396
|
+
*
|
|
397
|
+
* Logged as one record, kind 'batch', with the joined text and `count`.
|
|
398
|
+
* Without `queryIPC` (mocks, custom connections), or with
|
|
399
|
+
* `QueryEngineOptions.unbatch`, the statements go one by one.
|
|
400
|
+
*/
|
|
401
|
+
execBatch(statements: string[]): Promise<Table>;
|
|
193
402
|
/**
|
|
194
403
|
* Execute DDL/DML statements (CREATE TABLE, DROP TABLE, INSERT, etc.).
|
|
195
404
|
* Accepts a single SQL string or an array of statements to execute
|
|
196
405
|
* sequentially.
|
|
197
406
|
*/
|
|
198
|
-
exec(sql: string | string[]
|
|
407
|
+
exec(sql: string | string[], opts?: {
|
|
408
|
+
class?: StatementClass;
|
|
409
|
+
connectionLocal?: boolean;
|
|
410
|
+
signal?: AbortSignal;
|
|
411
|
+
}): Promise<void>;
|
|
199
412
|
/**
|
|
200
413
|
* Get column info for a table via DESCRIBE.
|
|
201
414
|
* Results are cached — same table returns the same promise.
|
|
202
415
|
*/
|
|
203
416
|
describe(table: string): Promise<ColumnInfo[]>;
|
|
417
|
+
/**
|
|
418
|
+
* Tell the engine how a source reads, overriding its prior: `'slow'` for a
|
|
419
|
+
* table that is slow to query (it reads sliced, so it never holds up
|
|
420
|
+
* other charts), `'fast'` for a view that is cheap (it reads on the main
|
|
421
|
+
* connection from its first query). A measured read still corrects it.
|
|
422
|
+
*/
|
|
423
|
+
sourceCost(source: string, cost: 'fast' | 'slow'): void;
|
|
204
424
|
/** Clear cached DESCRIBE results. Pass a table name to clear one, or omit for all. */
|
|
205
425
|
clearDescribeCache(table?: string): void;
|
|
206
426
|
/**
|