@svgrid/enterprise 3.1.1 → 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 +7 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +10534 -10332
- package/dist/designer/assets/{index-DkhD2kIe.js → index-V_cMXdxt.js} +311 -311
- package/dist/designer/assets/validate-YxSrBF1H.js +1 -0
- package/dist/designer/index.html +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/node/studio.js +16 -4
- 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/studio/emit-project.js +8 -3
- 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/sv-sheet-element.js +14 -9
- 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/validate-C3B8QKsM.js +0 -1
|
@@ -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';
|