@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.
Files changed (167) hide show
  1. package/README.md +3 -1
  2. package/dist/animation/EntranceGate.d.ts +52 -0
  3. package/dist/animation/EntranceGate.js +93 -0
  4. package/dist/animation/TransitionClock.d.ts +61 -30
  5. package/dist/animation/TransitionClock.js +67 -29
  6. package/dist/animation/transition-log.d.ts +41 -0
  7. package/dist/animation/transition-log.js +57 -0
  8. package/dist/components/axes/AxisBottom.svelte +27 -8
  9. package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
  10. package/dist/components/axes/AxisCenter.svelte +4 -2
  11. package/dist/components/axes/AxisLeft.svelte +81 -10
  12. package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
  13. package/dist/components/axes/BandAxisTicks.svelte +4 -3
  14. package/dist/components/axes/axis-title.d.ts +30 -0
  15. package/dist/components/axes/axis-title.js +77 -0
  16. package/dist/components/charts/ChartCell.svelte +33 -6
  17. package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
  18. package/dist/components/charts/ChartContainer.svelte +55 -5
  19. package/dist/components/charts/ChartContainer.svelte.d.ts +14 -1
  20. package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
  21. package/dist/components/charts/ChartHover.svelte.js +22 -0
  22. package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
  23. package/dist/components/charts/ChartVisibility.svelte.js +67 -0
  24. package/dist/components/guides/Grid.svelte +12 -1
  25. package/dist/components/guides/legend/ContinuousColorLegend.svelte +7 -1
  26. package/dist/components/guides/legend/OrdinalColorLegend.svelte +8 -5
  27. package/dist/components/marks/AreaMarkX.svelte +18 -4
  28. package/dist/components/marks/AreaMarkY.svelte +18 -4
  29. package/dist/components/marks/BarMarkX.svelte +20 -4
  30. package/dist/components/marks/BarMarkY.svelte +20 -4
  31. package/dist/components/marks/DotsMark.svelte +8 -3
  32. package/dist/components/marks/LineMark.svelte +13 -6
  33. package/dist/components/marks/ParallelCoordinatesMark.svelte +11 -2
  34. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +4 -0
  35. package/dist/components/marks/RectMark.svelte +8 -3
  36. package/dist/components/marks/RectXMark.svelte +9 -3
  37. package/dist/components/marks/RectYMark.svelte +9 -3
  38. package/dist/components/marks/RuleMarkX.svelte +31 -25
  39. package/dist/components/marks/RuleMarkY.svelte +31 -25
  40. package/dist/components/marks/TextMark.svelte +52 -14
  41. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +22 -1
  42. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +579 -66
  43. package/dist/components/marks/lifecycle/domain-queries.d.ts +13 -2
  44. package/dist/components/marks/lifecycle/domain-queries.js +43 -11
  45. package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
  46. package/dist/components/marks/lifecycle/domain-registration.js +17 -20
  47. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +112 -0
  48. package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
  49. package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
  50. package/dist/components/marks/lifecycle/other-split.js +112 -0
  51. package/dist/components/marks/lifecycle/query-builder.d.ts +53 -3
  52. package/dist/components/marks/lifecycle/query-builder.js +46 -23
  53. package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
  54. package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
  55. package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
  56. package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
  57. package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
  58. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  59. package/dist/components/marks/utils/reactive-config.js +9 -0
  60. package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
  61. package/dist/components/marks/utils/tooltip-helpers.js +2 -1
  62. package/dist/components/rendering/Areas.svelte +57 -19
  63. package/dist/components/rendering/Areas.svelte.d.ts +12 -0
  64. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
  65. package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -2
  66. package/dist/components/rendering/Dots.svelte +23 -3
  67. package/dist/components/rendering/Dots.svelte.d.ts +6 -0
  68. package/dist/components/rendering/GridLine.svelte +13 -1
  69. package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
  70. package/dist/components/rendering/Lines.svelte +48 -13
  71. package/dist/components/rendering/Lines.svelte.d.ts +12 -0
  72. package/dist/components/rendering/ParallelLines.svelte +5 -5
  73. package/dist/components/rendering/Rects.svelte +164 -30
  74. package/dist/components/rendering/Rects.svelte.d.ts +23 -2
  75. package/dist/components/rendering/RuleX.svelte +1 -1
  76. package/dist/components/rendering/RuleY.svelte +1 -1
  77. package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
  78. package/dist/components/rendering/activity-reads.d.ts +47 -12
  79. package/dist/components/rendering/activity-reads.js +61 -19
  80. package/dist/components/rendering/uniform-sizes.d.ts +1 -1
  81. package/dist/components/rendering/uniform-sizes.js +1 -1
  82. package/dist/config/ChartConfig.svelte.d.ts +10 -1
  83. package/dist/config/ChartConfig.svelte.js +17 -0
  84. package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
  85. package/dist/gpu/GPUCoordinator.svelte.js +164 -7
  86. package/dist/gpu/MorphedChannels.d.ts +11 -28
  87. package/dist/gpu/MorphedChannels.js +72 -8
  88. package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
  89. package/dist/gpu/ScaleTemplateSystem.js +83 -6
  90. package/dist/index.d.ts +19 -4
  91. package/dist/index.js +22 -4
  92. package/dist/interactors/Interval1D.svelte +6 -1
  93. package/dist/interactors/Interval2D.svelte +6 -1
  94. package/dist/interactors/ParallelAxisSource.svelte +10 -1
  95. package/dist/interactors/Toggle.svelte +32 -3
  96. package/dist/interactors/Tooltip.svelte +138 -41
  97. package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
  98. package/dist/interactors/TooltipManager.svelte.js +46 -12
  99. package/dist/interactors/other-tooltip.d.ts +16 -0
  100. package/dist/interactors/other-tooltip.js +22 -0
  101. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
  102. package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
  103. package/dist/query-engine/FieldActivity.svelte.d.ts +49 -1
  104. package/dist/query-engine/FieldActivity.svelte.js +175 -5
  105. package/dist/query-engine/FilterGroup.svelte.d.ts +23 -1
  106. package/dist/query-engine/FilterGroup.svelte.js +43 -3
  107. package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +31 -3
  108. package/dist/query-engine/FilterGroupRegistry.svelte.js +62 -15
  109. package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
  110. package/dist/query-engine/MarkHandle.svelte.js +33 -1
  111. package/dist/query-engine/QueryEngine.d.ts +225 -5
  112. package/dist/query-engine/QueryEngine.js +697 -47
  113. package/dist/query-engine/categorical-identity.d.ts +41 -0
  114. package/dist/query-engine/categorical-identity.js +145 -65
  115. package/dist/query-engine/debug/query-log.d.ts +39 -0
  116. package/dist/query-engine/debug/query-log.js +90 -0
  117. package/dist/query-engine/duckdb.d.ts +55 -8
  118. package/dist/query-engine/duckdb.js +82 -30
  119. package/dist/query-engine/index.d.ts +7 -4
  120. package/dist/query-engine/index.js +3 -3
  121. package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
  122. package/dist/query-engine/preagg/BuildQueue.js +51 -15
  123. package/dist/query-engine/preagg/Preagg.d.ts +46 -3
  124. package/dist/query-engine/preagg/Preagg.js +159 -31
  125. package/dist/query-engine/preagg/plan.d.ts +33 -1
  126. package/dist/query-engine/preagg/plan.js +158 -14
  127. package/dist/query-engine/sliced-lane.d.ts +108 -0
  128. package/dist/query-engine/sliced-lane.js +299 -0
  129. package/dist/query-engine/source-routing.d.ts +106 -0
  130. package/dist/query-engine/source-routing.js +189 -0
  131. package/dist/query-engine/toggle-predicate.d.ts +21 -0
  132. package/dist/query-engine/toggle-predicate.js +29 -1
  133. package/dist/scales/ActivityTable.d.ts +5 -1
  134. package/dist/scales/ActivityTable.js +12 -3
  135. package/dist/scales/BandScaleState.svelte.d.ts +22 -9
  136. package/dist/scales/BandScaleState.svelte.js +39 -21
  137. package/dist/scales/ChartScales.svelte.d.ts +3 -0
  138. package/dist/scales/ChartScales.svelte.js +26 -8
  139. package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
  140. package/dist/scales/ColorScaleState.svelte.js +138 -26
  141. package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
  142. package/dist/scales/LinearScaleState.svelte.js +21 -39
  143. package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
  144. package/dist/scales/ScaleGeneration.svelte.js +42 -0
  145. package/dist/scales/ScaleRegistry.svelte.js +3 -1
  146. package/dist/scales/SlotTransition.d.ts +15 -1
  147. package/dist/scales/SlotTransition.js +33 -4
  148. package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
  149. package/dist/scales/TimeScaleState.svelte.js +7 -7
  150. package/dist/scales/color-utils.d.ts +0 -3
  151. package/dist/scales/color-utils.js +0 -3
  152. package/dist/scales/entered.d.ts +8 -0
  153. package/dist/scales/entered.js +10 -0
  154. package/dist/spec/LazyQueryEngine.js +2 -19
  155. package/dist/spec/MarkRegistry.d.ts +3 -1
  156. package/dist/spec/MarkRegistry.js +13 -1
  157. package/dist/spec/PlotFromSpec.svelte +1 -0
  158. package/dist/spec/export.js +11 -2
  159. package/dist/spec/types.d.ts +43 -13
  160. package/dist/sql/bin.d.ts +25 -0
  161. package/dist/sql/bin.js +108 -5
  162. package/dist/theme.css +5 -4
  163. package/dist/transforms/TransformCompiler.d.ts +47 -2
  164. package/dist/transforms/TransformCompiler.js +70 -1
  165. package/dist/transforms/index.js +176 -42
  166. package/dist/types.d.ts +177 -2
  167. package/package.json +4 -3
