@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,10 +1,12 @@
1
1
  // src/features/search/search.ts
2
+ import { debounce } from "kk-debounce/debounce";
2
3
  import { debouncedSignal } from "kk-debounce/debounceSignal";
3
4
  import { throttle } from "kk-debounce/throttle";
4
5
  import { optionDiagnostics } from "../../catalog/codes.js";
5
6
  import { defineOptions } from "../../catalog/options.js";
6
7
  import { throwConfigError } from "../../diagnostics/build.js";
7
8
  import { keepDiagnostics } from "../../diagnostics/catalog.js";
9
+ import "../../diagnostics/process-guard.js";
8
10
  import { defineFeature } from "../../feature/define.js";
9
11
  import { defineInterceptor } from "../../intercept/define.js";
10
12
  import { runJob, sliced } from "../../pipeline/slice.js";
@@ -15,7 +17,8 @@ import {
15
17
  SUPERSEDED
16
18
  } from "./search-fn-brand.js";
17
19
  import { buildIndex, probeExSearch } from "./search-index.js";
18
- keepDiagnostics(optionDiagnostics);
20
+ import { skippedColumnDiagnostics } from "./skipped-codes.js";
21
+ keepDiagnostics(optionDiagnostics, skippedColumnDiagnostics);
19
22
  var DEFAULT_WAIT = 300;
