@atscript/ui-table 0.1.140 → 0.1.141
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/index.cjs +205 -26
- package/dist/index.d.cts +153 -17
- package/dist/index.d.mts +153 -17
- package/dist/index.mjs +203 -27
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -528,6 +528,7 @@ function fieldsOf(expr, out) {
|
|
|
528
528
|
else if (!out.includes(key)) out.push(key);
|
|
529
529
|
return out;
|
|
530
530
|
}
|
|
531
|
+
/** @internal Module-shared with the URL parser; not a package export. */
|
|
531
532
|
function unsupported(reason, expr) {
|
|
532
533
|
const fields = fieldsOf(expr, []);
|
|
533
534
|
return {
|
|
@@ -753,10 +754,10 @@ function warnUnsupported(issue) {
|
|
|
753
754
|
/**
|
|
754
755
|
* Split a Uniquery `FilterExpr` into what the table's field-filter model
|
|
755
756
|
* holds exactly (`filters`), what it cannot hold but carries as residual
|
|
756
|
-
* conditions (`residual`, with `carry`),
|
|
757
|
-
* (`
|
|
758
|
-
* `
|
|
759
|
-
* `
|
|
757
|
+
* conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
|
|
758
|
+
* and what names a field outside `knownFields` (`unknown`). Nothing is
|
|
759
|
+
* approximated: `filters AND residual` selects `expr` minus the
|
|
760
|
+
* `unsupported` and `unknown` pieces.
|
|
760
761
|
*
|
|
761
762
|
* Never throws, never warns — the caller decides how to report.
|
|
762
763
|
*
|
|
@@ -767,7 +768,8 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
767
768
|
const result = {
|
|
768
769
|
filters,
|
|
769
770
|
residual: [],
|
|
770
|
-
unsupported: []
|
|
771
|
+
unsupported: [],
|
|
772
|
+
unknown: []
|
|
771
773
|
};
|
|
772
774
|
if (!expr) return result;
|
|
773
775
|
const knownFields = opts.knownFields;
|
|
@@ -788,8 +790,12 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
788
790
|
}];
|
|
789
791
|
}
|
|
790
792
|
const positives = /* @__PURE__ */ new Map();
|
|
793
|
+
const unknown = /* @__PURE__ */ new Map();
|
|
791
794
|
for (const term of out.terms) {
|
|
792
|
-
if (!isKnown(term.field))
|
|
795
|
+
if (!isKnown(term.field)) {
|
|
796
|
+
unknown.set(term.expr, [term.field]);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
793
799
|
const list = filters[term.field] ??= [];
|
|
794
800
|
if (isNegative(term)) {
|
|
795
801
|
list.push(...term.conds);
|
|
@@ -819,11 +825,16 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
819
825
|
}
|
|
820
826
|
for (const issue of out.issues) {
|
|
821
827
|
const fields = issue.fields;
|
|
822
|
-
|
|
823
|
-
if (
|
|
828
|
+
const outside = fields.filter((f) => !isKnown(f));
|
|
829
|
+
if (outside.length > 0) unknown.set(issue.expr, outside);
|
|
830
|
+
else if (opts.carry && fields.length > 0) carried.push(issue.expr);
|
|
824
831
|
else result.unsupported.push(issue);
|
|
825
832
|
}
|
|
826
833
|
result.residual = normalizeResidualFilters(carried);
|
|
834
|
+
result.unknown = [...unknown].map(([expr, fields]) => ({
|
|
835
|
+
expr,
|
|
836
|
+
fields
|
|
837
|
+
}));
|
|
827
838
|
return result;
|
|
828
839
|
}
|
|
829
840
|
/**
|
|
@@ -874,9 +885,10 @@ function normalizeResidualFilters(exprs) {
|
|
|
874
885
|
* to a dev-mode `console.warn` when no handler is given. To keep those
|
|
875
886
|
* pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
|
|
876
887
|
*
|
|
877
|
-
*
|
|
878
|
-
* silently: they are not this table's (a host page flag, a
|
|
879
|
-
*
|
|
888
|
+
* Pieces that name a field outside `knownFields` (when provided) are ignored
|
|
889
|
+
* silently: they are not this table's (a host page flag, a hidden or stale
|
|
890
|
+
* column). Since 0.1.141 that includes a piece mixing known and unknown
|
|
891
|
+
* fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
|
|
880
892
|
*
|
|
881
893
|
* Returns `{}` for an empty/missing expression. Never throws.
|
|
882
894
|
*/
|
|
@@ -1146,6 +1158,137 @@ function resolveSystemPresets(input) {
|
|
|
1146
1158
|
}, ...named];
|
|
1147
1159
|
}
|
|
1148
1160
|
//#endregion
|
|
1161
|
+
//#region src/presets/prune-preset-snapshot.ts
|
|
1162
|
+
/** `[kept, dropped]` in one pass. */
|
|
1163
|
+
function partition(list, keep) {
|
|
1164
|
+
const kept = [];
|
|
1165
|
+
const dropped = [];
|
|
1166
|
+
for (const x of list) (keep(x) ? kept : dropped).push(x);
|
|
1167
|
+
return [kept, dropped];
|
|
1168
|
+
}
|
|
1169
|
+
/** The columns aspect, an empty width map spelled by omission — the way capture writes it. */
|
|
1170
|
+
function columnsAspect(columnNames, columnWidths) {
|
|
1171
|
+
return columnWidths && Object.keys(columnWidths).length > 0 ? {
|
|
1172
|
+
columnNames,
|
|
1173
|
+
columnWidths
|
|
1174
|
+
} : { columnNames };
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Remove every entry of `snapshot` that names a field outside `known`, per
|
|
1178
|
+
* aspect:
|
|
1179
|
+
*
|
|
1180
|
+
* - `columns.columnNames` — unknown names go, the rest keep their order. When
|
|
1181
|
+
* none survives, it falls back to every column, never an empty grid.
|
|
1182
|
+
* - `columns.columnWidths` — unknown keys go.
|
|
1183
|
+
* - `filters` (displayed inputs) and `filterOps` — entries on a field outside
|
|
1184
|
+
* `known.server` go. A field's conditions are one AND-ed conjunct, so
|
|
1185
|
+
* dropping one only broadens the result.
|
|
1186
|
+
* - `sorters` — unknown entries go, the rest keep their priority.
|
|
1187
|
+
* - `itemsPerPage` — untouched.
|
|
1188
|
+
*
|
|
1189
|
+
* `dropped` is `null` when nothing user-visible went (a width-only drop is
|
|
1190
|
+
* hygiene). The input is never mutated.
|
|
1191
|
+
*
|
|
1192
|
+
* @since 0.1.141
|
|
1193
|
+
*/
|
|
1194
|
+
function prunePresetSnapshot(snapshot, known) {
|
|
1195
|
+
const isColumn = (p) => known.columns.has(p);
|
|
1196
|
+
const isServer = (p) => known.server.has(p);
|
|
1197
|
+
const out = { ...snapshot };
|
|
1198
|
+
const dropped = {
|
|
1199
|
+
fields: [],
|
|
1200
|
+
columns: [],
|
|
1201
|
+
filterFields: [],
|
|
1202
|
+
filters: {},
|
|
1203
|
+
residual: [],
|
|
1204
|
+
sorters: []
|
|
1205
|
+
};
|
|
1206
|
+
if (snapshot.columns) {
|
|
1207
|
+
const [names, gone] = partition(snapshot.columns.columnNames, isColumn);
|
|
1208
|
+
const [widths] = partition(Object.entries(snapshot.columns.columnWidths ?? {}), ([p]) => isColumn(p));
|
|
1209
|
+
dropped.columns = gone;
|
|
1210
|
+
out.columns = columnsAspect(names.length > 0 || gone.length === 0 ? names : [...known.columns], Object.fromEntries(widths));
|
|
1211
|
+
}
|
|
1212
|
+
if (snapshot.filters) [out.filters, dropped.filterFields] = partition(snapshot.filters, isServer);
|
|
1213
|
+
if (snapshot.filterOps) {
|
|
1214
|
+
const [kept, gone] = partition(Object.entries(snapshot.filterOps), ([p]) => isServer(p));
|
|
1215
|
+
out.filterOps = Object.fromEntries(kept);
|
|
1216
|
+
dropped.filters = Object.fromEntries(gone);
|
|
1217
|
+
}
|
|
1218
|
+
if (snapshot.sorters) [out.sorters, dropped.sorters] = partition(snapshot.sorters, (s) => isColumn(s.field));
|
|
1219
|
+
dropped.fields = [...new Set([
|
|
1220
|
+
...dropped.columns,
|
|
1221
|
+
...dropped.filterFields,
|
|
1222
|
+
...Object.keys(dropped.filters),
|
|
1223
|
+
...dropped.sorters.map((s) => s.field)
|
|
1224
|
+
])];
|
|
1225
|
+
return {
|
|
1226
|
+
snapshot: out,
|
|
1227
|
+
dropped: dropped.fields.length > 0 ? dropped : null
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Split residual filter conditions, in one pass, into the ones that name
|
|
1232
|
+
* only `known` fields and the ones that do not. A condition naming any
|
|
1233
|
+
* unknown field is dropped WHOLE — never pruned inside an `$or` (that would
|
|
1234
|
+
* narrow the result) or a `$not` (that would invert it). Each condition is an
|
|
1235
|
+
* AND-ed conjunct, so dropping one only broadens the result.
|
|
1236
|
+
*
|
|
1237
|
+
* `dropped` holds the conditions in `residual` and the unknown paths in
|
|
1238
|
+
* `fields`; `null` when every condition is kept.
|
|
1239
|
+
*
|
|
1240
|
+
* @since 0.1.141
|
|
1241
|
+
*/
|
|
1242
|
+
function pruneResidualFilters(exprs, known) {
|
|
1243
|
+
const kept = [];
|
|
1244
|
+
const residual = [];
|
|
1245
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1246
|
+
for (const expr of exprs) {
|
|
1247
|
+
const unknown = filterExprFields(expr).filter((p) => !known.has(p));
|
|
1248
|
+
if (unknown.length === 0) {
|
|
1249
|
+
kept.push(expr);
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
residual.push(expr);
|
|
1253
|
+
for (const p of unknown) fields.add(p);
|
|
1254
|
+
}
|
|
1255
|
+
if (residual.length === 0) return {
|
|
1256
|
+
kept,
|
|
1257
|
+
dropped: null
|
|
1258
|
+
};
|
|
1259
|
+
return {
|
|
1260
|
+
kept,
|
|
1261
|
+
dropped: {
|
|
1262
|
+
fields: [...fields],
|
|
1263
|
+
columns: [],
|
|
1264
|
+
filterFields: [],
|
|
1265
|
+
filters: {},
|
|
1266
|
+
residual,
|
|
1267
|
+
sorters: []
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Append what a prune dropped back onto a snapshot captured from (pruned)
|
|
1273
|
+
* table state, so overwriting a preset with a narrower view does not destroy
|
|
1274
|
+
* the parts the saver cannot see. Column names, filter inputs and sorters go
|
|
1275
|
+
* after the captured ones; field filters are added back. Only aspects
|
|
1276
|
+
* `captured` carries are touched; widths of hidden columns are not kept.
|
|
1277
|
+
*
|
|
1278
|
+
* @since 0.1.141
|
|
1279
|
+
*/
|
|
1280
|
+
function restoreDroppedEntries(captured, dropped) {
|
|
1281
|
+
const out = { ...captured };
|
|
1282
|
+
if (captured.columns) out.columns = columnsAspect([...captured.columns.columnNames, ...dropped.columns], captured.columns.columnWidths);
|
|
1283
|
+
if (captured.filters) out.filters = [...captured.filters, ...dropped.filterFields];
|
|
1284
|
+
if (captured.filterOps) out.filterOps = {
|
|
1285
|
+
...captured.filterOps,
|
|
1286
|
+
...dropped.filters
|
|
1287
|
+
};
|
|
1288
|
+
if (captured.sorters) out.sorters = [...captured.sorters, ...dropped.sorters];
|
|
1289
|
+
return out;
|
|
1290
|
+
}
|
|
1291
|
+
//#endregion
|
|
1149
1292
|
//#region src/presets/preset-dirty.ts
|
|
1150
1293
|
/**
|
|
1151
1294
|
* JSON-stringify with object keys sorted alphabetically at every depth so
|
|
@@ -1749,6 +1892,20 @@ const CONSUMED_CONTROLS = new Set([
|
|
|
1749
1892
|
"$skip",
|
|
1750
1893
|
URL_SNAPSHOT_KEY
|
|
1751
1894
|
]);
|
|
1895
|
+
function toSet(paths) {
|
|
1896
|
+
if (!paths) return null;
|
|
1897
|
+
return paths instanceof Set ? paths : new Set(paths);
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* `field=value` (or `field=null`) — the one filter shape a host page flag
|
|
1901
|
+
* shares, so a bare one on an unknown field is not reported as dropped.
|
|
1902
|
+
*/
|
|
1903
|
+
function isBareEquality(expr) {
|
|
1904
|
+
const keys = Object.keys(expr);
|
|
1905
|
+
if (keys.length !== 1 || keys[0].startsWith("$")) return false;
|
|
1906
|
+
const v = expr[keys[0]];
|
|
1907
|
+
return v === null || typeof v !== "object";
|
|
1908
|
+
}
|
|
1752
1909
|
/**
|
|
1753
1910
|
* Characters that can only appear in a uniqu filter key, never in a page flag:
|
|
1754
1911
|
* comparison operators, `^` (OR), `( )` (groups) and `{ }` (`$in` lists).
|
|
@@ -1775,9 +1932,10 @@ function urlQueryConsumesKey(key) {
|
|
|
1775
1932
|
/**
|
|
1776
1933
|
* Parse a URL query string back into the table state subset.
|
|
1777
1934
|
*
|
|
1778
|
-
* Robust by design — schema drift
|
|
1779
|
-
* recipient's view:
|
|
1780
|
-
* - unknown fields (not in `knownFields`) →
|
|
1935
|
+
* Robust by design — schema drift, fields hidden from the recipient and
|
|
1936
|
+
* copy-paste errors must not break the recipient's view:
|
|
1937
|
+
* - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
|
|
1938
|
+
* `unknownSorters` (the parser does not warn — the caller decides)
|
|
1781
1939
|
* - filter pieces field filters cannot express (cross-field OR, unknown
|
|
1782
1940
|
* operator, …) → left out of `filters` and listed in `unsupported`, never
|
|
1783
1941
|
* approximated (the parser does not warn — the caller decides). Unless
|
|
@@ -1810,12 +1968,15 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1810
1968
|
const sortersGate = resolveAspectGate(opts.sync?.sorters);
|
|
1811
1969
|
const searchOff = opts.sync?.search === false;
|
|
1812
1970
|
const paginationOff = opts.sync?.pagination === false;
|
|
1813
|
-
const knownSet =
|
|
1971
|
+
const knownSet = toSet(opts.knownFields);
|
|
1972
|
+
const localSet = toSet(opts.localFields);
|
|
1973
|
+
const isHidden = (f) => !!knownSet && !knownSet.has(f) && !localSet?.has(f);
|
|
1814
1974
|
let filterKnown;
|
|
1815
1975
|
if (filtersGate === "all") filterKnown = knownSet ?? void 0;
|
|
1816
1976
|
else if (filtersGate !== "none") if (knownSet) {
|
|
1817
|
-
|
|
1818
|
-
for (const path of filtersGate) if (knownSet.has(path))
|
|
1977
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1978
|
+
for (const path of filtersGate) if (knownSet.has(path)) owned.add(path);
|
|
1979
|
+
filterKnown = owned;
|
|
1819
1980
|
} else filterKnown = filtersGate;
|
|
1820
1981
|
const decomposed = filtersGate === "none" ? null : decomposeUniqueryFilter(parsed.filter, {
|
|
1821
1982
|
knownFields: filterKnown,
|
|
@@ -1823,20 +1984,19 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1823
1984
|
});
|
|
1824
1985
|
const filters = decomposed?.filters ?? {};
|
|
1825
1986
|
const sorters = [];
|
|
1987
|
+
const unknownSorters = [];
|
|
1826
1988
|
if (sortersGate !== "none") {
|
|
1827
1989
|
const $sort = parsed.controls?.$sort;
|
|
1828
1990
|
if ($sort && typeof $sort === "object") for (const field in $sort) {
|
|
1829
|
-
if (knownSet && !knownSet.has(field)) continue;
|
|
1830
1991
|
if (!gateOwns(sortersGate, field)) continue;
|
|
1831
1992
|
const dir = $sort[field];
|
|
1832
|
-
if (dir
|
|
1833
|
-
|
|
1834
|
-
direction: "asc"
|
|
1835
|
-
});
|
|
1836
|
-
else if (dir === -1) sorters.push({
|
|
1993
|
+
if (dir !== 1 && dir !== -1) continue;
|
|
1994
|
+
const sorter = {
|
|
1837
1995
|
field,
|
|
1838
|
-
direction: "desc"
|
|
1839
|
-
}
|
|
1996
|
+
direction: dir === 1 ? "asc" : "desc"
|
|
1997
|
+
};
|
|
1998
|
+
if (!knownSet || knownSet.has(field)) sorters.push(sorter);
|
|
1999
|
+
else if (isHidden(field)) unknownSorters.push(sorter);
|
|
1840
2000
|
}
|
|
1841
2001
|
}
|
|
1842
2002
|
const $search = parsed.controls?.$search;
|
|
@@ -1845,8 +2005,24 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1845
2005
|
sorters,
|
|
1846
2006
|
searchTerm: !searchOff && typeof $search === "string" ? $search : ""
|
|
1847
2007
|
};
|
|
1848
|
-
|
|
2008
|
+
const unsupported$1 = decomposed?.unsupported ?? [];
|
|
2009
|
+
const unknown = [];
|
|
2010
|
+
for (const piece of decomposed?.unknown ?? []) {
|
|
2011
|
+
const hidden = piece.fields.filter(isHidden);
|
|
2012
|
+
if (hidden.length > 0) {
|
|
2013
|
+
if (!isBareEquality(piece.expr)) unknown.push({
|
|
2014
|
+
expr: piece.expr,
|
|
2015
|
+
fields: hidden
|
|
2016
|
+
});
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
const issue = unsupported("cross-field", piece.expr);
|
|
2020
|
+
if (issue.fields.some((f) => filterKnown?.has(f))) unsupported$1.push(issue);
|
|
2021
|
+
}
|
|
2022
|
+
if (unsupported$1.length) out.unsupported = unsupported$1;
|
|
1849
2023
|
if (decomposed?.residual.length) out.residual = decomposed.residual;
|
|
2024
|
+
if (unknown.length) out.unknown = unknown;
|
|
2025
|
+
if (unknownSorters.length) out.unknownSorters = unknownSorters;
|
|
1850
2026
|
if (opts.sync?.snapshot !== false && parsed.controls && "$snapshot" in parsed.controls) out.snapshot = true;
|
|
1851
2027
|
if (!searchOff) {
|
|
1852
2028
|
const $relevance = parsed.controls?.$relevance;
|
|
@@ -2464,12 +2640,15 @@ exports.pageAlignedBlocksFor = pageAlignedBlocksFor;
|
|
|
2464
2640
|
exports.parseColumnFilterInput = parseColumnFilterInput;
|
|
2465
2641
|
exports.parseFilterInput = parseFilterInput;
|
|
2466
2642
|
exports.planFetch = planFetch;
|
|
2643
|
+
exports.prunePresetSnapshot = prunePresetSnapshot;
|
|
2644
|
+
exports.pruneResidualFilters = pruneResidualFilters;
|
|
2467
2645
|
exports.reconcileColumnWidthDefaults = reconcileColumnWidthDefaults;
|
|
2468
2646
|
exports.reorderColumnNames = reorderColumnNames;
|
|
2469
2647
|
exports.residualGateOwns = residualGateOwns;
|
|
2470
2648
|
exports.resolveAspectGate = resolveAspectGate;
|
|
2471
2649
|
exports.resolveExportValue = resolveExportValue;
|
|
2472
2650
|
exports.resolveSystemPresets = resolveSystemPresets;
|
|
2651
|
+
exports.restoreDroppedEntries = restoreDroppedEntries;
|
|
2473
2652
|
exports.rowsToPks = rowsToPks;
|
|
2474
2653
|
exports.sameColumnSet = sameColumnSet;
|
|
2475
2654
|
exports.serializeDraft = serializeDraft;
|
package/dist/index.d.cts
CHANGED
|
@@ -175,6 +175,18 @@ interface UnsupportedFilter {
|
|
|
175
175
|
/** Field paths the sub-expression references, in order of appearance. */
|
|
176
176
|
fields: string[];
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* One AND-ed piece of a Uniquery filter left out because it names a field
|
|
180
|
+
* outside `knownFields` — alone or next to known ones.
|
|
181
|
+
*
|
|
182
|
+
* @since 0.1.141
|
|
183
|
+
*/
|
|
184
|
+
interface UnknownFilter {
|
|
185
|
+
/** The left-out sub-expression, as it appeared in the input. */
|
|
186
|
+
expr: FilterExpr;
|
|
187
|
+
/** The paths it names that are outside `knownFields`. */
|
|
188
|
+
fields: string[];
|
|
189
|
+
}
|
|
178
190
|
/**
|
|
179
191
|
* Field paths a Uniquery filter expression references, deduped, in order of
|
|
180
192
|
* appearance (logical operators are walked, operator keys skipped).
|
|
@@ -185,9 +197,10 @@ declare function filterExprFields(expr: unknown): string[];
|
|
|
185
197
|
/** Options for {@link decomposeUniqueryFilter}. @since 0.1.140 */
|
|
186
198
|
interface DecomposeUniqueryFilterOptions {
|
|
187
199
|
/**
|
|
188
|
-
* Field paths the table knows.
|
|
189
|
-
*
|
|
190
|
-
* carried
|
|
200
|
+
* Field paths the table knows. A piece that names a field outside it —
|
|
201
|
+
* alone or mixed with known ones — is left out whole and listed in
|
|
202
|
+
* `unknown`, never carried or reported as `unsupported`. Omit to accept
|
|
203
|
+
* every field.
|
|
191
204
|
*/
|
|
192
205
|
knownFields?: Iterable<string>;
|
|
193
206
|
/**
|
|
@@ -205,14 +218,22 @@ interface DecomposedUniqueryFilter {
|
|
|
205
218
|
residual: FilterExpr[];
|
|
206
219
|
/** Pieces left out and lost — the result is broader by exactly these. */
|
|
207
220
|
unsupported: UnsupportedFilter[];
|
|
221
|
+
/**
|
|
222
|
+
* Pieces left out because they name a field outside `knownFields` (hidden
|
|
223
|
+
* from the caller, or not in the schema). Each is a whole AND-ed piece, so
|
|
224
|
+
* the result is broader by exactly these too. `[]` without `knownFields`.
|
|
225
|
+
* Since 0.1.141 — before, pieces on unknown fields only were dropped
|
|
226
|
+
* silently and mixed known/unknown pieces went to `unsupported`.
|
|
227
|
+
*/
|
|
228
|
+
unknown: UnknownFilter[];
|
|
208
229
|
}
|
|
209
230
|
/**
|
|
210
231
|
* Split a Uniquery `FilterExpr` into what the table's field-filter model
|
|
211
232
|
* holds exactly (`filters`), what it cannot hold but carries as residual
|
|
212
|
-
* conditions (`residual`, with `carry`),
|
|
213
|
-
* (`
|
|
214
|
-
* `
|
|
215
|
-
* `
|
|
233
|
+
* conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
|
|
234
|
+
* and what names a field outside `knownFields` (`unknown`). Nothing is
|
|
235
|
+
* approximated: `filters AND residual` selects `expr` minus the
|
|
236
|
+
* `unsupported` and `unknown` pieces.
|
|
216
237
|
*
|
|
217
238
|
* Never throws, never warns — the caller decides how to report.
|
|
218
239
|
*
|
|
@@ -253,9 +274,10 @@ declare function normalizeResidualFilters(exprs: readonly FilterExpr[]): FilterE
|
|
|
253
274
|
* to a dev-mode `console.warn` when no handler is given. To keep those
|
|
254
275
|
* pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
|
|
255
276
|
*
|
|
256
|
-
*
|
|
257
|
-
* silently: they are not this table's (a host page flag, a
|
|
258
|
-
*
|
|
277
|
+
* Pieces that name a field outside `knownFields` (when provided) are ignored
|
|
278
|
+
* silently: they are not this table's (a host page flag, a hidden or stale
|
|
279
|
+
* column). Since 0.1.141 that includes a piece mixing known and unknown
|
|
280
|
+
* fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
|
|
259
281
|
*
|
|
260
282
|
* Returns `{}` for an empty/missing expression. Never throws.
|
|
261
283
|
*/
|
|
@@ -530,6 +552,96 @@ interface SystemPresetInput {
|
|
|
530
552
|
*/
|
|
531
553
|
declare function resolveSystemPresets(input?: SystemPresetInput[]): SystemPreset[];
|
|
532
554
|
//#endregion
|
|
555
|
+
//#region src/presets/prune-preset-snapshot.d.ts
|
|
556
|
+
/**
|
|
557
|
+
* The field paths a table can use, derived from its column list. A path
|
|
558
|
+
* outside these sets is one the server does not expose to the current caller
|
|
559
|
+
* (hidden by role, removed from the schema) — naming it in a query is rejected.
|
|
560
|
+
*
|
|
561
|
+
* @since 0.1.141
|
|
562
|
+
*/
|
|
563
|
+
interface KnownFields {
|
|
564
|
+
/**
|
|
565
|
+
* Every column path, client-owned (display) columns included, in column
|
|
566
|
+
* order. Gates `columnNames`, column widths and sorters (a sorter on a
|
|
567
|
+
* client-owned column is applied in memory).
|
|
568
|
+
*/
|
|
569
|
+
columns: ReadonlySet<string>;
|
|
570
|
+
/**
|
|
571
|
+
* Server-backed column paths. Gates displayed filter inputs, field filters
|
|
572
|
+
* and residual filter conditions.
|
|
573
|
+
*/
|
|
574
|
+
server: ReadonlySet<string>;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* What pruning left out of a preset snapshot or of residual conditions
|
|
578
|
+
* because it names a field outside {@link KnownFields}.
|
|
579
|
+
*
|
|
580
|
+
* @since 0.1.141
|
|
581
|
+
*/
|
|
582
|
+
interface DroppedFields {
|
|
583
|
+
/** The unavailable field paths behind the drops, deduped, in order of appearance. */
|
|
584
|
+
fields: string[];
|
|
585
|
+
/** Dropped `columnNames` entries. */
|
|
586
|
+
columns: string[];
|
|
587
|
+
/** Dropped displayed filter inputs. */
|
|
588
|
+
filterFields: string[];
|
|
589
|
+
/** Dropped field filters — whole entries, one per field. */
|
|
590
|
+
filters: FieldFilters;
|
|
591
|
+
/** Dropped filter conditions — each a whole AND-ed conjunct. */
|
|
592
|
+
residual: FilterExpr[];
|
|
593
|
+
/** Dropped sorters. */
|
|
594
|
+
sorters: SortControl[];
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Remove every entry of `snapshot` that names a field outside `known`, per
|
|
598
|
+
* aspect:
|
|
599
|
+
*
|
|
600
|
+
* - `columns.columnNames` — unknown names go, the rest keep their order. When
|
|
601
|
+
* none survives, it falls back to every column, never an empty grid.
|
|
602
|
+
* - `columns.columnWidths` — unknown keys go.
|
|
603
|
+
* - `filters` (displayed inputs) and `filterOps` — entries on a field outside
|
|
604
|
+
* `known.server` go. A field's conditions are one AND-ed conjunct, so
|
|
605
|
+
* dropping one only broadens the result.
|
|
606
|
+
* - `sorters` — unknown entries go, the rest keep their priority.
|
|
607
|
+
* - `itemsPerPage` — untouched.
|
|
608
|
+
*
|
|
609
|
+
* `dropped` is `null` when nothing user-visible went (a width-only drop is
|
|
610
|
+
* hygiene). The input is never mutated.
|
|
611
|
+
*
|
|
612
|
+
* @since 0.1.141
|
|
613
|
+
*/
|
|
614
|
+
declare function prunePresetSnapshot(snapshot: PresetSnapshot, known: KnownFields): {
|
|
615
|
+
snapshot: PresetSnapshot;
|
|
616
|
+
dropped: DroppedFields | null;
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* Split residual filter conditions, in one pass, into the ones that name
|
|
620
|
+
* only `known` fields and the ones that do not. A condition naming any
|
|
621
|
+
* unknown field is dropped WHOLE — never pruned inside an `$or` (that would
|
|
622
|
+
* narrow the result) or a `$not` (that would invert it). Each condition is an
|
|
623
|
+
* AND-ed conjunct, so dropping one only broadens the result.
|
|
624
|
+
*
|
|
625
|
+
* `dropped` holds the conditions in `residual` and the unknown paths in
|
|
626
|
+
* `fields`; `null` when every condition is kept.
|
|
627
|
+
*
|
|
628
|
+
* @since 0.1.141
|
|
629
|
+
*/
|
|
630
|
+
declare function pruneResidualFilters(exprs: FilterExpr[], known: ReadonlySet<string>): {
|
|
631
|
+
kept: FilterExpr[];
|
|
632
|
+
dropped: DroppedFields | null;
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Append what a prune dropped back onto a snapshot captured from (pruned)
|
|
636
|
+
* table state, so overwriting a preset with a narrower view does not destroy
|
|
637
|
+
* the parts the saver cannot see. Column names, filter inputs and sorters go
|
|
638
|
+
* after the captured ones; field filters are added back. Only aspects
|
|
639
|
+
* `captured` carries are touched; widths of hidden columns are not kept.
|
|
640
|
+
*
|
|
641
|
+
* @since 0.1.141
|
|
642
|
+
*/
|
|
643
|
+
declare function restoreDroppedEntries(captured: PresetSnapshot, dropped: DroppedFields): PresetSnapshot;
|
|
644
|
+
//#endregion
|
|
533
645
|
//#region src/presets/preset-dirty.d.ts
|
|
534
646
|
/**
|
|
535
647
|
* JSON-stringify with object keys sorted alphabetically at every depth so
|
|
@@ -888,6 +1000,21 @@ interface UrlQueryStateSnapshot {
|
|
|
888
1000
|
* or `sync.residual` is `false`. Since 0.1.140.
|
|
889
1001
|
*/
|
|
890
1002
|
residual?: FilterExpr[];
|
|
1003
|
+
/**
|
|
1004
|
+
* Filter pieces left out because they name a field outside `knownFields`
|
|
1005
|
+
* and `localFields` — a column hidden from this caller, or gone from the
|
|
1006
|
+
* schema — with those paths in `fields`. Each is a whole AND-ed piece (a
|
|
1007
|
+
* mixed known/unknown one included), so the result is broader by exactly
|
|
1008
|
+
* these. A bare `field=value` piece is not listed: without the table
|
|
1009
|
+
* definition it reads the same as a flag of the host page. Omitted when
|
|
1010
|
+
* none. Since 0.1.141 — before, a mixed piece was listed in `unsupported`.
|
|
1011
|
+
*/
|
|
1012
|
+
unknown?: UnknownFilter[];
|
|
1013
|
+
/**
|
|
1014
|
+
* `$sort` entries left out because their field is outside `knownFields`
|
|
1015
|
+
* and `localFields`. Omitted when none. Since 0.1.141.
|
|
1016
|
+
*/
|
|
1017
|
+
unknownSorters?: SortControl[];
|
|
891
1018
|
/**
|
|
892
1019
|
* Raw record offset from `$skip` (omitted when no `$skip` in URL). The
|
|
893
1020
|
* decoder does NOT compute a page index — that requires `itemsPerPage`,
|
|
@@ -1013,20 +1140,29 @@ declare function stateToUrlQueryString(state: UrlQueryStateLike, defaults: UrlQu
|
|
|
1013
1140
|
declare function urlQueryConsumesKey(key: string): boolean;
|
|
1014
1141
|
interface UrlQueryParseOptions {
|
|
1015
1142
|
/**
|
|
1016
|
-
* Field paths the table knows about. Conditions on fields
|
|
1017
|
-
*
|
|
1018
|
-
* definition isn't loaded
|
|
1143
|
+
* Field paths the table knows about. Conditions and sorters on fields
|
|
1144
|
+
* outside this set are dropped and listed in `unknown` / `unknownSorters`.
|
|
1145
|
+
* Omit to accept any field (useful when the table definition isn't loaded
|
|
1146
|
+
* yet).
|
|
1019
1147
|
*/
|
|
1020
1148
|
knownFields?: Iterable<string>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Client-owned column paths. The URL never restores them (no server query
|
|
1151
|
+
* can use them), but they are not unknown either: a sorter on one is
|
|
1152
|
+
* ignored, and a filter piece correlating one with a known field is
|
|
1153
|
+
* reported as `unsupported` (`"cross-field"`). Since 0.1.141.
|
|
1154
|
+
*/
|
|
1155
|
+
localFields?: Iterable<string>;
|
|
1021
1156
|
/** Per-aspect sync gates — must match the encoder's config to keep the round-trip symmetric. */
|
|
1022
1157
|
sync?: UrlQuerySync;
|
|
1023
1158
|
}
|
|
1024
1159
|
/**
|
|
1025
1160
|
* Parse a URL query string back into the table state subset.
|
|
1026
1161
|
*
|
|
1027
|
-
* Robust by design — schema drift
|
|
1028
|
-
* recipient's view:
|
|
1029
|
-
* - unknown fields (not in `knownFields`) →
|
|
1162
|
+
* Robust by design — schema drift, fields hidden from the recipient and
|
|
1163
|
+
* copy-paste errors must not break the recipient's view:
|
|
1164
|
+
* - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
|
|
1165
|
+
* `unknownSorters` (the parser does not warn — the caller decides)
|
|
1030
1166
|
* - filter pieces field filters cannot express (cross-field OR, unknown
|
|
1031
1167
|
* operator, …) → left out of `filters` and listed in `unsupported`, never
|
|
1032
1168
|
* approximated (the parser does not warn — the caller decides). Unless
|
|
@@ -1561,4 +1697,4 @@ declare function sortRowsLocally<T extends Record<string, unknown>>(rows: T[], s
|
|
|
1561
1697
|
//#region src/utils/dev.d.ts
|
|
1562
1698
|
declare const DEV: boolean;
|
|
1563
1699
|
//#endregion
|
|
1564
|
-
export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
|
1700
|
+
export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, type DroppedFields, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, type KnownFields, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnknownFilter, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
package/dist/index.d.mts
CHANGED
|
@@ -175,6 +175,18 @@ interface UnsupportedFilter {
|
|
|
175
175
|
/** Field paths the sub-expression references, in order of appearance. */
|
|
176
176
|
fields: string[];
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* One AND-ed piece of a Uniquery filter left out because it names a field
|
|
180
|
+
* outside `knownFields` — alone or next to known ones.
|
|
181
|
+
*
|
|
182
|
+
* @since 0.1.141
|
|
183
|
+
*/
|
|
184
|
+
interface UnknownFilter {
|
|
185
|
+
/** The left-out sub-expression, as it appeared in the input. */
|
|
186
|
+
expr: FilterExpr;
|
|
187
|
+
/** The paths it names that are outside `knownFields`. */
|
|
188
|
+
fields: string[];
|
|
189
|
+
}
|
|
178
190
|
/**
|
|
179
191
|
* Field paths a Uniquery filter expression references, deduped, in order of
|
|
180
192
|
* appearance (logical operators are walked, operator keys skipped).
|
|
@@ -185,9 +197,10 @@ declare function filterExprFields(expr: unknown): string[];
|
|
|
185
197
|
/** Options for {@link decomposeUniqueryFilter}. @since 0.1.140 */
|
|
186
198
|
interface DecomposeUniqueryFilterOptions {
|
|
187
199
|
/**
|
|
188
|
-
* Field paths the table knows.
|
|
189
|
-
*
|
|
190
|
-
* carried
|
|
200
|
+
* Field paths the table knows. A piece that names a field outside it —
|
|
201
|
+
* alone or mixed with known ones — is left out whole and listed in
|
|
202
|
+
* `unknown`, never carried or reported as `unsupported`. Omit to accept
|
|
203
|
+
* every field.
|
|
191
204
|
*/
|
|
192
205
|
knownFields?: Iterable<string>;
|
|
193
206
|
/**
|
|
@@ -205,14 +218,22 @@ interface DecomposedUniqueryFilter {
|
|
|
205
218
|
residual: FilterExpr[];
|
|
206
219
|
/** Pieces left out and lost — the result is broader by exactly these. */
|
|
207
220
|
unsupported: UnsupportedFilter[];
|
|
221
|
+
/**
|
|
222
|
+
* Pieces left out because they name a field outside `knownFields` (hidden
|
|
223
|
+
* from the caller, or not in the schema). Each is a whole AND-ed piece, so
|
|
224
|
+
* the result is broader by exactly these too. `[]` without `knownFields`.
|
|
225
|
+
* Since 0.1.141 — before, pieces on unknown fields only were dropped
|
|
226
|
+
* silently and mixed known/unknown pieces went to `unsupported`.
|
|
227
|
+
*/
|
|
228
|
+
unknown: UnknownFilter[];
|
|
208
229
|
}
|
|
209
230
|
/**
|
|
210
231
|
* Split a Uniquery `FilterExpr` into what the table's field-filter model
|
|
211
232
|
* holds exactly (`filters`), what it cannot hold but carries as residual
|
|
212
|
-
* conditions (`residual`, with `carry`),
|
|
213
|
-
* (`
|
|
214
|
-
* `
|
|
215
|
-
* `
|
|
233
|
+
* conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
|
|
234
|
+
* and what names a field outside `knownFields` (`unknown`). Nothing is
|
|
235
|
+
* approximated: `filters AND residual` selects `expr` minus the
|
|
236
|
+
* `unsupported` and `unknown` pieces.
|
|
216
237
|
*
|
|
217
238
|
* Never throws, never warns — the caller decides how to report.
|
|
218
239
|
*
|
|
@@ -253,9 +274,10 @@ declare function normalizeResidualFilters(exprs: readonly FilterExpr[]): FilterE
|
|
|
253
274
|
* to a dev-mode `console.warn` when no handler is given. To keep those
|
|
254
275
|
* pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
|
|
255
276
|
*
|
|
256
|
-
*
|
|
257
|
-
* silently: they are not this table's (a host page flag, a
|
|
258
|
-
*
|
|
277
|
+
* Pieces that name a field outside `knownFields` (when provided) are ignored
|
|
278
|
+
* silently: they are not this table's (a host page flag, a hidden or stale
|
|
279
|
+
* column). Since 0.1.141 that includes a piece mixing known and unknown
|
|
280
|
+
* fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
|
|
259
281
|
*
|
|
260
282
|
* Returns `{}` for an empty/missing expression. Never throws.
|
|
261
283
|
*/
|
|
@@ -530,6 +552,96 @@ interface SystemPresetInput {
|
|
|
530
552
|
*/
|
|
531
553
|
declare function resolveSystemPresets(input?: SystemPresetInput[]): SystemPreset[];
|
|
532
554
|
//#endregion
|
|
555
|
+
//#region src/presets/prune-preset-snapshot.d.ts
|
|
556
|
+
/**
|
|
557
|
+
* The field paths a table can use, derived from its column list. A path
|
|
558
|
+
* outside these sets is one the server does not expose to the current caller
|
|
559
|
+
* (hidden by role, removed from the schema) — naming it in a query is rejected.
|
|
560
|
+
*
|
|
561
|
+
* @since 0.1.141
|
|
562
|
+
*/
|
|
563
|
+
interface KnownFields {
|
|
564
|
+
/**
|
|
565
|
+
* Every column path, client-owned (display) columns included, in column
|
|
566
|
+
* order. Gates `columnNames`, column widths and sorters (a sorter on a
|
|
567
|
+
* client-owned column is applied in memory).
|
|
568
|
+
*/
|
|
569
|
+
columns: ReadonlySet<string>;
|
|
570
|
+
/**
|
|
571
|
+
* Server-backed column paths. Gates displayed filter inputs, field filters
|
|
572
|
+
* and residual filter conditions.
|
|
573
|
+
*/
|
|
574
|
+
server: ReadonlySet<string>;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* What pruning left out of a preset snapshot or of residual conditions
|
|
578
|
+
* because it names a field outside {@link KnownFields}.
|
|
579
|
+
*
|
|
580
|
+
* @since 0.1.141
|
|
581
|
+
*/
|
|
582
|
+
interface DroppedFields {
|
|
583
|
+
/** The unavailable field paths behind the drops, deduped, in order of appearance. */
|
|
584
|
+
fields: string[];
|
|
585
|
+
/** Dropped `columnNames` entries. */
|
|
586
|
+
columns: string[];
|
|
587
|
+
/** Dropped displayed filter inputs. */
|
|
588
|
+
filterFields: string[];
|
|
589
|
+
/** Dropped field filters — whole entries, one per field. */
|
|
590
|
+
filters: FieldFilters;
|
|
591
|
+
/** Dropped filter conditions — each a whole AND-ed conjunct. */
|
|
592
|
+
residual: FilterExpr[];
|
|
593
|
+
/** Dropped sorters. */
|
|
594
|
+
sorters: SortControl[];
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Remove every entry of `snapshot` that names a field outside `known`, per
|
|
598
|
+
* aspect:
|
|
599
|
+
*
|
|
600
|
+
* - `columns.columnNames` — unknown names go, the rest keep their order. When
|
|
601
|
+
* none survives, it falls back to every column, never an empty grid.
|
|
602
|
+
* - `columns.columnWidths` — unknown keys go.
|
|
603
|
+
* - `filters` (displayed inputs) and `filterOps` — entries on a field outside
|
|
604
|
+
* `known.server` go. A field's conditions are one AND-ed conjunct, so
|
|
605
|
+
* dropping one only broadens the result.
|
|
606
|
+
* - `sorters` — unknown entries go, the rest keep their priority.
|
|
607
|
+
* - `itemsPerPage` — untouched.
|
|
608
|
+
*
|
|
609
|
+
* `dropped` is `null` when nothing user-visible went (a width-only drop is
|
|
610
|
+
* hygiene). The input is never mutated.
|
|
611
|
+
*
|
|
612
|
+
* @since 0.1.141
|
|
613
|
+
*/
|
|
614
|
+
declare function prunePresetSnapshot(snapshot: PresetSnapshot, known: KnownFields): {
|
|
615
|
+
snapshot: PresetSnapshot;
|
|
616
|
+
dropped: DroppedFields | null;
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* Split residual filter conditions, in one pass, into the ones that name
|
|
620
|
+
* only `known` fields and the ones that do not. A condition naming any
|
|
621
|
+
* unknown field is dropped WHOLE — never pruned inside an `$or` (that would
|
|
622
|
+
* narrow the result) or a `$not` (that would invert it). Each condition is an
|
|
623
|
+
* AND-ed conjunct, so dropping one only broadens the result.
|
|
624
|
+
*
|
|
625
|
+
* `dropped` holds the conditions in `residual` and the unknown paths in
|
|
626
|
+
* `fields`; `null` when every condition is kept.
|
|
627
|
+
*
|
|
628
|
+
* @since 0.1.141
|
|
629
|
+
*/
|
|
630
|
+
declare function pruneResidualFilters(exprs: FilterExpr[], known: ReadonlySet<string>): {
|
|
631
|
+
kept: FilterExpr[];
|
|
632
|
+
dropped: DroppedFields | null;
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Append what a prune dropped back onto a snapshot captured from (pruned)
|
|
636
|
+
* table state, so overwriting a preset with a narrower view does not destroy
|
|
637
|
+
* the parts the saver cannot see. Column names, filter inputs and sorters go
|
|
638
|
+
* after the captured ones; field filters are added back. Only aspects
|
|
639
|
+
* `captured` carries are touched; widths of hidden columns are not kept.
|
|
640
|
+
*
|
|
641
|
+
* @since 0.1.141
|
|
642
|
+
*/
|
|
643
|
+
declare function restoreDroppedEntries(captured: PresetSnapshot, dropped: DroppedFields): PresetSnapshot;
|
|
644
|
+
//#endregion
|
|
533
645
|
//#region src/presets/preset-dirty.d.ts
|
|
534
646
|
/**
|
|
535
647
|
* JSON-stringify with object keys sorted alphabetically at every depth so
|
|
@@ -888,6 +1000,21 @@ interface UrlQueryStateSnapshot {
|
|
|
888
1000
|
* or `sync.residual` is `false`. Since 0.1.140.
|
|
889
1001
|
*/
|
|
890
1002
|
residual?: FilterExpr[];
|
|
1003
|
+
/**
|
|
1004
|
+
* Filter pieces left out because they name a field outside `knownFields`
|
|
1005
|
+
* and `localFields` — a column hidden from this caller, or gone from the
|
|
1006
|
+
* schema — with those paths in `fields`. Each is a whole AND-ed piece (a
|
|
1007
|
+
* mixed known/unknown one included), so the result is broader by exactly
|
|
1008
|
+
* these. A bare `field=value` piece is not listed: without the table
|
|
1009
|
+
* definition it reads the same as a flag of the host page. Omitted when
|
|
1010
|
+
* none. Since 0.1.141 — before, a mixed piece was listed in `unsupported`.
|
|
1011
|
+
*/
|
|
1012
|
+
unknown?: UnknownFilter[];
|
|
1013
|
+
/**
|
|
1014
|
+
* `$sort` entries left out because their field is outside `knownFields`
|
|
1015
|
+
* and `localFields`. Omitted when none. Since 0.1.141.
|
|
1016
|
+
*/
|
|
1017
|
+
unknownSorters?: SortControl[];
|
|
891
1018
|
/**
|
|
892
1019
|
* Raw record offset from `$skip` (omitted when no `$skip` in URL). The
|
|
893
1020
|
* decoder does NOT compute a page index — that requires `itemsPerPage`,
|
|
@@ -1013,20 +1140,29 @@ declare function stateToUrlQueryString(state: UrlQueryStateLike, defaults: UrlQu
|
|
|
1013
1140
|
declare function urlQueryConsumesKey(key: string): boolean;
|
|
1014
1141
|
interface UrlQueryParseOptions {
|
|
1015
1142
|
/**
|
|
1016
|
-
* Field paths the table knows about. Conditions on fields
|
|
1017
|
-
*
|
|
1018
|
-
* definition isn't loaded
|
|
1143
|
+
* Field paths the table knows about. Conditions and sorters on fields
|
|
1144
|
+
* outside this set are dropped and listed in `unknown` / `unknownSorters`.
|
|
1145
|
+
* Omit to accept any field (useful when the table definition isn't loaded
|
|
1146
|
+
* yet).
|
|
1019
1147
|
*/
|
|
1020
1148
|
knownFields?: Iterable<string>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Client-owned column paths. The URL never restores them (no server query
|
|
1151
|
+
* can use them), but they are not unknown either: a sorter on one is
|
|
1152
|
+
* ignored, and a filter piece correlating one with a known field is
|
|
1153
|
+
* reported as `unsupported` (`"cross-field"`). Since 0.1.141.
|
|
1154
|
+
*/
|
|
1155
|
+
localFields?: Iterable<string>;
|
|
1021
1156
|
/** Per-aspect sync gates — must match the encoder's config to keep the round-trip symmetric. */
|
|
1022
1157
|
sync?: UrlQuerySync;
|
|
1023
1158
|
}
|
|
1024
1159
|
/**
|
|
1025
1160
|
* Parse a URL query string back into the table state subset.
|
|
1026
1161
|
*
|
|
1027
|
-
* Robust by design — schema drift
|
|
1028
|
-
* recipient's view:
|
|
1029
|
-
* - unknown fields (not in `knownFields`) →
|
|
1162
|
+
* Robust by design — schema drift, fields hidden from the recipient and
|
|
1163
|
+
* copy-paste errors must not break the recipient's view:
|
|
1164
|
+
* - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
|
|
1165
|
+
* `unknownSorters` (the parser does not warn — the caller decides)
|
|
1030
1166
|
* - filter pieces field filters cannot express (cross-field OR, unknown
|
|
1031
1167
|
* operator, …) → left out of `filters` and listed in `unsupported`, never
|
|
1032
1168
|
* approximated (the parser does not warn — the caller decides). Unless
|
|
@@ -1561,4 +1697,4 @@ declare function sortRowsLocally<T extends Record<string, unknown>>(rows: T[], s
|
|
|
1561
1697
|
//#region src/utils/dev.d.ts
|
|
1562
1698
|
declare const DEV: boolean;
|
|
1563
1699
|
//#endregion
|
|
1564
|
-
export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
|
1700
|
+
export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, type DroppedFields, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, type KnownFields, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnknownFilter, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
package/dist/index.mjs
CHANGED
|
@@ -527,6 +527,7 @@ function fieldsOf(expr, out) {
|
|
|
527
527
|
else if (!out.includes(key)) out.push(key);
|
|
528
528
|
return out;
|
|
529
529
|
}
|
|
530
|
+
/** @internal Module-shared with the URL parser; not a package export. */
|
|
530
531
|
function unsupported(reason, expr) {
|
|
531
532
|
const fields = fieldsOf(expr, []);
|
|
532
533
|
return {
|
|
@@ -752,10 +753,10 @@ function warnUnsupported(issue) {
|
|
|
752
753
|
/**
|
|
753
754
|
* Split a Uniquery `FilterExpr` into what the table's field-filter model
|
|
754
755
|
* holds exactly (`filters`), what it cannot hold but carries as residual
|
|
755
|
-
* conditions (`residual`, with `carry`),
|
|
756
|
-
* (`
|
|
757
|
-
* `
|
|
758
|
-
* `
|
|
756
|
+
* conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
|
|
757
|
+
* and what names a field outside `knownFields` (`unknown`). Nothing is
|
|
758
|
+
* approximated: `filters AND residual` selects `expr` minus the
|
|
759
|
+
* `unsupported` and `unknown` pieces.
|
|
759
760
|
*
|
|
760
761
|
* Never throws, never warns — the caller decides how to report.
|
|
761
762
|
*
|
|
@@ -766,7 +767,8 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
766
767
|
const result = {
|
|
767
768
|
filters,
|
|
768
769
|
residual: [],
|
|
769
|
-
unsupported: []
|
|
770
|
+
unsupported: [],
|
|
771
|
+
unknown: []
|
|
770
772
|
};
|
|
771
773
|
if (!expr) return result;
|
|
772
774
|
const knownFields = opts.knownFields;
|
|
@@ -787,8 +789,12 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
787
789
|
}];
|
|
788
790
|
}
|
|
789
791
|
const positives = /* @__PURE__ */ new Map();
|
|
792
|
+
const unknown = /* @__PURE__ */ new Map();
|
|
790
793
|
for (const term of out.terms) {
|
|
791
|
-
if (!isKnown(term.field))
|
|
794
|
+
if (!isKnown(term.field)) {
|
|
795
|
+
unknown.set(term.expr, [term.field]);
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
792
798
|
const list = filters[term.field] ??= [];
|
|
793
799
|
if (isNegative(term)) {
|
|
794
800
|
list.push(...term.conds);
|
|
@@ -818,11 +824,16 @@ function decomposeUniqueryFilter(expr, opts = {}) {
|
|
|
818
824
|
}
|
|
819
825
|
for (const issue of out.issues) {
|
|
820
826
|
const fields = issue.fields;
|
|
821
|
-
|
|
822
|
-
if (
|
|
827
|
+
const outside = fields.filter((f) => !isKnown(f));
|
|
828
|
+
if (outside.length > 0) unknown.set(issue.expr, outside);
|
|
829
|
+
else if (opts.carry && fields.length > 0) carried.push(issue.expr);
|
|
823
830
|
else result.unsupported.push(issue);
|
|
824
831
|
}
|
|
825
832
|
result.residual = normalizeResidualFilters(carried);
|
|
833
|
+
result.unknown = [...unknown].map(([expr, fields]) => ({
|
|
834
|
+
expr,
|
|
835
|
+
fields
|
|
836
|
+
}));
|
|
826
837
|
return result;
|
|
827
838
|
}
|
|
828
839
|
/**
|
|
@@ -873,9 +884,10 @@ function normalizeResidualFilters(exprs) {
|
|
|
873
884
|
* to a dev-mode `console.warn` when no handler is given. To keep those
|
|
874
885
|
* pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
|
|
875
886
|
*
|
|
876
|
-
*
|
|
877
|
-
* silently: they are not this table's (a host page flag, a
|
|
878
|
-
*
|
|
887
|
+
* Pieces that name a field outside `knownFields` (when provided) are ignored
|
|
888
|
+
* silently: they are not this table's (a host page flag, a hidden or stale
|
|
889
|
+
* column). Since 0.1.141 that includes a piece mixing known and unknown
|
|
890
|
+
* fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
|
|
879
891
|
*
|
|
880
892
|
* Returns `{}` for an empty/missing expression. Never throws.
|
|
881
893
|
*/
|
|
@@ -1145,6 +1157,137 @@ function resolveSystemPresets(input) {
|
|
|
1145
1157
|
}, ...named];
|
|
1146
1158
|
}
|
|
1147
1159
|
//#endregion
|
|
1160
|
+
//#region src/presets/prune-preset-snapshot.ts
|
|
1161
|
+
/** `[kept, dropped]` in one pass. */
|
|
1162
|
+
function partition(list, keep) {
|
|
1163
|
+
const kept = [];
|
|
1164
|
+
const dropped = [];
|
|
1165
|
+
for (const x of list) (keep(x) ? kept : dropped).push(x);
|
|
1166
|
+
return [kept, dropped];
|
|
1167
|
+
}
|
|
1168
|
+
/** The columns aspect, an empty width map spelled by omission — the way capture writes it. */
|
|
1169
|
+
function columnsAspect(columnNames, columnWidths) {
|
|
1170
|
+
return columnWidths && Object.keys(columnWidths).length > 0 ? {
|
|
1171
|
+
columnNames,
|
|
1172
|
+
columnWidths
|
|
1173
|
+
} : { columnNames };
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Remove every entry of `snapshot` that names a field outside `known`, per
|
|
1177
|
+
* aspect:
|
|
1178
|
+
*
|
|
1179
|
+
* - `columns.columnNames` — unknown names go, the rest keep their order. When
|
|
1180
|
+
* none survives, it falls back to every column, never an empty grid.
|
|
1181
|
+
* - `columns.columnWidths` — unknown keys go.
|
|
1182
|
+
* - `filters` (displayed inputs) and `filterOps` — entries on a field outside
|
|
1183
|
+
* `known.server` go. A field's conditions are one AND-ed conjunct, so
|
|
1184
|
+
* dropping one only broadens the result.
|
|
1185
|
+
* - `sorters` — unknown entries go, the rest keep their priority.
|
|
1186
|
+
* - `itemsPerPage` — untouched.
|
|
1187
|
+
*
|
|
1188
|
+
* `dropped` is `null` when nothing user-visible went (a width-only drop is
|
|
1189
|
+
* hygiene). The input is never mutated.
|
|
1190
|
+
*
|
|
1191
|
+
* @since 0.1.141
|
|
1192
|
+
*/
|
|
1193
|
+
function prunePresetSnapshot(snapshot, known) {
|
|
1194
|
+
const isColumn = (p) => known.columns.has(p);
|
|
1195
|
+
const isServer = (p) => known.server.has(p);
|
|
1196
|
+
const out = { ...snapshot };
|
|
1197
|
+
const dropped = {
|
|
1198
|
+
fields: [],
|
|
1199
|
+
columns: [],
|
|
1200
|
+
filterFields: [],
|
|
1201
|
+
filters: {},
|
|
1202
|
+
residual: [],
|
|
1203
|
+
sorters: []
|
|
1204
|
+
};
|
|
1205
|
+
if (snapshot.columns) {
|
|
1206
|
+
const [names, gone] = partition(snapshot.columns.columnNames, isColumn);
|
|
1207
|
+
const [widths] = partition(Object.entries(snapshot.columns.columnWidths ?? {}), ([p]) => isColumn(p));
|
|
1208
|
+
dropped.columns = gone;
|
|
1209
|
+
out.columns = columnsAspect(names.length > 0 || gone.length === 0 ? names : [...known.columns], Object.fromEntries(widths));
|
|
1210
|
+
}
|
|
1211
|
+
if (snapshot.filters) [out.filters, dropped.filterFields] = partition(snapshot.filters, isServer);
|
|
1212
|
+
if (snapshot.filterOps) {
|
|
1213
|
+
const [kept, gone] = partition(Object.entries(snapshot.filterOps), ([p]) => isServer(p));
|
|
1214
|
+
out.filterOps = Object.fromEntries(kept);
|
|
1215
|
+
dropped.filters = Object.fromEntries(gone);
|
|
1216
|
+
}
|
|
1217
|
+
if (snapshot.sorters) [out.sorters, dropped.sorters] = partition(snapshot.sorters, (s) => isColumn(s.field));
|
|
1218
|
+
dropped.fields = [...new Set([
|
|
1219
|
+
...dropped.columns,
|
|
1220
|
+
...dropped.filterFields,
|
|
1221
|
+
...Object.keys(dropped.filters),
|
|
1222
|
+
...dropped.sorters.map((s) => s.field)
|
|
1223
|
+
])];
|
|
1224
|
+
return {
|
|
1225
|
+
snapshot: out,
|
|
1226
|
+
dropped: dropped.fields.length > 0 ? dropped : null
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Split residual filter conditions, in one pass, into the ones that name
|
|
1231
|
+
* only `known` fields and the ones that do not. A condition naming any
|
|
1232
|
+
* unknown field is dropped WHOLE — never pruned inside an `$or` (that would
|
|
1233
|
+
* narrow the result) or a `$not` (that would invert it). Each condition is an
|
|
1234
|
+
* AND-ed conjunct, so dropping one only broadens the result.
|
|
1235
|
+
*
|
|
1236
|
+
* `dropped` holds the conditions in `residual` and the unknown paths in
|
|
1237
|
+
* `fields`; `null` when every condition is kept.
|
|
1238
|
+
*
|
|
1239
|
+
* @since 0.1.141
|
|
1240
|
+
*/
|
|
1241
|
+
function pruneResidualFilters(exprs, known) {
|
|
1242
|
+
const kept = [];
|
|
1243
|
+
const residual = [];
|
|
1244
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1245
|
+
for (const expr of exprs) {
|
|
1246
|
+
const unknown = filterExprFields(expr).filter((p) => !known.has(p));
|
|
1247
|
+
if (unknown.length === 0) {
|
|
1248
|
+
kept.push(expr);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
residual.push(expr);
|
|
1252
|
+
for (const p of unknown) fields.add(p);
|
|
1253
|
+
}
|
|
1254
|
+
if (residual.length === 0) return {
|
|
1255
|
+
kept,
|
|
1256
|
+
dropped: null
|
|
1257
|
+
};
|
|
1258
|
+
return {
|
|
1259
|
+
kept,
|
|
1260
|
+
dropped: {
|
|
1261
|
+
fields: [...fields],
|
|
1262
|
+
columns: [],
|
|
1263
|
+
filterFields: [],
|
|
1264
|
+
filters: {},
|
|
1265
|
+
residual,
|
|
1266
|
+
sorters: []
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Append what a prune dropped back onto a snapshot captured from (pruned)
|
|
1272
|
+
* table state, so overwriting a preset with a narrower view does not destroy
|
|
1273
|
+
* the parts the saver cannot see. Column names, filter inputs and sorters go
|
|
1274
|
+
* after the captured ones; field filters are added back. Only aspects
|
|
1275
|
+
* `captured` carries are touched; widths of hidden columns are not kept.
|
|
1276
|
+
*
|
|
1277
|
+
* @since 0.1.141
|
|
1278
|
+
*/
|
|
1279
|
+
function restoreDroppedEntries(captured, dropped) {
|
|
1280
|
+
const out = { ...captured };
|
|
1281
|
+
if (captured.columns) out.columns = columnsAspect([...captured.columns.columnNames, ...dropped.columns], captured.columns.columnWidths);
|
|
1282
|
+
if (captured.filters) out.filters = [...captured.filters, ...dropped.filterFields];
|
|
1283
|
+
if (captured.filterOps) out.filterOps = {
|
|
1284
|
+
...captured.filterOps,
|
|
1285
|
+
...dropped.filters
|
|
1286
|
+
};
|
|
1287
|
+
if (captured.sorters) out.sorters = [...captured.sorters, ...dropped.sorters];
|
|
1288
|
+
return out;
|
|
1289
|
+
}
|
|
1290
|
+
//#endregion
|
|
1148
1291
|
//#region src/presets/preset-dirty.ts
|
|
1149
1292
|
/**
|
|
1150
1293
|
* JSON-stringify with object keys sorted alphabetically at every depth so
|
|
@@ -1748,6 +1891,20 @@ const CONSUMED_CONTROLS = new Set([
|
|
|
1748
1891
|
"$skip",
|
|
1749
1892
|
URL_SNAPSHOT_KEY
|
|
1750
1893
|
]);
|
|
1894
|
+
function toSet(paths) {
|
|
1895
|
+
if (!paths) return null;
|
|
1896
|
+
return paths instanceof Set ? paths : new Set(paths);
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* `field=value` (or `field=null`) — the one filter shape a host page flag
|
|
1900
|
+
* shares, so a bare one on an unknown field is not reported as dropped.
|
|
1901
|
+
*/
|
|
1902
|
+
function isBareEquality(expr) {
|
|
1903
|
+
const keys = Object.keys(expr);
|
|
1904
|
+
if (keys.length !== 1 || keys[0].startsWith("$")) return false;
|
|
1905
|
+
const v = expr[keys[0]];
|
|
1906
|
+
return v === null || typeof v !== "object";
|
|
1907
|
+
}
|
|
1751
1908
|
/**
|
|
1752
1909
|
* Characters that can only appear in a uniqu filter key, never in a page flag:
|
|
1753
1910
|
* comparison operators, `^` (OR), `( )` (groups) and `{ }` (`$in` lists).
|
|
@@ -1774,9 +1931,10 @@ function urlQueryConsumesKey(key) {
|
|
|
1774
1931
|
/**
|
|
1775
1932
|
* Parse a URL query string back into the table state subset.
|
|
1776
1933
|
*
|
|
1777
|
-
* Robust by design — schema drift
|
|
1778
|
-
* recipient's view:
|
|
1779
|
-
* - unknown fields (not in `knownFields`) →
|
|
1934
|
+
* Robust by design — schema drift, fields hidden from the recipient and
|
|
1935
|
+
* copy-paste errors must not break the recipient's view:
|
|
1936
|
+
* - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
|
|
1937
|
+
* `unknownSorters` (the parser does not warn — the caller decides)
|
|
1780
1938
|
* - filter pieces field filters cannot express (cross-field OR, unknown
|
|
1781
1939
|
* operator, …) → left out of `filters` and listed in `unsupported`, never
|
|
1782
1940
|
* approximated (the parser does not warn — the caller decides). Unless
|
|
@@ -1809,12 +1967,15 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1809
1967
|
const sortersGate = resolveAspectGate(opts.sync?.sorters);
|
|
1810
1968
|
const searchOff = opts.sync?.search === false;
|
|
1811
1969
|
const paginationOff = opts.sync?.pagination === false;
|
|
1812
|
-
const knownSet =
|
|
1970
|
+
const knownSet = toSet(opts.knownFields);
|
|
1971
|
+
const localSet = toSet(opts.localFields);
|
|
1972
|
+
const isHidden = (f) => !!knownSet && !knownSet.has(f) && !localSet?.has(f);
|
|
1813
1973
|
let filterKnown;
|
|
1814
1974
|
if (filtersGate === "all") filterKnown = knownSet ?? void 0;
|
|
1815
1975
|
else if (filtersGate !== "none") if (knownSet) {
|
|
1816
|
-
|
|
1817
|
-
for (const path of filtersGate) if (knownSet.has(path))
|
|
1976
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1977
|
+
for (const path of filtersGate) if (knownSet.has(path)) owned.add(path);
|
|
1978
|
+
filterKnown = owned;
|
|
1818
1979
|
} else filterKnown = filtersGate;
|
|
1819
1980
|
const decomposed = filtersGate === "none" ? null : decomposeUniqueryFilter(parsed.filter, {
|
|
1820
1981
|
knownFields: filterKnown,
|
|
@@ -1822,20 +1983,19 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1822
1983
|
});
|
|
1823
1984
|
const filters = decomposed?.filters ?? {};
|
|
1824
1985
|
const sorters = [];
|
|
1986
|
+
const unknownSorters = [];
|
|
1825
1987
|
if (sortersGate !== "none") {
|
|
1826
1988
|
const $sort = parsed.controls?.$sort;
|
|
1827
1989
|
if ($sort && typeof $sort === "object") for (const field in $sort) {
|
|
1828
|
-
if (knownSet && !knownSet.has(field)) continue;
|
|
1829
1990
|
if (!gateOwns(sortersGate, field)) continue;
|
|
1830
1991
|
const dir = $sort[field];
|
|
1831
|
-
if (dir
|
|
1832
|
-
|
|
1833
|
-
direction: "asc"
|
|
1834
|
-
});
|
|
1835
|
-
else if (dir === -1) sorters.push({
|
|
1992
|
+
if (dir !== 1 && dir !== -1) continue;
|
|
1993
|
+
const sorter = {
|
|
1836
1994
|
field,
|
|
1837
|
-
direction: "desc"
|
|
1838
|
-
}
|
|
1995
|
+
direction: dir === 1 ? "asc" : "desc"
|
|
1996
|
+
};
|
|
1997
|
+
if (!knownSet || knownSet.has(field)) sorters.push(sorter);
|
|
1998
|
+
else if (isHidden(field)) unknownSorters.push(sorter);
|
|
1839
1999
|
}
|
|
1840
2000
|
}
|
|
1841
2001
|
const $search = parsed.controls?.$search;
|
|
@@ -1844,8 +2004,24 @@ function urlQueryStringToState(urlString, opts = {}) {
|
|
|
1844
2004
|
sorters,
|
|
1845
2005
|
searchTerm: !searchOff && typeof $search === "string" ? $search : ""
|
|
1846
2006
|
};
|
|
1847
|
-
|
|
2007
|
+
const unsupported$1 = decomposed?.unsupported ?? [];
|
|
2008
|
+
const unknown = [];
|
|
2009
|
+
for (const piece of decomposed?.unknown ?? []) {
|
|
2010
|
+
const hidden = piece.fields.filter(isHidden);
|
|
2011
|
+
if (hidden.length > 0) {
|
|
2012
|
+
if (!isBareEquality(piece.expr)) unknown.push({
|
|
2013
|
+
expr: piece.expr,
|
|
2014
|
+
fields: hidden
|
|
2015
|
+
});
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
const issue = unsupported("cross-field", piece.expr);
|
|
2019
|
+
if (issue.fields.some((f) => filterKnown?.has(f))) unsupported$1.push(issue);
|
|
2020
|
+
}
|
|
2021
|
+
if (unsupported$1.length) out.unsupported = unsupported$1;
|
|
1848
2022
|
if (decomposed?.residual.length) out.residual = decomposed.residual;
|
|
2023
|
+
if (unknown.length) out.unknown = unknown;
|
|
2024
|
+
if (unknownSorters.length) out.unknownSorters = unknownSorters;
|
|
1849
2025
|
if (opts.sync?.snapshot !== false && parsed.controls && "$snapshot" in parsed.controls) out.snapshot = true;
|
|
1850
2026
|
if (!searchOff) {
|
|
1851
2027
|
const $relevance = parsed.controls?.$relevance;
|
|
@@ -2397,4 +2573,4 @@ function sortRowsLocally(rows, sorters, getValue = (row, field) => row[field]) {
|
|
|
2397
2573
|
});
|
|
2398
2574
|
}
|
|
2399
2575
|
//#endregion
|
|
2400
|
-
export { APP_CONF_PREFIX, AppPrefsClient, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, ExportAbortError, MAX_DEFAULT_COLUMN_WIDTH_PX, NULL_OPS, PRESET_ASPECTS, PresetsClient, PresetsHttpError, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
|
2576
|
+
export { APP_CONF_PREFIX, AppPrefsClient, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, ExportAbortError, MAX_DEFAULT_COLUMN_WIDTH_PX, NULL_OPS, PRESET_ASPECTS, PresetsClient, PresetsHttpError, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/ui-table",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.141",
|
|
4
4
|
"description": "Framework-agnostic filter model, filter-to-Uniquery conversion, and preset serialization for atscript tables",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@atscript/ui": "0.1.
|
|
45
|
+
"@atscript/ui": "0.1.141"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@atscript/db-client": "^0.1.133",
|