@datakable/core 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/README.md +5 -3
  2. package/dist/catalog/codes.cjs +12 -8
  3. package/dist/catalog/codes.cjs.map +1 -1
  4. package/dist/catalog/codes.js +12 -8
  5. package/dist/catalog/codes.js.map +1 -1
  6. package/dist/catalog/slots.cjs +10 -6
  7. package/dist/catalog/slots.cjs.map +1 -1
  8. package/dist/catalog/slots.js +10 -6
  9. package/dist/catalog/slots.js.map +1 -1
  10. package/dist/{codes-OsjAR3Wz.d.cts → codes-B9bCK73-.d.cts} +30 -4
  11. package/dist/{codes-OsjAR3Wz.d.ts → codes-B9bCK73-.d.ts} +30 -4
  12. package/dist/column/column-def.cjs.map +1 -1
  13. package/dist/column/get-value.cjs +6 -1
  14. package/dist/column/get-value.cjs.map +1 -1
  15. package/dist/column/get-value.js +6 -1
  16. package/dist/column/get-value.js.map +1 -1
  17. package/dist/column-filters.d.cts +2 -2
  18. package/dist/column-filters.d.ts +2 -2
  19. package/dist/diagnostics/build.cjs +4 -1
  20. package/dist/diagnostics/build.cjs.map +1 -1
  21. package/dist/diagnostics/build.js +4 -1
  22. package/dist/diagnostics/build.js.map +1 -1
  23. package/dist/diagnostics/catalog.cjs +11 -5
  24. package/dist/diagnostics/catalog.cjs.map +1 -1
  25. package/dist/diagnostics/catalog.js +10 -5
  26. package/dist/diagnostics/catalog.js.map +1 -1
  27. package/dist/diagnostics/codes.cjs +12 -8
  28. package/dist/diagnostics/codes.cjs.map +1 -1
  29. package/dist/diagnostics/codes.js +12 -8
  30. package/dist/diagnostics/codes.js.map +1 -1
  31. package/dist/diagnostics/console.cjs +13 -6
  32. package/dist/diagnostics/console.cjs.map +1 -1
  33. package/dist/diagnostics/console.js +13 -6
  34. package/dist/diagnostics/console.js.map +1 -1
  35. package/dist/diagnostics/env.cjs +1 -0
  36. package/dist/diagnostics/env.cjs.map +1 -1
  37. package/dist/diagnostics/env.js +1 -0
  38. package/dist/diagnostics/env.js.map +1 -1
  39. package/dist/diagnostics/explain-text.cjs +2 -1
  40. package/dist/diagnostics/explain-text.cjs.map +1 -1
  41. package/dist/diagnostics/explain-text.js +2 -1
  42. package/dist/diagnostics/explain-text.js.map +1 -1
  43. package/dist/diagnostics/explain.cjs +3 -2
  44. package/dist/diagnostics/explain.cjs.map +1 -1
  45. package/dist/diagnostics/explain.js +3 -2
  46. package/dist/diagnostics/explain.js.map +1 -1
  47. package/dist/diagnostics/hub.cjs +5 -4
  48. package/dist/diagnostics/hub.cjs.map +1 -1
  49. package/dist/diagnostics/hub.js +5 -4
  50. package/dist/diagnostics/hub.js.map +1 -1
  51. package/dist/diagnostics/process-guard.cjs +26 -0
  52. package/dist/diagnostics/process-guard.cjs.map +1 -0
  53. package/dist/diagnostics/process-guard.js +9 -0
  54. package/dist/diagnostics/process-guard.js.map +1 -0
  55. package/dist/diagnostics/prose.cjs +120 -0
  56. package/dist/diagnostics/prose.cjs.map +1 -0
  57. package/dist/diagnostics/prose.js +93 -0
  58. package/dist/diagnostics/prose.js.map +1 -0
  59. package/dist/diagnostics/redact.cjs +7 -4
  60. package/dist/diagnostics/redact.cjs.map +1 -1
  61. package/dist/diagnostics/redact.js +6 -4
  62. package/dist/diagnostics/redact.js.map +1 -1
  63. package/dist/diagnostics/trace-store.cjs +6 -0
  64. package/dist/diagnostics/trace-store.cjs.map +1 -1
  65. package/dist/diagnostics/trace-store.js +4 -0
  66. package/dist/diagnostics/trace-store.js.map +1 -1
  67. package/dist/diagnostics/tracer.cjs +74 -13
  68. package/dist/diagnostics/tracer.cjs.map +1 -1
  69. package/dist/diagnostics/tracer.js +75 -14
  70. package/dist/diagnostics/tracer.js.map +1 -1
  71. package/dist/diagnostics/types.cjs.map +1 -1
  72. package/dist/{explain-vkD2cewE.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
  73. package/dist/{explain-Dn99IQt0.d.ts → explain-i0OATi_W.d.ts} +13 -4
  74. package/dist/feature/codes.cjs +1 -0
  75. package/dist/feature/codes.cjs.map +1 -1
  76. package/dist/feature/codes.js +1 -0
  77. package/dist/feature/codes.js.map +1 -1
  78. package/dist/feature/install.cjs +4 -3
  79. package/dist/feature/install.cjs.map +1 -1
  80. package/dist/feature/install.js +4 -3
  81. package/dist/feature/install.js.map +1 -1
  82. package/dist/features/column-filters/codes.cjs +1 -0
  83. package/dist/features/column-filters/codes.cjs.map +1 -1
  84. package/dist/features/column-filters/codes.js +1 -0
  85. package/dist/features/column-filters/codes.js.map +1 -1
  86. package/dist/features/column-filters/column-filters.cjs +4 -2
  87. package/dist/features/column-filters/column-filters.cjs.map +1 -1
  88. package/dist/features/column-filters/column-filters.js +4 -2
  89. package/dist/features/column-filters/column-filters.js.map +1 -1
  90. package/dist/features/column-filters/match.cjs +5 -5
  91. package/dist/features/column-filters/match.cjs.map +1 -1
  92. package/dist/features/column-filters/match.js +5 -5
  93. package/dist/features/column-filters/match.js.map +1 -1
  94. package/dist/features/pagination/codes.cjs +1 -0
  95. package/dist/features/pagination/codes.cjs.map +1 -1
  96. package/dist/features/pagination/codes.js +1 -0
  97. package/dist/features/pagination/codes.js.map +1 -1
  98. package/dist/features/pagination/pagination.cjs +16 -13
  99. package/dist/features/pagination/pagination.cjs.map +1 -1
  100. package/dist/features/pagination/pagination.js +16 -13
  101. package/dist/features/pagination/pagination.js.map +1 -1
  102. package/dist/features/search/codes.cjs +1 -0
  103. package/dist/features/search/codes.cjs.map +1 -1
  104. package/dist/features/search/codes.js +1 -0
  105. package/dist/features/search/codes.js.map +1 -1
  106. package/dist/features/search/search-index.cjs +68 -15
  107. package/dist/features/search/search-index.cjs.map +1 -1
  108. package/dist/features/search/search-index.js +68 -15
  109. package/dist/features/search/search-index.js.map +1 -1
  110. package/dist/features/search/search.cjs +41 -9
  111. package/dist/features/search/search.cjs.map +1 -1
  112. package/dist/features/search/search.js +41 -9
  113. package/dist/features/search/search.js.map +1 -1
  114. package/dist/features/search/skipped-codes.cjs +45 -0
  115. package/dist/features/search/skipped-codes.cjs.map +1 -0
  116. package/dist/features/search/skipped-codes.js +20 -0
  117. package/dist/features/search/skipped-codes.js.map +1 -0
  118. package/dist/features/sorting/codes.cjs +1 -0
  119. package/dist/features/sorting/codes.cjs.map +1 -1
  120. package/dist/features/sorting/codes.js +1 -0
  121. package/dist/features/sorting/codes.js.map +1 -1
  122. package/dist/features/sorting/sorting.cjs +16 -20
  123. package/dist/features/sorting/sorting.cjs.map +1 -1
  124. package/dist/features/sorting/sorting.js +16 -20
  125. package/dist/features/sorting/sorting.js.map +1 -1
  126. package/dist/features/sorting/strategy.cjs +4 -4
  127. package/dist/features/sorting/strategy.cjs.map +1 -1
  128. package/dist/features/sorting/strategy.js +4 -4
  129. package/dist/features/sorting/strategy.js.map +1 -1
  130. package/dist/features/validity/codes.cjs +1 -0
  131. package/dist/features/validity/codes.cjs.map +1 -1
  132. package/dist/features/validity/codes.js +1 -0
  133. package/dist/features/validity/codes.js.map +1 -1
  134. package/dist/features/validity/validity.cjs +3 -3
  135. package/dist/features/validity/validity.cjs.map +1 -1
  136. package/dist/features/validity/validity.js +3 -3
  137. package/dist/features/validity/validity.js.map +1 -1
  138. package/dist/index.d.cts +13 -10
  139. package/dist/index.d.ts +13 -10
  140. package/dist/ingest/data-check-codes.cjs +4 -2
  141. package/dist/ingest/data-check-codes.cjs.map +1 -1
  142. package/dist/ingest/data-check-codes.js +4 -2
  143. package/dist/ingest/data-check-codes.js.map +1 -1
  144. package/dist/ingest/data-checks.cjs +1 -1
  145. package/dist/ingest/data-checks.cjs.map +1 -1
  146. package/dist/ingest/data-checks.js +1 -1
  147. package/dist/ingest/data-checks.js.map +1 -1
  148. package/dist/ingest/row-key-codes.cjs +1 -0
  149. package/dist/ingest/row-key-codes.cjs.map +1 -1
  150. package/dist/ingest/row-key-codes.js +1 -0
  151. package/dist/ingest/row-key-codes.js.map +1 -1
  152. package/dist/ingest/row-key.cjs +23 -20
  153. package/dist/ingest/row-key.cjs.map +1 -1
  154. package/dist/ingest/row-key.js +23 -20
  155. package/dist/ingest/row-key.js.map +1 -1
  156. package/dist/ingest/schema-codes.cjs +1 -0
  157. package/dist/ingest/schema-codes.cjs.map +1 -1
  158. package/dist/ingest/schema-codes.js +1 -0
  159. package/dist/ingest/schema-codes.js.map +1 -1
  160. package/dist/ingest/unique-fields.cjs +2 -2
  161. package/dist/ingest/unique-fields.cjs.map +1 -1
  162. package/dist/ingest/unique-fields.js +2 -2
  163. package/dist/ingest/unique-fields.js.map +1 -1
  164. package/dist/intercept/boundary.cjs +4 -2
  165. package/dist/intercept/boundary.cjs.map +1 -1
  166. package/dist/intercept/boundary.js +4 -2
  167. package/dist/intercept/boundary.js.map +1 -1
  168. package/dist/intercept/cell-batch.cjs +4 -4
  169. package/dist/intercept/cell-batch.cjs.map +1 -1
  170. package/dist/intercept/cell-batch.js +4 -4
  171. package/dist/intercept/cell-batch.js.map +1 -1
  172. package/dist/intercept/codes.cjs +1 -0
  173. package/dist/intercept/codes.cjs.map +1 -1
  174. package/dist/intercept/codes.js +1 -0
  175. package/dist/intercept/codes.js.map +1 -1
  176. package/dist/intercept/fallbacks.cjs +12 -20
  177. package/dist/intercept/fallbacks.cjs.map +1 -1
  178. package/dist/intercept/fallbacks.js +12 -20
  179. package/dist/intercept/fallbacks.js.map +1 -1
  180. package/dist/intercept/runtime.cjs +7 -9
  181. package/dist/intercept/runtime.cjs.map +1 -1
  182. package/dist/intercept/runtime.js +7 -9
  183. package/dist/intercept/runtime.js.map +1 -1
  184. package/dist/messages/index.cjs +4 -1
  185. package/dist/messages/index.cjs.map +1 -1
  186. package/dist/messages/index.js +4 -1
  187. package/dist/messages/index.js.map +1 -1
  188. package/dist/pagination.d.cts +2 -2
  189. package/dist/pagination.d.ts +2 -2
  190. package/dist/pipeline/codes.cjs +1 -0
  191. package/dist/pipeline/codes.cjs.map +1 -1
  192. package/dist/pipeline/codes.js +1 -0
  193. package/dist/pipeline/codes.js.map +1 -1
  194. package/dist/pipeline/column-cache.cjs +94 -76
  195. package/dist/pipeline/column-cache.cjs.map +1 -1
  196. package/dist/pipeline/column-cache.js +92 -75
  197. package/dist/pipeline/column-cache.js.map +1 -1
  198. package/dist/pipeline/dev-checks.cjs +1 -0
  199. package/dist/pipeline/dev-checks.cjs.map +1 -1
  200. package/dist/pipeline/dev-checks.js +1 -0
  201. package/dist/pipeline/dev-checks.js.map +1 -1
  202. package/dist/pipeline/env-codes.cjs +3 -2
  203. package/dist/pipeline/env-codes.cjs.map +1 -1
  204. package/dist/pipeline/env-codes.js +3 -2
  205. package/dist/pipeline/env-codes.js.map +1 -1
  206. package/dist/pipeline/perf-codes.cjs +1 -0
  207. package/dist/pipeline/perf-codes.cjs.map +1 -1
  208. package/dist/pipeline/perf-codes.js +1 -0
  209. package/dist/pipeline/perf-codes.js.map +1 -1
  210. package/dist/pipeline/runner.cjs +17 -9
  211. package/dist/pipeline/runner.cjs.map +1 -1
  212. package/dist/pipeline/runner.js +17 -9
  213. package/dist/pipeline/runner.js.map +1 -1
  214. package/dist/pipeline/slice.cjs +3 -1
  215. package/dist/pipeline/slice.cjs.map +1 -1
  216. package/dist/pipeline/slice.js +3 -1
  217. package/dist/pipeline/slice.js.map +1 -1
  218. package/dist/schema.d.cts +2 -2
  219. package/dist/schema.d.ts +2 -2
  220. package/dist/{search-async-entry-v5JdEk5Y.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
  221. package/dist/{search-async-entry-DGSw2pDL.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
  222. package/dist/search-async.d.cts +3 -3
  223. package/dist/search-async.d.ts +3 -3
  224. package/dist/search.d.cts +8 -6
  225. package/dist/search.d.ts +8 -6
  226. package/dist/server.d.cts +3 -3
  227. package/dist/server.d.ts +3 -3
  228. package/dist/sorting.d.cts +2 -2
  229. package/dist/sorting.d.ts +2 -2
  230. package/dist/source/codes.cjs +1 -0
  231. package/dist/source/codes.cjs.map +1 -1
  232. package/dist/source/codes.js +1 -0
  233. package/dist/source/codes.js.map +1 -1
  234. package/dist/source/fetch-codes.cjs +1 -0
  235. package/dist/source/fetch-codes.cjs.map +1 -1
  236. package/dist/source/fetch-codes.js +1 -0
  237. package/dist/source/fetch-codes.js.map +1 -1
  238. package/dist/table/codes.cjs +1 -0
  239. package/dist/table/codes.cjs.map +1 -1
  240. package/dist/table/codes.js +1 -0
  241. package/dist/table/codes.js.map +1 -1
  242. package/dist/table/create-table.cjs +39 -9
  243. package/dist/table/create-table.cjs.map +1 -1
  244. package/dist/table/create-table.js +39 -9
  245. package/dist/table/create-table.js.map +1 -1
  246. package/dist/table/data-mutation.cjs +1 -0
  247. package/dist/table/data-mutation.cjs.map +1 -1
  248. package/dist/table/data-mutation.js +1 -0
  249. package/dist/table/data-mutation.js.map +1 -1
  250. package/dist/table/internals.cjs.map +1 -1
  251. package/dist/table/internals.js.map +1 -1
  252. package/dist/table/ssr-codes.cjs +1 -0
  253. package/dist/table/ssr-codes.cjs.map +1 -1
  254. package/dist/table/ssr-codes.js +1 -0
  255. package/dist/table/ssr-codes.js.map +1 -1
  256. package/dist/table/ssr.cjs +4 -1
  257. package/dist/table/ssr.cjs.map +1 -1
  258. package/dist/table/ssr.js +4 -1
  259. package/dist/table/ssr.js.map +1 -1
  260. package/dist/table/state-feedback.cjs +19 -0
  261. package/dist/table/state-feedback.cjs.map +1 -1
  262. package/dist/table/state-feedback.js +19 -0
  263. package/dist/table/state-feedback.js.map +1 -1
  264. package/dist/table/types.cjs.map +1 -1
  265. package/dist/table/types.js.map +1 -1
  266. package/dist/validity.d.cts +2 -2
  267. package/dist/validity.d.ts +2 -2
  268. package/dist/version.cjs +1 -1
  269. package/dist/version.cjs.map +1 -1
  270. package/dist/version.js +1 -1
  271. package/dist/version.js.map +1 -1
  272. package/dist/versioning/codes.cjs +18 -0
  273. package/dist/versioning/codes.cjs.map +1 -1
  274. package/dist/versioning/codes.js +18 -0
  275. package/dist/versioning/codes.js.map +1 -1
  276. package/dist/versioning/registry.cjs +4 -2
  277. package/dist/versioning/registry.cjs.map +1 -1
  278. package/dist/versioning/registry.js +4 -2
  279. package/dist/versioning/registry.js.map +1 -1
  280. package/package.json +39 -3
@@ -1,6 +1,7 @@
1
1
  // src/feature/install.ts
2
2
  import { throwConfigError } from "../diagnostics/build.js";
3
3
  import { keepDiagnostics } from "../diagnostics/catalog.js";
4
+ import "../diagnostics/process-guard.js";
4
5
  import { errorName } from "../intercept/classify.js";
5
6
  import { featureDiagnostics } from "./codes.js";
6
7
  keepDiagnostics(featureDiagnostics);
@@ -20,7 +21,7 @@ function initialSlices(features, given, emit) {
20
21
  out[f.name] = f.initialState();
21
22
  } catch (e) {
22
23
  out[f.name] = void 0;
23
- reportFailure(emit, f.name, "initialState", "the slice starts as undefined", e);
24
+ reportFailure(emit, f.name, "initialState", "slice-undefined", e);
24
25
  }
25
26
  }
26
27
  return given ? Object.assign(out, given) : out;
@@ -43,7 +44,7 @@ function installApis(features, host) {
43
44
  try {
44
45
  api = f.createApi(ctx);
45
46
  } catch (e) {
46
- reportFailure(host.emit, name, "createApi", "its API was not installed", e);
47
+ reportFailure(host.emit, name, "createApi", "api-not-installed", e);
47
48
  failed.push(name);
48
49
  continue;
49
50
  }
@@ -95,7 +96,7 @@ function missingApi(failed, host) {
95
96
  feature,
96
97
  hook: "createApi",
97
98
  method: key,
98
- fallback: `table.${key}() did nothing (the API of ${failed.length === 1 ? "that feature" : "those features"} was not installed)`
99
+ fallback: `${failed.length === 1 ? "api" : "apis"}-missing|${key}`
99
100
  })
100
101
  );
101
102
  return void 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'the slice starts as undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'its API was not installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `table.${key}() did nothing (the API of ${failed.length === 1 ? 'that feature' : 'those features'} was not installed)`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'slice-undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'api-not-installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `${failed.length === 1 ? 'api' : 'apis'}-missing|${key}`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,OAAO;AAEP,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B;AAGnC,gBAAgB,kBAAkB;AAkBlC,SAAS,cACP,MACA,SACA,MACA,UACA,GACA;AACA,QAAM,OAAO,UAAU,CAAC;AACxB;AAAA,IACE;AAAA,IACA,EAAE,SAAS,MAAM,UAAU,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK,EAAG;AAAA,IAC9E,EAAE,OAAO,EAAE;AAAA,EACb;AACF;AAMO,SAAS,cACd,UACA,OACA,MACY;AACZ,QAAM,MAAkB,CAAC;AACzB,aAAW,KAAK,UAAU;AACxB,QAAI,SAAS,OAAO,OAAO,OAAO,EAAE,IAAI,EAAG;AAC3C,QAAI;AACF,UAAI,EAAE,IAAI,IAAI,EAAE,aAAa;AAAA,IAC/B,SAAS,GAAG;AACV,UAAI,EAAE,IAAI,IAAI;AACd,oBAAc,MAAM,EAAE,MAAM,gBAAgB,mBAAmB,CAAC;AAAA,IAClE;AAAA,EACF;AACA,SAAO,QAAQ,OAAO,OAAO,KAAK,KAAK,IAAI;AAC7C;AAOO,SAAS,YAAY,UAAiC,MAAyB;AACpF,QAAM,SAAS,KAAK;AACpB,QAAM,SAAS,oBAAI,IAA2B;AAC9C,aAAW,KAAK,OAAO,KAAK,MAAM,EAAG,QAAO,IAAI,GAAG,IAAI;AAEvD,QAAM,SAAmB,CAAC;AAE1B,aAAW,KAAK,UAAU;AACxB,UAAM,OAAO,EAAE;AACf,UAAM,MAA+B;AAAA,MACnC;AAAA,MACA,UAAU,MAAM,KAAK,SAAS,IAAI;AAAA,MAClC,UAAU,CAAC,YAAY,KAAK,SAAS,MAAM,OAAO;AAAA,MAClD,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,IACb;AACA,QAAI;AACJ,QAAI;AACF,YAAM,EAAE,UAAU,GAAG;AAAA,IACvB,SAAS,GAAG;AACV,oBAAc,KAAK,MAAM,MAAM,aAAa,qBAAqB,CAAC;AAClE,aAAO,KAAK,IAAI;AAChB;AAAA,IACF;AACA,QAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU;AAC7C,UAAM,UAAU;AAChB,UAAM,OAAO,OAAO,KAAK,OAAO;AAChC,eAAW,UAAU,MAAM;AACzB,UAAI,OAAO,IAAI,MAAM,GAAG;AACtB;AAAA,UACE;AAAA,UACA,EAAE,QAAQ,SAAS,MAAM,OAAO,OAAO,IAAI,MAAM,KAAK,KAAK;AAAA,UAC3D,EAAE,OAAO,KAAK,UAAU;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA,eAAW,UAAU,MAAM;AACzB,aAAO,IAAI,QAAQ,IAAI;AACvB,YAAM,SAAS,QAAQ,MAAM;AAC7B,YAAM,YAAY,EAAE,kBAAkB,MAAM;AAC5C,aAAO,MAAM,IACX,OAAO,WAAW,aACd,IAAI,SAAoB,KAAK,KAAK,QAAQ,MAAM,OAAO,MAAM,SAAS,IAAI,GAAG,SAAS,IACtF;AAAA,IACR;AAAA,EACF;AACA,MAAK,QAAQ,IAAI,aAAa,gBAAiB,OAAO,SAAS,EAAG,QAAO,eAAe,QAAQ,WAAW,QAAQ,IAAI,CAAC;AAC1H;AAMA,IAAM,SAAS,oBAAI,IAAI;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,UAAU,CAAC,MAAc,OAAO,IAAI,CAAC,KAAK,SAAS,KAAK,CAAC;AAU/D,SAAS,WAAW,QAA2B,MAA2B;AACxE,QAAM,UAAU,OAAO,KAAK,IAAI;AAChC,QAAM,QAAQ,oBAAI,IAA6B;AAC/C,SAAO,IAAI,MAAM,OAAO,WAAW;AAAA,IACjC,IAAI,OAAO,KAAK,UAAU;AACxB,UAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,QAAQ,GAAG,GAAG;AAC3D,eAAO,QAAQ,IAAI,OAAO,KAAK,QAAQ;AAAA,MACzC;AACA,UAAI,OAAO,MAAM,IAAI,GAAG;AACxB,UAAI,CAAC,MAAM;AACT,eAAO,MAAM;AACX,eAAK;AAAA,YAAK;AAAA,YAAK,MACb,KAAK,KAAK,qBAAqB;AAAA,cAC7B;AAAA,cACA,MAAM;AAAA,cACN,QAAQ;AAAA,cACR,UAAU,GAAG,OAAO,WAAW,IAAI,QAAQ,MAAM,YAAY,GAAG;AAAA,YAClE,CAAC;AAAA,UACH;AACA,iBAAO;AAAA,QACT;AACA,cAAM,IAAI,KAAK,IAAI;AAAA,MACrB;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -27,6 +27,7 @@ __export(codes_exports, {
27
27
  module.exports = __toCommonJS(codes_exports);
28
28
  var import_suggest = require("../../catalog/suggest.cjs");
29
29
  var import_catalog = require("../../diagnostics/catalog.cjs");
30
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
30
31
  var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
31
32
  var rowList = (rows, count) => {
32
33
  if (rows.length === 0) return "";
@@ -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'\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 DK_STATE_UNKNOWN_KEY: {\n /** The state slice holding the key, e.g. `columnFilters`. */\n slice: string\n /** The key that names no column. */\n key: string\n /** The table's column ids (did-you-mean). */\n known: readonly string[]\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`. */\nexport const columnFiltersMessages: DiagnosticMessages<\n 'DK_FILTER_TYPE_MISMATCH' | 'DK_STATE_UNKNOWN_KEY'\n> = {\n DK_STATE_UNKNOWN_KEY: {\n problem: (d) => `state.${d.slice} has an entry for \"${d.key}\", which is not a column`,\n impact: () =>\n 'the entry is ignored (the other entries still apply); it may come from an older URL or ' +\n 'saved state, or the column was renamed or removed',\n fix: (d) => {\n const near = suggest(d.key, d.known)\n return [\n near.length > 0\n ? `did you mean \"${near[0]}\"? (state.${d.slice}[\"${near[0]}\"])`\n : `use one of the column ids: ${d.known.map((k) => `\"${k}\"`).join(', ')}`,\n `drop \"${d.key}\" when parsing initialState / state (e.g. from the URL), or add the column`,\n ]\n },\n },\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 defineDiagnostic(\n {\n code: 'DK_STATE_UNKNOWN_KEY',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}.${d.key}` }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_STATE_UNKNOWN_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
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 DK_STATE_UNKNOWN_KEY: {\n /** The state slice holding the key, e.g. `columnFilters`. */\n slice: string\n /** The key that names no column. */\n key: string\n /** The table's column ids (did-you-mean). */\n known: readonly string[]\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`. */\nexport const columnFiltersMessages: DiagnosticMessages<\n 'DK_FILTER_TYPE_MISMATCH' | 'DK_STATE_UNKNOWN_KEY'\n> = {\n DK_STATE_UNKNOWN_KEY: {\n problem: (d) => `state.${d.slice} has an entry for \"${d.key}\", which is not a column`,\n impact: () =>\n 'the entry is ignored (the other entries still apply); it may come from an older URL or ' +\n 'saved state, or the column was renamed or removed',\n fix: (d) => {\n const near = suggest(d.key, d.known)\n return [\n near.length > 0\n ? `did you mean \"${near[0]}\"? (state.${d.slice}[\"${near[0]}\"])`\n : `use one of the column ids: ${d.known.map((k) => `\"${k}\"`).join(', ')}`,\n `drop \"${d.key}\" when parsing initialState / state (e.g. from the URL), or add the column`,\n ]\n },\n },\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 defineDiagnostic(\n {\n code: 'DK_STATE_UNKNOWN_KEY',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}.${d.key}` }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_STATE_UNKNOWN_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
@@ -1,6 +1,7 @@
1
1
  // src/features/column-filters/codes.ts
2
2
  import { suggest } from "../../catalog/suggest.js";
3
3
  import { defineDiagnostic } from "../../diagnostics/catalog.js";
4
+ import "../../diagnostics/process-guard.js";
4
5
  var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
5
6
  var rowList = (rows, count) => {
6
7
  if (rows.length === 0) return "";
@@ -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'\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 DK_STATE_UNKNOWN_KEY: {\n /** The state slice holding the key, e.g. `columnFilters`. */\n slice: string\n /** The key that names no column. */\n key: string\n /** The table's column ids (did-you-mean). */\n known: readonly string[]\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`. */\nexport const columnFiltersMessages: DiagnosticMessages<\n 'DK_FILTER_TYPE_MISMATCH' | 'DK_STATE_UNKNOWN_KEY'\n> = {\n DK_STATE_UNKNOWN_KEY: {\n problem: (d) => `state.${d.slice} has an entry for \"${d.key}\", which is not a column`,\n impact: () =>\n 'the entry is ignored (the other entries still apply); it may come from an older URL or ' +\n 'saved state, or the column was renamed or removed',\n fix: (d) => {\n const near = suggest(d.key, d.known)\n return [\n near.length > 0\n ? `did you mean \"${near[0]}\"? (state.${d.slice}[\"${near[0]}\"])`\n : `use one of the column ids: ${d.known.map((k) => `\"${k}\"`).join(', ')}`,\n `drop \"${d.key}\" when parsing initialState / state (e.g. from the URL), or add the column`,\n ]\n },\n },\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 defineDiagnostic(\n {\n code: 'DK_STATE_UNKNOWN_KEY',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}.${d.key}` }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_STATE_UNKNOWN_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
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 DK_STATE_UNKNOWN_KEY: {\n /** The state slice holding the key, e.g. `columnFilters`. */\n slice: string\n /** The key that names no column. */\n key: string\n /** The table's column ids (did-you-mean). */\n known: readonly string[]\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`. */\nexport const columnFiltersMessages: DiagnosticMessages<\n 'DK_FILTER_TYPE_MISMATCH' | 'DK_STATE_UNKNOWN_KEY'\n> = {\n DK_STATE_UNKNOWN_KEY: {\n problem: (d) => `state.${d.slice} has an entry for \"${d.key}\", which is not a column`,\n impact: () =>\n 'the entry is ignored (the other entries still apply); it may come from an older URL or ' +\n 'saved state, or the column was renamed or removed',\n fix: (d) => {\n const near = suggest(d.key, d.known)\n return [\n near.length > 0\n ? `did you mean \"${near[0]}\"? (state.${d.slice}[\"${near[0]}\"])`\n : `use one of the column ids: ${d.known.map((k) => `\"${k}\"`).join(', ')}`,\n `drop \"${d.key}\" when parsing initialState / state (e.g. from the URL), or add the column`,\n ]\n },\n },\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 defineDiagnostic(\n {\n code: 'DK_STATE_UNKNOWN_KEY',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}.${d.key}` }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_STATE_UNKNOWN_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
@@ -25,6 +25,7 @@ __export(column_filters_exports, {
25
25
  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
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
28
29
  var import_define = require("../../feature/define.cjs");
29
30
  var import_cell_batch = require("../../intercept/cell-batch.cjs");
30
31
  var import_classify = require("../../intercept/classify.cjs");
@@ -105,7 +106,7 @@ function report(ctx, c, id, kind, index) {
105
106
  column: id,
106
107
  rows: Array.from(failures.sample()),
107
108
  count: failures.count,
108
- fallback: "the failed rows are treated as non-matching",
109
+ fallback: "rows-unmatched",
109
110
  ...name === void 0 ? {} : { errorName: name }
110
111
  },
111
112
  { cause: first.error }
@@ -191,12 +192,13 @@ var columnFilters = (opts = {}) => (0, import_define.defineFeature)({
191
192
  initialState: () => ({}),
192
193
  createApi: (ctx) => ({
193
194
  setColumnFilter: (id, filter) => ctx.setState((prev) => {
195
+ if (prev[id] === filter) return prev;
194
196
  const next = { ...prev };
195
197
  if (filter === void 0) delete next[id];
196
198
  else next[id] = filter;
197
199
  return next;
198
200
  }),
199
- resetColumnFilters: () => ctx.setState({})
201
+ resetColumnFilters: () => ctx.setState((prev) => Object.keys(prev).length ? {} : prev)
200
202
  }),
201
203
  rowModel: filterRows
202
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 { 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: 'the failed rows are treated as non-matching',\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 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({}),\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 { 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,6 +1,7 @@
1
1
  // src/features/column-filters/column-filters.ts
2
2
  import { defineOptions } from "../../catalog/options.js";
3
3
  import { keepDiagnostics } from "../../diagnostics/catalog.js";
4
+ import "../../diagnostics/process-guard.js";
4
5
  import { defineFeature } from "../../feature/define.js";
5
6
  import { CellFailures } from "../../intercept/cell-batch.js";
6
7
  import { errorName } from "../../intercept/classify.js";
@@ -87,7 +88,7 @@ function report(ctx, c, id, kind, index) {
87
88
  column: id,
88
89
  rows: Array.from(failures.sample()),
89
90
  count: failures.count,
90
- fallback: "the failed rows are treated as non-matching",
91
+ fallback: "rows-unmatched",
91
92
  ...name === void 0 ? {} : { errorName: name }
92
93
  },
93
94
  { cause: first.error }
@@ -173,12 +174,13 @@ var columnFilters = (opts = {}) => defineFeature({
173
174
  initialState: () => ({}),
174
175
  createApi: (ctx) => ({
175
176
  setColumnFilter: (id, filter) => ctx.setState((prev) => {
177
+ if (prev[id] === filter) return prev;
176
178
  const next = { ...prev };
177
179
  if (filter === void 0) delete next[id];
178
180
  else next[id] = filter;
179
181
  return next;
180
182
  }),
181
- resetColumnFilters: () => ctx.setState({})
183
+ resetColumnFilters: () => ctx.setState((prev) => Object.keys(prev).length ? {} : prev)
182
184
  }),
183
185
  rowModel: filterRows
184
186
  });
@@ -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 { 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: 'the failed rows are treated as non-matching',\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 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({}),\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 { 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":[]}
@@ -62,7 +62,7 @@ function equals(fv) {
62
62
  }
63
63
  const type = typeof fv;
64
64
  if (type === "object" || type === "function" || type === "symbol") {
65
- return bad("equals", "a primitive or a Date", fv);
65
+ return bad("equals", "prim-or-date", fv);
66
66
  }
67
67
  return {
68
68
  kind: "test",
@@ -103,7 +103,7 @@ function inList(fv) {
103
103
  if (item === void 0 || item === null) continue;
104
104
  if (item instanceof Date) times.add(item.getTime());
105
105
  else if (typeof item === "object" || typeof item === "function") {
106
- return bad("in", "an array of primitives or Dates", item);
106
+ return bad("in", "prims-or-dates", item);
107
107
  } else prims.add(item);
108
108
  types.add(typeName(item));
109
109
  }
@@ -133,12 +133,12 @@ function range(fv) {
133
133
  const { min: rawMin, max: rawMax } = fv;
134
134
  const min = bound(rawMin);
135
135
  const max = bound(rawMax);
136
- if (min === false) return bad("range", `${NUMERIC} or a valid Date bound`, rawMin);
137
- if (max === false) return bad("range", `${NUMERIC} or a valid Date bound`, rawMax);
136
+ if (min === false) return bad("range", "range-bound", rawMin);
137
+ if (max === false) return bad("range", "range-bound", rawMax);
138
138
  if (!min && !max) return OFF;
139
139
  const family = min || max;
140
140
  if (min && max && min.family !== max.family) {
141
- return bad("range", `min and max of the same type (${min.family})`, rawMax);
141
+ return bad("range", `same-type|${min.family}`, rawMax);
142
142
  }
143
143
  const lo = min ? min.at : void 0;
144
144
  const hi = max ? max.at : void 0;
@@ -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', 'a primitive or a 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', 'an array of primitives 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 if (min === false) return bad('range', `${NUMERIC} or a valid Date bound`, rawMin)\n if (max === false) return bad('range', `${NUMERIC} or a valid Date 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', `min and max of the 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,yBAAyB,EAAE;AAAA,EAClD;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,mCAAmC,IAAI;AAAA,IAC1D,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;AACxB,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,GAAG,OAAO,0BAA0B,MAAM;AACjF,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,GAAG,OAAO,0BAA0B,MAAM;AACjF,MAAI,CAAC,OAAO,CAAC,IAAK,QAAO;AACzB,QAAM,SAAU,OAAO;AACvB,MAAI,OAAO,OAAO,IAAI,WAAW,IAAI,QAAQ;AAC3C,WAAO,IAAI,SAAS,iCAAiC,IAAI,MAAM,KAAK,MAAM;AAAA,EAC5E;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 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":"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","names":[]}
@@ -34,7 +34,7 @@ function equals(fv) {
34
34
  }
35
35
  const type = typeof fv;
36
36
  if (type === "object" || type === "function" || type === "symbol") {
37
- return bad("equals", "a primitive or a Date", fv);
37
+ return bad("equals", "prim-or-date", fv);
38
38
  }
39
39
  return {
40
40
  kind: "test",
@@ -75,7 +75,7 @@ function inList(fv) {
75
75
  if (item === void 0 || item === null) continue;
76
76
  if (item instanceof Date) times.add(item.getTime());
77
77
  else if (typeof item === "object" || typeof item === "function") {
78
- return bad("in", "an array of primitives or Dates", item);
78
+ return bad("in", "prims-or-dates", item);
79
79
  } else prims.add(item);
80
80
  types.add(typeName(item));
81
81
  }
@@ -105,12 +105,12 @@ function range(fv) {
105
105
  const { min: rawMin, max: rawMax } = fv;
106
106
  const min = bound(rawMin);
107
107
  const max = bound(rawMax);
108
- if (min === false) return bad("range", `${NUMERIC} or a valid Date bound`, rawMin);
109
- if (max === false) return bad("range", `${NUMERIC} or a valid Date bound`, rawMax);
108
+ if (min === false) return bad("range", "range-bound", rawMin);
109
+ if (max === false) return bad("range", "range-bound", rawMax);
110
110
  if (!min && !max) return OFF;
111
111
  const family = min || max;
112
112
  if (min && max && min.family !== max.family) {
113
- return bad("range", `min and max of the same type (${min.family})`, rawMax);
113
+ return bad("range", `same-type|${min.family}`, rawMax);
114
114
  }
115
115
  const lo = min ? min.at : void 0;
116
116
  const hi = max ? max.at : void 0;
@@ -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', 'a primitive or a 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', 'an array of primitives 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 if (min === false) return bad('range', `${NUMERIC} or a valid Date bound`, rawMin)\n if (max === false) return bad('range', `${NUMERIC} or a valid Date 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', `min and max of the 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":";AACA,SAAS,oBAAoB;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,yBAAyB,EAAE;AAAA,EAClD;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,mCAAmC,IAAI;AAAA,IAC1D,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;AACxB,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,GAAG,OAAO,0BAA0B,MAAM;AACjF,MAAI,QAAQ,MAAO,QAAO,IAAI,SAAS,GAAG,OAAO,0BAA0B,MAAM;AACjF,MAAI,CAAC,OAAO,CAAC,IAAK,QAAO;AACzB,QAAM,SAAU,OAAO;AACvB,MAAI,OAAO,OAAO,IAAI,WAAW,IAAI,QAAQ;AAC3C,WAAO,IAAI,SAAS,iCAAiC,IAAI,MAAM,KAAK,MAAM;AAAA,EAC5E;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,aAAa,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 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":"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","names":[]}
@@ -25,6 +25,7 @@ __export(codes_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(codes_exports);
27
27
  var import_catalog = require("../../diagnostics/catalog.cjs");
28
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
28
29
  var ACCEPTED = {
29
30
  pageIndex: "an integer \u2265 0",
30
31
  pageSize: "an integer > 0"