@datakable/core 1.0.0 → 1.1.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 (290) hide show
  1. package/README.md +4 -2
  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-BKdS9EiO.d.cts → codes-B9bCK73-.d.cts} +46 -8
  11. package/dist/{codes-BKdS9EiO.d.ts → codes-B9bCK73-.d.ts} +46 -8
  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 +10 -4
  20. package/dist/diagnostics/build.cjs.map +1 -1
  21. package/dist/diagnostics/build.js +8 -3
  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 +6 -5
  48. package/dist/diagnostics/hub.cjs.map +1 -1
  49. package/dist/diagnostics/hub.js +7 -6
  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-aagaYHcy.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
  73. package/dist/{explain-DcUZfFh5.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/define.cjs +3 -1
  79. package/dist/feature/define.cjs.map +1 -1
  80. package/dist/feature/define.js +3 -1
  81. package/dist/feature/define.js.map +1 -1
  82. package/dist/feature/install.cjs +4 -3
  83. package/dist/feature/install.cjs.map +1 -1
  84. package/dist/feature/install.js +4 -3
  85. package/dist/feature/install.js.map +1 -1
  86. package/dist/feature/runtime.cjs.map +1 -1
  87. package/dist/feature/runtime.js.map +1 -1
  88. package/dist/features/column-filters/codes.cjs +1 -0
  89. package/dist/features/column-filters/codes.cjs.map +1 -1
  90. package/dist/features/column-filters/codes.js +1 -0
  91. package/dist/features/column-filters/codes.js.map +1 -1
  92. package/dist/features/column-filters/column-filters.cjs +4 -2
  93. package/dist/features/column-filters/column-filters.cjs.map +1 -1
  94. package/dist/features/column-filters/column-filters.js +4 -2
  95. package/dist/features/column-filters/column-filters.js.map +1 -1
  96. package/dist/features/column-filters/match.cjs +5 -5
  97. package/dist/features/column-filters/match.cjs.map +1 -1
  98. package/dist/features/column-filters/match.js +5 -5
  99. package/dist/features/column-filters/match.js.map +1 -1
  100. package/dist/features/pagination/codes.cjs +6 -3
  101. package/dist/features/pagination/codes.cjs.map +1 -1
  102. package/dist/features/pagination/codes.js +6 -3
  103. package/dist/features/pagination/codes.js.map +1 -1
  104. package/dist/features/pagination/pagination.cjs +19 -14
  105. package/dist/features/pagination/pagination.cjs.map +1 -1
  106. package/dist/features/pagination/pagination.js +19 -14
  107. package/dist/features/pagination/pagination.js.map +1 -1
  108. package/dist/features/search/codes.cjs +1 -0
  109. package/dist/features/search/codes.cjs.map +1 -1
  110. package/dist/features/search/codes.js +1 -0
  111. package/dist/features/search/codes.js.map +1 -1
  112. package/dist/features/search/search-index.cjs +105 -48
  113. package/dist/features/search/search-index.cjs.map +1 -1
  114. package/dist/features/search/search-index.js +106 -49
  115. package/dist/features/search/search-index.js.map +1 -1
  116. package/dist/features/search/search.cjs +82 -24
  117. package/dist/features/search/search.cjs.map +1 -1
  118. package/dist/features/search/search.js +82 -24
  119. package/dist/features/search/search.js.map +1 -1
  120. package/dist/features/search/skipped-codes.cjs +45 -0
  121. package/dist/features/search/skipped-codes.cjs.map +1 -0
  122. package/dist/features/search/skipped-codes.js +20 -0
  123. package/dist/features/search/skipped-codes.js.map +1 -0
  124. package/dist/features/sorting/codes.cjs +1 -0
  125. package/dist/features/sorting/codes.cjs.map +1 -1
  126. package/dist/features/sorting/codes.js +1 -0
  127. package/dist/features/sorting/codes.js.map +1 -1
  128. package/dist/features/sorting/sorting.cjs +19 -23
  129. package/dist/features/sorting/sorting.cjs.map +1 -1
  130. package/dist/features/sorting/sorting.js +19 -23
  131. package/dist/features/sorting/sorting.js.map +1 -1
  132. package/dist/features/sorting/strategy.cjs +4 -4
  133. package/dist/features/sorting/strategy.cjs.map +1 -1
  134. package/dist/features/sorting/strategy.js +4 -4
  135. package/dist/features/sorting/strategy.js.map +1 -1
  136. package/dist/features/validity/codes.cjs +1 -0
  137. package/dist/features/validity/codes.cjs.map +1 -1
  138. package/dist/features/validity/codes.js +1 -0
  139. package/dist/features/validity/codes.js.map +1 -1
  140. package/dist/features/validity/validity.cjs +3 -3
  141. package/dist/features/validity/validity.cjs.map +1 -1
  142. package/dist/features/validity/validity.js +3 -3
  143. package/dist/features/validity/validity.js.map +1 -1
  144. package/dist/index.d.cts +16 -11
  145. package/dist/index.d.ts +16 -11
  146. package/dist/ingest/data-check-codes.cjs +4 -2
  147. package/dist/ingest/data-check-codes.cjs.map +1 -1
  148. package/dist/ingest/data-check-codes.js +4 -2
  149. package/dist/ingest/data-check-codes.js.map +1 -1
  150. package/dist/ingest/data-checks.cjs +1 -1
  151. package/dist/ingest/data-checks.cjs.map +1 -1
  152. package/dist/ingest/data-checks.js +1 -1
  153. package/dist/ingest/data-checks.js.map +1 -1
  154. package/dist/ingest/row-key-codes.cjs +1 -0
  155. package/dist/ingest/row-key-codes.cjs.map +1 -1
  156. package/dist/ingest/row-key-codes.js +1 -0
  157. package/dist/ingest/row-key-codes.js.map +1 -1
  158. package/dist/ingest/row-key.cjs +23 -20
  159. package/dist/ingest/row-key.cjs.map +1 -1
  160. package/dist/ingest/row-key.js +23 -20
  161. package/dist/ingest/row-key.js.map +1 -1
  162. package/dist/ingest/schema-codes.cjs +1 -0
  163. package/dist/ingest/schema-codes.cjs.map +1 -1
  164. package/dist/ingest/schema-codes.js +1 -0
  165. package/dist/ingest/schema-codes.js.map +1 -1
  166. package/dist/ingest/unique-fields.cjs +2 -2
  167. package/dist/ingest/unique-fields.cjs.map +1 -1
  168. package/dist/ingest/unique-fields.js +2 -2
  169. package/dist/ingest/unique-fields.js.map +1 -1
  170. package/dist/intercept/boundary.cjs +4 -2
  171. package/dist/intercept/boundary.cjs.map +1 -1
  172. package/dist/intercept/boundary.js +4 -2
  173. package/dist/intercept/boundary.js.map +1 -1
  174. package/dist/intercept/cell-batch.cjs +7 -4
  175. package/dist/intercept/cell-batch.cjs.map +1 -1
  176. package/dist/intercept/cell-batch.js +7 -4
  177. package/dist/intercept/cell-batch.js.map +1 -1
  178. package/dist/intercept/codes.cjs +1 -0
  179. package/dist/intercept/codes.cjs.map +1 -1
  180. package/dist/intercept/codes.js +1 -0
  181. package/dist/intercept/codes.js.map +1 -1
  182. package/dist/intercept/fallbacks.cjs +12 -20
  183. package/dist/intercept/fallbacks.cjs.map +1 -1
  184. package/dist/intercept/fallbacks.js +12 -20
  185. package/dist/intercept/fallbacks.js.map +1 -1
  186. package/dist/intercept/runtime.cjs +7 -9
  187. package/dist/intercept/runtime.cjs.map +1 -1
  188. package/dist/intercept/runtime.js +7 -9
  189. package/dist/intercept/runtime.js.map +1 -1
  190. package/dist/messages/index.cjs +4 -1
  191. package/dist/messages/index.cjs.map +1 -1
  192. package/dist/messages/index.js +4 -1
  193. package/dist/messages/index.js.map +1 -1
  194. package/dist/pagination.d.cts +2 -2
  195. package/dist/pagination.d.ts +2 -2
  196. package/dist/pipeline/codes.cjs +1 -0
  197. package/dist/pipeline/codes.cjs.map +1 -1
  198. package/dist/pipeline/codes.js +1 -0
  199. package/dist/pipeline/codes.js.map +1 -1
  200. package/dist/pipeline/column-cache.cjs +94 -76
  201. package/dist/pipeline/column-cache.cjs.map +1 -1
  202. package/dist/pipeline/column-cache.js +92 -75
  203. package/dist/pipeline/column-cache.js.map +1 -1
  204. package/dist/pipeline/dev-checks.cjs +1 -0
  205. package/dist/pipeline/dev-checks.cjs.map +1 -1
  206. package/dist/pipeline/dev-checks.js +1 -0
  207. package/dist/pipeline/dev-checks.js.map +1 -1
  208. package/dist/pipeline/env-codes.cjs +3 -2
  209. package/dist/pipeline/env-codes.cjs.map +1 -1
  210. package/dist/pipeline/env-codes.js +3 -2
  211. package/dist/pipeline/env-codes.js.map +1 -1
  212. package/dist/pipeline/perf-codes.cjs +1 -0
  213. package/dist/pipeline/perf-codes.cjs.map +1 -1
  214. package/dist/pipeline/perf-codes.js +1 -0
  215. package/dist/pipeline/perf-codes.js.map +1 -1
  216. package/dist/pipeline/row.cjs +8 -7
  217. package/dist/pipeline/row.cjs.map +1 -1
  218. package/dist/pipeline/row.js +8 -7
  219. package/dist/pipeline/row.js.map +1 -1
  220. package/dist/pipeline/runner.cjs +17 -9
  221. package/dist/pipeline/runner.cjs.map +1 -1
  222. package/dist/pipeline/runner.js +17 -9
  223. package/dist/pipeline/runner.js.map +1 -1
  224. package/dist/pipeline/slice.cjs +3 -1
  225. package/dist/pipeline/slice.cjs.map +1 -1
  226. package/dist/pipeline/slice.js +3 -1
  227. package/dist/pipeline/slice.js.map +1 -1
  228. package/dist/schema.d.cts +2 -2
  229. package/dist/schema.d.ts +2 -2
  230. package/dist/{search-async-entry-C3mXxLP8.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
  231. package/dist/{search-async-entry-BmQYT120.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
  232. package/dist/search-async.d.cts +3 -3
  233. package/dist/search-async.d.ts +3 -3
  234. package/dist/search.d.cts +8 -6
  235. package/dist/search.d.ts +8 -6
  236. package/dist/server.d.cts +3 -3
  237. package/dist/server.d.ts +3 -3
  238. package/dist/sorting.d.cts +6 -3
  239. package/dist/sorting.d.ts +6 -3
  240. package/dist/source/codes.cjs +1 -0
  241. package/dist/source/codes.cjs.map +1 -1
  242. package/dist/source/codes.js +1 -0
  243. package/dist/source/codes.js.map +1 -1
  244. package/dist/source/fetch-codes.cjs +1 -0
  245. package/dist/source/fetch-codes.cjs.map +1 -1
  246. package/dist/source/fetch-codes.js +1 -0
  247. package/dist/source/fetch-codes.js.map +1 -1
  248. package/dist/table/codes.cjs +1 -0
  249. package/dist/table/codes.cjs.map +1 -1
  250. package/dist/table/codes.js +1 -0
  251. package/dist/table/codes.js.map +1 -1
  252. package/dist/table/create-table.cjs +39 -9
  253. package/dist/table/create-table.cjs.map +1 -1
  254. package/dist/table/create-table.js +39 -9
  255. package/dist/table/create-table.js.map +1 -1
  256. package/dist/table/data-mutation.cjs +1 -0
  257. package/dist/table/data-mutation.cjs.map +1 -1
  258. package/dist/table/data-mutation.js +1 -0
  259. package/dist/table/data-mutation.js.map +1 -1
  260. package/dist/table/internals.cjs.map +1 -1
  261. package/dist/table/internals.js.map +1 -1
  262. package/dist/table/ssr-codes.cjs +1 -0
  263. package/dist/table/ssr-codes.cjs.map +1 -1
  264. package/dist/table/ssr-codes.js +1 -0
  265. package/dist/table/ssr-codes.js.map +1 -1
  266. package/dist/table/ssr.cjs +4 -1
  267. package/dist/table/ssr.cjs.map +1 -1
  268. package/dist/table/ssr.js +4 -1
  269. package/dist/table/ssr.js.map +1 -1
  270. package/dist/table/state-feedback.cjs +19 -0
  271. package/dist/table/state-feedback.cjs.map +1 -1
  272. package/dist/table/state-feedback.js +19 -0
  273. package/dist/table/state-feedback.js.map +1 -1
  274. package/dist/table/types.cjs.map +1 -1
  275. package/dist/table/types.js.map +1 -1
  276. package/dist/validity.d.cts +2 -2
  277. package/dist/validity.d.ts +2 -2
  278. package/dist/version.cjs +1 -1
  279. package/dist/version.cjs.map +1 -1
  280. package/dist/version.js +1 -1
  281. package/dist/version.js.map +1 -1
  282. package/dist/versioning/codes.cjs +18 -0
  283. package/dist/versioning/codes.cjs.map +1 -1
  284. package/dist/versioning/codes.js +18 -0
  285. package/dist/versioning/codes.js.map +1 -1
  286. package/dist/versioning/registry.cjs +4 -2
  287. package/dist/versioning/registry.cjs.map +1 -1
  288. package/dist/versioning/registry.js +4 -2
  289. package/dist/versioning/registry.js.map +1 -1
  290. package/package.json +39 -3
@@ -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 => (typeof q === 'string' ? q : 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\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 === undefined) {\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 shown && shown.input === indices ? shown.output : indices\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: engine ? ['table:destroy', 'pipeline:run'] : ['table:destroy'],\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 return (engine as SearchEngine).run(table, next)\n },\n }),\n ],\n createApi: (ctx): SearchApi => {\n const table = ctx.table as unknown as object\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\n }\n ctx.setState(\n status === 'error' ? { input, query, status, error } : { input, query, status },\n )\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 /** Resolves the waiters of `query` with `outcome`, every other one `'superseded'`. */\n const settleWaiting = (query: string, outcome: SearchOutcome) => {\n const list = waiting\n waiting = []\n for (const w of list) w.resolve(w.query === query ? outcome : SUPERSEDED)\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 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 write(inp, query, 'idle')\n settleWaiting(query, 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 settleWaiting('\\0', SUPERSEDED)\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 settleWaiting('\\0', SUPERSEDED)\n const s = ctx.getState()\n if (s?.status === 'pending' || s?.status === 'searching') {\n ctx.setState({ input: s.input, query: s.query, 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":["import_define","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":["import_define","matching","input"]}
@@ -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;
@@ -70,7 +73,7 @@ var searchOptions = {
70
73
  }
71
74
  };
72
75
  var settledSuperseded = () => Promise.resolve(SUPERSEDED);
73
- var toText = (q) => typeof q === "string" ? q : q == null ? "" : String(q);
76
+ var toText = (q) => q == null ? "" : String(q);
74
77
  var search = (opts = {}) => {
75
78
  const config = {
76
79
  threshold: opts.threshold ?? 0.3,
@@ -95,13 +98,17 @@ 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();
110
+ const ran = /* @__PURE__ */ new WeakMap();
111
+ let waited = false;
105
112
  function indexFor(ctx) {
106
113
  let byColumns = indexes.get(ctx.data);
107
114
  if (!byColumns) {
@@ -109,7 +116,7 @@ var search = (opts = {}) => {
109
116
  indexes.set(ctx.data, byColumns);
110
117
  }
111
118
  let key = byColumns.get(ctx.columns);
112
- if (key === void 0) {
119
+ if (!key) {
113
120
  key = {};
114
121
  byColumns.set(ctx.columns, key);
115
122
  }
@@ -128,7 +135,10 @@ var search = (opts = {}) => {
128
135
  }
129
136
  return sliced(ctx, matching2.key, () => index.match(rows, query));
130
137
  }
131
- const standIn = (indices) => shown && shown.input === indices ? shown.output : indices;
138
+ const standIn = (indices) => {
139
+ waited = true;
140
+ return shown && shown.input === indices ? shown.output : indices;
141
+ };
132
142
  function step(indices, state, ctx) {
133
143
  const query = typeof state?.query === "string" ? state.query.trim() : "";
134
144
  if (query === "") return indices;
@@ -141,7 +151,7 @@ var search = (opts = {}) => {
141
151
  ctx.emit("DK_OPTION_NO_EFFECT", {
142
152
  path: "query",
143
153
  feature: "search",
144
- 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",
145
155
  once: false
146
156
  });
147
157
  }
@@ -170,14 +180,27 @@ var search = (opts = {}) => {
170
180
  interceptors: [
171
181
  defineInterceptor({
172
182
  name: "search",
173
- on: engine ? ["table:destroy", "pipeline:run"] : ["table:destroy"],
183
+ on: ["table:destroy", "pipeline:run", "table:init", "state:dispatch", "table:setOptions"],
174
184
  intercept(ctx, next) {
175
185
  const table = ctx.table;
176
186
  if (ctx.op === "table:destroy") {
177
187
  instances.get(table)?.();
178
188
  return next();
179
189
  }
180
- return engine.run(table, next);
190
+ if (ctx.op !== "pipeline:run") {
191
+ const out = next();
192
+ resumes.get(table)?.();
193
+ return out;
194
+ }
195
+ const prev = waited;
196
+ waited = false;
197
+ try {
198
+ return engine ? engine.run(table, next) : next();
199
+ } finally {
200
+ const w = waited;
201
+ waited = prev;
202
+ ran.get(table)?.(w);
203
+ }
181
204
  }
182
205
  })
183
206
  ],
@@ -186,11 +209,12 @@ var search = (opts = {}) => {
186
209
  const write = (input2, query, status, error) => {
187
210
  const s = ctx.getState();
188
211
  if (status !== "error" && s?.input === input2 && s.query === query && s.status === status && !("error" in s)) {
189
- return;
212
+ return false;
190
213
  }
191
214
  ctx.setState(
192
215
  status === "error" ? { input: input2, query, status, error } : { input: input2, query, status }
193
216
  );
217
+ return ctx.getState() !== s;
194
218
  };
195
219
  const applied = () => ctx.getState()?.query ?? "";
196
220
  const input = () => ctx.getState()?.input ?? "";
@@ -198,11 +222,6 @@ var search = (opts = {}) => {
198
222
  const waitFor = (query) => new Promise((resolve) => {
199
223
  waiting.push({ query, resolve });
200
224
  });
201
- const settleWaiting = (query, outcome) => {
202
- const list = waiting;
203
- waiting = [];
204
- for (const w of list) w.resolve(w.query === query ? outcome : SUPERSEDED);
205
- };
206
225
  const replaceWaiting = (query) => {
207
226
  if (waiting.length === 0) return;
208
227
  const keep = [];
@@ -218,6 +237,22 @@ var search = (opts = {}) => {
218
237
  waiting = [];
219
238
  return keep;
220
239
  };
240
+ let held = [];
241
+ let busy = false;
242
+ const release = (outcome) => {
243
+ const list = held;
244
+ held = [];
245
+ for (const w of list) w.resolve(outcome);
246
+ };
247
+ const supersede = () => {
248
+ held = [...waiting, ...held];
249
+ waiting = [];
250
+ release(SUPERSEDED);
251
+ };
252
+ ran.set(table, (w) => {
253
+ busy = w;
254
+ if (!w) release(APPLIED);
255
+ });
221
256
  const session = engine?.session({
222
257
  ctx,
223
258
  write,
@@ -225,10 +260,13 @@ var search = (opts = {}) => {
225
260
  input,
226
261
  claim
227
262
  });
263
+ let scheduled = false;
228
264
  const applyQuery = (query, inp = input()) => {
265
+ scheduled = false;
229
266
  if (session?.apply(query, inp)) return;
230
- write(inp, query, "idle");
231
- settleWaiting(query, APPLIED);
267
+ if (held[0]?.query !== query) release(SUPERSEDED);
268
+ held.push(...claim(query));
269
+ if (!write(inp, query, "idle") && !busy) release(APPLIED);
232
270
  };
233
271
  let calling = false;
234
272
  let appliedNow;
@@ -238,10 +276,27 @@ var search = (opts = {}) => {
238
276
  };
239
277
  const timer = strategy === "debounce" ? debouncedSignal(applied, apply, wait, { behavior: typing.behavior ?? "trailing" }) : strategy === "throttle" ? throttle(apply, wait) : void 0;
240
278
  const resting = () => session?.busy() ? "searching" : "idle";
241
- 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 = () => {
242
293
  timer?.cancel();
294
+ drop.cancel();
295
+ };
296
+ instances.set(table, () => {
297
+ stop();
243
298
  session?.destroy();
244
- settleWaiting("\0", SUPERSEDED);
299
+ supersede();
245
300
  });
246
301
  const commit = (next) => {
247
302
  const current = applied();
@@ -253,7 +308,7 @@ var search = (opts = {}) => {
253
308
  return p;
254
309
  }
255
310
  if (!timer || query === current) {
256
- timer?.cancel();
311
+ stop();
257
312
  applyQuery(query, next);
258
313
  return p;
259
314
  }
@@ -270,13 +325,15 @@ var search = (opts = {}) => {
270
325
  }
271
326
  if (strategy === "debounce" && typing.behavior === "leading") {
272
327
  write(next, current, resting());
328
+ drop();
273
329
  return p;
274
330
  }
331
+ scheduled = true;
275
332
  write(next, current, "pending");
276
333
  return p;
277
334
  };
278
335
  const applyNow = (next) => {
279
- timer?.cancel();
336
+ stop();
280
337
  const query = target(next);
281
338
  replaceWaiting(query);
282
339
  const p = waitFor(query);
@@ -287,12 +344,13 @@ var search = (opts = {}) => {
287
344
  setSearch: (query) => commit(toText(query)),
288
345
  flushSearch: () => applyNow(input()),
289
346
  cancelSearch: () => {
290
- timer?.cancel();
347
+ scheduled = false;
348
+ stop();
291
349
  session?.abort();
292
- settleWaiting("\0", SUPERSEDED);
350
+ supersede();
293
351
  const s = ctx.getState();
294
352
  if (s?.status === "pending" || s?.status === "searching") {
295
- ctx.setState({ input: s.input, query: s.query, status: "idle" });
353
+ ctx.setState({ ...s, status: "idle" });
296
354
  }
297
355
  },
298
356
  clearSearch: () => {
@@ -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 => (typeof q === 'string' ? q : 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\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 === undefined) {\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 shown && shown.input === indices ? shown.output : indices\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: engine ? ['table:destroy', 'pipeline:run'] : ['table:destroy'],\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 return (engine as SearchEngine).run(table, next)\n },\n }),\n ],\n createApi: (ctx): SearchApi => {\n const table = ctx.table as unknown as object\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\n }\n ctx.setState(\n status === 'error' ? { input, query, status, error } : { input, query, status },\n )\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 /** Resolves the waiters of `query` with `outcome`, every other one `'superseded'`. */\n const settleWaiting = (query: string, outcome: SearchOutcome) => {\n const list = waiting\n waiting = []\n for (const w of list) w.resolve(w.query === query ? outcome : SUPERSEDED)\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 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 write(inp, query, 'idle')\n settleWaiting(query, 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 settleWaiting('\\0', SUPERSEDED)\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 settleWaiting('\\0', SUPERSEDED)\n const s = ctx.getState()\n if (s?.status === 'pending' || s?.status === 'searching') {\n ctx.setState({ input: s.input, query: s.query, 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