@@ -1,43 +1,47 @@
1
1
  /**
2
2
  * DuckDB-WASM initialization — replaces mosaic-core's wasmConnector.
3
3
  *
4
- * Returns a Promise<DuckDBConnection> suitable for passing to setQueryContext().
5
- * Uses jsdelivr CDN bundles (same as mosaic-core's wasmConnector).
4
+ * initDuckDB() returns a Promise<DuckDBConnection> suitable for passing to
5
+ * setQueryContext(). It loads the jsDelivr bundles unless the app passes its own.
6
6
  *
7
- * Uses the fast IPC query path: bypasses duckdb-wasm's Arrow JS deserialization
8
- * and returns raw Arrow IPC bytes. QueryEngine decodes them with flechette for
9
- * zero-copy typed arrays that go straight to GPU buffers.
7
+ * connectionFrom() adapts a duckdb-wasm connection the app created itself, for
8
+ * apps that need the AsyncDuckDB instance (registerFileBuffer, extensions, …).
10
9
  *
11
- * Safe to call during SSR — returns a permanently pending promise on the
12
- * server. The component re-runs on the client where Worker is available.
10
+ * Both use the fast IPC query path: bypasses duckdb-wasm's Arrow JS
11
+ * deserialization and returns raw Arrow IPC bytes. QueryEngine decodes them with
12
+ * flechette into typed arrays for the GPU buffers. These are views on the IPC
13
+ * bytes only up to one record batch: DuckDB-WASM writes a batch per DuckDB vector
14
+ * (~2k rows), and flechette concatenates a column that spans several.
13
15
  */
