@svgrid/enterprise 3.1.0 → 3.1.2

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 (78) hide show
  1. package/dist/SvSheet.svelte +93 -8
  2. package/dist/cdn/svgrid-enterprise.svelte-external.js +9339 -9077
  3. package/dist/designer/assets/{GridMenus-8gTNssgV.js → GridMenus-DGZJKFvX.js} +1 -1
  4. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +1 -0
  5. package/dist/designer/assets/{SvGridCellEditor-DMoWJjEI.js → SvGridCellEditor-C7NWcnNC.js} +1 -1
  6. package/dist/designer/assets/{SvGridChart-Cy1ATrWh.js → SvGridChart-Fg43Yi0M.js} +2 -2
  7. package/dist/designer/assets/{SvGridChartBuilder-eQ3mn0L9.js → SvGridChartBuilder-BqGuA3yU.js} +1 -1
  8. package/dist/designer/assets/{SvGridChartPanel-CATdVc9X.js → SvGridChartPanel-BsiXRAnx.js} +3 -3
  9. package/dist/designer/assets/{SvGridChartView-CmsZtCED.js → SvGridChartView-BgttK0tR.js} +1 -1
  10. package/dist/designer/assets/{SvListBox-BDS66lmZ.js → SvListBox-v2jm0-vf.js} +1 -1
  11. package/dist/designer/assets/cell-formatting-BWrW_O0n.js +1 -0
  12. package/dist/designer/assets/{chart-Cn7MnNqA.js → chart-BIl-dIRv.js} +1 -1
  13. package/dist/designer/assets/{chart-panel-messages-Dz7PTIqT.js → chart-panel-messages-4cF2vw7L.js} +1 -1
  14. package/dist/designer/assets/{chart-summary-BD1qYB2R.js → chart-summary-B7bTtSCC.js} +1 -1
  15. package/dist/designer/assets/{chart-trend-B8xr5LuD.js → chart-trend-CLsn5Hgd.js} +1 -1
  16. package/dist/designer/assets/export-format-DGscOjS9.js +6 -0
  17. package/dist/designer/assets/{index-BSjv6Qec.js → index-V_cMXdxt.js} +314 -314
  18. package/dist/designer/assets/index-l21iGkpF.css +1 -0
  19. package/dist/designer/assets/src-Mu7AGqT8.js +58 -0
  20. package/dist/designer/assets/validate-YxSrBF1H.js +1 -0
  21. package/dist/designer/index.html +12 -12
  22. package/dist/export-ooxml.d.ts +10 -0
  23. package/dist/export-ooxml.js +22 -5
  24. package/dist/import.js +11 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +1 -1
  27. package/dist/node/studio.js +236 -132
  28. package/dist/server/index.d.ts +1 -0
  29. package/dist/server/index.js +1 -0
  30. package/dist/server/server-row-model.d.ts +9 -0
  31. package/dist/server/server-row-model.js +120 -3
  32. package/dist/server/worker-data-source.d.ts +62 -0
  33. package/dist/server/worker-data-source.js +133 -0
  34. package/dist/sheet/objects.d.ts +47 -0
  35. package/dist/sheet/objects.js +33 -0
  36. package/dist/studio/cli.js +55 -1
  37. package/dist/studio/emit-project.js +8 -3
  38. package/dist/studio/init-flow.d.ts +6 -1
  39. package/dist/studio/init-flow.js +43 -2
  40. package/dist/studio/scaffold-app.d.ts +6 -0
  41. package/dist/studio/scaffold-app.js +5 -3
  42. package/dist/studio/scaffold.js +29 -12
  43. package/dist/sveltekit/in-memory.d.ts +21 -1
  44. package/dist/sveltekit/in-memory.js +153 -105
  45. package/dist/sveltekit/index.d.ts +1 -1
  46. package/dist/version.d.ts +14 -1
  47. package/dist/version.js +14 -1
  48. package/dist/wc/{GridMenus-By9vPuq0.js → GridMenus-6_fF5jIm.js} +2 -2
  49. package/dist/wc/{SvGrid.helpers-BdVDtjUm.js → SvGrid.helpers-zBkDTryR.js} +36 -37
  50. package/dist/wc/{SvGridCellEditor-Bnf1Kr5C.js → SvGridCellEditor-CzUKEbkI.js} +1 -1
  51. package/dist/wc/{SvGridChart-BqUJh2pG.js → SvGridChart-C6UvNIbN.js} +3 -3
  52. package/dist/wc/{SvGridChartBuilder-CGycC5cn.js → SvGridChartBuilder-ikf_5LyW.js} +3 -3
  53. package/dist/wc/{SvGridChartPanel-CRnizsHD.js → SvGridChartPanel-Dc_jj7B-.js} +4 -4
  54. package/dist/wc/{SvGridChartView-D1pa_xMy.js → SvGridChartView-thkznAOb.js} +2 -2
  55. package/dist/wc/{cell-formatting-CBfgahv1.js → cell-formatting-hb3vzxhJ.js} +27 -5
  56. package/dist/wc/{chart-6OXlybVT.js → chart-CV5D7_k8.js} +1 -1
  57. package/dist/wc/{chart-summary-2m3ia1-n.js → chart-summary-DGx0QhJa.js} +1 -1
  58. package/dist/wc/{chart-trend-CQOQtTMl.js → chart-trend-BIRH_xH8.js} +1 -1
  59. package/dist/wc/sv-sheet-element.js +7454 -7352
  60. package/dist/wc/{svelte-virtualizer.svelte-CXtQy2TQ.js → svelte-virtualizer.svelte-Du4tjrIP.js} +7 -3
  61. package/dist/wc/{validate-BqDthkNr.js → validate-JAuMsk6Z.js} +36 -36
  62. package/dist/worker/columnar.d.ts +42 -0
  63. package/dist/worker/columnar.js +410 -0
  64. package/dist/worker/index.d.ts +12 -0
  65. package/dist/worker/index.js +12 -0
  66. package/dist/worker/infer-schema.d.ts +21 -0
  67. package/dist/worker/infer-schema.js +34 -0
  68. package/dist/worker/protocol.d.ts +73 -0
  69. package/dist/worker/protocol.js +14 -0
  70. package/dist/worker/serve.d.ts +44 -0
  71. package/dist/worker/serve.js +100 -0
  72. package/package.json +7 -3
  73. package/dist/designer/assets/SvGrid.helpers-BgT-gnCo.js +0 -1
  74. package/dist/designer/assets/cell-formatting-BbubJTVz.js +0 -1
  75. package/dist/designer/assets/export-format-DGckmgKr.js +0 -6
  76. package/dist/designer/assets/index-_JVFxt02.css +0 -1
  77. package/dist/designer/assets/src-DUQ4xE3x.js +0 -58
  78. package/dist/designer/assets/validate-C3B8QKsM.js +0 -1
