@datakable/core 2.0.1 → 2.0.3
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/README.md +3 -2
- package/dist/{codes-B9bCK73-.d.cts → codes-4sYQv_7J.d.cts} +42 -12
- package/dist/{codes-B9bCK73-.d.ts → codes-4sYQv_7J.d.ts} +42 -12
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +1 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +2 -2
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/console.cjs +15 -4
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +15 -4
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/explain.cjs.map +1 -1
- package/dist/diagnostics/explain.js.map +1 -1
- package/dist/diagnostics/hub.cjs +7 -1
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +7 -1
- package/dist/diagnostics/hub.js.map +1 -1
- package/dist/diagnostics/prose.cjs +10 -1
- package/dist/diagnostics/prose.cjs.map +1 -1
- package/dist/diagnostics/prose.js +9 -1
- package/dist/diagnostics/prose.js.map +1 -1
- package/dist/diagnostics/redact.cjs +1 -1
- package/dist/diagnostics/redact.cjs.map +1 -1
- package/dist/diagnostics/redact.js +1 -1
- package/dist/diagnostics/redact.js.map +1 -1
- package/dist/diagnostics/types.cjs.map +1 -1
- package/dist/{explain-i0OATi_W.d.ts → explain-DmwN_RG9.d.cts} +13 -6
- package/dist/{explain-Q8Cg0cZy.d.cts → explain-Dr3SmMNY.d.ts} +13 -6
- package/dist/feature/install.cjs +2 -1
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +2 -1
- package/dist/feature/install.js.map +1 -1
- package/dist/feature/slice-guard.cjs +76 -0
- package/dist/feature/slice-guard.cjs.map +1 -0
- package/dist/feature/slice-guard.js +50 -0
- package/dist/feature/slice-guard.js.map +1 -0
- package/dist/features/column-filters/codes.cjs +2 -19
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +2 -19
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +37 -22
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +37 -22
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +6 -2
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +6 -2
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +2 -2
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +2 -2
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +18 -2
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +16 -1
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-engine.cjs +24 -5
- package/dist/features/search/search-engine.cjs.map +1 -1
- package/dist/features/search/search-engine.js +25 -6
- package/dist/features/search/search-engine.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +55 -24
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +55 -24
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/index.d.cts +26 -17
- package/dist/index.d.ts +26 -17
- package/dist/intercept/runtime.cjs +3 -2
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +3 -2
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +2 -0
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +2 -0
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-CuLYPfY_.d.cts → search-async-entry-BiemO3JT.d.cts} +2 -2
- package/dist/{search-async-entry-Cx7xpuVZ.d.ts → search-async-entry-GRj0E06n.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +4 -4
- package/dist/search.d.ts +4 -4
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/async-control.cjs +2 -1
- package/dist/source/async-control.cjs.map +1 -1
- package/dist/source/async-control.js +2 -1
- package/dist/source/async-control.js.map +1 -1
- package/dist/source/fetcher.cjs.map +1 -1
- package/dist/source/fetcher.js.map +1 -1
- package/dist/source/server.cjs +58 -5
- package/dist/source/server.cjs.map +1 -1
- package/dist/source/server.js +58 -5
- package/dist/source/server.js.map +1 -1
- package/dist/table/create-table.cjs +30 -20
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +30 -20
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/index.cjs +3 -0
- package/dist/table/index.cjs.map +1 -1
- package/dist/table/index.js +5 -1
- package/dist/table/index.js.map +1 -1
- package/dist/table/state-codes.cjs +72 -0
- package/dist/table/state-codes.cjs.map +1 -0
- package/dist/table/state-codes.js +45 -0
- package/dist/table/state-codes.js.map +1 -0
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/dist/ingest/test-schemas.cjs +0 -73
- package/dist/ingest/test-schemas.cjs.map +0 -1
- package/dist/ingest/test-schemas.js +0 -46
- package/dist/ingest/test-schemas.js.map +0 -1
|
@@ -28,6 +28,7 @@ module.exports = __toCommonJS(codes_exports);
|
|
|
28
28
|
var import_suggest = require("../../catalog/suggest.cjs");
|
|
29
29
|
var import_catalog = require("../../diagnostics/catalog.cjs");
|
|
30
30
|
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
31
|
+
var import_state_codes = require("../../table/state-codes.cjs");
|
|
31
32
|
var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
|
|
32
33
|
var rowList = (rows, count) => {
|
|
33
34
|
if (rows.length === 0) return "";
|
|
@@ -42,17 +43,6 @@ function convert(expected, received) {
|
|
|
42
43
|
return void 0;
|
|
43
44
|
}
|
|
44
45
|
var columnFiltersMessages = {
|
|
45
|
-
DK_STATE_UNKNOWN_KEY: {
|
|
46
|
-
problem: (d) => `state.${d.slice} has an entry for "${d.key}", which is not a column`,
|
|
47
|
-
impact: () => "the entry is ignored (the other entries still apply); it may come from an older URL or saved state, or the column was renamed or removed",
|
|
48
|
-
fix: (d) => {
|
|
49
|
-
const near = (0, import_suggest.suggest)(d.key, d.known);
|
|
50
|
-
return [
|
|
51
|
-
near.length > 0 ? `did you mean "${near[0]}"? (state.${d.slice}["${near[0]}"])` : `use one of the column ids: ${d.known.map((k) => `"${k}"`).join(", ")}`,
|
|
52
|
-
`drop "${d.key}" when parsing initialState / state (e.g. from the URL), or add the column`
|
|
53
|
-
];
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
46
|
DK_FILTER_TYPE_MISMATCH: {
|
|
57
47
|
problem: (d) => d.source === "filter" ? `the "${d.filter}" filter on column "${d.column}" has a value of type ${d.received}; it expects ${d.expected}` : `the "${d.filter}" filter on column "${d.column}" compares ${d.expected} values, but ${plural(d.count, "cell holds", "cells hold")} another type (first: ${d.received})${rowList(d.rows, d.count)}`,
|
|
58
48
|
impact: (d) => d.source === "filter" ? "the filter matches no rows until its value is fixed" : "those rows are treated as non-matching; the other rows are filtered normally",
|
|
@@ -98,14 +88,7 @@ var columnFiltersDiagnostics = [
|
|
|
98
88
|
},
|
|
99
89
|
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH
|
|
100
90
|
),
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
code: "DK_STATE_UNKNOWN_KEY",
|
|
104
|
-
level: "warn",
|
|
105
|
-
where: (d) => ({ kind: "option", path: `state.${d.slice}.${d.key}` })
|
|
106
|
-
},
|
|
107
|
-
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_STATE_UNKNOWN_KEY
|
|
108
|
-
)
|
|
91
|
+
...import_state_codes.stateDiagnostics
|
|
109
92
|
];
|
|
110
93
|
// Annotate the CommonJS export names for ESM import in node:
|
|
111
94
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { stateDiagnostics } from '../../table/state-codes'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n }\n}\n\n/** The filter kinds of §6.1. */\nexport const FILTER_KINDS = ['equals', 'contains', 'in', 'range', 'custom'] as const\n\nconst rowList = (rows: readonly number[], count: number) => {\n if (rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? ` (first: ${list}, …)` : ` (rows ${list})`\n}\n\nconst plural = (n: number, one: string, many: string) => (n === 1 ? `1 ${one}` : `${n} ${many}`)\n\n/** A conversion from `received` to `expected`, as code, when one is obvious. */\nfunction convert(expected: string, received: string): string | undefined {\n if (expected.includes('number') && received === 'string') return 'Number(x)'\n if (expected === 'string' && (received === 'number' || received === 'bigint')) return 'String(x)'\n if (expected === 'Date' && (received === 'string' || received === 'number')) return 'new Date(x)'\n return undefined\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\n// DK_STATE_UNKNOWN_KEY is shared with core (a `__proto__` key, #102): table/state-codes.ts.\nexport const columnFiltersMessages: DiagnosticMessages<'DK_FILTER_TYPE_MISMATCH'> = {\n DK_FILTER_TYPE_MISMATCH: {\n problem: (d) =>\n d.source === 'filter'\n ? `the \"${d.filter}\" filter on column \"${d.column}\" has a value of type ${d.received}; ` +\n `it expects ${d.expected}`\n : `the \"${d.filter}\" filter on column \"${d.column}\" compares ${d.expected} values, but ` +\n `${plural(d.count, 'cell holds', 'cells hold')} another type (first: ` +\n `${d.received})${rowList(d.rows, d.count)}`,\n impact: (d) =>\n d.source === 'filter'\n ? 'the filter matches no rows until its value is fixed'\n : 'those rows are treated as non-matching; the other rows are filtered normally',\n fix: (d) => {\n const at = `state.columnFilters[\"${d.column}\"]`\n if (d.source === 'filter') {\n if (!(FILTER_KINDS as readonly string[]).includes(d.filter)) {\n const near = suggest(d.filter, FILTER_KINDS)\n return [\n near.length > 0\n ? `did you mean type: '${near[0]}'? (${at})`\n : `use one of the filter kinds ${FILTER_KINDS.join(', ')} in ${at}`,\n ]\n }\n return [\n `give ${at} a value of type ${d.expected}: { type: '${d.filter}', value: … }`,\n 'remove the filter (setColumnFilter(id, undefined)) while there is no value',\n ]\n }\n const out: string[] = []\n const c = convert(d.expected, d.received)\n if (c) {\n out.push(\n `if the filter value is the wrong type, convert it before setting it: ${c.replace('x', 'input')}`,\n `if the data is the wrong type, normalise the column: ` +\n `{ id: '${d.column}', accessor: (row) => ${c.replace('x', 'row.<field>')} } ` +\n `(columns[${d.index}])`,\n )\n } else {\n out.push(\n `normalise the column so every cell is ${d.expected}: an accessor on columns[${d.index}]`,\n )\n }\n out.push(\n `or filter with a kind that fits ${d.received} cells, or a custom filter: ` +\n `{ type: 'custom', value, fn: (cell, value, row) => boolean }`,\n )\n return out\n },\n },\n}\n\n/** Codes owned by the `columnFilters` feature (§6.1, §8.3). */\nexport const columnFiltersDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_FILTER_TYPE_MISMATCH',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH,\n ),\n ...stateDiagnostics,\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,qBAA0D;AAC1D,2BAAO;AACP,yBAAiC;AA0B1B,IAAM,eAAe,CAAC,UAAU,YAAY,MAAM,SAAS,QAAQ;AAE1E,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,YAAY,IAAI,cAAS,UAAU,IAAI;AACtE;AAEA,IAAM,SAAS,CAAC,GAAW,KAAa,SAAkB,MAAM,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,IAAI,IAAI;AAG7F,SAAS,QAAQ,UAAkB,UAAsC;AACvE,MAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,SAAU,QAAO;AACjE,MAAI,aAAa,aAAa,aAAa,YAAY,aAAa,UAAW,QAAO;AACtF,MAAI,aAAa,WAAW,aAAa,YAAY,aAAa,UAAW,QAAO;AACpF,SAAO;AACT;AAIO,IAAM,wBAAuE;AAAA,EAClF,yBAAyB;AAAA,IACvB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,yBAAyB,EAAE,QAAQ,gBACpE,EAAE,QAAQ,KACxB,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,cAAc,EAAE,QAAQ,gBACpE,OAAO,EAAE,OAAO,cAAc,YAAY,CAAC,yBAC3C,EAAE,QAAQ,IAAI,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC/C,QAAQ,CAAC,MACP,EAAE,WAAW,WACT,wDACA;AAAA,IACN,KAAK,CAAC,MAAM;AACV,YAAM,KAAK,wBAAwB,EAAE,MAAM;AAC3C,UAAI,EAAE,WAAW,UAAU;AACzB,YAAI,CAAE,aAAmC,SAAS,EAAE,MAAM,GAAG;AAC3D,gBAAM,WAAO,wBAAQ,EAAE,QAAQ,YAAY;AAC3C,iBAAO;AAAA,YACL,KAAK,SAAS,IACV,uBAAuB,KAAK,CAAC,CAAC,OAAO,EAAE,MACvC,+BAA+B,aAAa,KAAK,IAAI,CAAC,OAAO,EAAE;AAAA,UACrE;AAAA,QACF;AACA,eAAO;AAAA,UACL,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE,MAAM;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAgB,CAAC;AACvB,YAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,QAAQ;AACxC,UAAI,GAAG;AACL,YAAI;AAAA,UACF,wEAAwE,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,UAC/F,+DACY,EAAE,MAAM,yBAAyB,EAAE,QAAQ,KAAK,aAAa,CAAC,eAC5D,EAAE,KAAK;AAAA,QACvB;AAAA,MACF,OAAO;AACL,YAAI;AAAA,UACF,yCAAyC,EAAE,QAAQ,4BAA4B,EAAE,KAAK;AAAA,QACxF;AAAA,MACF;AACA,UAAI;AAAA,QACF,mCAAmC,EAAE,QAAQ;AAAA,MAE/C;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGO,IAAM,2BAA2B;AAAA,MACtC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,sBAAsB;AAAA,EACnE;AAAA,EACA,GAAG;AACL;","names":[]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { suggest } from "../../catalog/suggest.js";
|
|
3
3
|
import { defineDiagnostic } from "../../diagnostics/catalog.js";
|
|
4
4
|
import "../../diagnostics/process-guard.js";
|
|
5
|
+
import { stateDiagnostics } from "../../table/state-codes.js";
|
|
5
6
|
var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
|
|
6
7
|
var rowList = (rows, count) => {
|
|
7
8
|
if (rows.length === 0) return "";
|
|
@@ -16,17 +17,6 @@ function convert(expected, received) {
|
|
|
16
17
|
return void 0;
|
|
17
18
|
}
|
|
18
19
|
var columnFiltersMessages = {
|
|
19
|
-
DK_STATE_UNKNOWN_KEY: {
|
|
20
|
-
problem: (d) => `state.${d.slice} has an entry for "${d.key}", which is not a column`,
|
|
21
|
-
impact: () => "the entry is ignored (the other entries still apply); it may come from an older URL or saved state, or the column was renamed or removed",
|
|
22
|
-
fix: (d) => {
|
|
23
|
-
const near = suggest(d.key, d.known);
|
|
24
|
-
return [
|
|
25
|
-
near.length > 0 ? `did you mean "${near[0]}"? (state.${d.slice}["${near[0]}"])` : `use one of the column ids: ${d.known.map((k) => `"${k}"`).join(", ")}`,
|
|
26
|
-
`drop "${d.key}" when parsing initialState / state (e.g. from the URL), or add the column`
|
|
27
|
-
];
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
20
|
DK_FILTER_TYPE_MISMATCH: {
|
|
31
21
|
problem: (d) => d.source === "filter" ? `the "${d.filter}" filter on column "${d.column}" has a value of type ${d.received}; it expects ${d.expected}` : `the "${d.filter}" filter on column "${d.column}" compares ${d.expected} values, but ${plural(d.count, "cell holds", "cells hold")} another type (first: ${d.received})${rowList(d.rows, d.count)}`,
|
|
32
22
|
impact: (d) => d.source === "filter" ? "the filter matches no rows until its value is fixed" : "those rows are treated as non-matching; the other rows are filtered normally",
|
|
@@ -72,14 +62,7 @@ var columnFiltersDiagnostics = [
|
|
|
72
62
|
},
|
|
73
63
|
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH
|
|
74
64
|
),
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
code: "DK_STATE_UNKNOWN_KEY",
|
|
78
|
-
level: "warn",
|
|
79
|
-
where: (d) => ({ kind: "option", path: `state.${d.slice}.${d.key}` })
|
|
80
|
-
},
|
|
81
|
-
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_STATE_UNKNOWN_KEY
|
|
82
|
-
)
|
|
65
|
+
...stateDiagnostics
|
|
83
66
|
];
|
|
84
67
|
export {
|
|
85
68
|
FILTER_KINDS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { stateDiagnostics } from '../../table/state-codes'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n }\n}\n\n/** The filter kinds of §6.1. */\nexport const FILTER_KINDS = ['equals', 'contains', 'in', 'range', 'custom'] as const\n\nconst rowList = (rows: readonly number[], count: number) => {\n if (rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? ` (first: ${list}, …)` : ` (rows ${list})`\n}\n\nconst plural = (n: number, one: string, many: string) => (n === 1 ? `1 ${one}` : `${n} ${many}`)\n\n/** A conversion from `received` to `expected`, as code, when one is obvious. */\nfunction convert(expected: string, received: string): string | undefined {\n if (expected.includes('number') && received === 'string') return 'Number(x)'\n if (expected === 'string' && (received === 'number' || received === 'bigint')) return 'String(x)'\n if (expected === 'Date' && (received === 'string' || received === 'number')) return 'new Date(x)'\n return undefined\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\n// DK_STATE_UNKNOWN_KEY is shared with core (a `__proto__` key, #102): table/state-codes.ts.\nexport const columnFiltersMessages: DiagnosticMessages<'DK_FILTER_TYPE_MISMATCH'> = {\n DK_FILTER_TYPE_MISMATCH: {\n problem: (d) =>\n d.source === 'filter'\n ? `the \"${d.filter}\" filter on column \"${d.column}\" has a value of type ${d.received}; ` +\n `it expects ${d.expected}`\n : `the \"${d.filter}\" filter on column \"${d.column}\" compares ${d.expected} values, but ` +\n `${plural(d.count, 'cell holds', 'cells hold')} another type (first: ` +\n `${d.received})${rowList(d.rows, d.count)}`,\n impact: (d) =>\n d.source === 'filter'\n ? 'the filter matches no rows until its value is fixed'\n : 'those rows are treated as non-matching; the other rows are filtered normally',\n fix: (d) => {\n const at = `state.columnFilters[\"${d.column}\"]`\n if (d.source === 'filter') {\n if (!(FILTER_KINDS as readonly string[]).includes(d.filter)) {\n const near = suggest(d.filter, FILTER_KINDS)\n return [\n near.length > 0\n ? `did you mean type: '${near[0]}'? (${at})`\n : `use one of the filter kinds ${FILTER_KINDS.join(', ')} in ${at}`,\n ]\n }\n return [\n `give ${at} a value of type ${d.expected}: { type: '${d.filter}', value: … }`,\n 'remove the filter (setColumnFilter(id, undefined)) while there is no value',\n ]\n }\n const out: string[] = []\n const c = convert(d.expected, d.received)\n if (c) {\n out.push(\n `if the filter value is the wrong type, convert it before setting it: ${c.replace('x', 'input')}`,\n `if the data is the wrong type, normalise the column: ` +\n `{ id: '${d.column}', accessor: (row) => ${c.replace('x', 'row.<field>')} } ` +\n `(columns[${d.index}])`,\n )\n } else {\n out.push(\n `normalise the column so every cell is ${d.expected}: an accessor on columns[${d.index}]`,\n )\n }\n out.push(\n `or filter with a kind that fits ${d.received} cells, or a custom filter: ` +\n `{ type: 'custom', value, fn: (cell, value, row) => boolean }`,\n )\n return out\n },\n },\n}\n\n/** Codes owned by the `columnFilters` feature (§6.1, §8.3). */\nexport const columnFiltersDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_FILTER_TYPE_MISMATCH',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH,\n ),\n ...stateDiagnostics,\n]\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAkC,wBAAwB;AAC1D,OAAO;AACP,SAAS,wBAAwB;AA0B1B,IAAM,eAAe,CAAC,UAAU,YAAY,MAAM,SAAS,QAAQ;AAE1E,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,YAAY,IAAI,cAAS,UAAU,IAAI;AACtE;AAEA,IAAM,SAAS,CAAC,GAAW,KAAa,SAAkB,MAAM,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,IAAI,IAAI;AAG7F,SAAS,QAAQ,UAAkB,UAAsC;AACvE,MAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,SAAU,QAAO;AACjE,MAAI,aAAa,aAAa,aAAa,YAAY,aAAa,UAAW,QAAO;AACtF,MAAI,aAAa,WAAW,aAAa,YAAY,aAAa,UAAW,QAAO;AACpF,SAAO;AACT;AAIO,IAAM,wBAAuE;AAAA,EAClF,yBAAyB;AAAA,IACvB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,yBAAyB,EAAE,QAAQ,gBACpE,EAAE,QAAQ,KACxB,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,cAAc,EAAE,QAAQ,gBACpE,OAAO,EAAE,OAAO,cAAc,YAAY,CAAC,yBAC3C,EAAE,QAAQ,IAAI,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC/C,QAAQ,CAAC,MACP,EAAE,WAAW,WACT,wDACA;AAAA,IACN,KAAK,CAAC,MAAM;AACV,YAAM,KAAK,wBAAwB,EAAE,MAAM;AAC3C,UAAI,EAAE,WAAW,UAAU;AACzB,YAAI,CAAE,aAAmC,SAAS,EAAE,MAAM,GAAG;AAC3D,gBAAM,OAAO,QAAQ,EAAE,QAAQ,YAAY;AAC3C,iBAAO;AAAA,YACL,KAAK,SAAS,IACV,uBAAuB,KAAK,CAAC,CAAC,OAAO,EAAE,MACvC,+BAA+B,aAAa,KAAK,IAAI,CAAC,OAAO,EAAE;AAAA,UACrE;AAAA,QACF;AACA,eAAO;AAAA,UACL,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE,MAAM;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAgB,CAAC;AACvB,YAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,QAAQ;AACxC,UAAI,GAAG;AACL,YAAI;AAAA,UACF,wEAAwE,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,UAC/F,+DACY,EAAE,MAAM,yBAAyB,EAAE,QAAQ,KAAK,aAAa,CAAC,eAC5D,EAAE,KAAK;AAAA,QACvB;AAAA,MACF,OAAO;AACL,YAAI;AAAA,UACF,yCAAyC,EAAE,QAAQ,4BAA4B,EAAE,KAAK;AAAA,QACxF;AAAA,MACF;AACA,UAAI;AAAA,QACF,mCAAmC,EAAE,QAAQ;AAAA,MAE/C;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGO,IAAM,2BAA2B;AAAA,EACtC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,sBAAsB;AAAA,EACnE;AAAA,EACA,GAAG;AACL;","names":[]}
|
|
@@ -26,7 +26,9 @@ module.exports = __toCommonJS(column_filters_exports);
|
|
|
26
26
|
var import_options = require("../../catalog/options.cjs");
|
|
27
27
|
var import_catalog = require("../../diagnostics/catalog.cjs");
|
|
28
28
|
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
29
|
+
var import_redact = require("../../diagnostics/redact.cjs");
|
|
29
30
|
var import_define = require("../../feature/define.cjs");
|
|
31
|
+
var import_slice_guard = require("../../feature/slice-guard.cjs");
|
|
30
32
|
var import_cell_batch = require("../../intercept/cell-batch.cjs");
|
|
31
33
|
var import_classify = require("../../intercept/classify.cjs");
|
|
32
34
|
var import_codes = require("../../intercept/codes.cjs");
|
|
@@ -180,28 +182,41 @@ function filterRows(indices, state, ctx) {
|
|
|
180
182
|
}
|
|
181
183
|
return out ? out.subarray(0, n) : indices;
|
|
182
184
|
}
|
|
183
|
-
var
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
opts
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
185
|
+
var isSlice = (s) => s !== null && typeof s === "object" && !Array.isArray(s);
|
|
186
|
+
var fixSlice = (s) => isSlice(s) ? void 0 : [{}, (0, import_redact.describeValue)(s), "{}"];
|
|
187
|
+
var columnFilters = (opts = {}) => {
|
|
188
|
+
const guard = process.env.NODE_ENV !== "production" ? (0, import_slice_guard.sliceGuard)("columnFilters", fixSlice) : void 0;
|
|
189
|
+
return (0, import_define.defineFeature)({
|
|
190
|
+
name: "columnFilters",
|
|
191
|
+
stage: "filter",
|
|
192
|
+
mode: opts.mode ?? "client",
|
|
193
|
+
options: (0, import_options.defineOptions)(
|
|
194
|
+
// The option validator runs in development only (§9.3): production drops the schema.
|
|
195
|
+
process.env.NODE_ENV !== "production" ? columnFiltersOptions : {},
|
|
196
|
+
opts
|
|
197
|
+
),
|
|
198
|
+
...guard ? { interceptors: [guard.interceptor] } : void 0,
|
|
199
|
+
initialState: () => ({}),
|
|
200
|
+
createApi: (ctx) => {
|
|
201
|
+
guard?.install(ctx);
|
|
202
|
+
return {
|
|
203
|
+
setColumnFilter: (id, filter) => {
|
|
204
|
+
const s = ctx.getState();
|
|
205
|
+
const prev = s && typeof s === "object" ? s : {};
|
|
206
|
+
if (prev[id] === filter) return;
|
|
207
|
+
const next = { ...prev, [id]: filter };
|
|
208
|
+
if (filter === void 0) delete next[id];
|
|
209
|
+
ctx.setState(next);
|
|
210
|
+
},
|
|
211
|
+
resetColumnFilters: () => {
|
|
212
|
+
const s = ctx.getState();
|
|
213
|
+
if (!s || Object.keys(s).length > 0) ctx.setState({});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
rowModel: filterRows
|
|
218
|
+
});
|
|
219
|
+
};
|
|
205
220
|
// Annotate the CommonJS export names for ESM import in node:
|
|
206
221
|
0 && (module.exports = {
|
|
207
222
|
columnFilters
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/column-filters.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { defineFeature } from '../../feature/define'\nimport type { RowModelContext } from '../../feature/types'\nimport { CellFailures } from '../../intercept/cell-batch'\nimport { errorName } from '../../intercept/classify'\nimport { boundaryDiagnostics } from '../../intercept/codes'\nimport type { ColumnValues } from '../../pipeline/types'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { columnFiltersDiagnostics } from './codes'\nimport {\n BAD,\n type Compiled,\n compile,\n MATCH,\n NO,\n type Outcome,\n type RawFilter,\n type Test,\n typeName,\n} from './match'\nimport type { ColumnFiltersState, ColumnFiltersTypes } from './types'\n\nkeepDiagnostics(columnFiltersDiagnostics, boundaryDiagnostics)\n\nexport interface ColumnFiltersOptions {\n /**\n * `'server'`: the server filters (the slice goes to `options.fetcher` as `filters`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst columnFiltersOptions = { mode: modeOption } as const\n\n/** Position of column `id` in `options.columns` (diagnostic `where`); `-1` if absent. */\nfunction columnIndex(columns: readonly unknown[], id: string): number {\n for (let i = 0; i < columns.length; i++) {\n const c = columns[i] as { key?: unknown; id?: unknown } | null | undefined\n if (c && typeof c === 'object' && (typeof c.key === 'string' ? c.key : c.id) === id) return i\n }\n return -1\n}\n\nfunction columnIds(columns: readonly unknown[]): string[] {\n const out: string[] = []\n for (const c of columns as readonly ({ key?: unknown; id?: unknown } | null)[]) {\n const id = c && (typeof c.key === 'string' ? c.key : c.id)\n if (typeof id === 'string') out.push(id)\n }\n return out\n}\n\n/** Nullish and `NaN` (nullish in a numeric column cache) match no active filter (§6.1). */\nconst absent = (v: unknown) =>\n v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v))\n\n/** One cell: an array cell matches if any element matches, else `BAD` if one had a wrong type. */\nfunction testCell(test: Test, v: unknown, r: number): Outcome {\n if (!Array.isArray(v)) return test(v, r)\n let out: Outcome = NO\n for (let i = 0; i < v.length; i++) {\n const e = v[i]\n if (absent(e)) continue\n const o = test(e, -1)\n if (o === MATCH) return MATCH\n if (o === BAD) out = BAD\n }\n return out\n}\n\n/**\n * Per-batch failures, recorded as numbers (§3.4) and reused across runs: wrong-type cells, or\n * throwing `custom` calls. Text and `where` are built only when something failed.\n */\nconst failures = new CellFailures()\nconst first = { type: '', error: undefined as unknown }\n\n/** Keeps the rows of `rows[0..n)` that pass `c`, compacted into `out` (may be `rows`). */\nfunction apply(\n c: Extract<Compiled, { kind: 'test' | 'custom' }>,\n values: ColumnValues,\n data: readonly unknown[],\n rows: Uint32Array,\n n: number,\n out: Uint32Array,\n): number {\n let k = 0\n if (c.kind === 'custom') {\n const { fn, value } = c\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n try {\n if (fn(v, value, data[r])) out[k++] = r\n } catch (e) {\n if (failures.count === 0) first.error = e\n failures.add(r, e)\n }\n }\n return k\n }\n const test = c.test\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n const o = testCell(test, v, r)\n if (o === MATCH) out[k++] = r\n else if (o === BAD) {\n if (failures.count === 0) first.type = typeName(v)\n failures.add(r, undefined)\n }\n }\n return k\n}\n\nfunction report(ctx: RowModelContext, c: Compiled, id: string, kind: string, index: number) {\n if (c.kind === 'custom') {\n // Like a column:batch filterFn failure (§3.4); `where` is the filter's state path.\n const name = errorName(first.error)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `state.columnFilters.${id}.fn`,\n column: id,\n rows: Array.from(failures.sample()),\n count: failures.count,\n fallback: 'rows-unmatched',\n ...(name === undefined ? {} : { errorName: name }),\n },\n { cause: first.error },\n )\n } else if (c.kind === 'test') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: kind,\n source: 'cell',\n expected: c.expected,\n received: first.type,\n count: failures.count,\n rows: Array.from(failures.sample()),\n })\n }\n}\n\n/**\n * Applies every active column filter (AND) to `indices`: one pass per filtered column over the\n * column cache (§9.1), no per-cell allocation. Returns `indices` itself when nothing is active;\n * otherwise a new array (`indices` is never mutated).\n */\nfunction filterRows(\n indices: Uint32Array,\n state: Readonly<Record<string, unknown>> | undefined,\n ctx: RowModelContext,\n): Uint32Array {\n if (!state || typeof state !== 'object') return indices\n let out: Uint32Array | undefined\n let n = indices.length\n for (const id of Object.keys(state)) {\n const filter = state[id]\n if (filter === undefined) continue\n const values = ctx.getColumnValues(id)\n if (!values) {\n // Not a column (§7.2): ignored. No columns at all (zero-config, no rows yet) is not reported.\n if (ctx.columns.length > 0) {\n ctx.emit('DK_STATE_UNKNOWN_KEY', {\n slice: 'columnFilters',\n key: id,\n known: columnIds(ctx.columns),\n })\n }\n continue\n }\n const c = compile(filter, values)\n if (c.kind === 'off') continue\n const index = columnIndex(ctx.columns, id)\n if (c.kind === 'bad') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: c.filter,\n source: 'filter',\n expected: c.expected,\n received: c.received,\n count: 0,\n rows: [],\n })\n return new Uint32Array(0)\n }\n const src = out ?? indices\n out ??= new Uint32Array(indices.length)\n failures.reset()\n first.error = undefined\n try {\n if (c.kind === 'custom') {\n // A user callback per cell: one `filterFn` column:batch (§3.4). A skipped batch keeps\n // the slot fallback: no row matches.\n const rows = src.subarray(0, n)\n const dst = out\n let k = 0\n ctx.batch(id, 'filterFn', rows, () => {\n k = apply(c, values, ctx.data, rows, rows.length, dst)\n })\n n = k\n } else n = apply(c, values, ctx.data, src, n, out)\n if (failures.count > 0) report(ctx, c, id, String((filter as RawFilter).type), index)\n } finally {\n failures.reset()\n first.error = undefined\n }\n }\n return out ? out.subarray(0, n) : indices\n}\n\n/**\n * Column filters (§6.1): `equals`, `contains` (case-insensitive, NFC), `in`, `range` (inclusive),\n * `custom`. Filters combine with AND; nullish cells match no active filter; array cells match if\n * any element matches; a cell of the wrong runtime type is a non-match + `DK_FILTER_TYPE_MISMATCH`.\n *\n * ```ts\n * const table = createTable({ data: users, columns, features: [columnFilters()] })\n * table.setColumnFilter('age', { type: 'range', value: { min: 18 } })\n * table.setColumnFilter('name', { type: 'contains', value: 'ada' })\n * ```\n */\nexport const columnFilters = (opts: ColumnFiltersOptions = {}) =>\n defineFeature<'columnFilters', ColumnFiltersTypes>({\n name: 'columnFilters',\n stage: 'filter',\n mode: opts.mode ?? 'client',\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? columnFiltersOptions : ({} as typeof columnFiltersOptions),\n opts,\n ),\n initialState: () => ({}),\n createApi: (ctx) => ({\n setColumnFilter: (id, filter) =>\n ctx.setState((prev) => {\n // No change (the same filter, or clearing a column without one): keep the slice.\n if ((prev as Record<string, unknown>)[id] === filter) return prev\n const next: Record<string, unknown> = { ...prev }\n if (filter === undefined) delete next[id]\n else next[id] = filter\n return next as ColumnFiltersState<unknown>\n }),\n resetColumnFilters: () => ctx.setState((prev) => (Object.keys(prev).length ? {} : prev)),\n }),\n rowModel: filterRows,\n })\n"],"mappings":"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","names":["import_codes"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/column-filters.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport { type SliceFix, sliceGuard } from '../../feature/slice-guard'\nimport type { RowModelContext } from '../../feature/types'\nimport { CellFailures } from '../../intercept/cell-batch'\nimport { errorName } from '../../intercept/classify'\nimport { boundaryDiagnostics } from '../../intercept/codes'\nimport type { ColumnValues } from '../../pipeline/types'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { columnFiltersDiagnostics } from './codes'\nimport {\n BAD,\n type Compiled,\n compile,\n MATCH,\n NO,\n type Outcome,\n type RawFilter,\n type Test,\n typeName,\n} from './match'\nimport type { ColumnFiltersState, ColumnFiltersTypes } from './types'\n\nkeepDiagnostics(columnFiltersDiagnostics, boundaryDiagnostics)\n\nexport interface ColumnFiltersOptions {\n /**\n * `'server'`: the server filters (the slice goes to `options.fetcher` as `filters`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst columnFiltersOptions = { mode: modeOption } as const\n\n/** Position of column `id` in `options.columns` (diagnostic `where`); `-1` if absent. */\nfunction columnIndex(columns: readonly unknown[], id: string): number {\n for (let i = 0; i < columns.length; i++) {\n const c = columns[i] as { key?: unknown; id?: unknown } | null | undefined\n if (c && typeof c === 'object' && (typeof c.key === 'string' ? c.key : c.id) === id) return i\n }\n return -1\n}\n\nfunction columnIds(columns: readonly unknown[]): string[] {\n const out: string[] = []\n for (const c of columns as readonly ({ key?: unknown; id?: unknown } | null)[]) {\n const id = c && (typeof c.key === 'string' ? c.key : c.id)\n if (typeof id === 'string') out.push(id)\n }\n return out\n}\n\n/** Nullish and `NaN` (nullish in a numeric column cache) match no active filter (§6.1). */\nconst absent = (v: unknown) =>\n v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v))\n\n/** One cell: an array cell matches if any element matches, else `BAD` if one had a wrong type. */\nfunction testCell(test: Test, v: unknown, r: number): Outcome {\n if (!Array.isArray(v)) return test(v, r)\n let out: Outcome = NO\n for (let i = 0; i < v.length; i++) {\n const e = v[i]\n if (absent(e)) continue\n const o = test(e, -1)\n if (o === MATCH) return MATCH\n if (o === BAD) out = BAD\n }\n return out\n}\n\n/**\n * Per-batch failures, recorded as numbers (§3.4) and reused across runs: wrong-type cells, or\n * throwing `custom` calls. Text and `where` are built only when something failed.\n */\nconst failures = new CellFailures()\nconst first = { type: '', error: undefined as unknown }\n\n/** Keeps the rows of `rows[0..n)` that pass `c`, compacted into `out` (may be `rows`). */\nfunction apply(\n c: Extract<Compiled, { kind: 'test' | 'custom' }>,\n values: ColumnValues,\n data: readonly unknown[],\n rows: Uint32Array,\n n: number,\n out: Uint32Array,\n): number {\n let k = 0\n if (c.kind === 'custom') {\n const { fn, value } = c\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n try {\n if (fn(v, value, data[r])) out[k++] = r\n } catch (e) {\n if (failures.count === 0) first.error = e\n failures.add(r, e)\n }\n }\n return k\n }\n const test = c.test\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n const o = testCell(test, v, r)\n if (o === MATCH) out[k++] = r\n else if (o === BAD) {\n if (failures.count === 0) first.type = typeName(v)\n failures.add(r, undefined)\n }\n }\n return k\n}\n\nfunction report(ctx: RowModelContext, c: Compiled, id: string, kind: string, index: number) {\n if (c.kind === 'custom') {\n // Like a column:batch filterFn failure (§3.4); `where` is the filter's state path.\n const name = errorName(first.error)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `state.columnFilters.${id}.fn`,\n column: id,\n rows: Array.from(failures.sample()),\n count: failures.count,\n fallback: 'rows-unmatched',\n ...(name === undefined ? {} : { errorName: name }),\n },\n { cause: first.error },\n )\n } else if (c.kind === 'test') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: kind,\n source: 'cell',\n expected: c.expected,\n received: first.type,\n count: failures.count,\n rows: Array.from(failures.sample()),\n })\n }\n}\n\n/**\n * Applies every active column filter (AND) to `indices`: one pass per filtered column over the\n * column cache (§9.1), no per-cell allocation. Returns `indices` itself when nothing is active;\n * otherwise a new array (`indices` is never mutated).\n */\nfunction filterRows(\n indices: Uint32Array,\n state: Readonly<Record<string, unknown>> | undefined,\n ctx: RowModelContext,\n): Uint32Array {\n if (!state || typeof state !== 'object') return indices\n let out: Uint32Array | undefined\n let n = indices.length\n for (const id of Object.keys(state)) {\n const filter = state[id]\n if (filter === undefined) continue\n const values = ctx.getColumnValues(id)\n if (!values) {\n // Not a column (§7.2): ignored. No columns at all (zero-config, no rows yet) is not reported.\n if (ctx.columns.length > 0) {\n ctx.emit('DK_STATE_UNKNOWN_KEY', {\n slice: 'columnFilters',\n key: id,\n known: columnIds(ctx.columns),\n })\n }\n continue\n }\n const c = compile(filter, values)\n if (c.kind === 'off') continue\n const index = columnIndex(ctx.columns, id)\n if (c.kind === 'bad') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: c.filter,\n source: 'filter',\n expected: c.expected,\n received: c.received,\n count: 0,\n rows: [],\n })\n return new Uint32Array(0)\n }\n const src = out ?? indices\n out ??= new Uint32Array(indices.length)\n failures.reset()\n first.error = undefined\n try {\n if (c.kind === 'custom') {\n // A user callback per cell: one `filterFn` column:batch (§3.4). A skipped batch keeps\n // the slot fallback: no row matches.\n const rows = src.subarray(0, n)\n const dst = out\n let k = 0\n ctx.batch(id, 'filterFn', rows, () => {\n k = apply(c, values, ctx.data, rows, rows.length, dst)\n })\n n = k\n } else n = apply(c, values, ctx.data, src, n, out)\n if (failures.count > 0) report(ctx, c, id, String((filter as RawFilter).type), index)\n } finally {\n failures.reset()\n first.error = undefined\n }\n }\n return out ? out.subarray(0, n) : indices\n}\n\nconst isSlice = (s: unknown): s is Record<string, unknown> =>\n s !== null && typeof s === 'object' && !Array.isArray(s)\n\n/** A restored slice that is not an object (e.g. `null`) is replaced with no filters (#89). */\nconst fixSlice = (s: unknown): SliceFix<ColumnFiltersState<unknown>> | undefined =>\n isSlice(s) ? undefined : [{}, describeValue(s), '{}']\n\n/**\n * Column filters (§6.1): `equals`, `contains` (case-insensitive, NFC), `in`, `range` (inclusive),\n * `custom`. Filters combine with AND; nullish cells match no active filter; array cells match if\n * any element matches; a cell of the wrong runtime type is a non-match + `DK_FILTER_TYPE_MISMATCH`.\n *\n * ```ts\n * const table = createTable({ data: users, columns, features: [columnFilters()] })\n * table.setColumnFilter('age', { type: 'range', value: { min: 18 } })\n * table.setColumnFilter('name', { type: 'contains', value: 'ada' })\n * ```\n */\nexport const columnFilters = (opts: ColumnFiltersOptions = {}) => {\n // Dev only (§9.3): production reads a malformed slice as no filters and stays cheap.\n const guard = (process.env.NODE_ENV !== \"production\") ? sliceGuard('columnFilters', fixSlice) : undefined\n return defineFeature<'columnFilters', ColumnFiltersTypes>({\n name: 'columnFilters',\n stage: 'filter',\n mode: opts.mode ?? 'client',\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? columnFiltersOptions : ({} as typeof columnFiltersOptions),\n opts,\n ),\n ...(guard ? { interceptors: [guard.interceptor] } : undefined),\n initialState: () => ({}),\n createApi: (ctx) => {\n guard?.install(ctx)\n // Values, not updaters: a library step is never reported as a failing user `updater` (#89).\n return {\n setColumnFilter: (id, filter) => {\n // A controlled slice not fed back yet may still be malformed: it reads as no filters.\n const s = ctx.getState()\n const prev: Record<string, unknown> = s && typeof s === 'object' ? s : {}\n // No change (the same filter, or clearing a column without one): keep the slice.\n if (prev[id] === filter) return\n // A computed key defines an own property: `\"__proto__\"` never swaps the prototype\n // (#102); the row model reports it as DK_STATE_UNKNOWN_KEY like any unknown column.\n const next: Record<string, unknown> = { ...prev, [id]: filter }\n if (filter === undefined) delete next[id]\n ctx.setState(next as ColumnFiltersState<unknown>)\n },\n resetColumnFilters: () => {\n const s = ctx.getState()\n if (!s || Object.keys(s).length > 0) ctx.setState({})\n },\n }\n },\n rowModel: filterRows,\n })\n}\n"],"mappings":"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","names":["import_codes"]}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { defineOptions } from "../../catalog/options.js";
|
|
3
3
|
import { keepDiagnostics } from "../../diagnostics/catalog.js";
|
|
4
4
|
import "../../diagnostics/process-guard.js";
|
|
5
|
+
import { describeValue } from "../../diagnostics/redact.js";
|
|
5
6
|
import { defineFeature } from "../../feature/define.js";
|
|
7
|
+
import { sliceGuard } from "../../feature/slice-guard.js";
|
|
6
8
|
import { CellFailures } from "../../intercept/cell-batch.js";
|
|
7
9
|
import { errorName } from "../../intercept/classify.js";
|
|
8
10
|
import { boundaryDiagnostics } from "../../intercept/codes.js";
|
|
@@ -162,28 +164,41 @@ function filterRows(indices, state, ctx) {
|
|
|
162
164
|
}
|
|
163
165
|
return out ? out.subarray(0, n) : indices;
|
|
164
166
|
}
|
|
165
|
-
var
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
opts
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
167
|
+
var isSlice = (s) => s !== null && typeof s === "object" && !Array.isArray(s);
|
|
168
|
+
var fixSlice = (s) => isSlice(s) ? void 0 : [{}, describeValue(s), "{}"];
|
|
169
|
+
var columnFilters = (opts = {}) => {
|
|
170
|
+
const guard = process.env.NODE_ENV !== "production" ? sliceGuard("columnFilters", fixSlice) : void 0;
|
|
171
|
+
return defineFeature({
|
|
172
|
+
name: "columnFilters",
|
|
173
|
+
stage: "filter",
|
|
174
|
+
mode: opts.mode ?? "client",
|
|
175
|
+
options: defineOptions(
|
|
176
|
+
// The option validator runs in development only (§9.3): production drops the schema.
|
|
177
|
+
process.env.NODE_ENV !== "production" ? columnFiltersOptions : {},
|
|
178
|
+
opts
|
|
179
|
+
),
|
|
180
|
+
...guard ? { interceptors: [guard.interceptor] } : void 0,
|
|
181
|
+
initialState: () => ({}),
|
|
182
|
+
createApi: (ctx) => {
|
|
183
|
+
guard?.install(ctx);
|
|
184
|
+
return {
|
|
185
|
+
setColumnFilter: (id, filter) => {
|
|
186
|
+
const s = ctx.getState();
|
|
187
|
+
const prev = s && typeof s === "object" ? s : {};
|
|
188
|
+
if (prev[id] === filter) return;
|
|
189
|
+
const next = { ...prev, [id]: filter };
|
|
190
|
+
if (filter === void 0) delete next[id];
|
|
191
|
+
ctx.setState(next);
|
|
192
|
+
},
|
|
193
|
+
resetColumnFilters: () => {
|
|
194
|
+
const s = ctx.getState();
|
|
195
|
+
if (!s || Object.keys(s).length > 0) ctx.setState({});
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
rowModel: filterRows
|
|
200
|
+
});
|
|
201
|
+
};
|
|
187
202
|
export {
|
|
188
203
|
columnFilters
|
|
189
204
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/column-filters.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { defineFeature } from '../../feature/define'\nimport type { RowModelContext } from '../../feature/types'\nimport { CellFailures } from '../../intercept/cell-batch'\nimport { errorName } from '../../intercept/classify'\nimport { boundaryDiagnostics } from '../../intercept/codes'\nimport type { ColumnValues } from '../../pipeline/types'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { columnFiltersDiagnostics } from './codes'\nimport {\n BAD,\n type Compiled,\n compile,\n MATCH,\n NO,\n type Outcome,\n type RawFilter,\n type Test,\n typeName,\n} from './match'\nimport type { ColumnFiltersState, ColumnFiltersTypes } from './types'\n\nkeepDiagnostics(columnFiltersDiagnostics, boundaryDiagnostics)\n\nexport interface ColumnFiltersOptions {\n /**\n * `'server'`: the server filters (the slice goes to `options.fetcher` as `filters`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst columnFiltersOptions = { mode: modeOption } as const\n\n/** Position of column `id` in `options.columns` (diagnostic `where`); `-1` if absent. */\nfunction columnIndex(columns: readonly unknown[], id: string): number {\n for (let i = 0; i < columns.length; i++) {\n const c = columns[i] as { key?: unknown; id?: unknown } | null | undefined\n if (c && typeof c === 'object' && (typeof c.key === 'string' ? c.key : c.id) === id) return i\n }\n return -1\n}\n\nfunction columnIds(columns: readonly unknown[]): string[] {\n const out: string[] = []\n for (const c of columns as readonly ({ key?: unknown; id?: unknown } | null)[]) {\n const id = c && (typeof c.key === 'string' ? c.key : c.id)\n if (typeof id === 'string') out.push(id)\n }\n return out\n}\n\n/** Nullish and `NaN` (nullish in a numeric column cache) match no active filter (§6.1). */\nconst absent = (v: unknown) =>\n v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v))\n\n/** One cell: an array cell matches if any element matches, else `BAD` if one had a wrong type. */\nfunction testCell(test: Test, v: unknown, r: number): Outcome {\n if (!Array.isArray(v)) return test(v, r)\n let out: Outcome = NO\n for (let i = 0; i < v.length; i++) {\n const e = v[i]\n if (absent(e)) continue\n const o = test(e, -1)\n if (o === MATCH) return MATCH\n if (o === BAD) out = BAD\n }\n return out\n}\n\n/**\n * Per-batch failures, recorded as numbers (§3.4) and reused across runs: wrong-type cells, or\n * throwing `custom` calls. Text and `where` are built only when something failed.\n */\nconst failures = new CellFailures()\nconst first = { type: '', error: undefined as unknown }\n\n/** Keeps the rows of `rows[0..n)` that pass `c`, compacted into `out` (may be `rows`). */\nfunction apply(\n c: Extract<Compiled, { kind: 'test' | 'custom' }>,\n values: ColumnValues,\n data: readonly unknown[],\n rows: Uint32Array,\n n: number,\n out: Uint32Array,\n): number {\n let k = 0\n if (c.kind === 'custom') {\n const { fn, value } = c\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n try {\n if (fn(v, value, data[r])) out[k++] = r\n } catch (e) {\n if (failures.count === 0) first.error = e\n failures.add(r, e)\n }\n }\n return k\n }\n const test = c.test\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n const o = testCell(test, v, r)\n if (o === MATCH) out[k++] = r\n else if (o === BAD) {\n if (failures.count === 0) first.type = typeName(v)\n failures.add(r, undefined)\n }\n }\n return k\n}\n\nfunction report(ctx: RowModelContext, c: Compiled, id: string, kind: string, index: number) {\n if (c.kind === 'custom') {\n // Like a column:batch filterFn failure (§3.4); `where` is the filter's state path.\n const name = errorName(first.error)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `state.columnFilters.${id}.fn`,\n column: id,\n rows: Array.from(failures.sample()),\n count: failures.count,\n fallback: 'rows-unmatched',\n ...(name === undefined ? {} : { errorName: name }),\n },\n { cause: first.error },\n )\n } else if (c.kind === 'test') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: kind,\n source: 'cell',\n expected: c.expected,\n received: first.type,\n count: failures.count,\n rows: Array.from(failures.sample()),\n })\n }\n}\n\n/**\n * Applies every active column filter (AND) to `indices`: one pass per filtered column over the\n * column cache (§9.1), no per-cell allocation. Returns `indices` itself when nothing is active;\n * otherwise a new array (`indices` is never mutated).\n */\nfunction filterRows(\n indices: Uint32Array,\n state: Readonly<Record<string, unknown>> | undefined,\n ctx: RowModelContext,\n): Uint32Array {\n if (!state || typeof state !== 'object') return indices\n let out: Uint32Array | undefined\n let n = indices.length\n for (const id of Object.keys(state)) {\n const filter = state[id]\n if (filter === undefined) continue\n const values = ctx.getColumnValues(id)\n if (!values) {\n // Not a column (§7.2): ignored. No columns at all (zero-config, no rows yet) is not reported.\n if (ctx.columns.length > 0) {\n ctx.emit('DK_STATE_UNKNOWN_KEY', {\n slice: 'columnFilters',\n key: id,\n known: columnIds(ctx.columns),\n })\n }\n continue\n }\n const c = compile(filter, values)\n if (c.kind === 'off') continue\n const index = columnIndex(ctx.columns, id)\n if (c.kind === 'bad') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: c.filter,\n source: 'filter',\n expected: c.expected,\n received: c.received,\n count: 0,\n rows: [],\n })\n return new Uint32Array(0)\n }\n const src = out ?? indices\n out ??= new Uint32Array(indices.length)\n failures.reset()\n first.error = undefined\n try {\n if (c.kind === 'custom') {\n // A user callback per cell: one `filterFn` column:batch (§3.4). A skipped batch keeps\n // the slot fallback: no row matches.\n const rows = src.subarray(0, n)\n const dst = out\n let k = 0\n ctx.batch(id, 'filterFn', rows, () => {\n k = apply(c, values, ctx.data, rows, rows.length, dst)\n })\n n = k\n } else n = apply(c, values, ctx.data, src, n, out)\n if (failures.count > 0) report(ctx, c, id, String((filter as RawFilter).type), index)\n } finally {\n failures.reset()\n first.error = undefined\n }\n }\n return out ? out.subarray(0, n) : indices\n}\n\n/**\n * Column filters (§6.1): `equals`, `contains` (case-insensitive, NFC), `in`, `range` (inclusive),\n * `custom`. Filters combine with AND; nullish cells match no active filter; array cells match if\n * any element matches; a cell of the wrong runtime type is a non-match + `DK_FILTER_TYPE_MISMATCH`.\n *\n * ```ts\n * const table = createTable({ data: users, columns, features: [columnFilters()] })\n * table.setColumnFilter('age', { type: 'range', value: { min: 18 } })\n * table.setColumnFilter('name', { type: 'contains', value: 'ada' })\n * ```\n */\nexport const columnFilters = (opts: ColumnFiltersOptions = {}) =>\n defineFeature<'columnFilters', ColumnFiltersTypes>({\n name: 'columnFilters',\n stage: 'filter',\n mode: opts.mode ?? 'client',\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? columnFiltersOptions : ({} as typeof columnFiltersOptions),\n opts,\n ),\n initialState: () => ({}),\n createApi: (ctx) => ({\n setColumnFilter: (id, filter) =>\n ctx.setState((prev) => {\n // No change (the same filter, or clearing a column without one): keep the slice.\n if ((prev as Record<string, unknown>)[id] === filter) return prev\n const next: Record<string, unknown> = { ...prev }\n if (filter === undefined) delete next[id]\n else next[id] = filter\n return next as ColumnFiltersState<unknown>\n }),\n resetColumnFilters: () => ctx.setState((prev) => (Object.keys(prev).length ? {} : prev)),\n }),\n rowModel: filterRows,\n })\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/column-filters.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport { type SliceFix, sliceGuard } from '../../feature/slice-guard'\nimport type { RowModelContext } from '../../feature/types'\nimport { CellFailures } from '../../intercept/cell-batch'\nimport { errorName } from '../../intercept/classify'\nimport { boundaryDiagnostics } from '../../intercept/codes'\nimport type { ColumnValues } from '../../pipeline/types'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { columnFiltersDiagnostics } from './codes'\nimport {\n BAD,\n type Compiled,\n compile,\n MATCH,\n NO,\n type Outcome,\n type RawFilter,\n type Test,\n typeName,\n} from './match'\nimport type { ColumnFiltersState, ColumnFiltersTypes } from './types'\n\nkeepDiagnostics(columnFiltersDiagnostics, boundaryDiagnostics)\n\nexport interface ColumnFiltersOptions {\n /**\n * `'server'`: the server filters (the slice goes to `options.fetcher` as `filters`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst columnFiltersOptions = { mode: modeOption } as const\n\n/** Position of column `id` in `options.columns` (diagnostic `where`); `-1` if absent. */\nfunction columnIndex(columns: readonly unknown[], id: string): number {\n for (let i = 0; i < columns.length; i++) {\n const c = columns[i] as { key?: unknown; id?: unknown } | null | undefined\n if (c && typeof c === 'object' && (typeof c.key === 'string' ? c.key : c.id) === id) return i\n }\n return -1\n}\n\nfunction columnIds(columns: readonly unknown[]): string[] {\n const out: string[] = []\n for (const c of columns as readonly ({ key?: unknown; id?: unknown } | null)[]) {\n const id = c && (typeof c.key === 'string' ? c.key : c.id)\n if (typeof id === 'string') out.push(id)\n }\n return out\n}\n\n/** Nullish and `NaN` (nullish in a numeric column cache) match no active filter (§6.1). */\nconst absent = (v: unknown) =>\n v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v))\n\n/** One cell: an array cell matches if any element matches, else `BAD` if one had a wrong type. */\nfunction testCell(test: Test, v: unknown, r: number): Outcome {\n if (!Array.isArray(v)) return test(v, r)\n let out: Outcome = NO\n for (let i = 0; i < v.length; i++) {\n const e = v[i]\n if (absent(e)) continue\n const o = test(e, -1)\n if (o === MATCH) return MATCH\n if (o === BAD) out = BAD\n }\n return out\n}\n\n/**\n * Per-batch failures, recorded as numbers (§3.4) and reused across runs: wrong-type cells, or\n * throwing `custom` calls. Text and `where` are built only when something failed.\n */\nconst failures = new CellFailures()\nconst first = { type: '', error: undefined as unknown }\n\n/** Keeps the rows of `rows[0..n)` that pass `c`, compacted into `out` (may be `rows`). */\nfunction apply(\n c: Extract<Compiled, { kind: 'test' | 'custom' }>,\n values: ColumnValues,\n data: readonly unknown[],\n rows: Uint32Array,\n n: number,\n out: Uint32Array,\n): number {\n let k = 0\n if (c.kind === 'custom') {\n const { fn, value } = c\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n try {\n if (fn(v, value, data[r])) out[k++] = r\n } catch (e) {\n if (failures.count === 0) first.error = e\n failures.add(r, e)\n }\n }\n return k\n }\n const test = c.test\n for (let i = 0; i < n; i++) {\n const r = rows[i] as number\n const v = values[r]\n if (absent(v)) continue\n const o = testCell(test, v, r)\n if (o === MATCH) out[k++] = r\n else if (o === BAD) {\n if (failures.count === 0) first.type = typeName(v)\n failures.add(r, undefined)\n }\n }\n return k\n}\n\nfunction report(ctx: RowModelContext, c: Compiled, id: string, kind: string, index: number) {\n if (c.kind === 'custom') {\n // Like a column:batch filterFn failure (§3.4); `where` is the filter's state path.\n const name = errorName(first.error)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `state.columnFilters.${id}.fn`,\n column: id,\n rows: Array.from(failures.sample()),\n count: failures.count,\n fallback: 'rows-unmatched',\n ...(name === undefined ? {} : { errorName: name }),\n },\n { cause: first.error },\n )\n } else if (c.kind === 'test') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: kind,\n source: 'cell',\n expected: c.expected,\n received: first.type,\n count: failures.count,\n rows: Array.from(failures.sample()),\n })\n }\n}\n\n/**\n * Applies every active column filter (AND) to `indices`: one pass per filtered column over the\n * column cache (§9.1), no per-cell allocation. Returns `indices` itself when nothing is active;\n * otherwise a new array (`indices` is never mutated).\n */\nfunction filterRows(\n indices: Uint32Array,\n state: Readonly<Record<string, unknown>> | undefined,\n ctx: RowModelContext,\n): Uint32Array {\n if (!state || typeof state !== 'object') return indices\n let out: Uint32Array | undefined\n let n = indices.length\n for (const id of Object.keys(state)) {\n const filter = state[id]\n if (filter === undefined) continue\n const values = ctx.getColumnValues(id)\n if (!values) {\n // Not a column (§7.2): ignored. No columns at all (zero-config, no rows yet) is not reported.\n if (ctx.columns.length > 0) {\n ctx.emit('DK_STATE_UNKNOWN_KEY', {\n slice: 'columnFilters',\n key: id,\n known: columnIds(ctx.columns),\n })\n }\n continue\n }\n const c = compile(filter, values)\n if (c.kind === 'off') continue\n const index = columnIndex(ctx.columns, id)\n if (c.kind === 'bad') {\n ctx.emit('DK_FILTER_TYPE_MISMATCH', {\n column: id,\n index,\n filter: c.filter,\n source: 'filter',\n expected: c.expected,\n received: c.received,\n count: 0,\n rows: [],\n })\n return new Uint32Array(0)\n }\n const src = out ?? indices\n out ??= new Uint32Array(indices.length)\n failures.reset()\n first.error = undefined\n try {\n if (c.kind === 'custom') {\n // A user callback per cell: one `filterFn` column:batch (§3.4). A skipped batch keeps\n // the slot fallback: no row matches.\n const rows = src.subarray(0, n)\n const dst = out\n let k = 0\n ctx.batch(id, 'filterFn', rows, () => {\n k = apply(c, values, ctx.data, rows, rows.length, dst)\n })\n n = k\n } else n = apply(c, values, ctx.data, src, n, out)\n if (failures.count > 0) report(ctx, c, id, String((filter as RawFilter).type), index)\n } finally {\n failures.reset()\n first.error = undefined\n }\n }\n return out ? out.subarray(0, n) : indices\n}\n\nconst isSlice = (s: unknown): s is Record<string, unknown> =>\n s !== null && typeof s === 'object' && !Array.isArray(s)\n\n/** A restored slice that is not an object (e.g. `null`) is replaced with no filters (#89). */\nconst fixSlice = (s: unknown): SliceFix<ColumnFiltersState<unknown>> | undefined =>\n isSlice(s) ? undefined : [{}, describeValue(s), '{}']\n\n/**\n * Column filters (§6.1): `equals`, `contains` (case-insensitive, NFC), `in`, `range` (inclusive),\n * `custom`. Filters combine with AND; nullish cells match no active filter; array cells match if\n * any element matches; a cell of the wrong runtime type is a non-match + `DK_FILTER_TYPE_MISMATCH`.\n *\n * ```ts\n * const table = createTable({ data: users, columns, features: [columnFilters()] })\n * table.setColumnFilter('age', { type: 'range', value: { min: 18 } })\n * table.setColumnFilter('name', { type: 'contains', value: 'ada' })\n * ```\n */\nexport const columnFilters = (opts: ColumnFiltersOptions = {}) => {\n // Dev only (§9.3): production reads a malformed slice as no filters and stays cheap.\n const guard = (process.env.NODE_ENV !== \"production\") ? sliceGuard('columnFilters', fixSlice) : undefined\n return defineFeature<'columnFilters', ColumnFiltersTypes>({\n name: 'columnFilters',\n stage: 'filter',\n mode: opts.mode ?? 'client',\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? columnFiltersOptions : ({} as typeof columnFiltersOptions),\n opts,\n ),\n ...(guard ? { interceptors: [guard.interceptor] } : undefined),\n initialState: () => ({}),\n createApi: (ctx) => {\n guard?.install(ctx)\n // Values, not updaters: a library step is never reported as a failing user `updater` (#89).\n return {\n setColumnFilter: (id, filter) => {\n // A controlled slice not fed back yet may still be malformed: it reads as no filters.\n const s = ctx.getState()\n const prev: Record<string, unknown> = s && typeof s === 'object' ? s : {}\n // No change (the same filter, or clearing a column without one): keep the slice.\n if (prev[id] === filter) return\n // A computed key defines an own property: `\"__proto__\"` never swaps the prototype\n // (#102); the row model reports it as DK_STATE_UNKNOWN_KEY like any unknown column.\n const next: Record<string, unknown> = { ...prev, [id]: filter }\n if (filter === undefined) delete next[id]\n ctx.setState(next as ColumnFiltersState<unknown>)\n },\n resetColumnFilters: () => {\n const s = ctx.getState()\n if (!s || Object.keys(s).length > 0) ctx.setState({})\n },\n }\n },\n rowModel: filterRows,\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -44,11 +44,13 @@ function foldedFor(values) {
|
|
|
44
44
|
}
|
|
45
45
|
return f;
|
|
46
46
|
}
|
|
47
|
+
var isNaNValue = (v) => typeof v === "number" && Number.isNaN(v);
|
|
47
48
|
var bad = (filter, expected, received) => ({
|
|
48
49
|
kind: "bad",
|
|
49
50
|
filter,
|
|
50
51
|
expected,
|
|
51
|
-
|
|
52
|
+
// `number(NaN)`, as `describeValue` names it: "a number, expects a number" would read as a bug.
|
|
53
|
+
received: isNaNValue(received) ? "number(NaN)" : typeName(received)
|
|
52
54
|
});
|
|
53
55
|
function equals(fv) {
|
|
54
56
|
if (empty(fv)) return OFF;
|
|
@@ -60,6 +62,7 @@ function equals(fv) {
|
|
|
60
62
|
test: (e) => e instanceof Date ? e.getTime() === t ? MATCH : NO : BAD
|
|
61
63
|
};
|
|
62
64
|
}
|
|
65
|
+
if (isNaNValue(fv)) return bad("equals", "not-nan", fv);
|
|
63
66
|
const type = typeof fv;
|
|
64
67
|
if (type === "object" || type === "function" || type === "symbol") {
|
|
65
68
|
return bad("equals", "prim-or-date", fv);
|
|
@@ -120,7 +123,8 @@ function inList(fv) {
|
|
|
120
123
|
}
|
|
121
124
|
function bound(v) {
|
|
122
125
|
if (empty(v)) return null;
|
|
123
|
-
if (typeof v === "number" || typeof v === "bigint")
|
|
126
|
+
if (typeof v === "number" && !Number.isNaN(v) || typeof v === "bigint")
|
|
127
|
+
return { family: "number", at: v };
|
|
124
128
|
if (v instanceof Date && !Number.isNaN(v.getTime())) return { family: "Date", at: v.getTime() };
|
|
125
129
|
return false;
|
|
126
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/match.ts"],"sourcesContent":["import type { ColumnValues } from '../../pipeline/types'\nimport { FILTER_KINDS } from './codes'\n\n/** Result of testing one value: a match, no match, or a value of the wrong type (non-match). */\nexport const NO = 0\nexport const MATCH = 1\nexport const BAD = 2\nexport type Outcome = typeof NO | typeof MATCH | typeof BAD\n\n/**\n * Tests one non-nullish value. `r` is the row index when the value is the whole cell, `-1` for an\n * element of an array cell (per-row caches apply to whole cells only).\n */\nexport type Test = (value: unknown, r: number) => Outcome\n\n/** A runtime filter, as read from state (runtime JSON ignores the types). */\nexport interface RawFilter {\n readonly type?: unknown\n readonly value?: unknown\n readonly fn?: unknown\n}\n\nexport type Compiled =\n /** Inactive: the step skips it. */\n | { readonly kind: 'off' }\n /** A value test with what it accepts (for `DK_FILTER_TYPE_MISMATCH`). */\n | { readonly kind: 'test'; readonly test: Test; readonly expected: string }\n | {\n readonly kind: 'custom'\n readonly fn: (v: unknown, fv: unknown, row: unknown) => unknown\n readonly value: unknown\n }\n /** Malformed filter value: matches nothing; reported once with `source: 'filter'`. */\n | {\n readonly kind: 'bad'\n readonly filter: string\n readonly expected: string\n readonly received: string\n }\n\nconst OFF: Compiled = { kind: 'off' }\n\n/** A short type name for diagnostics: `null`, `array`, `Date`, else `typeof`. Never a value. */\nexport const typeName = (v: unknown): string =>\n v === null ? 'null' : Array.isArray(v) ? 'array' : v instanceof Date ? 'Date' : typeof v\n\n/** `undefined`, `null` and `''` leave a filter (or a range bound) inactive (§6.1). */\nconst empty = (v: unknown) => v === undefined || v === null || v === ''\n\n/** Case-insensitive, NFC-normalised form for `contains` (Thai combining marks compare correctly). */\nconst fold = (s: string) => s.toLowerCase().normalize('NFC')\n\n/**\n * Folded cell strings per column-values array: the column cache returns the same array for the same\n * data and column, so typing into a `contains` filter folds each cell once.\n */\nconst folded = new WeakMap<ColumnValues, (string | undefined)[]>()\n\nfunction foldedFor(values: ColumnValues): (string | undefined)[] {\n let f = folded.get(values)\n if (!f) {\n f = new Array<string | undefined>(values.length)\n folded.set(values, f)\n }\n return f\n}\n\nconst bad = (filter: string, expected: string, received: unknown): Compiled => ({\n kind: 'bad',\n filter,\n expected,\n received: typeName(received),\n})\n\nfunction equals(fv: unknown): Compiled {\n if (empty(fv)) return OFF\n if (fv instanceof Date) {\n const t = fv.getTime()\n return {\n kind: 'test',\n expected: 'Date',\n test: (e) => (e instanceof Date ? (e.getTime() === t ? MATCH : NO) : BAD),\n }\n }\n const type = typeof fv\n if (type === 'object' || type === 'function' || type === 'symbol') {\n return bad('equals', 'prim-or-date', fv)\n }\n return {\n kind: 'test',\n expected: type,\n test: (e) => (typeof e === type ? (e === fv ? MATCH : NO) : BAD),\n }\n}\n\nfunction contains(fv: unknown, values: ColumnValues): Compiled {\n if (empty(fv)) return OFF\n if (typeof fv !== 'string') return bad('contains', 'string', fv)\n const needle = fold(fv)\n const cache = foldedFor(values)\n return {\n kind: 'test',\n expected: 'string',\n test: (e, r) => {\n if (typeof e !== 'string') return BAD\n let s: string | undefined\n if (r < 0) s = fold(e)\n else {\n s = cache[r]\n if (s === undefined) {\n s = fold(e)\n cache[r] = s\n }\n }\n return s.includes(needle) ? MATCH : NO\n },\n }\n}\n\nfunction inList(fv: unknown): Compiled {\n if (fv === undefined || fv === null) return OFF\n if (!Array.isArray(fv)) return bad('in', 'array', fv)\n const prims = new Set<unknown>()\n const times = new Set<number>()\n const types = new Set<string>()\n for (const item of fv as unknown[]) {\n if (item === undefined || item === null) continue\n if (item instanceof Date) times.add(item.getTime())\n else if (typeof item === 'object' || typeof item === 'function') {\n return bad('in', 'prims-or-dates', item)\n } else prims.add(item)\n types.add(typeName(item))\n }\n if (types.size === 0) return OFF\n const hasDate = times.size > 0\n return {\n kind: 'test',\n expected: [...types].join(' | '),\n test: (e) => {\n if (e instanceof Date) return hasDate ? (times.has(e.getTime()) ? MATCH : NO) : BAD\n return types.has(typeof e) ? (prims.has(e) ? MATCH : NO) : BAD\n },\n }\n}\n\ntype Family = 'number' | 'Date'\n\n/** A range bound: absent, its family + comparable value, or malformed. */\nfunction bound(v: unknown): { family: Family; at: number | bigint } | null | false {\n if (empty(v)) return null\n if (typeof v === 'number' || typeof v === 'bigint') return { family: 'number', at: v }\n if (v instanceof Date && !Number.isNaN(v.getTime())) return { family: 'Date', at: v.getTime() }\n return false\n}\n\nconst NUMERIC = 'number | bigint'\n\nfunction range(fv: unknown): Compiled {\n if (fv === undefined || fv === null) return OFF\n if (typeof fv !== 'object' || Array.isArray(fv) || fv instanceof Date) {\n return bad('range', '{ min?, max? }', fv)\n }\n const { min: rawMin, max: rawMax } = fv as { min?: unknown; max?: unknown }\n const min = bound(rawMin)\n const max = bound(rawMax)\n // `expected` prose keys (diagnostics/prose.ts).\n if (min === false) return bad('range', 'range-bound', rawMin)\n if (max === false) return bad('range', 'range-bound', rawMax)\n if (!min && !max) return OFF\n const family = (min || max) as { family: Family }\n if (min && max && min.family !== max.family) {\n return bad('range', `same-type|${min.family}`, rawMax)\n }\n // Mixed number / bigint comparisons are exact in JS (`3n >= 2.5`).\n const lo = min ? (min.at as number) : undefined\n const hi = max ? (max.at as number) : undefined\n const inside = (x: number) => (lo === undefined || x >= lo) && (hi === undefined || x <= hi)\n if (family.family === 'Date') {\n return {\n kind: 'test',\n expected: 'Date',\n test: (e) => (e instanceof Date ? (inside(e.getTime()) ? MATCH : NO) : BAD),\n }\n }\n return {\n kind: 'test',\n expected: NUMERIC,\n test: (e) =>\n typeof e === 'number' || typeof e === 'bigint' ? (inside(e as number) ? MATCH : NO) : BAD,\n }\n}\n\n/** Compiles one column's filter for this run (cold: once per active filter per run). */\nexport function compile(filter: unknown, values: ColumnValues): Compiled {\n if (filter === undefined || filter === null) return OFF\n if (typeof filter !== 'object') return bad('(none)', '{ type, value }', filter)\n const f = filter as RawFilter\n switch (f.type) {\n case 'equals':\n return equals(f.value)\n case 'contains':\n return contains(f.value, values)\n case 'in':\n return inList(f.value)\n case 'range':\n return range(f.value)\n case 'custom':\n return typeof f.fn === 'function'\n ? {\n kind: 'custom',\n fn: f.fn as (v: unknown, fv: unknown, row: unknown) => unknown,\n value: f.value,\n }\n : bad('custom', 'fn: (value, filterValue, row) => boolean', f.fn)\n default:\n return {\n kind: 'bad',\n filter: typeof f.type === 'string' ? f.type : '(none)',\n expected: FILTER_KINDS.join(' | '),\n received: 'unknown filter kind',\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAA6B;AAGtB,IAAM,KAAK;AACX,IAAM,QAAQ;AACd,IAAM,MAAM;AAkCnB,IAAM,MAAgB,EAAE,MAAM,MAAM;AAG7B,IAAM,WAAW,CAAC,MACvB,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC,IAAI,UAAU,aAAa,OAAO,SAAS,OAAO;AAGzF,IAAM,QAAQ,CAAC,MAAe,MAAM,UAAa,MAAM,QAAQ,MAAM;AAGrE,IAAM,OAAO,CAAC,MAAc,EAAE,YAAY,EAAE,UAAU,KAAK;AAM3D,IAAM,SAAS,oBAAI,QAA8C;AAEjE,SAAS,UAAU,QAA8C;AAC/D,MAAI,IAAI,OAAO,IAAI,MAAM;AACzB,MAAI,CAAC,GAAG;AACN,QAAI,IAAI,MAA0B,OAAO,MAAM;AAC/C,WAAO,IAAI,QAAQ,CAAC;AAAA,EACtB;AACA,SAAO;AACT;AAEA,IAAM,MAAM,CAAC,QAAgB,UAAkB,cAAiC;AAAA,EAC9E,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA,UAAU,SAAS,QAAQ;AAC7B;AAEA,SAAS,OAAO,IAAuB;AACrC,MAAI,MAAM,EAAE,EAAG,QAAO;AACtB,MAAI,cAAc,MAAM;AACtB,UAAM,IAAI,GAAG,QAAQ;AACrB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,CAAC,MAAO,aAAa,OAAQ,EAAE,QAAQ,MAAM,IAAI,QAAQ,KAAM;AAAA,IACvE;AAAA,EACF;AACA,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,YAAY,SAAS,cAAc,SAAS,UAAU;AACjE,WAAO,IAAI,UAAU,gBAAgB,EAAE;AAAA,EACzC;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC,MAAO,OAAO,MAAM,OAAQ,MAAM,KAAK,QAAQ,KAAM;AAAA,EAC9D;AACF;AAEA,SAAS,SAAS,IAAa,QAAgC;AAC7D,MAAI,MAAM,EAAE,EAAG,QAAO;AACtB,MAAI,OAAO,OAAO,SAAU,QAAO,IAAI,YAAY,UAAU,EAAE;AAC/D,QAAM,SAAS,KAAK,EAAE;AACtB,QAAM,QAAQ,UAAU,MAAM;AAC9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC,GAAG,MAAM;AACd,UAAI,OAAO,MAAM,SAAU,QAAO;AAClC,UAAI;AACJ,UAAI,IAAI,EAAG,KAAI,KAAK,CAAC;AAAA,WAChB;AACH,YAAI,MAAM,CAAC;AACX,YAAI,MAAM,QAAW;AACnB,cAAI,KAAK,CAAC;AACV,gBAAM,CAAC,IAAI;AAAA,QACb;AAAA,MACF;AACA,aAAO,EAAE,SAAS,MAAM,IAAI,QAAQ;AAAA,IACtC;AAAA,EACF;AACF;AAEA,SAAS,OAAO,IAAuB;AACrC,MAAI,OAAO,UAAa,OAAO,KAAM,QAAO;AAC5C,MAAI,CAAC,MAAM,QAAQ,EAAE,EAAG,QAAO,IAAI,MAAM,SAAS,EAAE;AACpD,QAAM,QAAQ,oBAAI,IAAa;AAC/B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,QAAQ,IAAiB;AAClC,QAAI,SAAS,UAAa,SAAS,KAAM;AACzC,QAAI,gBAAgB,KAAM,OAAM,IAAI,KAAK,QAAQ,CAAC;AAAA,aACzC,OAAO,SAAS,YAAY,OAAO,SAAS,YAAY;AAC/D,aAAO,IAAI,MAAM,kBAAkB,IAAI;AAAA,IACzC,MAAO,OAAM,IAAI,IAAI;AACrB,UAAM,IAAI,SAAS,IAAI,CAAC;AAAA,EAC1B;AACA,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,UAAU,MAAM,OAAO;AAC7B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,CAAC,GAAG,KAAK,EAAE,KAAK,KAAK;AAAA,IAC/B,MAAM,CAAC,MAAM;AACX,UAAI,aAAa,KAAM,QAAO,UAAW,MAAM,IAAI,EAAE,QAAQ,CAAC,IAAI,QAAQ,KAAM;AAChF,aAAO,MAAM,IAAI,OAAO,CAAC,IAAK,MAAM,IAAI,CAAC,IAAI,QAAQ,KAAM;AAAA,IAC7D;AAAA,EACF;AACF;AAKA,SAAS,MAAM,GAAoE;AACjF,MAAI,MAAM,CAAC,EAAG,QAAO;AACrB,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO,EAAE,QAAQ,UAAU,IAAI,EAAE;AACrF,MAAI,aAAa,QAAQ,CAAC,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO,EAAE,QAAQ,QAAQ,IAAI,EAAE,QAAQ,EAAE;AAC9F,SAAO;AACT;AAEA,IAAM,UAAU;AAEhB,SAAS,MAAM,IAAuB;AACpC,MAAI,OAAO,UAAa,OAAO,KAAM,QAAO;AAC5C,MAAI,OAAO,OAAO,YAAY,MAAM,QAAQ,EAAE,KAAK,cAAc,MAAM;AACrE,WAAO,IAAI,SAAS,kBAAkB,EAAE;AAAA,EAC1C;AACA,QAAM,EAAE,KAAK,QAAQ,KAAK,OAAO,IAAI;AACrC,QAAM,MAAM,MAAM,MAAM;AACxB,QAAM,MAAM,MAAM,MAAM;AAExB,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,eAAe,MAAM;AAC5D,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,eAAe,MAAM;AAC5D,MAAI,CAAC,OAAO,CAAC,IAAK,QAAO;AACzB,QAAM,SAAU,OAAO;AACvB,MAAI,OAAO,OAAO,IAAI,WAAW,IAAI,QAAQ;AAC3C,WAAO,IAAI,SAAS,aAAa,IAAI,MAAM,IAAI,MAAM;AAAA,EACvD;AAEA,QAAM,KAAK,MAAO,IAAI,KAAgB;AACtC,QAAM,KAAK,MAAO,IAAI,KAAgB;AACtC,QAAM,SAAS,CAAC,OAAe,OAAO,UAAa,KAAK,QAAQ,OAAO,UAAa,KAAK;AACzF,MAAI,OAAO,WAAW,QAAQ;AAC5B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,CAAC,MAAO,aAAa,OAAQ,OAAO,EAAE,QAAQ,CAAC,IAAI,QAAQ,KAAM;AAAA,IACzE;AAAA,EACF;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC,MACL,OAAO,MAAM,YAAY,OAAO,MAAM,WAAY,OAAO,CAAW,IAAI,QAAQ,KAAM;AAAA,EAC1F;AACF;AAGO,SAAS,QAAQ,QAAiB,QAAgC;AACvE,MAAI,WAAW,UAAa,WAAW,KAAM,QAAO;AACpD,MAAI,OAAO,WAAW,SAAU,QAAO,IAAI,UAAU,mBAAmB,MAAM;AAC9E,QAAM,IAAI;AACV,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,OAAO,EAAE,KAAK;AAAA,IACvB,KAAK;AACH,aAAO,SAAS,EAAE,OAAO,MAAM;AAAA,IACjC,KAAK;AACH,aAAO,OAAO,EAAE,KAAK;AAAA,IACvB,KAAK;AACH,aAAO,MAAM,EAAE,KAAK;AAAA,IACtB,KAAK;AACH,aAAO,OAAO,EAAE,OAAO,aACnB;AAAA,QACE,MAAM;AAAA,QACN,IAAI,EAAE;AAAA,QACN,OAAO,EAAE;AAAA,MACX,IACA,IAAI,UAAU,4CAA4C,EAAE,EAAE;AAAA,IACpE;AACE,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,QAC9C,UAAU,0BAAa,KAAK,KAAK;AAAA,QACjC,UAAU;AAAA,MACZ;AAAA,EACJ;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/match.ts"],"sourcesContent":["import type { ColumnValues } from '../../pipeline/types'\nimport { FILTER_KINDS } from './codes'\n\n/** Result of testing one value: a match, no match, or a value of the wrong type (non-match). */\nexport const NO = 0\nexport const MATCH = 1\nexport const BAD = 2\nexport type Outcome = typeof NO | typeof MATCH | typeof BAD\n\n/**\n * Tests one non-nullish value. `r` is the row index when the value is the whole cell, `-1` for an\n * element of an array cell (per-row caches apply to whole cells only).\n */\nexport type Test = (value: unknown, r: number) => Outcome\n\n/** A runtime filter, as read from state (runtime JSON ignores the types). */\nexport interface RawFilter {\n readonly type?: unknown\n readonly value?: unknown\n readonly fn?: unknown\n}\n\nexport type Compiled =\n /** Inactive: the step skips it. */\n | { readonly kind: 'off' }\n /** A value test with what it accepts (for `DK_FILTER_TYPE_MISMATCH`). */\n | { readonly kind: 'test'; readonly test: Test; readonly expected: string }\n | {\n readonly kind: 'custom'\n readonly fn: (v: unknown, fv: unknown, row: unknown) => unknown\n readonly value: unknown\n }\n /** Malformed filter value: matches nothing; reported once with `source: 'filter'`. */\n | {\n readonly kind: 'bad'\n readonly filter: string\n readonly expected: string\n readonly received: string\n }\n\nconst OFF: Compiled = { kind: 'off' }\n\n/** A short type name for diagnostics: `null`, `array`, `Date`, else `typeof`. Never a value. */\nexport const typeName = (v: unknown): string =>\n v === null ? 'null' : Array.isArray(v) ? 'array' : v instanceof Date ? 'Date' : typeof v\n\n/** `undefined`, `null` and `''` leave a filter (or a range bound) inactive (§6.1). */\nconst empty = (v: unknown) => v === undefined || v === null || v === ''\n\n/** Case-insensitive, NFC-normalised form for `contains` (Thai combining marks compare correctly). */\nconst fold = (s: string) => s.toLowerCase().normalize('NFC')\n\n/**\n * Folded cell strings per column-values array: the column cache returns the same array for the same\n * data and column, so typing into a `contains` filter folds each cell once.\n */\nconst folded = new WeakMap<ColumnValues, (string | undefined)[]>()\n\nfunction foldedFor(values: ColumnValues): (string | undefined)[] {\n let f = folded.get(values)\n if (!f) {\n f = new Array<string | undefined>(values.length)\n folded.set(values, f)\n }\n return f\n}\n\nconst isNaNValue = (v: unknown) => typeof v === 'number' && Number.isNaN(v)\n\nconst bad = (filter: string, expected: string, received: unknown): Compiled => ({\n kind: 'bad',\n filter,\n expected,\n // `number(NaN)`, as `describeValue` names it: \"a number, expects a number\" would read as a bug.\n received: isNaNValue(received) ? 'number(NaN)' : typeName(received),\n})\n\nfunction equals(fv: unknown): Compiled {\n if (empty(fv)) return OFF\n if (fv instanceof Date) {\n const t = fv.getTime()\n return {\n kind: 'test',\n expected: 'Date',\n test: (e) => (e instanceof Date ? (e.getTime() === t ? MATCH : NO) : BAD),\n }\n }\n // NaN equals nothing (`NaN !== NaN`): reported, not a silent empty table (#91).\n if (isNaNValue(fv)) return bad('equals', 'not-nan', fv)\n const type = typeof fv\n if (type === 'object' || type === 'function' || type === 'symbol') {\n return bad('equals', 'prim-or-date', fv)\n }\n return {\n kind: 'test',\n expected: type,\n test: (e) => (typeof e === type ? (e === fv ? MATCH : NO) : BAD),\n }\n}\n\nfunction contains(fv: unknown, values: ColumnValues): Compiled {\n if (empty(fv)) return OFF\n if (typeof fv !== 'string') return bad('contains', 'string', fv)\n const needle = fold(fv)\n const cache = foldedFor(values)\n return {\n kind: 'test',\n expected: 'string',\n test: (e, r) => {\n if (typeof e !== 'string') return BAD\n let s: string | undefined\n if (r < 0) s = fold(e)\n else {\n s = cache[r]\n if (s === undefined) {\n s = fold(e)\n cache[r] = s\n }\n }\n return s.includes(needle) ? MATCH : NO\n },\n }\n}\n\nfunction inList(fv: unknown): Compiled {\n if (fv === undefined || fv === null) return OFF\n if (!Array.isArray(fv)) return bad('in', 'array', fv)\n const prims = new Set<unknown>()\n const times = new Set<number>()\n const types = new Set<string>()\n for (const item of fv as unknown[]) {\n if (item === undefined || item === null) continue\n if (item instanceof Date) times.add(item.getTime())\n else if (typeof item === 'object' || typeof item === 'function') {\n return bad('in', 'prims-or-dates', item)\n } else prims.add(item)\n types.add(typeName(item))\n }\n if (types.size === 0) return OFF\n const hasDate = times.size > 0\n return {\n kind: 'test',\n expected: [...types].join(' | '),\n test: (e) => {\n if (e instanceof Date) return hasDate ? (times.has(e.getTime()) ? MATCH : NO) : BAD\n return types.has(typeof e) ? (prims.has(e) ? MATCH : NO) : BAD\n },\n }\n}\n\ntype Family = 'number' | 'Date'\n\n/**\n * A range bound: absent, its family + comparable value, or malformed. NaN is malformed: every\n * comparison with it is false, so it would hide every row without a report (#91).\n */\nfunction bound(v: unknown): { family: Family; at: number | bigint } | null | false {\n if (empty(v)) return null\n if ((typeof v === 'number' && !Number.isNaN(v)) || typeof v === 'bigint')\n return { family: 'number', at: v }\n if (v instanceof Date && !Number.isNaN(v.getTime())) return { family: 'Date', at: v.getTime() }\n return false\n}\n\nconst NUMERIC = 'number | bigint'\n\nfunction range(fv: unknown): Compiled {\n if (fv === undefined || fv === null) return OFF\n if (typeof fv !== 'object' || Array.isArray(fv) || fv instanceof Date) {\n return bad('range', '{ min?, max? }', fv)\n }\n const { min: rawMin, max: rawMax } = fv as { min?: unknown; max?: unknown }\n const min = bound(rawMin)\n const max = bound(rawMax)\n // `expected` prose keys (diagnostics/prose.ts).\n if (min === false) return bad('range', 'range-bound', rawMin)\n if (max === false) return bad('range', 'range-bound', rawMax)\n if (!min && !max) return OFF\n const family = (min || max) as { family: Family }\n if (min && max && min.family !== max.family) {\n return bad('range', `same-type|${min.family}`, rawMax)\n }\n // Mixed number / bigint comparisons are exact in JS (`3n >= 2.5`).\n const lo = min ? (min.at as number) : undefined\n const hi = max ? (max.at as number) : undefined\n const inside = (x: number) => (lo === undefined || x >= lo) && (hi === undefined || x <= hi)\n if (family.family === 'Date') {\n return {\n kind: 'test',\n expected: 'Date',\n test: (e) => (e instanceof Date ? (inside(e.getTime()) ? MATCH : NO) : BAD),\n }\n }\n return {\n kind: 'test',\n expected: NUMERIC,\n test: (e) =>\n typeof e === 'number' || typeof e === 'bigint' ? (inside(e as number) ? MATCH : NO) : BAD,\n }\n}\n\n/** Compiles one column's filter for this run (cold: once per active filter per run). */\nexport function compile(filter: unknown, values: ColumnValues): Compiled {\n if (filter === undefined || filter === null) return OFF\n if (typeof filter !== 'object') return bad('(none)', '{ type, value }', filter)\n const f = filter as RawFilter\n switch (f.type) {\n case 'equals':\n return equals(f.value)\n case 'contains':\n return contains(f.value, values)\n case 'in':\n return inList(f.value)\n case 'range':\n return range(f.value)\n case 'custom':\n return typeof f.fn === 'function'\n ? {\n kind: 'custom',\n fn: f.fn as (v: unknown, fv: unknown, row: unknown) => unknown,\n value: f.value,\n }\n : bad('custom', 'fn: (value, filterValue, row) => boolean', f.fn)\n default:\n return {\n kind: 'bad',\n filter: typeof f.type === 'string' ? f.type : '(none)',\n expected: FILTER_KINDS.join(' | '),\n received: 'unknown filter kind',\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,11 +16,13 @@ function foldedFor(values) {
|
|
|
16
16
|
}
|
|
17
17
|
return f;
|
|
18
18
|
}
|
|
19
|
+
var isNaNValue = (v) => typeof v === "number" && Number.isNaN(v);
|
|
19
20
|
var bad = (filter, expected, received) => ({
|
|
20
21
|
kind: "bad",
|
|
21
22
|
filter,
|
|
22
23
|
expected,
|
|
23
|
-
|
|
24
|
+
// `number(NaN)`, as `describeValue` names it: "a number, expects a number" would read as a bug.
|
|
25
|
+
received: isNaNValue(received) ? "number(NaN)" : typeName(received)
|
|
24
26
|
});
|
|
25
27
|
function equals(fv) {
|
|
26
28
|
if (empty(fv)) return OFF;
|
|
@@ -32,6 +34,7 @@ function equals(fv) {
|
|
|
32
34
|
test: (e) => e instanceof Date ? e.getTime() === t ? MATCH : NO : BAD
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
if (isNaNValue(fv)) return bad("equals", "not-nan", fv);
|
|
35
38
|
const type = typeof fv;
|
|
36
39
|
if (type === "object" || type === "function" || type === "symbol") {
|
|
37
40
|
return bad("equals", "prim-or-date", fv);
|
|
@@ -92,7 +95,8 @@ function inList(fv) {
|
|
|
92
95
|
}
|
|
93
96
|
function bound(v) {
|
|
94
97
|
if (empty(v)) return null;
|
|
95
|
-
if (typeof v === "number" || typeof v === "bigint")
|
|
98
|
+
if (typeof v === "number" && !Number.isNaN(v) || typeof v === "bigint")
|
|
99
|
+
return { family: "number", at: v };
|
|
96
100
|
if (v instanceof Date && !Number.isNaN(v.getTime())) return { family: "Date", at: v.getTime() };
|
|
97
101
|
return false;
|
|
98
102
|
}
|