16
+ import { PendingQueryLane } from './sliced-lane';
14
17
  /**
15
- * Initialize DuckDB-WASM and return a connection.
18
+ * How long the worker runs a sliced statement before serving other messages:
19
+ * a quick query on another connection waits at most about this long behind a
20
+ * slow one. Measured in Phase 0: 1 ms at 5 ms, ~50 ms at the default (~100 ms).
21
+ */
22
+ export const QUERY_POLLING_INTERVAL_MS = 5;
23
+ /**
24
+ * Adapt a duckdb-wasm connection to the DuckDBConnection QueryEngine expects,
25
+ * with the fast IPC query path, and sliced lanes: further connections on the
26
+ * same database (`openSlicedLane`).
16
27
  *
17
- * Usage:
18
- * const { queryEngine } = setQueryContext(initDuckDB());
28
+ * const db = new duckdb.AsyncDuckDB(logger, worker);
29
+ * await db.instantiate(…);
30
+ * await db.open({ query: { queryPollingInterval: 5 } }); // short slices, see below
31
+ * await db.registerFileBuffer('cached.parquet', bytes);
32
+ * initFjader(connectionFrom(await db.connect(), { pollingIntervalMs: 5 }));
33
+ *
34
+ * Sliced statements run in slices of the database's `queryPollingInterval`.
35
+ * At DuckDB's default (~100 ms) a quick query can wait ~50 ms behind a slow
36
+ * one; open the database with ~5 ms, as `initDuckDB` does. It is a setting of
37
+ * the database, which this function cannot change or read: say what you set
38
+ * as `pollingIntervalMs`. Without it, lanes measure their slices and warn if
39
+ * they run long.
19
40
  */