@@ -441,11 +441,14 @@ function D(e) {
441
441
  }
442
442
  return {
443
443
  setOptions(e) {
444
- let n = {
444
+ let r = {
445
445
  ...t,
446
446
  ...e
447
- };
448
- b(t, n) || (t = n, s());
447
+ }, i = typeof e.estimateSize == "function";
448
+ !i && b(t, r) || (t = r, i && (n = null), s());
449
+ },
450
+ measure() {
451
+ n = null, s();
449
452
  },
450
453
  setScrollOffset(e) {
451
454
  (t.scrollOffset ?? 0) !== e && (t = {
@@ -516,6 +519,7 @@ function O(r) {
516
519
  return e(a);
517
520
  },
518
521
  setOptions: i.setOptions,
522
+ measure: i.measure,
519
523
  setScrollOffset: i.setScrollOffset,
520
524
  setViewportHeight: i.setViewportHeight,
521
525
  scrollToIndex: i.scrollToIndex,
@@ -8,69 +8,69 @@ function t(t) {
8
8
  }
9
9
  //#endregion
10
10
  //#region ../grid/dist/validate.js
11
- var n = 10, r = "https://svgrid.com/docs/getting-started/3-data-and-columns/";
12
- function i(e, t = []) {
13
- for (let n of e) n.columns?.length ? i(n.columns, t) : t.push(n);
11
+ var n = 10, r = 1e3, i = "https://svgrid.com/docs/getting-started/3-data-and-columns/";
12
+ function a(e, t = []) {
13
+ for (let n of e) n.columns?.length ? a(n.columns, t) : t.push(n);
14
14
  return t;
15
15
  }
16
- function a(e) {
17
- let a = [], s = i(e.columns ?? []), c = [];
18
- for (let r of e.data ?? []) {
19
- if (c.length >= n) break;
20
- t(r) || c.push(r);
16
+ function o(e) {
17
+ let o = [], c = a(e.columns ?? []), l = [], u = e.data ?? [], d = Math.min(u.length, r);
18
+ for (let e = 0; e < d && l.length < n; e += 1) {
19
+ let n = u[e];
20
+ t(n) || l.push(n);
21
21
  }
22
- if (c.length && !e.serverGroup) {
22
+ if (l.length && !e.serverGroup) {
23
23
  let e = /* @__PURE__ */ new Set();
24
- for (let t of c) if (t && typeof t == "object") for (let n of Object.keys(t)) e.add(n);
25
- for (let t of s) {
24
+ for (let t of l) if (t && typeof t == "object") for (let n of Object.keys(t)) e.add(n);
25
+ for (let t of c) {
26
26
  let n = t.field;
27
27
  if (!n || t.fieldFn || e.has(n)) continue;
28
- let i = o(n, [...e]);
29
- a.push(`[svgrid] Column field "${n}" does not exist on your row data, so that column renders empty.${i ? ` Did you mean "${i}"?` : ""} Available keys: ${[...e].slice(0, 12).join(", ")}. See ${r}`);
28
+ let r = s(n, [...e]);
29
+ o.push(`[svgrid] Column field "${n}" does not exist on your row data, so that column renders empty.${r ? ` Did you mean "${r}"?` : ""} Available keys: ${[...e].slice(0, 12).join(", ")}. See ${i}`);
30
30
  }
31
31
  }
32
- let l = /* @__PURE__ */ new Map();
33
- for (let e of s) {
32
+ let f = /* @__PURE__ */ new Map();
33
+ for (let e of c) {
34
34
  let t = e.id ?? e.field;
35
- t && l.set(t, (l.get(t) ?? 0) + 1);
35
+ t && f.set(t, (f.get(t) ?? 0) + 1);
36
36
  }
37
- for (let [e, t] of l) t > 1 && a.push(`[svgrid] ${t} columns share the id "${e}". Column state (sorting, filtering, pinning, width) is keyed by id, so they will act as one. Give each an explicit \`id\`. See ${r}`);
38
- let u = e.pageable === void 0 && e.showPagination === void 0;
39
- e.pageSize !== void 0 && u && a.push("[svgrid] `pageSize` is set but pagination was never turned on, so it has no effect and every row renders. Add `pageable` to switch the pager on."), !(e.sortable === !0 || e.features?.rowSortingFeature) && s.some((e) => e.sortable === !0) && a.push("[svgrid] A column sets `sortable: true`, but sorting is not enabled on the grid, so its header does nothing. Add `sortable` to <SvGrid> (it registers the sorting feature for you).");
40
- let d = /* @__PURE__ */ new Set();
41
- for (let e of s) {
37
+ for (let [e, t] of f) t > 1 && o.push(`[svgrid] ${t} columns share the id "${e}". Column state (sorting, filtering, pinning, width) is keyed by id, so they will act as one. Give each an explicit \`id\`. See ${i}`);
38
+ let p = e.pageable === void 0 && e.showPagination === void 0;
39
+ e.pageSize !== void 0 && p && o.push("[svgrid] `pageSize` is set but pagination was never turned on, so it has no effect and every row renders. Add `pageable` to switch the pager on."), !(e.sortable === !0 || e.features?.rowSortingFeature) && c.some((e) => e.sortable === !0) && o.push("[svgrid] A column sets `sortable: true`, but sorting is not enabled on the grid, so its header does nothing. Add `sortable` to <SvGrid> (it registers the sorting feature for you).");
40
+ let m = /* @__PURE__ */ new Set();
41
+ for (let e of c) {
42
42
  let t = e.id ?? e.field;
43
- t && d.add(t);
43
+ t && m.add(t);
44
44
  }
45
45
  for (let t of e.groupBy ?? []) {
46
- if (d.has(t)) continue;
47
- let e = o(t, [...d]);
48
- a.push(`[svgrid] \`groupBy\` refers to column "${t}", which does not exist, so that grouping level is ignored.${e ? ` Did you mean "${e}"?` : ""} Column ids: ${[...d].slice(0, 12).join(", ")}`);
46
+ if (m.has(t)) continue;
47
+ let e = s(t, [...m]);
48
+ o.push(`[svgrid] \`groupBy\` refers to column "${t}", which does not exist, so that grouping level is ignored.${e ? ` Did you mean "${e}"?` : ""} Column ids: ${[...m].slice(0, 12).join(", ")}`);
49
49
  }
50
- let f = e.treeData?.column;
51
- if (f && !d.has(f) && a.push(`[svgrid] \`treeData.column\` refers to column "${f}", which does not exist, so the expander falls back to the first visible column.`), c.length && e.treeData) {
50
+ let h = e.treeData?.column;
51
+ if (h && !m.has(h) && o.push(`[svgrid] \`treeData.column\` refers to column "${h}", which does not exist, so the expander falls back to the first visible column.`), l.length && e.treeData) {
52
52
  let t = /* @__PURE__ */ new Set();
53
- for (let e of c) if (e && typeof e == "object") for (let n of Object.keys(e)) t.add(n);
53
+ for (let e of l) if (e && typeof e == "object") for (let n of Object.keys(e)) t.add(n);
54
54
  let n = e.treeData.parentField;
55
55
  if (n && !t.has(n)) {
56
- let e = o(n, [...t]);
57
- a.push(`[svgrid] \`treeData.parentField\` is "${n}", which is not on your row data, so every row becomes a root and no hierarchy appears.${e ? ` Did you mean "${e}"?` : ""}`);
56
+ let e = s(n, [...t]);
57
+ o.push(`[svgrid] \`treeData.parentField\` is "${n}", which is not on your row data, so every row becomes a root and no hierarchy appears.${e ? ` Did you mean "${e}"?` : ""}`);
58
58
  }
59
59
  let r = e.treeData.idField;
60
- r && !t.has(r) && a.push(`[svgrid] \`treeData.idField\` is "${r}", which is not on your row data, so parent lookups cannot match and the tree stays flat.`);
60
+ r && !t.has(r) && o.push(`[svgrid] \`treeData.idField\` is "${r}", which is not on your row data, so parent lookups cannot match and the tree stays flat.`);
61
61
  }
62
- return e.gantt && e.treeData && a.push("[svgrid] `gantt` and `treeData` are both set. The Gantt builds its own work-breakdown tree from `gantt.parentField` and needs every descendant row, but `treeData` hides collapsed children from the view, so summary bars will under-report. Remove `treeData` and set `gantt.parentField` instead."), (e.initialColumnPinning?.left?.length ?? 0) + (e.initialColumnPinning?.right?.length ?? 0) > 0 && e.columnVirtualization !== !1 && a.push("[svgrid] `initialColumnPinning` is set while column virtualization is on (its default), so the pinned columns will not stick - the virtualizer recycles column nodes. Add `columnVirtualization={false}`."), e.externalPagination === !0 && e.rowCount === void 0 && a.push("[svgrid] `externalPagination` is on but `rowCount` is not set, so the pager cannot know how many pages exist. Pass the server total as `rowCount`."), e.externalSort === !0 && !e.onSortingChange && a.push("[svgrid] `externalSort` is on but there is no `onSortingChange` handler, so clicking a header changes nothing - the grid stopped sorting and nobody is listening. Add `onSortingChange` and re-fetch in your handler."), e.externalFilter === !0 && !e.onFiltersChange && a.push("[svgrid] `externalFilter` is on but there is no `onFiltersChange` handler, so filtering the grid changes nothing. Add `onFiltersChange` and re-fetch in your handler."), a;
62
+ return e.gantt && e.treeData && o.push("[svgrid] `gantt` and `treeData` are both set. The Gantt builds its own work-breakdown tree from `gantt.parentField` and needs every descendant row, but `treeData` hides collapsed children from the view, so summary bars will under-report. Remove `treeData` and set `gantt.parentField` instead."), (e.initialColumnPinning?.left?.length ?? 0) + (e.initialColumnPinning?.right?.length ?? 0) > 0 && e.columnVirtualization !== !1 && o.push("[svgrid] `initialColumnPinning` is set while column virtualization is on (its default), so the pinned columns will not stick - the virtualizer recycles column nodes. Add `columnVirtualization={false}`."), e.externalPagination === !0 && e.rowCount === void 0 && o.push("[svgrid] `externalPagination` is on but `rowCount` is not set, so the pager cannot know how many pages exist. Pass the server total as `rowCount`."), e.externalSort === !0 && !e.onSortingChange && o.push("[svgrid] `externalSort` is on but there is no `onSortingChange` handler, so clicking a header changes nothing - the grid stopped sorting and nobody is listening. Add `onSortingChange` and re-fetch in your handler."), e.externalFilter === !0 && !e.onFiltersChange && o.push("[svgrid] `externalFilter` is on but there is no `onFiltersChange` handler, so filtering the grid changes nothing. Add `onFiltersChange` and re-fetch in your handler."), o;
63
63
  }
64
- function o(e, t) {
64
+ function s(e, t) {
65
65
  let n, r = Infinity;
66
66
  for (let i of t) {
67
- let t = s(e.toLowerCase(), i.toLowerCase());
67
+ let t = c(e.toLowerCase(), i.toLowerCase());
68
68
  t < r && (r = t, n = i);
69
69
  }
70
70
  let i = Math.min(3, Math.floor(e.length / 3) + 1);
71
71
  return r <= i ? n : void 0;
72
72
  }
73
- function s(e, t) {
73
+ function c(e, t) {
74
74
  if (e === t) return 0;
75
75
  let n = Array(t.length + 1);
76
76
  for (let e = 0; e <= t.length; e++) n[e] = e;
@@ -85,4 +85,4 @@ function s(e, t) {
85
85
  return n[t.length];
86
86
  }
87
87
  //#endregion
88
- export { a as validateGridConfig };
88
+ export { o as validateGridConfig };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The query engine behind `serveWorkerDataSource`: the in-memory reference
3
+ * source's answers, computed over columns instead of row objects.
4
+ *
5
+ * Each field becomes a dictionary - its distinct values, and one integer code
6
+ * per row - built once, in the background, while the worker is idle. Then:
7
+ *
8
+ * - A filter is decided once per DISTINCT value, with the reference's own
9
+ * `matchesPredicate`, and a row passes when its code does. Searching a
10
+ * million rows of 80 customers tests 80 strings.
11
+ * - A sort ranks the distinct values once (numerically, or with the same
12
+ * collator the reference uses) and orders rows with a stable counting sort
13
+ * over the ranks: integer work, linear in the row count, no string compares.
14
+ * - A grouping buckets rows by code and accumulates aggregates in row order,
15
+ * so sums come out bit-for-bit what the reference adds up.
16
+ *
17
+ * Exact equivalence is the contract, not a goal: anything this engine does
18
+ * not accelerate goes to the reference source holding the same rows - the
19
+ * advanced filter expression, pivot, and a sort on a column that mixes
20
+ * numbers with other values (where the reference's comparison is not a
21
+ * total order, so there is no ranking to reproduce). Writes go through the
22
+ * reference too, and the columns are rebuilt from its rows.
23
+ */
24
+ import type { RowData, ServerDataSource } from '@svgrid/grid';
25
+ import type { EntitySchema } from '../schema.js';
26
+ import type { AggregateBucket, AggregateRequest } from '../sources/aggregate.js';
27
+ export type ColumnarDataSource<TData extends RowData> = Required<Pick<ServerDataSource<TData>, 'getRows' | 'createRow' | 'updateRow' | 'deleteRow' | 'updateWhere'>> & {
28
+ getAggregate(request: AggregateRequest): Promise<AggregateBucket[]>;
29
+ rows(): ReadonlyArray<TData>;
30
+ /**
31
+ * Build one more column's dictionary and ranks. Returns false once every
32
+ * schema field is built. `serveWorkerDataSource` calls it between requests,
33
+ * so the first sort on any column finds its work already done.
34
+ */
35
+ warm(): boolean;
36
+ /** How many requests this engine answered, and how many it handed to the reference. */
37
+ stats(): {
38
+ columnar: number;
39
+ reference: number;
40
+ };
41
+ };
42
+ export declare function createColumnarDataSource<TData extends RowData>(initial: ReadonlyArray<TData>, schema: EntitySchema<TData>): ColumnarDataSource<TData>;
@@ -0,0 +1,410 @@
1
+ import { planQuery } from '../sveltekit/query-plan.js';
2
+ import { asString, createInMemoryDataSource, matchesPredicate, sortRows } from '../sveltekit/in-memory.js';
3
+ const RESULT_CACHE_SIZE = 8;
4
+ const collator = new Intl.Collator();
5
+ export function createColumnarDataSource(initial, schema) {
6
+ const reference = createInMemoryDataSource(initial, schema, { cacheResults: true });
7
+ let store = reference.rows();
8
+ let columns = new Map();
9
+ const cache = new Map();
10
+ const fields = schema.fields.map((f) => f.field);
11
+ // Index of the next schema field `warm()` builds.
12
+ let warmed = 0;
13
+ function invalidate() {
14
+ store = reference.rows();
15
+ columns = new Map();
16
+ cache.clear();
17
+ warmed = 0;
18
+ }
19
+ // ---- columns ------------------------------------------------------------
20
+ function column(field) {
21
+ let col = columns.get(field);
22
+ if (col)
23
+ return col;
24
+ const n = store.length;
25
+ const codes = new Uint32Array(n);
26
+ const values = [];
27
+ const index = new Map();
28
+ for (let i = 0; i < n; i += 1) {
29
+ const v = store[i][field];
30
+ let code = index.get(v);
31
+ if (code === undefined) {
32
+ code = values.length;
33
+ index.set(v, code);
34
+ values.push(v);
35
+ }
36
+ codes[i] = code;
37
+ }
38
+ col = { codes, values };
39
+ columns.set(field, col);
40
+ return col;
41
+ }
42
+ function ranked(col) {
43
+ if (col.ranks !== undefined)
44
+ return col.ranks;
45
+ const { values } = col;
46
+ let numbers = 0;
47
+ let finite = 0;
48
+ for (const v of values) {
49
+ if (typeof v === 'number') {
50
+ numbers += 1;
51
+ if (Number.isFinite(v))
52
+ finite += 1;
53
+ }
54
+ }
55
+ // The reference compares two numbers by subtraction and anything else as
56
+ // text. All finite numbers, or no numbers at all, is a total order a
57
+ // ranking can reproduce; a mix (or NaN / Infinity) is not.
58
+ if (numbers > 0 && (finite !== values.length)) {
59
+ col.ranks = null;
60
+ return null;
61
+ }
62
+ const byValue = values.map((_, code) => code);
63
+ const text = numbers === 0 ? values.map(asString) : null;
64
+ const cmp = text
65
+ ? (a, b) => collator.compare(text[a], text[b])
66
+ : (a, b) => values[a] - values[b];
67
+ byValue.sort(cmp);
68
+ const ranks = new Uint32Array(values.length);
69
+ let rank = 0;
70
+ for (let i = 0; i < byValue.length; i += 1) {
71
+ // Values the comparison calls equal share a rank, so the stable sort
72
+ // keeps them in row order, as the reference's index tiebreak does.
73
+ if (i > 0 && cmp(byValue[i - 1], byValue[i]) !== 0)
74
+ rank += 1;
75
+ ranks[byValue[i]] = rank;
76
+ }
77
+ col.ranks = ranks;
78
+ col.rankCount = values.length ? rank + 1 : 0;
79
+ return ranks;
80
+ }
81
+ function keyed(col) {
82
+ if (!col.keyOf) {
83
+ const keyOf = new Uint32Array(col.values.length);
84
+ const keys = [];
85
+ const index = new Map();
86
+ col.values.forEach((v, code) => {
87
+ const key = asString(v);
88
+ let k = index.get(key);
89
+ if (k === undefined) {
90
+ k = keys.length;
91
+ index.set(key, k);
92
+ keys.push(key);
93
+ }
94
+ keyOf[code] = k;
95
+ });
96
+ col.keyOf = keyOf;
97
+ col.keys = keys;
98
+ }
99
+ return { keyOf: col.keyOf, keys: col.keys };
100
+ }
101
+ function numbers(col) {
102
+ if (!col.nums) {
103
+ const nums = new Float64Array(col.values.length);
104
+ col.values.forEach((v, code) => {
105
+ const n = Number(v);
106
+ nums[code] = Number.isFinite(n) ? n : NaN;
107
+ });
108
+ col.nums = nums;
109
+ }
110
+ return col.nums;
111
+ }
112
+ // ---- filtering ----------------------------------------------------------
113
+ /** Row indexes passing `where` and the search, in store order. */
114
+ function select(where, search) {
115
+ const n = store.length;
116
+ const tests = [];
117
+ for (const p of where) {
118
+ const col = column(p.field);
119
+ const pass = new Uint8Array(col.values.length);
120
+ col.values.forEach((v, code) => {
121
+ pass[code] = matchesPredicate({ [p.field]: v }, p) ? 1 : 0;
122
+ });
123
+ tests.push({ codes: col.codes, pass });
124
+ }
125
+ const searched = [];
126
+ if (search) {
127
+ const term = search.term.toLowerCase();
128
+ for (const field of search.fields) {
129
+ const col = column(field);
130
+ const pass = new Uint8Array(col.values.length);
131
+ col.values.forEach((v, code) => {
132
+ pass[code] = asString(v).toLowerCase().includes(term) ? 1 : 0;
133
+ });
134
+ searched.push({ codes: col.codes, pass });
135
+ }
136
+ }
137
+ if (tests.length === 0 && !search) {
138
+ const all = new Uint32Array(n);
139
+ for (let i = 0; i < n; i += 1)
140
+ all[i] = i;
141
+ return all;
142
+ }
143
+ const out = new Uint32Array(n);
144
+ let m = 0;
145
+ rows: for (let i = 0; i < n; i += 1) {
146
+ if (search) {
147
+ let hit = false;
148
+ for (const s of searched) {
149
+ if (s.pass[s.codes[i]]) {
150
+ hit = true;
151
+ break;
152
+ }
153
+ }
154
+ if (!hit)
155
+ continue;
156
+ }
157
+ for (const t of tests)
158
+ if (!t.pass[t.codes[i]])
159
+ continue rows;
160
+ out[m++] = i;
161
+ }
162
+ return out.slice(0, m);
163
+ }
164
+ // ---- sorting ------------------------------------------------------------
165
+ /** Stable counting sort of `idx` by one column; LSD over several keys. */
166
+ function sortIndexes(idx, orderBy) {
167
+ const keys = [];
168
+ for (const o of orderBy) {
169
+ const col = column(o.field);
170
+ const ranks = ranked(col);
171
+ if (!ranks)
172
+ return null;
173
+ keys.push({ codes: col.codes, ranks, count: col.rankCount, desc: o.desc });
174
+ }
175
+ let cur = idx;
176
+ let next = new Uint32Array(idx.length);
177
+ // Least significant key first: each pass is stable, so earlier keys win.
178
+ for (let k = keys.length - 1; k >= 0; k -= 1) {
179
+ const { codes, ranks, count, desc } = keys[k];
180
+ const counts = new Uint32Array(count + 1);
181
+ for (let i = 0; i < cur.length; i += 1) {
182
+ const r = ranks[codes[cur[i]]];
183
+ counts[(desc ? count - 1 - r : r) + 1] += 1;
184
+ }
185
+ for (let r = 1; r <= count; r += 1)
186
+ counts[r] += counts[r - 1];
187
+ for (let i = 0; i < cur.length; i += 1) {
188
+ const row = cur[i];
189
+ const r = ranks[codes[row]];
190
+ next[counts[desc ? count - 1 - r : r]++] = row;
191
+ }
192
+ const swap = cur === idx ? new Uint32Array(idx.length) : cur;
193
+ cur = next;
194
+ next = swap;
195
+ }
196
+ return cur;
197
+ }
198
+ function accumulate(idx,
199
+ /** The group of the i-th row of `idx`, or null for one group. */
200
+ rowGroup, groupCount, aggregations) {
201
+ const out = new Map();
202
+ for (const agg of aggregations) {
203
+ if (out.has(agg.field))
204
+ continue;
205
+ const col = column(agg.field);
206
+ const codes = col.codes;
207
+ const nums = numbers(col);
208
+ const acc = {
209
+ count: new Float64Array(groupCount),
210
+ sum: new Float64Array(groupCount),
211
+ min: new Float64Array(groupCount).fill(Infinity),
212
+ max: new Float64Array(groupCount).fill(-Infinity),
213
+ n: new Float64Array(groupCount),
214
+ };
215
+ for (let i = 0; i < idx.length; i += 1) {
216
+ const row = idx[i];
217
+ const g = rowGroup ? rowGroup[i] : 0;
218
+ acc.count[g] += 1;
219
+ const v = nums[codes[row]];
220
+ if (v !== v)
221
+ continue; // NaN: not a finite number
222
+ acc.n[g] += 1;
223
+ acc.sum[g] += v;
224
+ if (v < acc.min[g])
225
+ acc.min[g] = v;
226
+ if (v > acc.max[g])
227
+ acc.max[g] = v;
228
+ }
229
+ out.set(agg.field, acc);
230
+ }
231
+ return out;
232
+ }
233
+ /** The reference's `aggregate`, read off the accumulators. */
234
+ function valueOf(acc, g, fn) {
235
+ if (fn === 'count')
236
+ return acc.count[g];
237
+ if (acc.n[g] === 0)
238
+ return null;
239
+ switch (fn) {
240
+ case 'sum':
241
+ return acc.sum[g];
242
+ case 'avg':
243
+ return acc.sum[g] / acc.n[g];
244
+ case 'min':
245
+ return acc.min[g];
246
+ case 'max':
247
+ return acc.max[g];
248
+ default:
249
+ return null;
250
+ }
251
+ }
252
+ function totals(idx, plan) {
253
+ const aggs = plan.aggregations ?? [];
254
+ const acc = accumulate(idx, null, 1, aggs);
255
+ const out = {};
256
+ for (const agg of aggs)
257
+ out[agg.field] = valueOf(acc.get(agg.field), 0, agg.fn);
258
+ return out;
259
+ }
260
+ // ---- answering ----------------------------------------------------------
261
+ function supported(plan) {
262
+ return !plan.expression && !plan.pivotBy?.length;
263
+ }
264
+ function prepare(plan) {
265
+ const key = JSON.stringify({ ...plan, limit: 0, offset: 0 });
266
+ const hit = cache.get(key);
267
+ if (hit) {
268
+ cache.delete(key);
269
+ cache.set(key, hit);
270
+ return hit;
271
+ }
272
+ const prepared = run(plan);
273
+ if (!prepared)
274
+ return null;
275
+ cache.set(key, prepared);
276
+ if (cache.size > RESULT_CACHE_SIZE)
277
+ cache.delete(cache.keys().next().value);
278
+ return prepared;
279
+ }
280
+ function run(plan) {
281
+ const idx = select(plan.where, plan.search);
282
+ const out = {};
283
+ if (plan.grandTotal) {
284
+ const filtersOnly = plan.where.slice(0, plan.where.length - (plan.pathPredicates ?? 0));
285
+ const all = filtersOnly.length === plan.where.length ? idx : select(filtersOnly, plan.search);
286
+ out.grandTotal = totals(all, plan);
287
+ }
288
+ if (!plan.groupBy) {
289
+ const order = plan.orderBy.length ? sortIndexes(idx, plan.orderBy) : idx;
290
+ if (!order)
291
+ return null;
292
+ out.order = order;
293
+ return out;
294
+ }
295
+ // Groups in order of first appearance, as the reference's Map buckets are.
296
+ const groupField = plan.groupBy;
297
+ const gcol = column(groupField);
298
+ const { keyOf, keys } = keyed(gcol);
299
+ const slot = new Int32Array(keys.length).fill(-1);
300
+ const groupKeys = [];
301
+ const gcodes = gcol.codes;
302
+ const rowGroup = new Uint32Array(idx.length);
303
+ for (let i = 0; i < idx.length; i += 1) {
304
+ const k = keyOf[gcodes[idx[i]]];
305
+ let g = slot[k];
306
+ if (g < 0) {
307
+ g = groupKeys.length;
308
+ slot[k] = g;
309
+ groupKeys.push(k);
310
+ }
311
+ rowGroup[i] = g;
312
+ }
313
+ const groupCount = groupKeys.length;
314
+ const acc = accumulate(idx, rowGroup, groupCount, plan.aggregations ?? []);
315
+ const childCounts = new Float64Array(groupCount);
316
+ if (plan.childGroupBy) {
317
+ const ccol = column(plan.childGroupBy);
318
+ const { keyOf: childKeyOf, keys: childKeys } = keyed(ccol);
319
+ const seen = new Set();
320
+ for (let i = 0; i < idx.length; i += 1) {
321
+ const g = rowGroup[i];
322
+ const c = childKeyOf[ccol.codes[idx[i]]];
323
+ const pair = g * childKeys.length + c;
324
+ if (!seen.has(pair)) {
325
+ seen.add(pair);
326
+ childCounts[g] += 1;
327
+ }
328
+ }
329
+ }
330
+ else {
331
+ for (let i = 0; i < idx.length; i += 1)
332
+ childCounts[rowGroup[i]] += 1;
333
+ }
334
+ const groups = groupKeys.map((k, g) => {
335
+ const row = { [groupField]: keys[k] };
336
+ for (const agg of plan.aggregations ?? [])
337
+ row[agg.field] = valueOf(acc.get(agg.field), g, agg.fn);
338
+ row.childCount = childCounts[g];
339
+ return row;
340
+ });
341
+ const produced = new Set([groupField, ...(plan.aggregations ?? []).map((a) => a.field)]);
342
+ const groupOrder = plan.orderBy.filter((o) => produced.has(o.field));
343
+ out.groups = groupOrder.length
344
+ ? sortRows(groups, groupOrder)
345
+ : sortRows(groups, [{ field: groupField, desc: false }]);
346
+ return out;
347
+ }
348
+ const counts = { columnar: 0, reference: 0 };
349
+ return {
350
+ async getRows(request) {
351
+ const plan = planQuery(schema, request);
352
+ const prepared = supported(plan) ? prepare(plan) : null;
353
+ if (!prepared) {
354
+ counts.reference += 1;
355
+ return reference.getRows(request);
356
+ }
357
+ counts.columnar += 1;
358
+ const from = plan.offset;
359
+ const to = plan.offset + plan.limit;
360
+ const total = prepared.groups ? prepared.groups.length : prepared.order.length;
361
+ const rows = prepared.groups
362
+ ? prepared.groups.slice(from, to)
363
+ : Array.from(prepared.order.subarray(from, Math.min(to, total)), (i) => store[i]);
364
+ return {
365
+ rows,
366
+ rowCount: total,
367
+ appliedExpression: false,
368
+ ...(prepared.grandTotal !== undefined ? { grandTotal: prepared.grandTotal } : {}),
369
+ };
370
+ },
371
+ async createRow(input) {
372
+ const row = await reference.createRow(input);
373
+ invalidate();
374
+ return row;
375
+ },
376
+ async updateRow(id, patch) {
377
+ const row = await reference.updateRow(id, patch);
378
+ invalidate();
379
+ return row;
380
+ },
381
+ async deleteRow(id) {
382
+ await reference.deleteRow(id);
383
+ invalidate();
384
+ },
385
+ async updateWhere(filterModel, patch, selection) {
386
+ const changed = await reference.updateWhere(filterModel, patch, selection);
387
+ invalidate();
388
+ return changed;
389
+ },
390
+ getAggregate: (request) => reference.getAggregate(request),
391
+ rows: () => store,
392
+ stats: () => ({ ...counts }),
393
+ warm() {
394
+ while (warmed < fields.length) {
395
+ const field = fields[warmed++];
396
+ const existing = columns.get(field);
397
+ if (existing?.ranks !== undefined && existing.keyOf && existing.nums)
398
+ continue;
399
+ // Everything a sort, a grouping and an aggregate read, so none of
400
+ // them pays for it on the first click.
401
+ const col = column(field);
402
+ ranked(col);
403
+ keyed(col);
404
+ numbers(col);
405
+ return true;
406
+ }
407
+ return false;
408
+ },
409
+ };
410
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@svgrid/enterprise/worker` - what runs INSIDE the worker behind
3
+ * `createWorkerDataSource`. Import it from your worker file and nowhere else:
4
+ * it has no Svelte and no DOM in its import graph, so the worker bundle stays
5
+ * the in-memory source and little more.
6
+ *
7
+ * The main-thread half, `createWorkerDataSource`, is on the package root and
8
+ * on `@svgrid/enterprise/server`.
9
+ */
10
+ export { serveWorkerDataSource, type ServeWorkerDataSourceOptions, type WorkerScopeLike, } from './serve.js';
11
+ export { inferSchemaFromRows, type InferSchemaOptions } from './infer-schema.js';
12
+ export { createColumnarDataSource, type ColumnarDataSource } from './columnar.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@svgrid/enterprise/worker` - what runs INSIDE the worker behind
3
+ * `createWorkerDataSource`. Import it from your worker file and nowhere else:
4
+ * it has no Svelte and no DOM in its import graph, so the worker bundle stays
5
+ * the in-memory source and little more.
6
+ *
7
+ * The main-thread half, `createWorkerDataSource`, is on the package root and
8
+ * on `@svgrid/enterprise/server`.
9
+ */
10
+ export { serveWorkerDataSource, } from './serve.js';
11
+ export { inferSchemaFromRows } from './infer-schema.js';
12
+ export { createColumnarDataSource } from './columnar.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * An `EntitySchema` read off the rows themselves, for a worker data source
3
+ * that was handed plain objects. The in-memory source only filters, sorts and
4
+ * groups on fields its schema declares, and the type decides how a request's
5
+ * string values are compared: `'35'` is a number for a number field and text
6
+ * for a text one.
7
+ */
8
+ import type { RowData } from '@svgrid/grid';
9
+ import type { EntitySchema } from '../schema.js';
10
+ export type InferSchemaOptions = {
11
+ /** The row id field. Default: `id` when the rows have one, else the first field. */
12
+ idField?: string;
13
+ /** The schema's name. Default `rows`. */
14
+ name?: string;
15
+ };
16
+ /**
17
+ * A field is `number` or `boolean` when every non-empty sampled value is one,
18
+ * and `text` otherwise. Dates come out as `text`: send them as ISO strings,
19
+ * which sort and compare correctly as text, or as epoch numbers.
20
+ */
21
+ export declare function inferSchemaFromRows<TData extends RowData>(rows: ReadonlyArray<TData>, options?: InferSchemaOptions): EntitySchema<TData>;