@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.
- package/dist/SvSheet.svelte +93 -8
- package/dist/cdn/svgrid-enterprise.svelte-external.js +9339 -9077
- package/dist/designer/assets/{GridMenus-8gTNssgV.js → GridMenus-DGZJKFvX.js} +1 -1
- package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +1 -0
- package/dist/designer/assets/{SvGridCellEditor-DMoWJjEI.js → SvGridCellEditor-C7NWcnNC.js} +1 -1
- package/dist/designer/assets/{SvGridChart-Cy1ATrWh.js → SvGridChart-Fg43Yi0M.js} +2 -2
- package/dist/designer/assets/{SvGridChartBuilder-eQ3mn0L9.js → SvGridChartBuilder-BqGuA3yU.js} +1 -1
- package/dist/designer/assets/{SvGridChartPanel-CATdVc9X.js → SvGridChartPanel-BsiXRAnx.js} +3 -3
- package/dist/designer/assets/{SvGridChartView-CmsZtCED.js → SvGridChartView-BgttK0tR.js} +1 -1
- package/dist/designer/assets/{SvListBox-BDS66lmZ.js → SvListBox-v2jm0-vf.js} +1 -1
- package/dist/designer/assets/cell-formatting-BWrW_O0n.js +1 -0
- package/dist/designer/assets/{chart-Cn7MnNqA.js → chart-BIl-dIRv.js} +1 -1
- package/dist/designer/assets/{chart-panel-messages-Dz7PTIqT.js → chart-panel-messages-4cF2vw7L.js} +1 -1
- package/dist/designer/assets/{chart-summary-BD1qYB2R.js → chart-summary-B7bTtSCC.js} +1 -1
- package/dist/designer/assets/{chart-trend-B8xr5LuD.js → chart-trend-CLsn5Hgd.js} +1 -1
- package/dist/designer/assets/export-format-DGscOjS9.js +6 -0
- package/dist/designer/assets/{index-BSjv6Qec.js → index-V_cMXdxt.js} +314 -314
- package/dist/designer/assets/index-l21iGkpF.css +1 -0
- package/dist/designer/assets/src-Mu7AGqT8.js +58 -0
- package/dist/designer/assets/validate-YxSrBF1H.js +1 -0
- package/dist/designer/index.html +12 -12
- package/dist/export-ooxml.d.ts +10 -0
- package/dist/export-ooxml.js +22 -5
- package/dist/import.js +11 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/node/studio.js +236 -132
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/server-row-model.d.ts +9 -0
- package/dist/server/server-row-model.js +120 -3
- package/dist/server/worker-data-source.d.ts +62 -0
- package/dist/server/worker-data-source.js +133 -0
- package/dist/sheet/objects.d.ts +47 -0
- package/dist/sheet/objects.js +33 -0
- package/dist/studio/cli.js +55 -1
- package/dist/studio/emit-project.js +8 -3
- package/dist/studio/init-flow.d.ts +6 -1
- package/dist/studio/init-flow.js +43 -2
- package/dist/studio/scaffold-app.d.ts +6 -0
- package/dist/studio/scaffold-app.js +5 -3
- package/dist/studio/scaffold.js +29 -12
- package/dist/sveltekit/in-memory.d.ts +21 -1
- package/dist/sveltekit/in-memory.js +153 -105
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/version.d.ts +14 -1
- package/dist/version.js +14 -1
- package/dist/wc/{GridMenus-By9vPuq0.js → GridMenus-6_fF5jIm.js} +2 -2
- package/dist/wc/{SvGrid.helpers-BdVDtjUm.js → SvGrid.helpers-zBkDTryR.js} +36 -37
- package/dist/wc/{SvGridCellEditor-Bnf1Kr5C.js → SvGridCellEditor-CzUKEbkI.js} +1 -1
- package/dist/wc/{SvGridChart-BqUJh2pG.js → SvGridChart-C6UvNIbN.js} +3 -3
- package/dist/wc/{SvGridChartBuilder-CGycC5cn.js → SvGridChartBuilder-ikf_5LyW.js} +3 -3
- package/dist/wc/{SvGridChartPanel-CRnizsHD.js → SvGridChartPanel-Dc_jj7B-.js} +4 -4
- package/dist/wc/{SvGridChartView-D1pa_xMy.js → SvGridChartView-thkznAOb.js} +2 -2
- package/dist/wc/{cell-formatting-CBfgahv1.js → cell-formatting-hb3vzxhJ.js} +27 -5
- package/dist/wc/{chart-6OXlybVT.js → chart-CV5D7_k8.js} +1 -1
- package/dist/wc/{chart-summary-2m3ia1-n.js → chart-summary-DGx0QhJa.js} +1 -1
- package/dist/wc/{chart-trend-CQOQtTMl.js → chart-trend-BIRH_xH8.js} +1 -1
- package/dist/wc/sv-sheet-element.js +7454 -7352
- package/dist/wc/{svelte-virtualizer.svelte-CXtQy2TQ.js → svelte-virtualizer.svelte-Du4tjrIP.js} +7 -3
- package/dist/wc/{validate-BqDthkNr.js → validate-JAuMsk6Z.js} +36 -36
- package/dist/worker/columnar.d.ts +42 -0
- package/dist/worker/columnar.js +410 -0
- package/dist/worker/index.d.ts +12 -0
- package/dist/worker/index.js +12 -0
- package/dist/worker/infer-schema.d.ts +21 -0
- package/dist/worker/infer-schema.js +34 -0
- package/dist/worker/protocol.d.ts +73 -0
- package/dist/worker/protocol.js +14 -0
- package/dist/worker/serve.d.ts +44 -0
- package/dist/worker/serve.js +100 -0
- package/package.json +7 -3
- package/dist/designer/assets/SvGrid.helpers-BgT-gnCo.js +0 -1
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +0 -1
- package/dist/designer/assets/export-format-DGckmgKr.js +0 -6
- package/dist/designer/assets/index-_JVFxt02.css +0 -1
- package/dist/designer/assets/src-DUQ4xE3x.js +0 -58
- package/dist/designer/assets/validate-C3B8QKsM.js +0 -1
|
@@ -14,12 +14,13 @@ import { scaffold, MANAGED_START, MANAGED_END } from './scaffold.js';
|
|
|
14
14
|
import { themeTokenCss } from './themes.js';
|
|
15
15
|
const MANAGED_NOTE = '// Regenerated by SvGrid Studio. Edits inside these markers are overwritten.';
|
|
16
16
|
const managed = (body) => `${MANAGED_START}\n${MANAGED_NOTE}\n${body}\n${MANAGED_END}`;
|
|
17
|
-
function layoutFile(nav, appTitle, theme) {
|
|
17
|
+
function layoutFile(nav, appTitle, theme, stylesheets) {
|
|
18
|
+
const imports = stylesheets.map((css) => ` import '../${css}'\n`).join('');
|
|
18
19
|
return {
|
|
19
20
|
path: 'src/routes/+layout.svelte',
|
|
20
21
|
description: 'App shell: a collapsible nav sidebar linking every entity screen (an off-canvas drawer on tablet/phone), with the routed page in the main area.',
|
|
21
22
|
contents: `<script lang="ts">
|
|
22
|
-
${managed(
|
|
23
|
+
${managed(`${imports} let { children } = $props()
|
|
23
24
|
const appTitle = ${JSON.stringify(appTitle)}
|
|
24
25
|
const nav = ${JSON.stringify(nav)}
|
|
25
26
|
|
|
@@ -143,6 +144,7 @@ export function scaffoldApp(schemas, options = {}) {
|
|
|
143
144
|
files.push(...scaffold(schema, { dataSource: options.dataSource }).files);
|
|
144
145
|
}
|
|
145
146
|
const nav = linked.map((s) => ({ href: `/${s.name}`, label: s.label ?? titleCase(s.name) }));
|
|
146
|
-
|
|
147
|
+
const theme = { preset: options.theme, mode: options.dark ? 'dark' : 'light' };
|
|
148
|
+
files.push(layoutFile(nav, appTitle, theme, options.stylesheets ?? []), homeFile(nav, appTitle));
|
|
147
149
|
return { files };
|
|
148
150
|
}
|
package/dist/studio/scaffold.js
CHANGED
|
@@ -213,7 +213,8 @@ ${managed(serverBody)}
|
|
|
213
213
|
const pageBody = ` const source = createKitDataSource<${rowType}>({ endpoint: '${apiRoute}' })
|
|
214
214
|
const columns = schemaToColumns(${constName})${lookupsSection}
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
// Not "state": a local of that name makes svelte-check read $state as a store of it.
|
|
217
|
+
let view = $state<ServerState<${rowType}>>({
|
|
217
218
|
rows: [], total: 0, loading: false, saving: false, error: null,
|
|
218
219
|
pageIndex: 0, pageSize: 25, pageCount: 1, sortModel: [], filterModel: {},
|
|
219
220
|
})
|
|
@@ -221,7 +222,7 @@ ${managed(serverBody)}
|
|
|
221
222
|
pageSize: 25,
|
|
222
223
|
optimistic: true,
|
|
223
224
|
getRowId: (r) => String(r.${idField}),
|
|
224
|
-
onChange: (s) => (
|
|
225
|
+
onChange: (s) => (view = s),
|
|
225
226
|
})
|
|
226
227
|
controller.refresh()
|
|
227
228
|
|
|
@@ -231,7 +232,7 @@ ${managed(serverBody)}
|
|
|
231
232
|
async function save({ mode, id, values }: { mode: 'create' | 'edit'; id: string | null; values: Partial<${rowType}> }) {
|
|
232
233
|
if (mode === 'create') {
|
|
233
234
|
await controller.createRow(values)
|
|
234
|
-
controller.setPage(
|
|
235
|
+
controller.setPage(view.pageCount - 1) // jump to the new row (appended at the end)
|
|
235
236
|
} else if (id) {
|
|
236
237
|
await controller.updateRow(id, values)
|
|
237
238
|
}
|
|
@@ -246,12 +247,11 @@ ${managed(serverBody)}
|
|
|
246
247
|
path: `src/routes/${route}/+page.svelte`,
|
|
247
248
|
description: 'The data screen: sort, filter, search, paging, multi-select delete, and create/edit - wired to the API route.',
|
|
248
249
|
contents: `<script lang="ts">
|
|
249
|
-
import { SvGrid, createServerDataSource } from '@svgrid/grid'
|
|
250
|
+
import { SvGrid, createServerDataSource, type ServerState } from '@svgrid/grid'
|
|
250
251
|
import {
|
|
251
252
|
SvGridEditPanel,
|
|
252
253
|
createKitDataSource,${hasLookups ? '\n createRelationLookup,' : ''}
|
|
253
254
|
schemaToColumns,
|
|
254
|
-
type ServerState,
|
|
255
255
|
} from '@svgrid/enterprise'
|
|
256
256
|
import { ${constName}, type ${rowType} } from '${schemaImport}'
|
|
257
257
|
|
|
@@ -273,12 +273,12 @@ ${themeTokenCss({ preset: options.theme, mode: options.dark ? 'dark' : 'light' }
|
|
|
273
273
|
</div>
|
|
274
274
|
</header>
|
|
275
275
|
|
|
276
|
-
{#if
|
|
276
|
+
{#if view.error}<p class="page__error" role="alert">{view.error}</p>{/if}
|
|
277
277
|
|
|
278
278
|
<SvGrid
|
|
279
|
-
data={
|
|
279
|
+
data={view.rows}
|
|
280
280
|
{columns}
|
|
281
|
-
loading={
|
|
281
|
+
loading={view.loading}
|
|
282
282
|
loadingOverlay
|
|
283
283
|
fitColumns
|
|
284
284
|
enableRowSummaries={false}
|
|
@@ -301,11 +301,11 @@ ${themeTokenCss({ preset: options.theme, mode: options.dark ? 'dark' : 'light' }
|
|
|
301
301
|
onRowSelectionChange={(_sel, rows) => (selected = rows)}
|
|
302
302
|
showPagination
|
|
303
303
|
externalPagination
|
|
304
|
-
rowCount={
|
|
305
|
-
pageIndex={
|
|
306
|
-
pageSize={
|
|
304
|
+
rowCount={view.total}
|
|
305
|
+
pageIndex={view.pageIndex}
|
|
306
|
+
pageSize={view.pageSize}
|
|
307
307
|
onPaginationChange={({ pageIndex, pageSize }) => {
|
|
308
|
-
if (pageSize !==
|
|
308
|
+
if (pageSize !== view.pageSize) controller.setPageSize(pageSize)
|
|
309
309
|
else controller.setPage(pageIndex)
|
|
310
310
|
}}
|
|
311
311
|
emptyMessage="No ${label} yet"
|
|
@@ -338,6 +338,7 @@ ${themeTokenCss({ preset: options.theme, mode: options.dark ? 'dark' : 'light' }
|
|
|
338
338
|
export function mergeManaged(existing, generated) {
|
|
339
339
|
if (!existing)
|
|
340
340
|
return generated;
|
|
341
|
+
existing = migrateStateLocal(existing);
|
|
341
342
|
const startIdx = existing.indexOf(MANAGED_START);
|
|
342
343
|
const endIdx = existing.indexOf(MANAGED_END);
|
|
343
344
|
if (startIdx === -1 || endIdx === -1 || endIdx < startIdx)
|
|
@@ -349,3 +350,19 @@ export function mergeManaged(existing, generated) {
|
|
|
349
350
|
const newRegion = generated.slice(genStart, genEnd + MANAGED_END.length);
|
|
350
351
|
return (existing.slice(0, startIdx) + newRegion + existing.slice(endIdx + MANAGED_END.length));
|
|
351
352
|
}
|
|
353
|
+
/** The managed line pages scaffolded before the rename carried. */
|
|
354
|
+
const OLD_STATE_LOCAL = /\blet state = \$state<ServerState</;
|
|
355
|
+
/**
|
|
356
|
+
* Pages scaffolded before the rename kept their server state in a local named
|
|
357
|
+
* `state`, which svelte-check reads as a store subscription whenever `$state`
|
|
358
|
+
* appears. The managed region now calls it `view`, but the markup below the
|
|
359
|
+
* region is the user's and still says `state.rows`; a plain merge would leave it
|
|
360
|
+
* pointing at nothing. Rename the references too. Nothing else in such a page
|
|
361
|
+
* could be called `state` (the old local would have clashed), and member reads
|
|
362
|
+
* like `page.state` are skipped.
|
|
363
|
+
*/
|
|
364
|
+
function migrateStateLocal(existing) {
|
|
365
|
+
if (!OLD_STATE_LOCAL.test(existing))
|
|
366
|
+
return existing;
|
|
367
|
+
return existing.replace(/(?<![.\w$])state(?=\s*[.?[]|\s*=[^=>])/g, 'view');
|
|
368
|
+
}
|
|
@@ -7,8 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { RowData, ServerDataSource } from '@svgrid/grid';
|
|
9
9
|
import { type EntitySchema } from '../schema.js';
|
|
10
|
+
import { type PlanPredicate, type QueryPlan } from './query-plan.js';
|
|
10
11
|
import type { WritableDataSource } from './types.js';
|
|
11
12
|
import { type AggregateSource } from '../sources/aggregate.js';
|
|
12
|
-
export declare function
|
|
13
|
+
export declare function asString(v: unknown): string;
|
|
14
|
+
export declare function matchesPredicate(row: RowData, p: PlanPredicate): boolean;
|
|
15
|
+
export declare function sortRows<T extends RowData>(rows: T[], orderBy: QueryPlan['orderBy']): T[];
|
|
16
|
+
export type InMemoryDataSourceOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Keep the last few filtered, sorted (and grouped) results and answer a
|
|
19
|
+
* request that differs only in its row range by slicing one, instead of
|
|
20
|
+
* filtering and sorting the whole array again. Scrolling a block-loading
|
|
21
|
+
* row model asks for the same query once per block, so on a large array
|
|
22
|
+
* this is the difference between one sort and one sort per block.
|
|
23
|
+
*
|
|
24
|
+
* Off by default because a cached result does not see a row object that
|
|
25
|
+
* was changed in place, only changes made through this source's own
|
|
26
|
+
* `createRow` / `updateRow` / `deleteRow` / `updateWhere`, which drop the
|
|
27
|
+
* cache. `createWorkerDataSource` turns it on: there the rows live in the
|
|
28
|
+
* worker and nothing else can reach them.
|
|
29
|
+
*/
|
|
30
|
+
cacheResults?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function createInMemoryDataSource<TData extends RowData>(initial: ReadonlyArray<TData>, schema: EntitySchema<TData>, options?: InMemoryDataSourceOptions): WritableDataSource<TData> & Required<Pick<ServerDataSource<TData>, 'updateWhere'>> & AggregateSource & {
|
|
13
33
|
rows(): ReadonlyArray<TData>;
|
|
14
34
|
};
|
|
@@ -2,13 +2,16 @@ import { resolveIdField } from '../schema.js';
|
|
|
2
2
|
import { planQuery } from './query-plan.js';
|
|
3
3
|
import { compilePredicate } from '../expressions/compile.js';
|
|
4
4
|
import { aggregateRows } from '../sources/aggregate.js';
|
|
5
|
-
|
|
5
|
+
// asString, matchesPredicate and sortRows are exported for the columnar
|
|
6
|
+
// engine behind the worker data source, which must answer exactly as this
|
|
7
|
+
// reference does. They are not part of the package API.
|
|
8
|
+
export function asString(v) {
|
|
6
9
|
return v == null ? '' : String(v);
|
|
7
10
|
}
|
|
8
11
|
function num(v) {
|
|
9
12
|
return typeof v === 'number' ? v : Number(v);
|
|
10
13
|
}
|
|
11
|
-
function matchesPredicate(row, p) {
|
|
14
|
+
export function matchesPredicate(row, p) {
|
|
12
15
|
const cell = row[p.field];
|
|
13
16
|
switch (p.op) {
|
|
14
17
|
case 'eq':
|
|
@@ -103,28 +106,42 @@ function grandTotalOf(all, plan, expression) {
|
|
|
103
106
|
function aggregate(rows, field, fn) {
|
|
104
107
|
if (fn === 'count')
|
|
105
108
|
return rows.length;
|
|
106
|
-
|
|
109
|
+
// One pass, no spread: `Math.min(...nums)` throws a RangeError once a
|
|
110
|
+
// group holds more than about 150,000 values (V8's argument limit).
|
|
111
|
+
let count = 0;
|
|
112
|
+
let sum = 0;
|
|
113
|
+
let min = Infinity;
|
|
114
|
+
let max = -Infinity;
|
|
107
115
|
for (const row of rows) {
|
|
108
116
|
const n = Number(row[field]);
|
|
109
|
-
if (Number.isFinite(n))
|
|
110
|
-
|
|
117
|
+
if (!Number.isFinite(n))
|
|
118
|
+
continue;
|
|
119
|
+
count += 1;
|
|
120
|
+
sum += n;
|
|
121
|
+
if (n < min)
|
|
122
|
+
min = n;
|
|
123
|
+
if (n > max)
|
|
124
|
+
max = n;
|
|
111
125
|
}
|
|
112
|
-
if (
|
|
126
|
+
if (count === 0)
|
|
113
127
|
return null;
|
|
114
128
|
switch (fn) {
|
|
115
129
|
case 'sum':
|
|
116
|
-
return
|
|
130
|
+
return sum;
|
|
117
131
|
case 'avg':
|
|
118
|
-
return
|
|
132
|
+
return sum / count;
|
|
119
133
|
case 'min':
|
|
120
|
-
return
|
|
134
|
+
return min;
|
|
121
135
|
case 'max':
|
|
122
|
-
return
|
|
136
|
+
return max;
|
|
123
137
|
default:
|
|
124
138
|
return null;
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
|
-
|
|
141
|
+
// What `localeCompare` with no arguments does, without building a collator
|
|
142
|
+
// per comparison - which is most of the cost of sorting a large array.
|
|
143
|
+
const collator = new Intl.Collator();
|
|
144
|
+
export function sortRows(rows, orderBy) {
|
|
128
145
|
if (orderBy.length === 0)
|
|
129
146
|
return rows;
|
|
130
147
|
// Stable multi-key sort: decorate with index, compare keys in order, fall back to index.
|
|
@@ -138,7 +155,7 @@ function sortRows(rows, orderBy) {
|
|
|
138
155
|
if (typeof av === 'number' && typeof bv === 'number')
|
|
139
156
|
cmp = av - bv;
|
|
140
157
|
else
|
|
141
|
-
cmp = asString(av)
|
|
158
|
+
cmp = collator.compare(asString(av), asString(bv));
|
|
142
159
|
if (cmp !== 0)
|
|
143
160
|
return desc ? -cmp : cmp;
|
|
144
161
|
}
|
|
@@ -146,106 +163,137 @@ function sortRows(rows, orderBy) {
|
|
|
146
163
|
})
|
|
147
164
|
.map((d) => d.row);
|
|
148
165
|
}
|
|
149
|
-
|
|
166
|
+
/** Results kept by `cacheResults`: a handful of sort / filter / group states. */
|
|
167
|
+
const RESULT_CACHE_SIZE = 8;
|
|
168
|
+
export function createInMemoryDataSource(initial, schema, options = {}) {
|
|
150
169
|
let store = [...initial];
|
|
151
170
|
const idField = resolveIdField(schema);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
// Every write reassigns `store`, so a cache filled from an older array is
|
|
172
|
+
// stale by identity alone.
|
|
173
|
+
const cache = new Map();
|
|
174
|
+
let cacheStore = null;
|
|
175
|
+
function prepare(plan) {
|
|
176
|
+
if (!options.cacheResults)
|
|
177
|
+
return run(plan);
|
|
178
|
+
if (cacheStore !== store) {
|
|
179
|
+
cache.clear();
|
|
180
|
+
cacheStore = store;
|
|
181
|
+
}
|
|
182
|
+
// The range is the only part of a plan that does not change the result.
|
|
183
|
+
const key = JSON.stringify({ ...plan, limit: 0, offset: 0 });
|
|
184
|
+
const hit = cache.get(key);
|
|
185
|
+
if (hit) {
|
|
186
|
+
// Re-insert so the Map's order is least-recently used first.
|
|
187
|
+
cache.delete(key);
|
|
188
|
+
cache.set(key, hit);
|
|
189
|
+
return hit;
|
|
190
|
+
}
|
|
191
|
+
const prepared = run(plan);
|
|
192
|
+
cache.set(key, prepared);
|
|
193
|
+
if (cache.size > RESULT_CACHE_SIZE)
|
|
194
|
+
cache.delete(cache.keys().next().value);
|
|
195
|
+
return prepared;
|
|
196
|
+
}
|
|
197
|
+
function run(plan) {
|
|
198
|
+
let filtered = store.filter((r) => matches(r, plan));
|
|
199
|
+
// Advanced filter. `planQuery` only admits an expression whose columns
|
|
200
|
+
// are all on the schema, so reaching here means it is safe to run. If it
|
|
201
|
+
// was rejected, or fails to compile, we must NOT acknowledge it - the
|
|
202
|
+
// grid then tells the user the filter did not run rather than showing a
|
|
203
|
+
// superset that looks filtered.
|
|
204
|
+
let appliedExpression = false;
|
|
205
|
+
let expressionPredicate = null;
|
|
206
|
+
if (plan.expression) {
|
|
207
|
+
const predicate = compilePredicate(plan.expression, {
|
|
208
|
+
getValue: (row, columnId) => row[columnId],
|
|
209
|
+
rows: filtered,
|
|
210
|
+
});
|
|
211
|
+
if (predicate) {
|
|
212
|
+
filtered = filtered.filter(predicate);
|
|
213
|
+
expressionPredicate = predicate;
|
|
214
|
+
appliedExpression = true;
|
|
173
215
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
// Sort by the group key unless the request sorted on a column we
|
|
224
|
-
// actually produced (the key or an aggregate).
|
|
225
|
-
const produced = new Set([
|
|
226
|
-
groupField,
|
|
227
|
-
...(plan.pivotBy?.length ? [] : (plan.aggregations ?? []).map((a) => a.field)),
|
|
228
|
-
]);
|
|
229
|
-
const groupOrder = plan.orderBy.filter((o) => produced.has(o.field));
|
|
230
|
-
const sorted = groupOrder.length
|
|
231
|
-
? sortRows(groups, groupOrder)
|
|
232
|
-
: sortRows(groups, [{ field: groupField, desc: false }]);
|
|
233
|
-
return {
|
|
234
|
-
rows: sorted.slice(plan.offset, plan.offset + plan.limit),
|
|
235
|
-
// The count is DISTINCT GROUPS, not underlying rows - the grid sizes
|
|
236
|
-
// its scrollbar and paging from this.
|
|
237
|
-
rowCount: sorted.length,
|
|
238
|
-
appliedExpression,
|
|
239
|
-
...grandTotal,
|
|
240
|
-
...(plan.pivotBy?.length ? { pivotResultFields: pivotFields } : {}),
|
|
241
|
-
};
|
|
216
|
+
}
|
|
217
|
+
// The grand total spans everything the FILTERS admit - the whole
|
|
218
|
+
// store minus the filters and the expression, but NOT minus the group
|
|
219
|
+
// path, which only scopes this request.
|
|
220
|
+
const grandTotal = plan.grandTotal
|
|
221
|
+
? { grandTotal: grandTotalOf(store, plan, expressionPredicate) }
|
|
222
|
+
: {};
|
|
223
|
+
// Grouped request: return one row per distinct key at this level, each
|
|
224
|
+
// carrying the requested aggregates. The chosen path is already in
|
|
225
|
+
// `plan.where`, so `filtered` is exactly this group's slice of the data.
|
|
226
|
+
// Reference behaviour for what a SQL backend does with `planToSql`'s
|
|
227
|
+
// `select` / `groupByText` / `countText`.
|
|
228
|
+
if (plan.groupBy) {
|
|
229
|
+
const groupField = plan.groupBy;
|
|
230
|
+
const buckets = new Map();
|
|
231
|
+
for (const row of filtered) {
|
|
232
|
+
const key = asString(row[groupField]);
|
|
233
|
+
const bucket = buckets.get(key);
|
|
234
|
+
if (bucket)
|
|
235
|
+
bucket.push(row);
|
|
236
|
+
else
|
|
237
|
+
buckets.set(key, [row]);
|
|
238
|
+
}
|
|
239
|
+
// Pivoting: every aggregate is computed once per distinct pivot key
|
|
240
|
+
// path inside the group, under a field named from the path, and the
|
|
241
|
+
// field list goes back so the grid can build the columns.
|
|
242
|
+
const pivotPaths = new Set();
|
|
243
|
+
const groups = [...buckets.entries()].map(([key, rows]) => {
|
|
244
|
+
const out = { [groupField]: key };
|
|
245
|
+
aggregateInto(out, rows, plan, pivotPaths);
|
|
246
|
+
// What opening this group would show: distinct keys of the next
|
|
247
|
+
// level, or the rows themselves at the innermost group level.
|
|
248
|
+
out.childCount = plan.childGroupBy
|
|
249
|
+
? new Set(rows.map((r) => asString(r[plan.childGroupBy]))).size
|
|
250
|
+
: rows.length;
|
|
251
|
+
return out;
|
|
252
|
+
});
|
|
253
|
+
// The pivoted fields in a fixed order - key paths in plain string
|
|
254
|
+
// order, aggregations as requested under each - so the grid builds
|
|
255
|
+
// the same columns whatever order the groups came in. Every group
|
|
256
|
+
// row carries every field; a cell with no rows under it is null, the
|
|
257
|
+
// way a conditional aggregate answers in SQL.
|
|
258
|
+
const pivotFields = [...pivotPaths]
|
|
259
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
260
|
+
.flatMap((path) => (plan.aggregations ?? []).map((agg) => `${path}_${agg.field}`));
|
|
261
|
+
for (const g of groups) {
|
|
262
|
+
for (const f of pivotFields)
|
|
263
|
+
if (!(f in g))
|
|
264
|
+
g[f] = null;
|
|
242
265
|
}
|
|
243
|
-
|
|
266
|
+
// Sort by the group key unless the request sorted on a column we
|
|
267
|
+
// actually produced (the key or an aggregate).
|
|
268
|
+
const produced = new Set([
|
|
269
|
+
groupField,
|
|
270
|
+
...(plan.pivotBy?.length ? [] : (plan.aggregations ?? []).map((a) => a.field)),
|
|
271
|
+
]);
|
|
272
|
+
const groupOrder = plan.orderBy.filter((o) => produced.has(o.field));
|
|
273
|
+
const sorted = groupOrder.length
|
|
274
|
+
? sortRows(groups, groupOrder)
|
|
275
|
+
: sortRows(groups, [{ field: groupField, desc: false }]);
|
|
244
276
|
return {
|
|
245
|
-
rows:
|
|
246
|
-
rowCount: sortedLeaves.length,
|
|
277
|
+
rows: sorted,
|
|
247
278
|
appliedExpression,
|
|
248
279
|
...grandTotal,
|
|
280
|
+
...(plan.pivotBy?.length ? { pivotFields } : {}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return { rows: sortRows(filtered, plan.orderBy), appliedExpression, ...grandTotal };
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
async getRows(request) {
|
|
287
|
+
const plan = planQuery(schema, request);
|
|
288
|
+
const prepared = prepare(plan);
|
|
289
|
+
return {
|
|
290
|
+
rows: prepared.rows.slice(plan.offset, plan.offset + plan.limit),
|
|
291
|
+
// On a grouped request the count is DISTINCT GROUPS, not underlying
|
|
292
|
+
// rows - the grid sizes its scrollbar and paging from this.
|
|
293
|
+
rowCount: prepared.rows.length,
|
|
294
|
+
appliedExpression: prepared.appliedExpression,
|
|
295
|
+
...(prepared.grandTotal !== undefined ? { grandTotal: prepared.grandTotal } : {}),
|
|
296
|
+
...(prepared.pivotFields ? { pivotResultFields: prepared.pivotFields } : {}),
|
|
249
297
|
};
|
|
250
298
|
},
|
|
251
299
|
async createRow(input) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { planQuery, coerce, BUILTIN_AGGREGATORS, type QueryPlan, type PlanPredicate, type PlanOp, type PlanAggregate, type PlanOptions, } from './query-plan.js';
|
|
2
|
-
export { createInMemoryDataSource } from './in-memory.js';
|
|
2
|
+
export { createInMemoryDataSource, type InMemoryDataSourceOptions } from './in-memory.js';
|
|
3
3
|
export { createKitDataSource, createKitHandlers, type KitDataSourceOptions, type KitHandlerOptions, type KitScope, type KitHandlers, type KitMessage, } from './transport.js';
|
|
4
4
|
export { planToSql, type SqlDialect, type SqlPlan } from './sql.js';
|
|
5
5
|
export { createSqlDataSource, type SqlExecutor, type SqlDataSourceConfig, } from './sql-source.js';
|
package/dist/version.d.ts
CHANGED
|
@@ -10,4 +10,17 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
12
12
|
*/
|
|
13
|
-
export declare const SVGRID_VERSION = "3.1.
|
|
13
|
+
export declare const SVGRID_VERSION = "3.1.2";
|
|
14
|
+
/**
|
|
15
|
+
* The version of `@svgrid/grid`, which releases on its own line: enterprise is
|
|
16
|
+
* on 3.1.x while the grid is on 3.0.x.
|
|
17
|
+
*
|
|
18
|
+
* A scaffolded app depends on BOTH packages, and pinning the grid to this
|
|
19
|
+
* package's version asked npm for `@svgrid/grid@^3.1.1`, which has never been
|
|
20
|
+
* published. Every `svgrid-studio init` therefore produced an app whose very
|
|
21
|
+
* next step, `npm install`, failed with ERESOLVE. Found 2026-10-05 by
|
|
22
|
+
* scaffolding one and running the install.
|
|
23
|
+
*
|
|
24
|
+
* `version.test.ts` asserts this matches packages/grid/package.json.
|
|
25
|
+
*/
|
|
26
|
+
export declare const GRID_VERSION = "3.0.8";
|
package/dist/version.js
CHANGED
|
@@ -10,4 +10,17 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
12
12
|
*/
|
|
13
|
-
export const SVGRID_VERSION = '3.1.
|
|
13
|
+
export const SVGRID_VERSION = '3.1.2';
|
|
14
|
+
/**
|
|
15
|
+
* The version of `@svgrid/grid`, which releases on its own line: enterprise is
|
|
16
|
+
* on 3.1.x while the grid is on 3.0.x.
|
|
17
|
+
*
|
|
18
|
+
* A scaffolded app depends on BOTH packages, and pinning the grid to this
|
|
19
|
+
* package's version asked npm for `@svgrid/grid@^3.1.1`, which has never been
|
|
20
|
+
* published. Every `svgrid-studio init` therefore produced an app whose very
|
|
21
|
+
* next step, `npm install`, failed with ERESOLVE. Found 2026-10-05 by
|
|
22
|
+
* scaffolding one and running the install.
|
|
23
|
+
*
|
|
24
|
+
* `version.test.ts` asserts this matches packages/grid/package.json.
|
|
25
|
+
*/
|
|
26
|
+
export const GRID_VERSION = '3.0.8';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ as e, D as t, G as n, H as r, I as i, L as a, M as o, O as s, P as c, Q as l, R as u, S as d, T as f, U as p, W as m, X as h, Y as g, Z as _, _ as v, a as y, at as b, b as x, d as S, dt as C, et as w, f as T, h as E, ht as D, it as O, j as k, k as A, l as ee, m as j, mt as M, n as N, nt as P, o as F, p as te, pt as I, q as L, st as R, tt as z, u as ne, ut as B, w as re, x as V, y as H, z as U } from "./client-DQIzjDBG.js";
|
|
2
|
-
import { c as W, s as ie, t as ae } from "./svelte-virtualizer.svelte-
|
|
3
|
-
import { S as oe, h as se, u as ce, w as le } from "./SvGrid.helpers-
|
|
2
|
+
import { c as W, s as ie, t as ae } from "./svelte-virtualizer.svelte-Du4tjrIP.js";
|
|
3
|
+
import { S as oe, h as se, u as ce, w as le } from "./SvGrid.helpers-zBkDTryR.js";
|
|
4
4
|
import { i as ue, t as G } from "./editor-contract-peBDxir_.js";
|
|
5
5
|
import { t as K } from "./SvField-B_kurx_P.js";
|
|
6
6
|
import { n as de } from "./dismissable-CQ6XFXsk.js";
|