20
- export function initDuckDB() {
21
- if (typeof window === 'undefined') {
22
- // SSR: return pending promise. QueryEngine defers until resolved.
23
- // Component re-runs on client where the real init happens.
24
- return new Promise(() => { });
25
- }
26
- return doInit();
27
- }
28
- async function doInit() {
29
- const duckdb = await import('@duckdb/duckdb-wasm');
30
- const bundles = duckdb.getJsDelivrBundles();
31
- const bundle = await duckdb.selectBundle(bundles);
32
- const workerUrl = URL.createObjectURL(new Blob([`importScripts("${bundle.mainWorker}");`], { type: 'text/javascript' }));
33
- const worker = new Worker(workerUrl);
34
- const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), worker);
35
- await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
36
- URL.revokeObjectURL(workerUrl);
37
- const conn = await db.connect();
38
- // Wrap the duckdb-wasm connection to expose the fast IPC query path.
41
+ export function connectionFrom(conn, options = {}) {
39
42
  // useUnsafe + bindings.runQuery bypasses Arrow JS deserialization entirely,
40
- // returning raw IPC bytes that flechette decodes into zero-copy typed arrays.
43
+ // returning raw IPC bytes that flechette decodes into typed arrays (one copy per
44
+ // column once a result spans more than one ~2k-row record batch).
41
45
  return {
42
46
  queryIPC(sql) {
43
47
  return conn.useUnsafe((bindings, connId) => bindings.runQuery(connId, sql));
@@ -56,6 +60,54 @@ async function doInit() {
56
60
  },
57
61
  insertArrowFromIPCStream(data, options) {
58
62
  return conn.insertArrowFromIPCStream(data, options);
63
+ },
64
+ async openSlicedLane(laneOptions = {}) {
65
+ // Another connection on the same AsyncDuckDB: one catalog, its own
66
+ // pending query.
67
+ const lane = await conn.bindings.connect();
68
+ return lane.useUnsafe((bindings, connId) => new PendingQueryLane(bindings, connId, () => lane.close(), {
69
+ pollingIntervalMs: options.pollingIntervalMs,
70
+ hold: laneOptions.hold
71
+ }));
59
72
  }
60
73
  };
61
74
  }
75
+ /**
76
+ * Initialize DuckDB-WASM and return a connection.
77
+ *
78
+ * Safe to call during SSR — returns a permanently pending promise on the
79
+ * server. The component re-runs on the client where Worker is available.
80
+ *
81
+ * Usage:
82
+ * const { queryEngine } = setQueryContext(initDuckDB());
83
+ */
84
+ export function initDuckDB(options = {}) {
85
+ if (typeof window === 'undefined') {
86
+ // SSR: return pending promise. QueryEngine defers until resolved.
87
+ // Component re-runs on client where the real init happens.
88
+ return new Promise(() => { });
89
+ }
90
+ return doInit(options);
91
+ }
92
+ async function doInit({ bundles }) {
93
+ const duckdb = await import('@duckdb/duckdb-wasm');
94
+ const bundle = await duckdb.selectBundle(bundles ?? duckdb.getJsDelivrBundles());
95
+ // The worker starts from a blob: URL so a cross-origin (CDN) worker script can
96
+ // load. importScripts there resolves against the blob: URL, so an app's
97
+ // relative bundle URL has to be made absolute first.
98
+ const mainWorker = new URL(bundle.mainWorker, location.href).href;
99
+ const workerUrl = URL.createObjectURL(new Blob([`importScripts(${JSON.stringify(mainWorker)});`], { type: 'text/javascript' }));
100
+ const worker = new Worker(workerUrl);
101
+ const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), worker);
102
+ try {
103
+ await db.instantiate(new URL(bundle.mainModule, location.href).href, bundle.pthreadWorker && new URL(bundle.pthreadWorker, location.href).href);
104
+ }
105
+ finally {
106
+ URL.revokeObjectURL(workerUrl);
107
+ }
108
+ // Short slices for sliced lanes. Only the polling interval: the query
109
+ // config's other fields decide how big integers, decimals, timestamps and
110
+ // durations come back, and stay at their defaults.
111
+ await db.open({ query: { queryPollingInterval: QUERY_POLLING_INTERVAL_MS } });
112
+ return connectionFrom(await db.connect(), { pollingIntervalMs: QUERY_POLLING_INTERVAL_MS });
113
+ }
@@ -11,7 +11,7 @@
11
11
  * context.set('filter-groups', filterGroups);