20
23
  var isDuration = (v) => {
21
24
  if (typeof v === "number") return Number.isFinite(v) && v >= 0;
@@ -95,13 +98,15 @@ var search = (opts = {}) => {
95
98
  throwConfigError("DK_INVALID_OPTION", {
96
99
  path: "searchFn",
97
100
  feature: "search",
98
- expected: "withSearchFn(searchFn)",
99
- received: "searchFn",
101
+ expected: "wrapped|withSearchFn|searchFn|search-async",
102
+ // prose key (diagnostics/prose.ts)
103
+ received: typeof opts.searchFn,
100
104
  suggestions: [],
101
105
  phase: "init"
102
106
  });
103
107
  }
104
108
  const instances = /* @__PURE__ */ new WeakMap();
109
+ const resumes = /* @__PURE__ */ new WeakMap();
105
110
  const ran = /* @__PURE__ */ new WeakMap();
106
111
  let waited = false;
107
112
  function indexFor(ctx) {
@@ -146,7 +151,7 @@ var search = (opts = {}) => {
146
151
  ctx.emit("DK_OPTION_NO_EFFECT", {
147
152
  path: "query",
148
153
  feature: "search",
149
- reason: "no column is searchable: pass `columns` (give the columns to search a `search` weight, or omit it everywhere to search every column)",
154
+ reason: "not-searchable",
150
155
  once: false
151
156
  });
152
157
  }
@@ -175,13 +180,18 @@ var search = (opts = {}) => {
175
180
  interceptors: [
176
181
  defineInterceptor({
177
182
  name: "search",
178
- on: ["table:destroy", "pipeline:run"],
183
+ on: ["table:destroy", "pipeline:run", "table:init", "state:dispatch", "table:setOptions"],
179
184
  intercept(ctx, next) {
180
185
  const table = ctx.table;
181
186
  if (ctx.op === "table:destroy") {
182
187
  instances.get(table)?.();
183
188
  return next();
184
189
  }
190
+ if (ctx.op !== "pipeline:run") {
191
+ const out = next();
192
+ resumes.get(table)?.();
193
+ return out;
194
+ }
185
195
  const prev = waited;
186
196
  waited = false;
187
197
  try {
@@ -250,7 +260,9 @@ var search = (opts = {}) => {
250
260
  input,
251
261
  claim
252
262
  });
263
+ let scheduled = false;
253
264
  const applyQuery = (query, inp = input()) => {
265
+ scheduled = false;
254
266
  if (session?.apply(query, inp)) return;
255
267
  if (held[0]?.query !== query) release(SUPERSEDED);
256
268
  held.push(...claim(query));
@@ -264,8 +276,25 @@ var search = (opts = {}) => {
264
276
  };
265
277
  const timer = strategy === "debounce" ? debouncedSignal(applied, apply, wait, { behavior: typing.behavior ?? "trailing" }) : strategy === "throttle" ? throttle(apply, wait) : void 0;
266
278
  const resting = () => session?.busy() ? "searching" : "idle";
267
- instances.set(table, () => {
279
+ resumes.set(table, () => {
280
+ const s = ctx.getState();
281
+ const pending = s?.status === "pending";
282
+ if (pending ? !scheduled : s?.status === "searching" && !session?.busy()) {
283
+ const inp = toText(s.input);
284
+ write(inp, pending && strategy !== "enter" ? target(inp) : toText(s.query), resting());
285
+ }
286
+ });
287
+ const drop = debounce(() => {
288
+ const list = waiting;
289
+ waiting = [];
290
+ for (const w of list) w.resolve(SUPERSEDED);
291
+ }, wait);
292
+ const stop = () => {
268
293
  timer?.cancel();
294
+ drop.cancel();
295
+ };
296
+ instances.set(table, () => {
297
+ stop();
269
298
  session?.destroy();
270
299
  supersede();
271
300
  });
@@ -279,7 +308,7 @@ var search = (opts = {}) => {
279
308
  return p;
280
309
  }
281
310
  if (!timer || query === current) {
282
- timer?.cancel();
311
+ stop();
283
312
  applyQuery(query, next);
284
313
  return p;
285
314
  }
@@ -296,13 +325,15 @@ var search = (opts = {}) => {
296
325
  }
297
326
  if (strategy === "debounce" && typing.behavior === "leading") {
298
327
  write(next, current, resting());
328
+ drop();
299
329
  return p;
300
330
  }
331
+ scheduled = true;
301
332
  write(next, current, "pending");
302
333
  return p;
303
334
  };
304
335
  const applyNow = (next) => {
305
- timer?.cancel();
336
+ stop();
306
337
  const query = target(next);
307
338
  replaceWaiting(query);
308
339
  const p = waitFor(query);
@@ -313,7 +344,8 @@ var search = (opts = {}) => {
313
344
  setSearch: (query) => commit(toText(query)),
314
345
  flushSearch: () => applyNow(input()),
315
346
  cancelSearch: () => {
316
- timer?.cancel();
347
+ scheduled = false;
348
+ stop();
317
349
  session?.abort();
318
350
  supersede();
319
351
  const s = ctx.getState();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/search/search.ts"],"sourcesContent":["import { debouncedSignal } from 'kk-debounce/debounceSignal'\nimport { throttle } from 'kk-debounce/throttle'\nimport { optionDiagnostics } from '../../catalog/codes'\nimport { defineOptions } from '../../catalog/options'\nimport { throwConfigError } from '../../diagnostics/build'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { defineFeature } from '../../feature/define'\nimport type { AnyRow, RowModelContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { runJob, sliced } from '../../pipeline/slice'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport {\n APPLIED,\n type AsyncSearchFn,\n SEARCH_ENGINE,\n type SearchEngine,\n type SearchOutcome,\n type SearchSession,\n type SearchState,\n type SearchStatus,\n SUPERSEDED,\n type Waiter,\n} from './search-fn-brand'\nimport { buildIndex, type MatchConfig, probeExSearch, type SearchIndex } from './search-index'\n\nkeepDiagnostics(optionDiagnostics)\n\nexport type {\n AsyncSearchFn,\n SearchFn,\n SearchFnContext,\n SearchOutcome,\n SearchResult,\n SearchState,\n SearchStatus,\n} from './search-fn-brand'\n\nexport interface SearchApi {\n /** Sets `input` (and `query`, with the client's immediate typing strategy). */\n setSearch(query: string): Promise<SearchOutcome>\n /** Applies the current `input` now (Enter). */\n flushSearch(): Promise<SearchOutcome>\n /** Drops a pending (debounced) query; `input` and the applied `query` stay. */\n cancelSearch(): void\n /** Empties `input` and `query`: every row is shown again. */\n clearSearch(): void\n}\n\n/** A duration: milliseconds, or a kk-debounce temporal object (`{ seconds: 1, ms: 500 }`). */\nexport type SearchWait =\n | number\n | { hours?: number; minutes?: number; seconds?: number; ms?: number }\n\n/** When `query` follows `input` (§6.3). */\nexport interface SearchTyping {\n /**\n * `'immediate'` (default): every keystroke is applied. `'debounce'`: applied after `wait` of\n * quiet (kk-debounce `debouncedSignal`). `'throttle'`: at most once per `wait` (the first\n * keystroke at once, the latest one at the end of the window).\n */\n strategy?: 'immediate' | 'debounce' | 'throttle'\n /** Default 300 ms. Used by `'debounce'` and `'throttle'`. */\n wait?: SearchWait\n /**\n * Debounce only. `'trailing'` (default): apply after the burst. `'leading'`: apply the first\n * keystroke at once and drop the rest of the burst (`flushSearch()` applies them).\n */\n behavior?: 'trailing' | 'leading'\n /** A trimmed input shorter than this is applied as an empty query (all rows). Default 0. */\n minLength?: number\n /** `'type'` (default): typing commits. `'enter'`: only `flushSearch()` commits. */\n commitOn?: 'type' | 'enter'\n}\n\n/** Client matching options (§6.3). */\nexport interface SearchOptions<T = AnyRow> {\n /** Minimum relevance (0–1, weight applied) to keep a row. Default `0.3`. */\n threshold?: number\n /** Maximum Levenshtein distance for a fuzzy match. Default `3`. */\n fuzzyMaxDistance?: number\n /** Default `false`. */\n caseSensitive?: boolean\n /**\n * Typing timing: when `query` follows `input`. Default: immediate; with `mode: 'server'`,\n * debounce 300 ms and `minLength` 2 (§6.3).\n */\n typing?: SearchTyping\n /**\n * `'server'`: the server matches `query` (its state goes to `options.fetcher`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n /**\n * Replaces the built-in matching (§6.3): called with each applied non-blank query; while it runs\n * `status` is `'searching'` and the previous results stay. A failure keeps them too\n * (`DK_SEARCH_FAILED`, `status: 'error'`); an `AbortError` is not a failure. New data runs it\n * again for the applied query. Wrap it with `withSearchFn()` from `@datakable/core/search-async`\n * (client search does not bundle the async engine): `searchFn: withSearchFn(fn)`.\n */\n searchFn?: AsyncSearchFn<T>\n}\n\nconst DEFAULT_WAIT = 300\n\nconst isDuration = (v: unknown): boolean => {\n if (typeof v === 'number') return Number.isFinite(v) && v >= 0\n for (const k of ['hours', 'minutes', 'seconds', 'ms'] as const) {\n const n = (v as Record<string, unknown>)[k]\n if (n !== undefined && !(typeof n === 'number' && Number.isFinite(n) && n >= 0)) return false\n }\n return true\n}\n\nconst typingOptions = {\n strategy: { type: 'string', oneOf: ['immediate', 'debounce', 'throttle'] },\n wait: {\n type: ['number', 'object'],\n check: isDuration,\n expected: 'milliseconds >= 0 or { hours?, minutes?, seconds?, ms? } with values >= 0',\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.commitOn === 'enter'\n ? \"commitOn is 'enter': only flushSearch() commits\"\n : o.strategy !== 'debounce' && o.strategy !== 'throttle'\n ? \"only the 'debounce' and 'throttle' strategies wait\"\n : undefined,\n },\n behavior: {\n type: 'string',\n oneOf: ['trailing', 'leading'],\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.commitOn === 'enter'\n ? \"commitOn is 'enter': only flushSearch() commits\"\n : o.strategy !== 'debounce'\n ? \"behavior applies to the 'debounce' strategy only\"\n : undefined,\n },\n minLength: {\n type: 'number',\n check: (v: number) => Number.isInteger(v) && v >= 0,\n expected: 'a non-negative integer',\n },\n commitOn: { type: 'string', oneOf: ['type', 'enter'] },\n} as const\n\n/** Built-in matching options do nothing when `searchFn` matches. */\nconst matching = (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.searchFn === undefined ? undefined : 'searchFn replaces the built-in matching'\n\nconst searchOptions = {\n threshold: {\n type: 'number',\n check: (v: number) => v >= 0 && v <= 1,\n expected: 'a number from 0 to 1',\n noEffect: matching,\n },\n fuzzyMaxDistance: {\n type: 'number',\n check: (v: number) => Number.isInteger(v) && v >= 0,\n expected: 'a non-negative integer',\n noEffect: matching,\n },\n caseSensitive: { type: 'boolean', noEffect: matching },\n typing: { type: 'object', shape: typingOptions },\n mode: modeOption,\n searchFn: {\n type: 'function',\n check: (v: object) => typeof (v as { [SEARCH_ENGINE]?: unknown })[SEARCH_ENGINE] === 'function',\n expected:\n \"a function wrapped with withSearchFn() from '@datakable/core/search-async': \" +\n 'searchFn: withSearchFn(fn)',\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.mode === 'server'\n ? \"mode is 'server': the server (options.fetcher, or the app) matches the query\"\n : undefined,\n },\n} as const\n\nconst settledSuperseded = () => Promise.resolve(SUPERSEDED)\n\nconst toText = (q: unknown): string => (q == null ? '' : String(q))\n\n/**\n * Global search (§6.3), client mode: fuzzy matching with `ex-search` on the formatted values of the\n * searchable columns (`search: weight`; every column at weight 1 when none declares one).\n *\n * - The matcher is built on the first non-blank query, once per data + columns (not per keystroke),\n * over the column cache's values; ex-search reads the projection `{ i, c0, c1, … }` one chunk of\n * rows at a time, built while matching and not kept (§9.3 memory); results map back by index.\n * - A blank or whitespace-only query keeps every row. The query is trimmed before matching.\n * - Rows are ordered by relevance (score descending, ties by original index); a sort stage after\n * search overrides that order.\n */\nexport const search = <T = AnyRow>(opts: SearchOptions<T> = {}) => {\n const config: MatchConfig = {\n threshold: opts.threshold ?? 0.3,\n fuzzyMaxDistance: opts.fuzzyMaxDistance ?? 3,\n caseSensitive: opts.caseSensitive ?? false,\n }\n /**\n * data → columns → the key of the projection job (`ctx.slice`, §9.1): its result is the index\n * (`null`: nothing searchable). Released with the data.\n */\n const indexes = new WeakMap<object, WeakMap<object, object>>()\n /** Job key → its finished index, also for runs without slicing (`explainRow` replays). */\n const built = new WeakMap<object, SearchIndex | null>()\n /** `ex-search` usable (probed once per feature instance, on the first build). */\n let useExSearch: boolean | undefined\n /** Columns references already reported as having no searchable column. */\n const reported = new WeakSet<object>()\n /** The last step: a slice change that keeps the query (typing, status) reuses its output. */\n let last:\n | { input: Uint32Array; query: string; index: SearchIndex; output: Uint32Array }\n | undefined\n /** The key of the latest matching job (one query over one set of rows). */\n let matching: { rows: Uint32Array; index: SearchIndex; query: string; key: object } | undefined\n /** The step's last output and its input: the stand-in while sliced work runs (§9.1). */\n let shown: { input: Uint32Array; output: Uint32Array } | undefined\n const server = opts.mode === 'server'\n // Server defaults (§6.3): debounce, minLength 2. Filled in before the option check, so `wait` /\n // `behavior` alone are not reported as having no effect.\n const typing: SearchTyping = server\n ? { strategy: 'debounce', minLength: 2, ...opts.typing }\n : (opts.typing ?? {})\n const strategy = typing.commitOn === 'enter' ? 'enter' : (typing.strategy ?? 'immediate')\n const wait = typing.wait ?? DEFAULT_WAIT\n const minLength = typing.minLength ?? 0\n /** The query an input applies (a shorter trimmed input is no search). */\n const target = (input: string) => (input.trim().length < minLength ? '' : input)\n /** The async engine a `withSearchFn` searchFn brings (§6.3); `undefined` for client search. */\n const engine: SearchEngine | undefined = opts.searchFn?.[SEARCH_ENGINE]?.(opts.searchFn as never)\n // Never silent (§8.6): in production (no option validator) an unwrapped searchFn would be\n // ignored; in development the validator reports it at createTable(), with the full text.\n if (!(process.env.NODE_ENV !== \"production\") && opts.searchFn && !engine) {\n throwConfigError('DK_INVALID_OPTION', {\n path: 'searchFn',\n feature: 'search',\n expected: 'withSearchFn(searchFn)',\n received: 'searchFn',\n suggestions: [],\n phase: 'init',\n })\n }\n /** Per table: ends its timers, searches and waiters on `table:destroy`. */\n const instances = new WeakMap<object, () => void>()\n /** Per table: after each `pipeline:run`, whether the step waited on sliced work (#6). */\n const ran = new WeakMap<object, (waited: boolean) => void>()\n /** The step of the run in progress returned a stand-in (sliced work runs, §9.1). */\n let waited = false\n\n /** The projection of (data, columns): built by a sliced job; `undefined` while it runs. */\n function indexFor(ctx: RowModelContext): SearchIndex | null | undefined {\n let byColumns = indexes.get(ctx.data)\n if (!byColumns) {\n byColumns = new WeakMap()\n indexes.set(ctx.data, byColumns)\n }\n let key = byColumns.get(ctx.columns)\n if (!key) {\n key = {}\n byColumns.set(ctx.columns, key)\n }\n const done = built.get(key)\n if (done !== undefined) return done\n useExSearch ??= probeExSearch(ctx.emit)\n const exSearch = useExSearch\n const index = sliced(ctx, key, () => buildIndex(ctx, config, exSearch))\n if (index !== undefined) built.set(key, index)\n return index\n }\n\n /** The ranked matches of `query` over `rows`: a sliced job; `undefined` while it runs. */\n function matchFor(\n ctx: RowModelContext,\n index: SearchIndex,\n rows: Uint32Array,\n query: string,\n ): Uint32Array | undefined {\n // A replay (`explainRow`) matches its one row at once, leaving the table's job alone.\n if (!ctx.slice) return runJob(() => index.match(rows, query))\n if (\n !matching ||\n matching.rows !== rows ||\n matching.index !== index ||\n matching.query !== query\n ) {\n matching = { rows, index, query, key: {} }\n }\n return sliced(ctx, matching.key, () => index.match(rows, query))\n }\n\n /** While sliced work runs: the previous output for the same input, else the input. */\n const standIn = (indices: Uint32Array) => {\n waited = true\n return shown && shown.input === indices ? shown.output : indices\n }\n\n /** The client step (§6.3); the rowModel below records its output for {@link standIn}. */\n function step(indices: Uint32Array, state: SearchState, ctx: RowModelContext): Uint32Array {\n const query = typeof state?.query === 'string' ? state.query.trim() : ''\n // ex-search returns [] for a blank query: blank means \"no search\".\n if (query === '') return indices\n if (engine) return engine.step(indices, ctx)\n const index = indexFor(ctx)\n // The projection is still being built (time-sliced, §9.1): the row model is 'pending'.\n if (index === undefined) return standIn(indices)\n if (index === null) {\n if (!reported.has(ctx.columns)) {\n reported.add(ctx.columns)\n ctx.emit('DK_OPTION_NO_EFFECT', {\n path: 'query',\n feature: 'search',\n reason:\n 'no column is searchable: pass `columns` (give the columns to search a `search` ' +\n 'weight, or omit it everywhere to search every column)',\n once: false,\n })\n }\n return indices\n }\n if (last && last.input === indices && last.query === query && last.index === index) {\n return last.output\n }\n // Search runs after filters: only the input rows are matched (order is re-ranked anyway).\n const output = matchFor(ctx, index, indices, query)\n if (output === undefined) return standIn(indices)\n last = { input: indices, query, index, output }\n return output\n }\n\n return defineFeature({\n name: 'search',\n stage: 'search',\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\") ? searchOptions : ({} as typeof searchOptions),\n server ? { ...opts, typing } : opts,\n ),\n initialState: (): SearchState => ({ input: '', query: '', status: 'idle' }),\n // After destroy(): still a promise, so `setSearch(q).then(…)` never throws.\n destroyedResult: { setSearch: settledSuperseded, flushSearch: settledSuperseded },\n interceptors: [\n defineInterceptor({\n name: 'search',\n on: ['table:destroy', 'pipeline:run'],\n intercept(ctx, next) {\n const table = ctx.table as object\n if (ctx.op === 'table:destroy') {\n instances.get(table)?.()\n return next()\n }\n const prev = waited\n waited = false\n try {\n return engine ? engine.run(table, next) : next()\n } finally {\n const w = waited\n waited = prev\n ran.get(table)?.(w)\n }\n },\n }),\n ],\n createApi: (ctx): SearchApi => {\n const table = ctx.table as unknown as object\n /** Writes the slice; `true` when the state changed (a pipeline run follows). */\n const write = (input: string, query: string, status: SearchStatus, error?: unknown) => {\n const s = ctx.getState()\n if (\n status !== 'error' &&\n s?.input === input &&\n s.query === query &&\n s.status === status &&\n !('error' in s)\n ) {\n return false\n }\n ctx.setState(\n status === 'error' ? { input, query, status, error } : { input, query, status },\n )\n return ctx.getState() !== s\n }\n const applied = () => ctx.getState()?.query ?? ''\n const input = () => ctx.getState()?.input ?? ''\n\n // --- promise outcomes (§6.3): resolved when the query is applied, replaced, or failed ---\n /** Waiters of queries not handed to a search yet (typing, pending debounce, Enter mode). */\n let waiting: Waiter[] = []\n const waitFor = (query: string) =>\n new Promise<SearchOutcome>((resolve) => {\n waiting.push({ query, resolve })\n })\n /** A newer query replaces the waiting ones that differ from it. */\n const replaceWaiting = (query: string) => {\n if (waiting.length === 0) return\n const keep: Waiter[] = []\n for (const w of waiting) {\n if (w.query === query) keep.push(w)\n else w.resolve(SUPERSEDED)\n }\n waiting = keep\n }\n\n /** Takes the waiters of `query`; every other waiting one resolves `'superseded'`. */\n const claim = (query: string) => {\n replaceWaiting(query)\n const keep = waiting\n waiting = []\n return keep\n }\n /**\n * Waiters of the applied query until the row model reflects it (#6): sliced work (§9.1)\n * keeps the row model 'pending' after the query is written.\n */\n let held: Waiter[] = []\n /** The last run's step waited on sliced work. */\n let busy = false\n const release = (outcome: SearchOutcome) => {\n const list = held\n held = []\n for (const w of list) w.resolve(outcome)\n }\n /** Cancel, destroy: every waiter, waiting then held, resolves `'superseded'`. */\n const supersede = () => {\n held = [...waiting, ...held]\n waiting = []\n release(SUPERSEDED)\n }\n ran.set(table, (w) => {\n busy = w\n if (!w) release(APPLIED)\n })\n const session: SearchSession | undefined = engine?.session({\n ctx,\n write,\n applied,\n input,\n claim,\n })\n\n /**\n * Applies `query` (typing committed it, Enter, clear): at once, or with `searchFn` through a\n * request (the slice shows `'searching'`).\n */\n const applyQuery = (query: string, inp: string = input()) => {\n if (session?.apply(query, inp)) return\n if (held[0]?.query !== query) release(SUPERSEDED)\n held.push(...claim(query))\n // A changed state runs the pipeline (its end releases them); else the last run decides.\n if (!write(inp, query, 'idle') && !busy) release(APPLIED)\n }\n\n // --- typing (T17) ---\n /** Inside a strategy call: a synchronous apply is collected here (one update, not two). */\n let calling = false\n let appliedNow: string | undefined\n const apply = (query: string) => {\n if (calling) appliedNow = query\n else applyQuery(query)\n }\n const timer =\n strategy === 'debounce'\n ? debouncedSignal(applied, apply, wait, { behavior: typing.behavior ?? 'trailing' })\n : strategy === 'throttle'\n ? throttle(apply, wait)\n : undefined\n /** The slice status while nothing new is applied: a search may still be in flight. */\n const resting = (): SearchStatus => (session?.busy() ? 'searching' : 'idle')\n\n instances.set(table, () => {\n timer?.cancel()\n session?.destroy()\n supersede()\n })\n\n /** Typing: `input` at once; `query` through the strategy (`pending` while scheduled). */\n const commit = (next: string): Promise<SearchOutcome> => {\n const current = applied()\n const query = target(next)\n replaceWaiting(query)\n const p = waitFor(query)\n if (strategy === 'enter') {\n write(next, current, resting())\n return p\n }\n if (!timer || query === current) {\n timer?.cancel()\n applyQuery(query, next)\n return p\n }\n calling = true\n appliedNow = undefined\n try {\n timer(query)\n } finally {\n calling = false\n }\n if (appliedNow !== undefined) {\n applyQuery(appliedNow, next)\n return p\n }\n // A leading debounce drops the rest of the burst: nothing is scheduled to commit it (the\n // silence window keeps running; `flushSearch()` commits the input).\n if (strategy === 'debounce' && typing.behavior === 'leading') {\n write(next, current, resting())\n return p\n }\n write(next, current, 'pending')\n return p\n }\n /** Applies `input` now (Enter, clear): any scheduled commit is dropped. */\n const applyNow = (next: string): Promise<SearchOutcome> => {\n timer?.cancel()\n const query = target(next)\n replaceWaiting(query)\n const p = waitFor(query)\n applyQuery(query, next)\n return p\n }\n return {\n setSearch: (query) => commit(toText(query)),\n flushSearch: () => applyNow(input()),\n cancelSearch: () => {\n timer?.cancel()\n session?.abort()\n supersede()\n const s = ctx.getState()\n if (s?.status === 'pending' || s?.status === 'searching') {\n ctx.setState({ ...s, status: 'idle' })\n }\n },\n clearSearch: () => {\n void applyNow('')\n },\n }\n },\n rowModel: (indices, state, ctx) => {\n const output = step(indices, state, ctx)\n shown = { input: indices, output }\n return output\n },\n })\n}\n"],"mappings":"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","names":["matching","input"]}
1
+ {"version":3,"sources":["../../../src/features/search/search.ts"],"sourcesContent":["import { debounce } from 'kk-debounce/debounce'\nimport { debouncedSignal } from 'kk-debounce/debounceSignal'\nimport { throttle } from 'kk-debounce/throttle'\nimport { optionDiagnostics } from '../../catalog/codes'\nimport { defineOptions } from '../../catalog/options'\nimport { throwConfigError } from '../../diagnostics/build'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { defineFeature } from '../../feature/define'\nimport type { AnyRow, RowModelContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { runJob, sliced } from '../../pipeline/slice'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport {\n APPLIED,\n type AsyncSearchFn,\n SEARCH_ENGINE,\n type SearchEngine,\n type SearchOutcome,\n type SearchSession,\n type SearchState,\n type SearchStatus,\n SUPERSEDED,\n type Waiter,\n} from './search-fn-brand'\nimport { buildIndex, type MatchConfig, probeExSearch, type SearchIndex } from './search-index'\nimport { skippedColumnDiagnostics } from './skipped-codes'\n\nkeepDiagnostics(optionDiagnostics, skippedColumnDiagnostics)\n\nexport type {\n AsyncSearchFn,\n SearchFn,\n SearchFnContext,\n SearchOutcome,\n SearchResult,\n SearchState,\n SearchStatus,\n} from './search-fn-brand'\n\nexport interface SearchApi {\n /** Sets `input` (and `query`, with the client's immediate typing strategy). */\n setSearch(query: string): Promise<SearchOutcome>\n /** Applies the current `input` now (Enter). */\n flushSearch(): Promise<SearchOutcome>\n /** Drops a pending (debounced) query; `input` and the applied `query` stay. */\n cancelSearch(): void\n /** Empties `input` and `query`: every row is shown again. */\n clearSearch(): void\n}\n\n/** A duration: milliseconds, or a kk-debounce temporal object (`{ seconds: 1, ms: 500 }`). */\nexport type SearchWait =\n | number\n | { hours?: number; minutes?: number; seconds?: number; ms?: number }\n\n/** When `query` follows `input` (§6.3). */\nexport interface SearchTyping {\n /**\n * `'immediate'` (default): every keystroke is applied. `'debounce'`: applied after `wait` of\n * quiet (kk-debounce `debouncedSignal`). `'throttle'`: at most once per `wait` (the first\n * keystroke at once, the latest one at the end of the window).\n */\n strategy?: 'immediate' | 'debounce' | 'throttle'\n /** Default 300 ms. Used by `'debounce'` and `'throttle'`. */\n wait?: SearchWait\n /**\n * Debounce only. `'trailing'` (default): apply after the burst. `'leading'`: apply the first\n * keystroke at once and drop the rest of the burst (`flushSearch()` applies them; otherwise\n * their `setSearch()` promises resolve `'superseded'` when the silence window ends).\n */\n behavior?: 'trailing' | 'leading'\n /** A trimmed input shorter than this is applied as an empty query (all rows). Default 0. */\n minLength?: number\n /** `'type'` (default): typing commits. `'enter'`: only `flushSearch()` commits. */\n commitOn?: 'type' | 'enter'\n}\n\n/** Client matching options (§6.3). */\nexport interface SearchOptions<T = AnyRow> {\n /** Minimum relevance (0–1, weight applied) to keep a row. Default `0.3`. */\n threshold?: number\n /** Maximum Levenshtein distance for a fuzzy match. Default `3`. */\n fuzzyMaxDistance?: number\n /** Default `false`. */\n caseSensitive?: boolean\n /**\n * Typing timing: when `query` follows `input`. Default: immediate; with `mode: 'server'`,\n * debounce 300 ms and `minLength` 2 (§6.3).\n */\n typing?: SearchTyping\n /**\n * `'server'`: the server matches `query` (its state goes to `options.fetcher`, or the app\n * fetches); core skips this step (§7.1). Default `'client'`.\n */\n mode?: DataMode\n /**\n * Replaces the built-in matching (§6.3): called with each applied non-blank query; while it runs\n * `status` is `'searching'` and the previous results stay. A failure keeps them too\n * (`DK_SEARCH_FAILED`, `status: 'error'`); an `AbortError` is not a failure. New data runs it\n * again for the applied query. Wrap it with `withSearchFn()` from `@datakable/core/search-async`\n * (client search does not bundle the async engine): `searchFn: withSearchFn(fn)`.\n */\n searchFn?: AsyncSearchFn<T>\n}\n\nconst DEFAULT_WAIT = 300\n\nconst isDuration = (v: unknown): boolean => {\n if (typeof v === 'number') return Number.isFinite(v) && v >= 0\n for (const k of ['hours', 'minutes', 'seconds', 'ms'] as const) {\n const n = (v as Record<string, unknown>)[k]\n if (n !== undefined && !(typeof n === 'number' && Number.isFinite(n) && n >= 0)) return false\n }\n return true\n}\n\nconst typingOptions = {\n strategy: { type: 'string', oneOf: ['immediate', 'debounce', 'throttle'] },\n wait: {\n type: ['number', 'object'],\n check: isDuration,\n expected: 'milliseconds >= 0 or { hours?, minutes?, seconds?, ms? } with values >= 0',\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.commitOn === 'enter'\n ? \"commitOn is 'enter': only flushSearch() commits\"\n : o.strategy !== 'debounce' && o.strategy !== 'throttle'\n ? \"only the 'debounce' and 'throttle' strategies wait\"\n : undefined,\n },\n behavior: {\n type: 'string',\n oneOf: ['trailing', 'leading'],\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.commitOn === 'enter'\n ? \"commitOn is 'enter': only flushSearch() commits\"\n : o.strategy !== 'debounce'\n ? \"behavior applies to the 'debounce' strategy only\"\n : undefined,\n },\n minLength: {\n type: 'number',\n check: (v: number) => Number.isInteger(v) && v >= 0,\n expected: 'a non-negative integer',\n },\n commitOn: { type: 'string', oneOf: ['type', 'enter'] },\n} as const\n\n/** Built-in matching options do nothing when `searchFn` matches. */\nconst matching = (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.searchFn === undefined ? undefined : 'searchFn replaces the built-in matching'\n\nconst searchOptions = {\n threshold: {\n type: 'number',\n check: (v: number) => v >= 0 && v <= 1,\n expected: 'a number from 0 to 1',\n noEffect: matching,\n },\n fuzzyMaxDistance: {\n type: 'number',\n check: (v: number) => Number.isInteger(v) && v >= 0,\n expected: 'a non-negative integer',\n noEffect: matching,\n },\n caseSensitive: { type: 'boolean', noEffect: matching },\n typing: { type: 'object', shape: typingOptions },\n mode: modeOption,\n searchFn: {\n type: 'function',\n check: (v: object) => typeof (v as { [SEARCH_ENGINE]?: unknown })[SEARCH_ENGINE] === 'function',\n expected:\n \"a function wrapped with withSearchFn() from '@datakable/core/search-async': \" +\n 'searchFn: withSearchFn(fn)',\n noEffect: (_: unknown, o: Readonly<Record<string, unknown>>) =>\n o.mode === 'server'\n ? \"mode is 'server': the server (options.fetcher, or the app) matches the query\"\n : undefined,\n },\n} as const\n\nconst settledSuperseded = () => Promise.resolve(SUPERSEDED)\n\nconst toText = (q: unknown): string => (q == null ? '' : String(q))\n\n/**\n * Global search (§6.3), client mode: fuzzy matching with `ex-search` on the formatted values of the\n * searchable columns (`search: weight`; when none declares one, every column of primitives at\n * weight 1, #27).\n *\n * - The matcher is built on the first non-blank query, once per data + columns (not per keystroke),\n * over the column cache's values; ex-search reads the projection `{ i, c0, c1, … }` one chunk of\n * rows at a time, built while matching and not kept (§9.3 memory); results map back by index.\n * - A blank or whitespace-only query keeps every row. The query is trimmed before matching.\n * - Rows are ordered by relevance (score descending, ties by original index); a sort stage after\n * search overrides that order.\n */\nexport const search = <T = AnyRow>(opts: SearchOptions<T> = {}) => {\n const config: MatchConfig = {\n threshold: opts.threshold ?? 0.3,\n fuzzyMaxDistance: opts.fuzzyMaxDistance ?? 3,\n caseSensitive: opts.caseSensitive ?? false,\n }\n /**\n * data → columns → the key of the projection job (`ctx.slice`, §9.1): its result is the index\n * (`null`: nothing searchable). Released with the data.\n */\n const indexes = new WeakMap<object, WeakMap<object, object>>()\n /** Job key → its finished index, also for runs without slicing (`explainRow` replays). */\n const built = new WeakMap<object, SearchIndex | null>()\n /** `ex-search` usable (probed once per feature instance, on the first build). */\n let useExSearch: boolean | undefined\n /** Columns references already reported as having no searchable column. */\n const reported = new WeakSet<object>()\n /** The last step: a slice change that keeps the query (typing, status) reuses its output. */\n let last:\n | { input: Uint32Array; query: string; index: SearchIndex; output: Uint32Array }\n | undefined\n /** The key of the latest matching job (one query over one set of rows). */\n let matching: { rows: Uint32Array; index: SearchIndex; query: string; key: object } | undefined\n /** The step's last output and its input: the stand-in while sliced work runs (§9.1). */\n let shown: { input: Uint32Array; output: Uint32Array } | undefined\n const server = opts.mode === 'server'\n // Server defaults (§6.3): debounce, minLength 2. Filled in before the option check, so `wait` /\n // `behavior` alone are not reported as having no effect.\n const typing: SearchTyping = server\n ? { strategy: 'debounce', minLength: 2, ...opts.typing }\n : (opts.typing ?? {})\n const strategy = typing.commitOn === 'enter' ? 'enter' : (typing.strategy ?? 'immediate')\n const wait = typing.wait ?? DEFAULT_WAIT\n const minLength = typing.minLength ?? 0\n /** The query an input applies (a shorter trimmed input is no search). */\n const target = (input: string) => (input.trim().length < minLength ? '' : input)\n /** The async engine a `withSearchFn` searchFn brings (§6.3); `undefined` for client search. */\n const engine: SearchEngine | undefined = opts.searchFn?.[SEARCH_ENGINE]?.(opts.searchFn as never)\n // Never silent (§8.6): in production (no option validator) an unwrapped searchFn would be\n // ignored; in development the validator reports it at createTable(), with the full text.\n if (!(process.env.NODE_ENV !== \"production\") && opts.searchFn && !engine) {\n throwConfigError('DK_INVALID_OPTION', {\n path: 'searchFn',\n feature: 'search',\n expected: 'wrapped|withSearchFn|searchFn|search-async', // prose key (diagnostics/prose.ts)\n received: typeof opts.searchFn,\n suggestions: [],\n phase: 'init',\n })\n }\n /** Per table: ends its timers, searches and waiters on `table:destroy`. */\n const instances = new WeakMap<object, () => void>()\n /** Per table: finishes a restored `'pending'` / `'searching'` slice nothing works on (#22). */\n const resumes = new WeakMap<object, () => void>()\n /** Per table: after each `pipeline:run`, whether the step waited on sliced work (#6). */\n const ran = new WeakMap<object, (waited: boolean) => void>()\n /** The step of the run in progress returned a stand-in (sliced work runs, §9.1). */\n let waited = false\n\n /** The projection of (data, columns): built by a sliced job; `undefined` while it runs. */\n function indexFor(ctx: RowModelContext): SearchIndex | null | undefined {\n let byColumns = indexes.get(ctx.data)\n if (!byColumns) {\n byColumns = new WeakMap()\n indexes.set(ctx.data, byColumns)\n }\n let key = byColumns.get(ctx.columns)\n if (!key) {\n key = {}\n byColumns.set(ctx.columns, key)\n }\n const done = built.get(key)\n if (done !== undefined) return done\n useExSearch ??= probeExSearch(ctx.emit)\n const exSearch = useExSearch\n const index = sliced(ctx, key, () => buildIndex(ctx, config, exSearch))\n if (index !== undefined) built.set(key, index)\n return index\n }\n\n /** The ranked matches of `query` over `rows`: a sliced job; `undefined` while it runs. */\n function matchFor(\n ctx: RowModelContext,\n index: SearchIndex,\n rows: Uint32Array,\n query: string,\n ): Uint32Array | undefined {\n // A replay (`explainRow`) matches its one row at once, leaving the table's job alone.\n if (!ctx.slice) return runJob(() => index.match(rows, query))\n if (\n !matching ||\n matching.rows !== rows ||\n matching.index !== index ||\n matching.query !== query\n ) {\n matching = { rows, index, query, key: {} }\n }\n return sliced(ctx, matching.key, () => index.match(rows, query))\n }\n\n /** While sliced work runs: the previous output for the same input, else the input. */\n const standIn = (indices: Uint32Array) => {\n waited = true\n return shown && shown.input === indices ? shown.output : indices\n }\n\n /** The client step (§6.3); the rowModel below records its output for {@link standIn}. */\n function step(indices: Uint32Array, state: SearchState, ctx: RowModelContext): Uint32Array {\n const query = typeof state?.query === 'string' ? state.query.trim() : ''\n // ex-search returns [] for a blank query: blank means \"no search\".\n if (query === '') return indices\n if (engine) return engine.step(indices, ctx)\n const index = indexFor(ctx)\n // The projection is still being built (time-sliced, §9.1): the row model is 'pending'.\n if (index === undefined) return standIn(indices)\n if (index === null) {\n if (!reported.has(ctx.columns)) {\n reported.add(ctx.columns)\n ctx.emit('DK_OPTION_NO_EFFECT', {\n path: 'query',\n feature: 'search',\n reason: 'not-searchable',\n once: false,\n })\n }\n return indices\n }\n if (last && last.input === indices && last.query === query && last.index === index) {\n return last.output\n }\n // Search runs after filters: only the input rows are matched (order is re-ranked anyway).\n const output = matchFor(ctx, index, indices, query)\n if (output === undefined) return standIn(indices)\n last = { input: indices, query, index, output }\n return output\n }\n\n return defineFeature({\n name: 'search',\n stage: 'search',\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\") ? searchOptions : ({} as typeof searchOptions),\n server ? { ...opts, typing } : opts,\n ),\n initialState: (): SearchState => ({ input: '', query: '', status: 'idle' }),\n // After destroy(): still a promise, so `setSearch(q).then(…)` never throws.\n destroyedResult: { setSearch: settledSuperseded, flushSearch: settledSuperseded },\n interceptors: [\n defineInterceptor({\n name: 'search',\n on: ['table:destroy', 'pipeline:run', 'table:init', 'state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const table = ctx.table as object\n if (ctx.op === 'table:destroy') {\n instances.get(table)?.()\n return next()\n }\n if (ctx.op !== 'pipeline:run') {\n // A slice set from outside (initialState, setState, controlled state) is checked.\n const out = next()\n resumes.get(table)?.()\n return out\n }\n const prev = waited\n waited = false\n try {\n return engine ? engine.run(table, next) : next()\n } finally {\n const w = waited\n waited = prev\n ran.get(table)?.(w)\n }\n },\n }),\n ],\n createApi: (ctx): SearchApi => {\n const table = ctx.table as unknown as object\n /** Writes the slice; `true` when the state changed (a pipeline run follows). */\n const write = (input: string, query: string, status: SearchStatus, error?: unknown) => {\n const s = ctx.getState()\n if (\n status !== 'error' &&\n s?.input === input &&\n s.query === query &&\n s.status === status &&\n !('error' in s)\n ) {\n return false\n }\n ctx.setState(\n status === 'error' ? { input, query, status, error } : { input, query, status },\n )\n return ctx.getState() !== s\n }\n const applied = () => ctx.getState()?.query ?? ''\n const input = () => ctx.getState()?.input ?? ''\n\n // --- promise outcomes (§6.3): resolved when the query is applied, replaced, or failed ---\n /** Waiters of queries not handed to a search yet (typing, pending debounce, Enter mode). */\n let waiting: Waiter[] = []\n const waitFor = (query: string) =>\n new Promise<SearchOutcome>((resolve) => {\n waiting.push({ query, resolve })\n })\n /** A newer query replaces the waiting ones that differ from it. */\n const replaceWaiting = (query: string) => {\n if (waiting.length === 0) return\n const keep: Waiter[] = []\n for (const w of waiting) {\n if (w.query === query) keep.push(w)\n else w.resolve(SUPERSEDED)\n }\n waiting = keep\n }\n\n /** Takes the waiters of `query`; every other waiting one resolves `'superseded'`. */\n const claim = (query: string) => {\n replaceWaiting(query)\n const keep = waiting\n waiting = []\n return keep\n }\n /**\n * Waiters of the applied query until the row model reflects it (#6): sliced work (§9.1)\n * keeps the row model 'pending' after the query is written.\n */\n let held: Waiter[] = []\n /** The last run's step waited on sliced work. */\n let busy = false\n const release = (outcome: SearchOutcome) => {\n const list = held\n held = []\n for (const w of list) w.resolve(outcome)\n }\n /** Cancel, destroy: every waiter, waiting then held, resolves `'superseded'`. */\n const supersede = () => {\n held = [...waiting, ...held]\n waiting = []\n release(SUPERSEDED)\n }\n ran.set(table, (w) => {\n busy = w\n if (!w) release(APPLIED)\n })\n const session: SearchSession | undefined = engine?.session({\n ctx,\n write,\n applied,\n input,\n claim,\n })\n\n /** A typing commit waits on the strategy's timer (the slice shows `'pending'`). */\n let scheduled = false\n /**\n * Applies `query` (typing committed it, Enter, clear): at once, or with `searchFn` through a\n * request (the slice shows `'searching'`).\n */\n const applyQuery = (query: string, inp: string = input()) => {\n scheduled = false\n if (session?.apply(query, inp)) return\n if (held[0]?.query !== query) release(SUPERSEDED)\n held.push(...claim(query))\n // A changed state runs the pipeline (its end releases them); else the last run decides.\n if (!write(inp, query, 'idle') && !busy) release(APPLIED)\n }\n\n // --- typing (T17) ---\n /** Inside a strategy call: a synchronous apply is collected here (one update, not two). */\n let calling = false\n let appliedNow: string | undefined\n const apply = (query: string) => {\n if (calling) appliedNow = query\n else applyQuery(query)\n }\n const timer =\n strategy === 'debounce'\n ? debouncedSignal(applied, apply, wait, { behavior: typing.behavior ?? 'trailing' })\n : strategy === 'throttle'\n ? throttle(apply, wait)\n : undefined\n /** The slice status while nothing new is applied: a search may still be in flight. */\n const resting = (): SearchStatus => (session?.busy() ? 'searching' : 'idle')\n // A restored transient status (URL / storage restore through initialState, setState or a\n // controlled slice) that nothing is working on would stay forever (#22). 'pending' commits the\n // restored input (Enter mode: keeps it), 'searching' ends; a searchFn then runs for the query.\n resumes.set(table, () => {\n const s = ctx.getState()\n const pending = s?.status === 'pending'\n if (pending ? !scheduled : s?.status === 'searching' && !session?.busy()) {\n const inp = toText(s.input)\n write(inp, pending && strategy !== 'enter' ? target(inp) : toText(s.query), resting())\n }\n })\n\n /**\n * Leading debounce (#28): the keystrokes it drops settle `'superseded'` when the silence\n * window ends (it restarts with each keystroke, like kk-debounce's), unless `flushSearch()`\n * applies them first.\n */\n const drop = debounce(() => {\n const list = waiting\n waiting = []\n for (const w of list) w.resolve(SUPERSEDED)\n }, wait)\n /** Drops the scheduled commit (and the end of a leading silence window). */\n const stop = () => {\n timer?.cancel()\n drop.cancel()\n }\n\n instances.set(table, () => {\n stop()\n session?.destroy()\n supersede()\n })\n\n /** Typing: `input` at once; `query` through the strategy (`pending` while scheduled). */\n const commit = (next: string): Promise<SearchOutcome> => {\n const current = applied()\n const query = target(next)\n replaceWaiting(query)\n const p = waitFor(query)\n if (strategy === 'enter') {\n write(next, current, resting())\n return p\n }\n if (!timer || query === current) {\n stop()\n applyQuery(query, next)\n return p\n }\n calling = true\n appliedNow = undefined\n try {\n timer(query)\n } finally {\n calling = false\n }\n if (appliedNow !== undefined) {\n applyQuery(appliedNow, next)\n return p\n }\n // A leading debounce drops the rest of the burst: nothing is scheduled to commit it (the\n // silence window keeps running; `flushSearch()` commits the input), and its promise settles\n // when the window ends.\n if (strategy === 'debounce' && typing.behavior === 'leading') {\n write(next, current, resting())\n drop()\n return p\n }\n scheduled = true\n write(next, current, 'pending')\n return p\n }\n /** Applies `input` now (Enter, clear): any scheduled commit is dropped. */\n const applyNow = (next: string): Promise<SearchOutcome> => {\n stop()\n const query = target(next)\n replaceWaiting(query)\n const p = waitFor(query)\n applyQuery(query, next)\n return p\n }\n return {\n setSearch: (query) => commit(toText(query)),\n flushSearch: () => applyNow(input()),\n cancelSearch: () => {\n scheduled = false\n stop()\n session?.abort()\n supersede()\n const s = ctx.getState()\n if (s?.status === 'pending' || s?.status === 'searching') {\n ctx.setState({ ...s, status: 'idle' })\n }\n },\n clearSearch: () => {\n void applyNow('')\n },\n }\n },\n rowModel: (indices, state, ctx) => {\n const output = step(indices, state, ctx)\n shown = { input: indices, output }\n return output\n },\n })\n}\n"],"mappings":"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","names":["matching","input"]}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/features/search/skipped-codes.ts
21
+ var skipped_codes_exports = {};
22
+ __export(skipped_codes_exports, {
23
+ skippedColumnDiagnostics: () => skippedColumnDiagnostics
24
+ });
25
+ module.exports = __toCommonJS(skipped_codes_exports);
26
+ var import_catalog = require("../../diagnostics/catalog.cjs");
27
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
28
+ var skippedColumnDiagnostics = process.env.NODE_ENV !== "production" ? [
29
+ (0, import_catalog.defineDiagnostic)({
30
+ code: "DK_SEARCH_COLUMN_SKIPPED",
31
+ level: "info",
32
+ where: (d) => ({ kind: "column", index: d.index, id: d.column }),
33
+ problem: (d) => `column "${d.column}" holds dates, arrays or other objects, and no column declares a search weight: such a column is not searched (only strings, numbers and booleans are)`,
34
+ impact: () => "search does not match the values of this column",
35
+ fix: (d) => [
36
+ `give it a search weight to search it: columns: [{ key: '${d.column}', search: 1 }, \u2026], and give one to every other column to search (once a column declares a weight, only weighted columns are searched)`,
37
+ `to choose the text it matches, add format: (value) => string to columns[${d.index}] (by default dates match their toLocaleString(), arrays their items joined by ", ")`
38
+ ]
39
+ })
40
+ ] : [];
41
+ // Annotate the CommonJS export names for ESM import in node:
42
+ 0 && (module.exports = {
43
+ skippedColumnDiagnostics
44
+ });
45
+ //# sourceMappingURL=skipped-codes.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/features/search/skipped-codes.ts"],"sourcesContent":["import { defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\n// Its own module, not `./codes`: `search()` imports it, and `./codes` registers DK_SEARCH_FAILED,\n// which only the `searchFn` engine (`search-engine.ts`) emits and keeps.\n\n/**\n * `DK_SEARCH_COLUMN_SKIPPED` (#27) is a development hint: with no column declaring a `search`\n * weight, a column of dates, arrays or other objects is not searched. Its definition is behind\n * `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const skippedColumnDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SEARCH_COLUMN_SKIPPED',\n level: 'info',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n problem: (d) =>\n `column \"${d.column}\" holds dates, arrays or other objects, and no column declares a ` +\n 'search weight: such a column is not searched (only strings, numbers and booleans are)',\n impact: () => 'search does not match the values of this column',\n fix: (d) => [\n `give it a search weight to search it: columns: [{ key: '${d.column}', search: 1 }, …], ` +\n 'and give one to every other column to search (once a column declares a weight, ' +\n 'only weighted columns are searched)',\n `to choose the text it matches, add format: (value) => string to columns[${d.index}] ` +\n '(by default dates match their toLocaleString(), arrays their items joined by \", \")',\n ],\n }),\n ]\n : []\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAiC;AACjC,2BAAO;AAUA,IAAM,2BAA4B,QAAQ,IAAI,aAAa,eAC9D;AAAA,MACE,iCAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAC9D,SAAS,CAAC,MACR,WAAW,EAAE,MAAM;AAAA,IAErB,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,2DAA2D,EAAE,MAAM;AAAA,MAGnE,2EAA2E,EAAE,KAAK;AAAA,IAEpF;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
@@ -0,0 +1,20 @@
1
+ // src/features/search/skipped-codes.ts
2
+ import { defineDiagnostic } from "../../diagnostics/catalog.js";
3
+ import "../../diagnostics/process-guard.js";
4
+ var skippedColumnDiagnostics = process.env.NODE_ENV !== "production" ? [
5
+ defineDiagnostic({
6
+ code: "DK_SEARCH_COLUMN_SKIPPED",
7
+ level: "info",
8
+ where: (d) => ({ kind: "column", index: d.index, id: d.column }),
9
+ problem: (d) => `column "${d.column}" holds dates, arrays or other objects, and no column declares a search weight: such a column is not searched (only strings, numbers and booleans are)`,
10
+ impact: () => "search does not match the values of this column",
11
+ fix: (d) => [
12
+ `give it a search weight to search it: columns: [{ key: '${d.column}', search: 1 }, \u2026], and give one to every other column to search (once a column declares a weight, only weighted columns are searched)`,
13
+ `to choose the text it matches, add format: (value) => string to columns[${d.index}] (by default dates match their toLocaleString(), arrays their items joined by ", ")`
14
+ ]
15
+ })
16
+ ] : [];
17
+ export {
18
+ skippedColumnDiagnostics
19
+ };
20
+ //# sourceMappingURL=skipped-codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/features/search/skipped-codes.ts"],"sourcesContent":["import { defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\n// Its own module, not `./codes`: `search()` imports it, and `./codes` registers DK_SEARCH_FAILED,\n// which only the `searchFn` engine (`search-engine.ts`) emits and keeps.\n\n/**\n * `DK_SEARCH_COLUMN_SKIPPED` (#27) is a development hint: with no column declaring a `search`\n * weight, a column of dates, arrays or other objects is not searched. Its definition is behind\n * `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const skippedColumnDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SEARCH_COLUMN_SKIPPED',\n level: 'info',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n problem: (d) =>\n `column \"${d.column}\" holds dates, arrays or other objects, and no column declares a ` +\n 'search weight: such a column is not searched (only strings, numbers and booleans are)',\n impact: () => 'search does not match the values of this column',\n fix: (d) => [\n `give it a search weight to search it: columns: [{ key: '${d.column}', search: 1 }, …], ` +\n 'and give one to every other column to search (once a column declares a weight, ' +\n 'only weighted columns are searched)',\n `to choose the text it matches, add format: (value) => string to columns[${d.index}] ` +\n '(by default dates match their toLocaleString(), arrays their items joined by \", \")',\n ],\n }),\n ]\n : []\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC,OAAO;AAUA,IAAM,2BAA4B,QAAQ,IAAI,aAAa,eAC9D;AAAA,EACE,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAC9D,SAAS,CAAC,MACR,WAAW,EAAE,MAAM;AAAA,IAErB,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,2DAA2D,EAAE,MAAM;AAAA,MAGnE,2EAA2E,EAAE,KAAK;AAAA,IAEpF;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
@@ -26,6 +26,7 @@ __export(codes_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(codes_exports);
28
28
  var import_catalog = require("../../diagnostics/catalog.cjs");
29
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
29
30
  var REASON = {
30
31
  nan: "returned NaN",
31
32
  "not-number": "returned a non-number",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/sorting/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SORT_STRATEGY: {\n /** The algorithm that sorted: `tim`, `radix`, `counting`, `native` or `custom`. */\n algorithm: string\n /** Why it was chosen (auto rule, override, or fallback). */\n reason: string\n /** Rows sorted. */\n n: number\n /** Time taken, in milliseconds (scan + sort). */\n ms: number\n /** The sort entries, e.g. `['age:desc']`. */\n by: readonly string[]\n /** The primary sort column (`where`), when it is in `options.columns`. */\n column?: { index: number; id: string }\n }\n DK_SORT_COMPARATOR_INVALID: {\n /** Column id. */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** `nan`: returned NaN; `not-number`: returned a non-number; `inconsistent`: f(a, b) and f(b, a) disagree. */\n reason: 'nan' | 'not-number' | 'inconsistent'\n /** The type of the bad return (`not-number`), never the value. */\n received?: string\n }\n }\n}\n\nconst REASON = {\n nan: 'returned NaN',\n 'not-number': 'returned a non-number',\n inconsistent: 'is inconsistent: sortFn(a, b) and sortFn(b, a) do not have opposite signs',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const sortingMessages: DiagnosticMessages<'DK_SORT_STRATEGY'> = {\n DK_SORT_STRATEGY: {\n problem: (d) =>\n `sorted ${d.n} rows by ${d.by.join(', ')} with ${d.algorithm} in ${d.ms.toFixed(2)} ms ` +\n `(${d.reason})`,\n impact: () => 'none; reported because sorting({ debug: true }) is set',\n fix: () => [\n \"to force an algorithm: sorting({ algorithm: 'tim' | 'radix' | 'counting' | \" +\n \"'native' }) or a column's sortAlgorithm\",\n 'remove debug: true from sorting() to stop these reports',\n ],\n },\n}\n\n/**\n * Codes owned by the `sorting` feature (§6.4, §8.3). `DK_SORT_COMPARATOR_INVALID` is a dev check\n * (sampled): its definition is behind `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const sortingDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SORT_STRATEGY',\n level: 'info',\n where: (d) =>\n d.column ? { kind: 'column', ...d.column } : { kind: 'feature', name: 'sorting' },\n },\n (process.env.NODE_ENV !== \"production\") && sortingMessages.DK_SORT_STRATEGY,\n ),\n]\n\nexport const comparatorDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SORT_COMPARATOR_INVALID',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `columns[${d.index}].sortFn` }),\n problem: (d) =>\n `columns[${d.index}].sortFn (column \"${d.column}\") ${REASON[d.reason]}` +\n (d.received === undefined ? '' : ` (${d.received})`),\n impact: () => 'the column was sorted with the default comparator instead',\n fix: (d) => [\n d.reason === 'inconsistent'\n ? 'return a negative number when a < b, positive when a > b and 0 when equal, so that ' +\n 'sortFn(a, b) === -sortFn(b, a), e.g. (a, b) => a.rank - b.rank'\n : 'return a finite number from sortFn: negative (a first), positive (b first) or 0, ' +\n 'e.g. (a, b) => a.rank - b.rank',\n `or sort by a primitive key instead: columns[${d.index}].sortBy: (row) => row.<field>`,\n ],\n }),\n ]\n : []\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AA+B1D,IAAM,SAAS;AAAA,EACb,KAAK;AAAA,EACL,cAAc;AAAA,EACd,cAAc;AAChB;AAGO,IAAM,kBAA0D;AAAA,EACrE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,UAAU,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,QAC9E,EAAE,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IACF;AAAA,EACF;AACF;AAMO,IAAM,qBAAqB;AAAA,MAChC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,SAAS,EAAE,MAAM,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,IACpF;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,gBAAgB;AAAA,EAC7D;AACF;AAEO,IAAM,wBAAyB,QAAQ,IAAI,aAAa,eAC3D;AAAA,MACE,iCAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACpE,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,MAAM,OAAO,EAAE,MAAM,CAAC,MACpE,EAAE,aAAa,SAAY,KAAK,KAAK,EAAE,QAAQ;AAAA,IAClD,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,WAAW,iBACT,sJAEA;AAAA,MAEJ,+CAA+C,EAAE,KAAK;AAAA,IACxD;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/sorting/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SORT_STRATEGY: {\n /** The algorithm that sorted: `tim`, `radix`, `counting`, `native` or `custom`. */\n algorithm: string\n /** Why it was chosen (auto rule, override, or fallback). */\n reason: string\n /** Rows sorted. */\n n: number\n /** Time taken, in milliseconds (scan + sort). */\n ms: number\n /** The sort entries, e.g. `['age:desc']`. */\n by: readonly string[]\n /** The primary sort column (`where`), when it is in `options.columns`. */\n column?: { index: number; id: string }\n }\n DK_SORT_COMPARATOR_INVALID: {\n /** Column id. */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** `nan`: returned NaN; `not-number`: returned a non-number; `inconsistent`: f(a, b) and f(b, a) disagree. */\n reason: 'nan' | 'not-number' | 'inconsistent'\n /** The type of the bad return (`not-number`), never the value. */\n received?: string\n }\n }\n}\n\nconst REASON = {\n nan: 'returned NaN',\n 'not-number': 'returned a non-number',\n inconsistent: 'is inconsistent: sortFn(a, b) and sortFn(b, a) do not have opposite signs',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const sortingMessages: DiagnosticMessages<'DK_SORT_STRATEGY'> = {\n DK_SORT_STRATEGY: {\n problem: (d) =>\n `sorted ${d.n} rows by ${d.by.join(', ')} with ${d.algorithm} in ${d.ms.toFixed(2)} ms ` +\n `(${d.reason})`,\n impact: () => 'none; reported because sorting({ debug: true }) is set',\n fix: () => [\n \"to force an algorithm: sorting({ algorithm: 'tim' | 'radix' | 'counting' | \" +\n \"'native' }) or a column's sortAlgorithm\",\n 'remove debug: true from sorting() to stop these reports',\n ],\n },\n}\n\n/**\n * Codes owned by the `sorting` feature (§6.4, §8.3). `DK_SORT_COMPARATOR_INVALID` is a dev check\n * (sampled): its definition is behind `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const sortingDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SORT_STRATEGY',\n level: 'info',\n where: (d) =>\n d.column ? { kind: 'column', ...d.column } : { kind: 'feature', name: 'sorting' },\n },\n (process.env.NODE_ENV !== \"production\") && sortingMessages.DK_SORT_STRATEGY,\n ),\n]\n\nexport const comparatorDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SORT_COMPARATOR_INVALID',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `columns[${d.index}].sortFn` }),\n problem: (d) =>\n `columns[${d.index}].sortFn (column \"${d.column}\") ${REASON[d.reason]}` +\n (d.received === undefined ? '' : ` (${d.received})`),\n impact: () => 'the column was sorted with the default comparator instead',\n fix: (d) => [\n d.reason === 'inconsistent'\n ? 'return a negative number when a < b, positive when a > b and 0 when equal, so that ' +\n 'sortFn(a, b) === -sortFn(b, a), e.g. (a, b) => a.rank - b.rank'\n : 'return a finite number from sortFn: negative (a first), positive (b first) or 0, ' +\n 'e.g. (a, b) => a.rank - b.rank',\n `or sort by a primitive key instead: columns[${d.index}].sortBy: (row) => row.<field>`,\n ],\n }),\n ]\n : []\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AA+BP,IAAM,SAAS;AAAA,EACb,KAAK;AAAA,EACL,cAAc;AAAA,EACd,cAAc;AAChB;AAGO,IAAM,kBAA0D;AAAA,EACrE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,UAAU,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,QAC9E,EAAE,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IACF;AAAA,EACF;AACF;AAMO,IAAM,qBAAqB;AAAA,MAChC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,SAAS,EAAE,MAAM,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,IACpF;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,gBAAgB;AAAA,EAC7D;AACF;AAEO,IAAM,wBAAyB,QAAQ,IAAI,aAAa,eAC3D;AAAA,MACE,iCAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACpE,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,MAAM,OAAO,EAAE,MAAM,CAAC,MACpE,EAAE,aAAa,SAAY,KAAK,KAAK,EAAE,QAAQ;AAAA,IAClD,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,WAAW,iBACT,sJAEA;AAAA,MAEJ,+CAA+C,EAAE,KAAK;AAAA,IACxD;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
@@ -1,5 +1,6 @@
1
1
  // src/features/sorting/codes.ts
2
2
  import { defineDiagnostic } from "../../diagnostics/catalog.js";
3
+ import "../../diagnostics/process-guard.js";
3
4
  var REASON = {
4
5
  nan: "returned NaN",
5
6
  "not-number": "returned a non-number",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/sorting/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SORT_STRATEGY: {\n /** The algorithm that sorted: `tim`, `radix`, `counting`, `native` or `custom`. */\n algorithm: string\n /** Why it was chosen (auto rule, override, or fallback). */\n reason: string\n /** Rows sorted. */\n n: number\n /** Time taken, in milliseconds (scan + sort). */\n ms: number\n /** The sort entries, e.g. `['age:desc']`. */\n by: readonly string[]\n /** The primary sort column (`where`), when it is in `options.columns`. */\n column?: { index: number; id: string }\n }\n DK_SORT_COMPARATOR_INVALID: {\n /** Column id. */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** `nan`: returned NaN; `not-number`: returned a non-number; `inconsistent`: f(a, b) and f(b, a) disagree. */\n reason: 'nan' | 'not-number' | 'inconsistent'\n /** The type of the bad return (`not-number`), never the value. */\n received?: string\n }\n }\n}\n\nconst REASON = {\n nan: 'returned NaN',\n 'not-number': 'returned a non-number',\n inconsistent: 'is inconsistent: sortFn(a, b) and sortFn(b, a) do not have opposite signs',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const sortingMessages: DiagnosticMessages<'DK_SORT_STRATEGY'> = {\n DK_SORT_STRATEGY: {\n problem: (d) =>\n `sorted ${d.n} rows by ${d.by.join(', ')} with ${d.algorithm} in ${d.ms.toFixed(2)} ms ` +\n `(${d.reason})`,\n impact: () => 'none; reported because sorting({ debug: true }) is set',\n fix: () => [\n \"to force an algorithm: sorting({ algorithm: 'tim' | 'radix' | 'counting' | \" +\n \"'native' }) or a column's sortAlgorithm\",\n 'remove debug: true from sorting() to stop these reports',\n ],\n },\n}\n\n/**\n * Codes owned by the `sorting` feature (§6.4, §8.3). `DK_SORT_COMPARATOR_INVALID` is a dev check\n * (sampled): its definition is behind `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const sortingDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SORT_STRATEGY',\n level: 'info',\n where: (d) =>\n d.column ? { kind: 'column', ...d.column } : { kind: 'feature', name: 'sorting' },\n },\n (process.env.NODE_ENV !== \"production\") && sortingMessages.DK_SORT_STRATEGY,\n ),\n]\n\nexport const comparatorDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SORT_COMPARATOR_INVALID',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `columns[${d.index}].sortFn` }),\n problem: (d) =>\n `columns[${d.index}].sortFn (column \"${d.column}\") ${REASON[d.reason]}` +\n (d.received === undefined ? '' : ` (${d.received})`),\n impact: () => 'the column was sorted with the default comparator instead',\n fix: (d) => [\n d.reason === 'inconsistent'\n ? 'return a negative number when a < b, positive when a > b and 0 when equal, so that ' +\n 'sortFn(a, b) === -sortFn(b, a), e.g. (a, b) => a.rank - b.rank'\n : 'return a finite number from sortFn: negative (a first), positive (b first) or 0, ' +\n 'e.g. (a, b) => a.rank - b.rank',\n `or sort by a primitive key instead: columns[${d.index}].sortBy: (row) => row.<field>`,\n ],\n }),\n ]\n : []\n"],"mappings":";AAAA,SAAkC,wBAAwB;AA+B1D,IAAM,SAAS;AAAA,EACb,KAAK;AAAA,EACL,cAAc;AAAA,EACd,cAAc;AAChB;AAGO,IAAM,kBAA0D;AAAA,EACrE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,UAAU,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,QAC9E,EAAE,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IACF;AAAA,EACF;AACF;AAMO,IAAM,qBAAqB;AAAA,EAChC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,SAAS,EAAE,MAAM,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,IACpF;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,gBAAgB;AAAA,EAC7D;AACF;AAEO,IAAM,wBAAyB,QAAQ,IAAI,aAAa,eAC3D;AAAA,EACE,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACpE,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,MAAM,OAAO,EAAE,MAAM,CAAC,MACpE,EAAE,aAAa,SAAY,KAAK,KAAK,EAAE,QAAQ;AAAA,IAClD,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,WAAW,iBACT,sJAEA;AAAA,MAEJ,+CAA+C,EAAE,KAAK;AAAA,IACxD;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/sorting/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SORT_STRATEGY: {\n /** The algorithm that sorted: `tim`, `radix`, `counting`, `native` or `custom`. */\n algorithm: string\n /** Why it was chosen (auto rule, override, or fallback). */\n reason: string\n /** Rows sorted. */\n n: number\n /** Time taken, in milliseconds (scan + sort). */\n ms: number\n /** The sort entries, e.g. `['age:desc']`. */\n by: readonly string[]\n /** The primary sort column (`where`), when it is in `options.columns`. */\n column?: { index: number; id: string }\n }\n DK_SORT_COMPARATOR_INVALID: {\n /** Column id. */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** `nan`: returned NaN; `not-number`: returned a non-number; `inconsistent`: f(a, b) and f(b, a) disagree. */\n reason: 'nan' | 'not-number' | 'inconsistent'\n /** The type of the bad return (`not-number`), never the value. */\n received?: string\n }\n }\n}\n\nconst REASON = {\n nan: 'returned NaN',\n 'not-number': 'returned a non-number',\n inconsistent: 'is inconsistent: sortFn(a, b) and sortFn(b, a) do not have opposite signs',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const sortingMessages: DiagnosticMessages<'DK_SORT_STRATEGY'> = {\n DK_SORT_STRATEGY: {\n problem: (d) =>\n `sorted ${d.n} rows by ${d.by.join(', ')} with ${d.algorithm} in ${d.ms.toFixed(2)} ms ` +\n `(${d.reason})`,\n impact: () => 'none; reported because sorting({ debug: true }) is set',\n fix: () => [\n \"to force an algorithm: sorting({ algorithm: 'tim' | 'radix' | 'counting' | \" +\n \"'native' }) or a column's sortAlgorithm\",\n 'remove debug: true from sorting() to stop these reports',\n ],\n },\n}\n\n/**\n * Codes owned by the `sorting` feature (§6.4, §8.3). `DK_SORT_COMPARATOR_INVALID` is a dev check\n * (sampled): its definition is behind `(process.env.NODE_ENV !== \"production\")`, so a production bundle has none of it.\n */\nexport const sortingDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SORT_STRATEGY',\n level: 'info',\n where: (d) =>\n d.column ? { kind: 'column', ...d.column } : { kind: 'feature', name: 'sorting' },\n },\n (process.env.NODE_ENV !== \"production\") && sortingMessages.DK_SORT_STRATEGY,\n ),\n]\n\nexport const comparatorDiagnostics = (process.env.NODE_ENV !== \"production\")\n ? [\n defineDiagnostic({\n code: 'DK_SORT_COMPARATOR_INVALID',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `columns[${d.index}].sortFn` }),\n problem: (d) =>\n `columns[${d.index}].sortFn (column \"${d.column}\") ${REASON[d.reason]}` +\n (d.received === undefined ? '' : ` (${d.received})`),\n impact: () => 'the column was sorted with the default comparator instead',\n fix: (d) => [\n d.reason === 'inconsistent'\n ? 'return a negative number when a < b, positive when a > b and 0 when equal, so that ' +\n 'sortFn(a, b) === -sortFn(b, a), e.g. (a, b) => a.rank - b.rank'\n : 'return a finite number from sortFn: negative (a first), positive (b first) or 0, ' +\n 'e.g. (a, b) => a.rank - b.rank',\n `or sort by a primitive key instead: columns[${d.index}].sortBy: (row) => row.<field>`,\n ],\n }),\n ]\n : []\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AA+BP,IAAM,SAAS;AAAA,EACb,KAAK;AAAA,EACL,cAAc;AAAA,EACd,cAAc;AAChB;AAGO,IAAM,kBAA0D;AAAA,EACrE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,UAAU,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,QAC9E,EAAE,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IACF;AAAA,EACF;AACF;AAMO,IAAM,qBAAqB;AAAA,EAChC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,SAAS,EAAE,MAAM,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,IACpF;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,gBAAgB;AAAA,EAC7D;AACF;AAEO,IAAM,wBAAyB,QAAQ,IAAI,aAAa,eAC3D;AAAA,EACE,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACpE,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,MAAM,OAAO,EAAE,MAAM,CAAC,MACpE,EAAE,aAAa,SAAY,KAAK,KAAK,EAAE,QAAQ;AAAA,IAClD,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,WAAW,iBACT,sJAEA;AAAA,MAEJ,+CAA+C,EAAE,KAAK;AAAA,IACxD;AAAA,EACF,CAAC;AACH,IACA,CAAC;","names":[]}
@@ -28,6 +28,7 @@ var import_options = require("../../catalog/options.cjs");
28
28
  var import_slots = require("../../catalog/slots.cjs");
29
29
  var import_get_value = require("../../column/get-value.cjs");
30
30
  var import_catalog = require("../../diagnostics/catalog.cjs");
31
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
31
32
  var import_define = require("../../feature/define.cjs");
32
33
  var import_cell_batch = require("../../intercept/cell-batch.cjs");
33
34
  var import_classify = require("../../intercept/classify.cjs");
@@ -49,6 +50,7 @@ var import_strategy = require("./strategy.cjs");
49
50
  import_codes.optionDiagnostics,
50
51
  import_env_codes.envDiagnostics
51
52
  );
53
+ var setSort = (ctx, by) => ctx.setState((s) => JSON.stringify(s.by) === JSON.stringify(by) ? s : { by: by.slice() });
52
54
  var validLocale = (v) => {
53
55
  try {
54
56
  Intl.getCanonicalLocales(v);
@@ -128,7 +130,7 @@ var sorting = (opts = {}) => {
128
130
  api: "Intl.Collator",
129
131
  feature: "sorting",
130
132
  requested: [].concat(locale).join(", "),
131
- fallback: `the runtime's collation for "${collator.resolvedOptions().locale}"`
133
+ fallback: `collation|${collator.resolvedOptions().locale}`
132
134
  });
133
135
  }
134
136
  return collator;
@@ -216,8 +218,8 @@ var sorting = (opts = {}) => {
216
218
  else out.splice(i, 1);
217
219
  return { by: out };
218
220
  }),
219
- setSorting: (by) => ctx.setState({ by: by.slice() }),
220
- clearSorting: () => ctx.setState({ by: [] }),
221
+ setSorting: (by) => setSort(ctx, by),
222
+ clearSorting: () => setSort(ctx, []),
221
223
  getSortDirection: (id) => {
222
224
  const e = ctx.getState().by.find((x) => x.id === id);
223
225
  return e ? e.desc ? "desc" : "asc" : false;
@@ -258,9 +260,7 @@ var sorting = (opts = {}) => {
258
260
  fromColumn ? { path: `columns[${primary.col?.index}].sortAlgorithm`, reason: reason2, once: false } : { path: "algorithm", feature: "sorting", reason: reason2, once: false }
259
261
  );
260
262
  if (typeof requested !== "function" && !ALGORITHMS.includes(requested)) {
261
- noEffect(
262
- `${JSON.stringify(requested)} is not a sort algorithm (accepted: ${ALGORITHMS.join(", ")}, or a function); auto was used`
263
- );
263
+ noEffect(`not-algorithm|${JSON.stringify(requested)}`);
264
264
  requested = "auto";
265
265
  }
266
266
  if (requested !== "native" && exsorted === void 0) {
@@ -324,7 +324,7 @@ var sorting = (opts = {}) => {
324
324
  } catch (error) {
325
325
  if (error instanceof SortFnFailed) throw error;
326
326
  reportCustom(true, error);
327
- return native("the custom algorithm threw");
327
+ return native("custom-threw");
328
328
  }
329
329
  if (res instanceof Uint32Array && res.length === indices.length) {
330
330
  algorithm = "custom";
@@ -332,7 +332,7 @@ var sorting = (opts = {}) => {
332
332
  return res;
333
333
  }
334
334
  reportCustom(false, res);
335
- return native("the custom algorithm returned a bad value");
335
+ return native("custom-bad");
336
336
  }
337
337
  if (exsorted === false) return native("exsorted unavailable");
338
338
  const by2 = fromColumn ? "column sortAlgorithm" : "sorting({ algorithm })";
@@ -343,17 +343,13 @@ var sorting = (opts = {}) => {
343
343
  try {
344
344
  const out = (0, import_strategy.sortIntKeys)(keys, want);
345
345
  algorithm = want;
346
- reason = `${by2}, safe-integer keys`;
346
+ reason = `int-keys|${by2}`;
347
347
  return out;
348
348
  } catch {
349
- noEffect(
350
- `exsorted ${want}Sort could not sort these keys` + (want === "counting" ? " (key range above 1,000,000)" : "") + "; auto was used"
351
- );
349
+ noEffect(`exsorted-${want === "counting" ? "range" : "failed"}|${want}`);
352
350
  }
353
351
  } else if ((0, import_strategy.needsIntKeys)(want)) {
354
- noEffect(
355
- `${want} sorts a single column of safe integer keys (numbers or Dates, no sortFn); auto was used`
356
- );
352
+ noEffect(`needs-int-keys|${want}`);
357
353
  } else {
358
354
  algorithm = want;
359
355
  reason = by2;
@@ -362,22 +358,22 @@ var sorting = (opts = {}) => {
362
358
  } else if (want === "native") return native(by2);
363
359
  if (!keys) {
364
360
  return native(
365
- single ? "keys are not all safe integers" : entries.length > 1 ? "multi-column" : "custom sortFn"
361
+ single ? "not-safe-int" : entries.length > 1 ? "multi-column" : "custom sortFn"
366
362
  );
367
363
  }
368
364
  const choice = (0, import_strategy.chooseAuto)(keys);
369
365
  algorithm = choice.algorithm;
370
- reason = `auto: ${ranked ? "collation ranks of a string column, " : ""}${choice.reason}`;
366
+ reason = `auto-${ranked ? "ranked-" : ""}${choice.reason}`;
371
367
  return (0, import_strategy.sortIntKeys)(keys, choice.algorithm);
372
368
  };
373
369
  const native = (why) => {
374
370
  algorithm = "native";
375
- reason = ranked ? `${why}, strings as collation ranks` : why;
371
+ reason = ranked ? `ranks|${why}` : why;
376
372
  return (0, import_strategy.sortWithComparator)(indices, build(), "native");
377
373
  };
378
374
  const reportCustom = (threw, value) => {
379
375
  const slot = fromColumn ? `columns[${primary.col?.index}].sortAlgorithm` : "sorting.algorithm";
380
- const fallback = "the rows were sorted with the native algorithm";
376
+ const fallback = "sorted-native";
381
377
  if (!threw) {
382
378
  if (process.env.NODE_ENV !== "production") {
383
379
  ctx.emit("DK_CALLBACK_BAD_RETURN", {
@@ -429,7 +425,7 @@ var sorting = (opts = {}) => {
429
425
  slot: `columns[${col.index}].sortFn`,
430
426
  column: col.id,
431
427
  count: 1,
432
- fallback: "the column was sorted with the default comparator",
428
+ fallback: "sorted-default",
433
429
  ...name === void 0 ? {} : { errorName: name }
434
430
  },
435
431
  { cause: e.error }