12
12
  */
13
13
  import { QueryEngine } from './QueryEngine';
14
- import type { DuckDBConnection } from './QueryEngine';
14
+ import type { DuckDBConnection, QueryEngineOptions } from './QueryEngine';
15
15
  import { FilterGroupRegistry } from './FilterGroupRegistry.svelte';
16
16
  /**
17
17
  * Create the FilterGroupRegistry for an engine and wire both to the schema
@@ -27,16 +27,19 @@ export declare function createQueryServices(queryEngine: QueryEngine): {
27
27
  * Call this synchronously in a component's <script> block.
28
28
  * Accepts a Promise<DuckDBConnection> — queries defer until it resolves.
29
29
  */
30
- export declare function setQueryContext(connection: DuckDBConnection | Promise<DuckDBConnection>): {
30
+ export declare function setQueryContext(connection: DuckDBConnection | Promise<DuckDBConnection>, options?: QueryEngineOptions): {
31
31
  queryEngine: QueryEngine;
32
32
  filterGroups: FilterGroupRegistry;
33
33
  };
34
34
  /** Get the FilterGroupRegistry from context. */
35
35
  export declare function getFilterGroups(): FilterGroupRegistry;
36
- export { initDuckDB } from './duckdb';
36
+ export { initDuckDB, connectionFrom } from './duckdb';
37
+ export type { SlicedLane } from './sliced-lane';
38
+ export type { InitDuckDBOptions } from './duckdb';
37
39
  export { QueryEngine } from './QueryEngine';
38
- export type { DuckDBConnection, QueryOpts, ColumnInfo, ColumnSchema, JsColumnType, QueryDescriptor, ActiveDim, ActivationSource, QueryEngineOptions } from './QueryEngine';
40
+ export type { DuckDBConnection, QueryOpts, ColumnInfo, ColumnSchema, JsColumnType, QueryDescriptor, ActiveDim, ActivationSource, QueryEngineOptions, StatementClass, StatementRecord } from './QueryEngine';
39
41
  export { mapSqlTypeToJs, buildSchemaMap } from './QueryEngine';
42
+ export type { BuildEvent } from './preagg/Preagg';
40
43
  export { MarkHandle } from './MarkHandle.svelte';
41
44
  export type { QueryParams } from './MarkHandle.svelte';
42
45
  export { FilterGroupRegistry } from './FilterGroupRegistry.svelte';
@@ -30,8 +30,8 @@ export function createQueryServices(queryEngine) {
30
30
  * Call this synchronously in a component's <script> block.
31
31
  * Accepts a Promise<DuckDBConnection> — queries defer until it resolves.
32
32
  */
33
- export function setQueryContext(connection) {
34
- const { queryEngine, filterGroups } = createQueryServices(new QueryEngine(connection));
33
+ export function setQueryContext(connection, options) {
34
+ const { queryEngine, filterGroups } = createQueryServices(new QueryEngine(connection, options));
35
35
  setContext('query-engine', queryEngine);
36
36
  setContext('filter-groups', filterGroups);
37
37
  return { queryEngine, filterGroups };
@@ -41,7 +41,7 @@ export function getFilterGroups() {
41
41
  return getContext('filter-groups');
42
42
  }
43
43
  // Re-exports
44
- export { initDuckDB } from './duckdb';
44
+ export { initDuckDB, connectionFrom } from './duckdb';
45
45
  export { QueryEngine } from './QueryEngine';
46
46
  export { mapSqlTypeToJs, buildSchemaMap } from './QueryEngine';
47
47
  export { MarkHandle } from './MarkHandle.svelte';
@@ -29,16 +29,27 @@ export interface QueuedBuild {
29
29
  }
30
30
  /** What the queue needs from its owner. */
31
31
  export interface BuildQueueHost {
32
- exec(sql: string): Promise<void>;
33
- /** Whether a mark query is running or waiting. */
32
+ exec(sql: string, signal: AbortSignal): Promise<void>;
33
+ /**
34
+ * Whether an interaction statement is on the connection, or a mark query is
35
+ * waiting to be sent. Background statements (other builds, tooltip writes)
36
+ * do not count.
37
+ */
34
38
  busy(): boolean;
35
39
  /**
36
- * Whether a build is still wanted, checked when it would be sent: a mark
37
- * re-registered, the view moved or a baked filter changed while it waited.
40
+ * Why a build is no longer wanted, checked when it would be sent (a mark
41
+ * re-registered, the view moved or a baked filter changed while it
42
+ * waited), or null when it still is.
43
+ */
44
+ unwanted(item: QueuedBuild): string | null;
45
+ /** A build is being sent. */
46
+ started?(item: QueuedBuild): void;
47
+ /**
48
+ * A build was dropped before it ran, or cancelled while running, so the
49
+ * next activation plans it again. `reason` says which: a check that
50
+ * failed (`unwanted`), 'newer batch', 'cleared' or 'cancelled'.
38
51
  */
39
- stillWanted(item: QueuedBuild): boolean;
40
- /** A queued build was dropped before it ran, so the next activation plans it again. */
41
- dropped(item: QueuedBuild): void;
52
+ dropped(item: QueuedBuild, reason: string): void;
42
53
  built(item: QueuedBuild): void;
43
54
  failed(item: QueuedBuild, error: unknown): void;
44
55
  }
@@ -52,6 +63,8 @@ export declare class BuildQueue {
52
63
  * behind the build instead.
53
64
  */
54
65
  constructor(host: BuildQueueHost, maxWaitMs: number);
66
+ /** The current hover batch's number, for build records. */
67
+ get batch(): number;
55
68
  /** Run a hover's activations as one batch. Nested calls join the outer batch. */
56
69
  runBatch(activations: () => void): void;
57
70
  /** Start a batch unless one is running: an activation outside runBatch() is a batch of one. */
@@ -59,11 +72,12 @@ export declare class BuildQueue {
59
72
  /** Queue a build in the current batch. Settles when it has run or been dropped. */
60
73
  push(build: Omit<QueuedBuild, 'batch' | 'queuedAt' | 'done'>): Promise<void>;
61
74
  /**
62
- * Send the next queued build if nothing else is on the connection: no build
63
- * running, and no mark query waiting or running. Called when a build is
64
- * queued, when one finishes, and when a mark query settles. Queries without
65
- * a caller id (identity lookups, describe, ad-hoc queries) do not hold builds
66
- * back and can wait behind a running one; none is on an interaction path.
75
+ * Send the next queued build if nothing someone waits for is on the
76
+ * connection: no build running, no interaction statement in flight, and no
77
+ * mark query waiting. Called when a build is queued, when one finishes, when
78
+ * a mark query settles, and when the last interaction statement settles.
79
+ * Background statements (tooltip-table writes, cleanup) do not hold builds
80
+ * back.
67
81
  *
68
82
  * Mark queries hold a build back for at most the wait limit. A drag that
69
83
  * starts before its source's tables are built reads the base table, each
@@ -71,7 +85,13 @@ export declare class BuildQueue {
71
85
  * never run; past the limit it is sent anyway, and one frame waits for it.
72
86
  */
73
87
  pump(): void;
74
- /** Drop every queued build (a data reload); a running build still finishes. */
88
+ /**
89
+ * Cancel the running build if `unwanted` says so. On a lane it is
90
+ * interrupted and rolls back (no table); on a shared connection it cannot
91
+ * be, and finishes, but is not registered.
92
+ */
93
+ abortRunning(unwanted: (item: QueuedBuild) => boolean): void;
94
+ /** Drop every queued build and cancel the running one (a data reload). */
75
95
  clear(): void;
76
96
  /** Stop for good: settle every waiting activation and send nothing more. */
77
97
  destroy(): void;
@@ -19,6 +19,8 @@ export class BuildQueue {
19
19
  /** Waiting builds, oldest first. */
20
20
  #queue = [];
21
21
  #running = false;
22
+ /** The build being sent, and how to cancel it. */
23
+ #current = null;
22
24
  #destroyed = false;
23
25
  /** Pumps the queue when the oldest build's wait runs out, if no query does first. */
24
26
  #waitTimer = null;
@@ -36,6 +38,10 @@ export class BuildQueue {
36
38
  this.#host = host;
37
39
  this.#maxWaitMs = maxWaitMs;
38
40
  }
41
+ /** The current hover batch's number, for build records. */
42
+ get batch() {
43
+ return this.#batch;
44
+ }
39
45
  /** Run a hover's activations as one batch. Nested calls join the outer batch. */
40
46
  runBatch(activations) {
41
47
  if (this.#inBatch) {
@@ -63,11 +69,12 @@ export class BuildQueue {
63
69
  });
64
70
  }
65
71
  /**
66
- * Send the next queued build if nothing else is on the connection: no build
67
- * running, and no mark query waiting or running. Called when a build is
68
- * queued, when one finishes, and when a mark query settles. Queries without
69
- * a caller id (identity lookups, describe, ad-hoc queries) do not hold builds
70
- * back and can wait behind a running one; none is on an interaction path.
72
+ * Send the next queued build if nothing someone waits for is on the
73
+ * connection: no build running, no interaction statement in flight, and no
74
+ * mark query waiting. Called when a build is queued, when one finishes, when
75
+ * a mark query settles, and when the last interaction statement settles.
76
+ * Background statements (tooltip-table writes, cleanup) do not hold builds
77
+ * back.
71
78
  *
72
79
  * Mark queries hold a build back for at most the wait limit. A drag that
73
80
  * starts before its source's tables are built reads the base table, each
@@ -90,32 +97,61 @@ export class BuildQueue {
90
97
  }
91
98
  }
92
99
  const item = this.#queue.shift();
93
- if (!this.#host.stillWanted(item)) {
94
- this.#drop(item);
100
+ const reason = this.#host.unwanted(item);
101
+ if (reason !== null) {
102
+ this.#drop(item, reason);
95
103
  continue;
96
104
  }
97
105
  this.#running = true;
106
+ this.#host.started?.(item);
107
+ const abort = new AbortController();
108
+ this.#current = { item, abort };
98
109
  this.#host
99
- .exec(item.createSQL)
100
- .then(() => this.#host.built(item))
101
- .catch((e) => this.#host.failed(item, e))
110
+ .exec(item.createSQL, abort.signal)
111
+ .then(() => {
112
+ // Cancelled but finished anyway (the cancel lost the race): the
113
+ // caller gave it up, so it does not land.
114
+ if (abort.signal.aborted)
115
+ this.#host.dropped(item, 'cancelled');
116
+ else
117
+ this.#host.built(item);
118
+ })
119
+ .catch((e) => {
120
+ if (abort.signal.aborted)
121
+ this.#host.dropped(item, 'cancelled');
122
+ else
123
+ this.#host.failed(item, e);
124
+ })
102
125
  .finally(() => {
103
126
  this.#running = false;
127
+ this.#current = null;
104
128
  item.done();
105
129
  this.pump();
106
130
  });
107
131
  return;
108
132
  }
109
133
  }
110
- /** Drop every queued build (a data reload); a running build still finishes. */
134
+ /**
135
+ * Cancel the running build if `unwanted` says so. On a lane it is
136
+ * interrupted and rolls back (no table); on a shared connection it cannot
137
+ * be, and finishes, but is not registered.
138
+ */
139
+ abortRunning(unwanted) {
140
+ const current = this.#current;
141
+ if (current && unwanted(current.item))
142
+ current.abort.abort();
143
+ }
144
+ /** Drop every queued build and cancel the running one (a data reload). */
111
145
  clear() {
112
146
  for (const item of this.#queue)
113
- this.#drop(item);
147
+ this.#drop(item, 'cleared');
114
148
  this.#queue = [];
149
+ this.abortRunning(() => true);
115
150
  }
116
151
  /** Stop for good: settle every waiting activation and send nothing more. */
117
152
  destroy() {
118
153
  this.#destroyed = true;
154
+ this.abortRunning(() => true);
119
155
  if (this.#waitTimer)
120
156
  clearTimeout(this.#waitTimer);
121
157
  this.#waitTimer = null;
@@ -130,12 +166,12 @@ export class BuildQueue {
130
166
  if (item.batch === this.#batch)
131
167
  keep.push(item);
132
168
  else
133
- this.#drop(item);
169
+ this.#drop(item, 'newer batch');
134
170
  }
135
171
  this.#queue = keep;
136
172
  }
137
- #drop(item) {
138
- this.#host.dropped(item);
173
+ #drop(item, reason) {
174
+ this.#host.dropped(item, reason);
139
175
  item.done();
140
176
  }
141
177
  }
@@ -25,18 +25,56 @@ export interface PreaggMark {
25
25
  handle: MarkHandle;
26
26
  filterGroup: FilterGroup;
27
27
  }
28
+ /**
29
+ * One step of a preagg build, for `QueryEngine.observeBuilds` (debugging and
30
+ * measurement). In order for one build: planned, queued, started, then built,
31
+ * failed or dropped. A planning that builds nothing is `skipped`.
32
+ */
33
+ export interface BuildEvent {
34
+ kind: 'skipped' | 'planned' | 'queued' | 'started' | 'built' | 'failed' | 'dropped';
35
+ markId: string;
36
+ sourceId: string;
37
+ /** The preagg table, once planned. */
38
+ table: string | null;
39
+ /** performance.now(). */
40
+ time: number;
41
+ /** The hover batch the build was queued in. */
42
+ batch?: number;
43
+ /**
44
+ * skipped: 'unplannable' or 'too large'. dropped: the check that failed
45
+ * ('unregistered', 'inactive', 'superseded', 'parts changed', 'bins do not
46
+ * fit the view', 'baked clauses changed'), or 'newer batch', 'cleared',
47
+ * 'cancelled'.
48
+ */
49
+ reason?: string;
50
+ /** The values the failed check compared (see #dropDetail), or the size guard's. */
51
+ detail?: Record<string, unknown>;
52
+ /** failed: the error. */
53
+ error?: unknown;
54
+ }
28
55
  /** What Preagg needs from the engine. */
29
56
  export interface PreaggHost {
30
- exec(sql: string): Promise<void>;
57
+ exec(sql: string, signal?: AbortSignal): Promise<void>;
31
58
  query(sql: string, opts: {
32
59
  cache: boolean;
33
60
  }): Promise<Table>;
34
61
  /** Whether a mark query is running or waiting. */
35
62
  busy(): boolean;
63
+ /**
64
+ * Whether background statements run sliced on their own connection (a
65
+ * lane), where a build no longer holds up interaction.
66
+ */
67
+ sliced(): boolean;
36
68
  identity: PreaggIdentity;
37
69
  /** The mark's current registration, which a re-setup replaces. */
38
70
  mark(markId: string): PreaggMark | undefined;
39
71
  marks(): Iterable<[string, PreaggMark]>;
72
+ /**
73
+ * Whether anything listens to onBuild. Checked first, so with no
74
+ * listener no record is built.
75
+ */
76
+ observingBuilds?(): boolean;
77
+ onBuild?(event: BuildEvent): void;
40
78
  }
41
79
  export declare class Preagg {
42
80
  #private;
@@ -97,13 +135,18 @@ export declare class Preagg {
97
135
  * A data reload: drop every queued build, every mark's tables and the tables
98
136
  * themselves (CREATE TABLE IF NOT EXISTS would otherwise keep serving
99
137
  * pre-reload aggregates), and the row counts.
138
+ *
139
+ * The running build is cancelled too (`clear`). On a lane it runs beside the
140
+ * reload, reading the old rows through its snapshot; should it finish anyway
141
+ * (the cancel lost the race), `built` finds its mark gone from `#marks` and
142
+ * does not register it, and the DROP below follows it on the same lane.
100
143
  */
101
144
  reset(): void;
102
145
  /**
103
146
  * The engine is going: settle waiting activations and drop this session's
104
147
  * tables. Resolves once the drops have run; call it while the engine can
105
- * still exec. A build already running is ahead of the drops on the one
106
- * connection, so its table is dropped too.
148
+ * still exec. A build already running is cancelled, and is ahead of the
149
+ * drops on its connection either way, so its table is dropped too.
107
150
  */
108
151
  destroy(): Promise<void>;
109
152
  }