@datakable/core 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/README.md +5 -3
  2. package/dist/catalog/codes.cjs +12 -8
  3. package/dist/catalog/codes.cjs.map +1 -1
  4. package/dist/catalog/codes.js +12 -8
  5. package/dist/catalog/codes.js.map +1 -1
  6. package/dist/catalog/slots.cjs +10 -6
  7. package/dist/catalog/slots.cjs.map +1 -1
  8. package/dist/catalog/slots.js +10 -6
  9. package/dist/catalog/slots.js.map +1 -1
  10. package/dist/{codes-OsjAR3Wz.d.cts → codes-B9bCK73-.d.cts} +30 -4
  11. package/dist/{codes-OsjAR3Wz.d.ts → codes-B9bCK73-.d.ts} +30 -4
  12. package/dist/column/column-def.cjs.map +1 -1
  13. package/dist/column/get-value.cjs +6 -1
  14. package/dist/column/get-value.cjs.map +1 -1
  15. package/dist/column/get-value.js +6 -1
  16. package/dist/column/get-value.js.map +1 -1
  17. package/dist/column-filters.d.cts +2 -2
  18. package/dist/column-filters.d.ts +2 -2
  19. package/dist/diagnostics/build.cjs +4 -1
  20. package/dist/diagnostics/build.cjs.map +1 -1
  21. package/dist/diagnostics/build.js +4 -1
  22. package/dist/diagnostics/build.js.map +1 -1
  23. package/dist/diagnostics/catalog.cjs +11 -5
  24. package/dist/diagnostics/catalog.cjs.map +1 -1
  25. package/dist/diagnostics/catalog.js +10 -5
  26. package/dist/diagnostics/catalog.js.map +1 -1
  27. package/dist/diagnostics/codes.cjs +12 -8
  28. package/dist/diagnostics/codes.cjs.map +1 -1
  29. package/dist/diagnostics/codes.js +12 -8
  30. package/dist/diagnostics/codes.js.map +1 -1
  31. package/dist/diagnostics/console.cjs +13 -6
  32. package/dist/diagnostics/console.cjs.map +1 -1
  33. package/dist/diagnostics/console.js +13 -6
  34. package/dist/diagnostics/console.js.map +1 -1
  35. package/dist/diagnostics/env.cjs +1 -0
  36. package/dist/diagnostics/env.cjs.map +1 -1
  37. package/dist/diagnostics/env.js +1 -0
  38. package/dist/diagnostics/env.js.map +1 -1
  39. package/dist/diagnostics/explain-text.cjs +2 -1
  40. package/dist/diagnostics/explain-text.cjs.map +1 -1
  41. package/dist/diagnostics/explain-text.js +2 -1
  42. package/dist/diagnostics/explain-text.js.map +1 -1
  43. package/dist/diagnostics/explain.cjs +3 -2
  44. package/dist/diagnostics/explain.cjs.map +1 -1
  45. package/dist/diagnostics/explain.js +3 -2
  46. package/dist/diagnostics/explain.js.map +1 -1
  47. package/dist/diagnostics/hub.cjs +5 -4
  48. package/dist/diagnostics/hub.cjs.map +1 -1
  49. package/dist/diagnostics/hub.js +5 -4
  50. package/dist/diagnostics/hub.js.map +1 -1
  51. package/dist/diagnostics/process-guard.cjs +26 -0
  52. package/dist/diagnostics/process-guard.cjs.map +1 -0
  53. package/dist/diagnostics/process-guard.js +9 -0
  54. package/dist/diagnostics/process-guard.js.map +1 -0
  55. package/dist/diagnostics/prose.cjs +120 -0
  56. package/dist/diagnostics/prose.cjs.map +1 -0
  57. package/dist/diagnostics/prose.js +93 -0
  58. package/dist/diagnostics/prose.js.map +1 -0
  59. package/dist/diagnostics/redact.cjs +7 -4
  60. package/dist/diagnostics/redact.cjs.map +1 -1
  61. package/dist/diagnostics/redact.js +6 -4
  62. package/dist/diagnostics/redact.js.map +1 -1
  63. package/dist/diagnostics/trace-store.cjs +6 -0
  64. package/dist/diagnostics/trace-store.cjs.map +1 -1
  65. package/dist/diagnostics/trace-store.js +4 -0
  66. package/dist/diagnostics/trace-store.js.map +1 -1
  67. package/dist/diagnostics/tracer.cjs +74 -13
  68. package/dist/diagnostics/tracer.cjs.map +1 -1
  69. package/dist/diagnostics/tracer.js +75 -14
  70. package/dist/diagnostics/tracer.js.map +1 -1
  71. package/dist/diagnostics/types.cjs.map +1 -1
  72. package/dist/{explain-vkD2cewE.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
  73. package/dist/{explain-Dn99IQt0.d.ts → explain-i0OATi_W.d.ts} +13 -4
  74. package/dist/feature/codes.cjs +1 -0
  75. package/dist/feature/codes.cjs.map +1 -1
  76. package/dist/feature/codes.js +1 -0
  77. package/dist/feature/codes.js.map +1 -1
  78. package/dist/feature/install.cjs +4 -3
  79. package/dist/feature/install.cjs.map +1 -1
  80. package/dist/feature/install.js +4 -3
  81. package/dist/feature/install.js.map +1 -1
  82. package/dist/features/column-filters/codes.cjs +1 -0
  83. package/dist/features/column-filters/codes.cjs.map +1 -1
  84. package/dist/features/column-filters/codes.js +1 -0
  85. package/dist/features/column-filters/codes.js.map +1 -1
  86. package/dist/features/column-filters/column-filters.cjs +4 -2
  87. package/dist/features/column-filters/column-filters.cjs.map +1 -1
  88. package/dist/features/column-filters/column-filters.js +4 -2
  89. package/dist/features/column-filters/column-filters.js.map +1 -1
  90. package/dist/features/column-filters/match.cjs +5 -5
  91. package/dist/features/column-filters/match.cjs.map +1 -1
  92. package/dist/features/column-filters/match.js +5 -5
  93. package/dist/features/column-filters/match.js.map +1 -1
  94. package/dist/features/pagination/codes.cjs +1 -0
  95. package/dist/features/pagination/codes.cjs.map +1 -1
  96. package/dist/features/pagination/codes.js +1 -0
  97. package/dist/features/pagination/codes.js.map +1 -1
  98. package/dist/features/pagination/pagination.cjs +16 -13
  99. package/dist/features/pagination/pagination.cjs.map +1 -1
  100. package/dist/features/pagination/pagination.js +16 -13
  101. package/dist/features/pagination/pagination.js.map +1 -1
  102. package/dist/features/search/codes.cjs +1 -0
  103. package/dist/features/search/codes.cjs.map +1 -1
  104. package/dist/features/search/codes.js +1 -0
  105. package/dist/features/search/codes.js.map +1 -1
  106. package/dist/features/search/search-index.cjs +68 -15
  107. package/dist/features/search/search-index.cjs.map +1 -1
  108. package/dist/features/search/search-index.js +68 -15
  109. package/dist/features/search/search-index.js.map +1 -1
  110. package/dist/features/search/search.cjs +41 -9
  111. package/dist/features/search/search.cjs.map +1 -1
  112. package/dist/features/search/search.js +41 -9
  113. package/dist/features/search/search.js.map +1 -1
  114. package/dist/features/search/skipped-codes.cjs +45 -0
  115. package/dist/features/search/skipped-codes.cjs.map +1 -0
  116. package/dist/features/search/skipped-codes.js +20 -0
  117. package/dist/features/search/skipped-codes.js.map +1 -0
  118. package/dist/features/sorting/codes.cjs +1 -0
  119. package/dist/features/sorting/codes.cjs.map +1 -1
  120. package/dist/features/sorting/codes.js +1 -0
  121. package/dist/features/sorting/codes.js.map +1 -1
  122. package/dist/features/sorting/sorting.cjs +16 -20
  123. package/dist/features/sorting/sorting.cjs.map +1 -1
  124. package/dist/features/sorting/sorting.js +16 -20
  125. package/dist/features/sorting/sorting.js.map +1 -1
  126. package/dist/features/sorting/strategy.cjs +4 -4
  127. package/dist/features/sorting/strategy.cjs.map +1 -1
  128. package/dist/features/sorting/strategy.js +4 -4
  129. package/dist/features/sorting/strategy.js.map +1 -1
  130. package/dist/features/validity/codes.cjs +1 -0
  131. package/dist/features/validity/codes.cjs.map +1 -1
  132. package/dist/features/validity/codes.js +1 -0
  133. package/dist/features/validity/codes.js.map +1 -1
  134. package/dist/features/validity/validity.cjs +3 -3
  135. package/dist/features/validity/validity.cjs.map +1 -1
  136. package/dist/features/validity/validity.js +3 -3
  137. package/dist/features/validity/validity.js.map +1 -1
  138. package/dist/index.d.cts +13 -10
  139. package/dist/index.d.ts +13 -10
  140. package/dist/ingest/data-check-codes.cjs +4 -2
  141. package/dist/ingest/data-check-codes.cjs.map +1 -1
  142. package/dist/ingest/data-check-codes.js +4 -2
  143. package/dist/ingest/data-check-codes.js.map +1 -1
  144. package/dist/ingest/data-checks.cjs +1 -1
  145. package/dist/ingest/data-checks.cjs.map +1 -1
  146. package/dist/ingest/data-checks.js +1 -1
  147. package/dist/ingest/data-checks.js.map +1 -1
  148. package/dist/ingest/row-key-codes.cjs +1 -0
  149. package/dist/ingest/row-key-codes.cjs.map +1 -1
  150. package/dist/ingest/row-key-codes.js +1 -0
  151. package/dist/ingest/row-key-codes.js.map +1 -1
  152. package/dist/ingest/row-key.cjs +23 -20
  153. package/dist/ingest/row-key.cjs.map +1 -1
  154. package/dist/ingest/row-key.js +23 -20
  155. package/dist/ingest/row-key.js.map +1 -1
  156. package/dist/ingest/schema-codes.cjs +1 -0
  157. package/dist/ingest/schema-codes.cjs.map +1 -1
  158. package/dist/ingest/schema-codes.js +1 -0
  159. package/dist/ingest/schema-codes.js.map +1 -1
  160. package/dist/ingest/unique-fields.cjs +2 -2
  161. package/dist/ingest/unique-fields.cjs.map +1 -1
  162. package/dist/ingest/unique-fields.js +2 -2
  163. package/dist/ingest/unique-fields.js.map +1 -1
  164. package/dist/intercept/boundary.cjs +4 -2
  165. package/dist/intercept/boundary.cjs.map +1 -1
  166. package/dist/intercept/boundary.js +4 -2
  167. package/dist/intercept/boundary.js.map +1 -1
  168. package/dist/intercept/cell-batch.cjs +4 -4
  169. package/dist/intercept/cell-batch.cjs.map +1 -1
  170. package/dist/intercept/cell-batch.js +4 -4
  171. package/dist/intercept/cell-batch.js.map +1 -1
  172. package/dist/intercept/codes.cjs +1 -0
  173. package/dist/intercept/codes.cjs.map +1 -1
  174. package/dist/intercept/codes.js +1 -0
  175. package/dist/intercept/codes.js.map +1 -1
  176. package/dist/intercept/fallbacks.cjs +12 -20
  177. package/dist/intercept/fallbacks.cjs.map +1 -1
  178. package/dist/intercept/fallbacks.js +12 -20
  179. package/dist/intercept/fallbacks.js.map +1 -1
  180. package/dist/intercept/runtime.cjs +7 -9
  181. package/dist/intercept/runtime.cjs.map +1 -1
  182. package/dist/intercept/runtime.js +7 -9
  183. package/dist/intercept/runtime.js.map +1 -1
  184. package/dist/messages/index.cjs +4 -1
  185. package/dist/messages/index.cjs.map +1 -1
  186. package/dist/messages/index.js +4 -1
  187. package/dist/messages/index.js.map +1 -1
  188. package/dist/pagination.d.cts +2 -2
  189. package/dist/pagination.d.ts +2 -2
  190. package/dist/pipeline/codes.cjs +1 -0
  191. package/dist/pipeline/codes.cjs.map +1 -1
  192. package/dist/pipeline/codes.js +1 -0
  193. package/dist/pipeline/codes.js.map +1 -1
  194. package/dist/pipeline/column-cache.cjs +94 -76
  195. package/dist/pipeline/column-cache.cjs.map +1 -1
  196. package/dist/pipeline/column-cache.js +92 -75
  197. package/dist/pipeline/column-cache.js.map +1 -1
  198. package/dist/pipeline/dev-checks.cjs +1 -0
  199. package/dist/pipeline/dev-checks.cjs.map +1 -1
  200. package/dist/pipeline/dev-checks.js +1 -0
  201. package/dist/pipeline/dev-checks.js.map +1 -1
  202. package/dist/pipeline/env-codes.cjs +3 -2
  203. package/dist/pipeline/env-codes.cjs.map +1 -1
  204. package/dist/pipeline/env-codes.js +3 -2
  205. package/dist/pipeline/env-codes.js.map +1 -1
  206. package/dist/pipeline/perf-codes.cjs +1 -0
  207. package/dist/pipeline/perf-codes.cjs.map +1 -1
  208. package/dist/pipeline/perf-codes.js +1 -0
  209. package/dist/pipeline/perf-codes.js.map +1 -1
  210. package/dist/pipeline/runner.cjs +17 -9
  211. package/dist/pipeline/runner.cjs.map +1 -1
  212. package/dist/pipeline/runner.js +17 -9
  213. package/dist/pipeline/runner.js.map +1 -1
  214. package/dist/pipeline/slice.cjs +3 -1
  215. package/dist/pipeline/slice.cjs.map +1 -1
  216. package/dist/pipeline/slice.js +3 -1
  217. package/dist/pipeline/slice.js.map +1 -1
  218. package/dist/schema.d.cts +2 -2
  219. package/dist/schema.d.ts +2 -2
  220. package/dist/{search-async-entry-v5JdEk5Y.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
  221. package/dist/{search-async-entry-DGSw2pDL.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
  222. package/dist/search-async.d.cts +3 -3
  223. package/dist/search-async.d.ts +3 -3
  224. package/dist/search.d.cts +8 -6
  225. package/dist/search.d.ts +8 -6
  226. package/dist/server.d.cts +3 -3
  227. package/dist/server.d.ts +3 -3
  228. package/dist/sorting.d.cts +2 -2
  229. package/dist/sorting.d.ts +2 -2
  230. package/dist/source/codes.cjs +1 -0
  231. package/dist/source/codes.cjs.map +1 -1
  232. package/dist/source/codes.js +1 -0
  233. package/dist/source/codes.js.map +1 -1
  234. package/dist/source/fetch-codes.cjs +1 -0
  235. package/dist/source/fetch-codes.cjs.map +1 -1
  236. package/dist/source/fetch-codes.js +1 -0
  237. package/dist/source/fetch-codes.js.map +1 -1
  238. package/dist/table/codes.cjs +1 -0
  239. package/dist/table/codes.cjs.map +1 -1
  240. package/dist/table/codes.js +1 -0
  241. package/dist/table/codes.js.map +1 -1
  242. package/dist/table/create-table.cjs +39 -9
  243. package/dist/table/create-table.cjs.map +1 -1
  244. package/dist/table/create-table.js +39 -9
  245. package/dist/table/create-table.js.map +1 -1
  246. package/dist/table/data-mutation.cjs +1 -0
  247. package/dist/table/data-mutation.cjs.map +1 -1
  248. package/dist/table/data-mutation.js +1 -0
  249. package/dist/table/data-mutation.js.map +1 -1
  250. package/dist/table/internals.cjs.map +1 -1
  251. package/dist/table/internals.js.map +1 -1
  252. package/dist/table/ssr-codes.cjs +1 -0
  253. package/dist/table/ssr-codes.cjs.map +1 -1
  254. package/dist/table/ssr-codes.js +1 -0
  255. package/dist/table/ssr-codes.js.map +1 -1
  256. package/dist/table/ssr.cjs +4 -1
  257. package/dist/table/ssr.cjs.map +1 -1
  258. package/dist/table/ssr.js +4 -1
  259. package/dist/table/ssr.js.map +1 -1
  260. package/dist/table/state-feedback.cjs +19 -0
  261. package/dist/table/state-feedback.cjs.map +1 -1
  262. package/dist/table/state-feedback.js +19 -0
  263. package/dist/table/state-feedback.js.map +1 -1
  264. package/dist/table/types.cjs.map +1 -1
  265. package/dist/table/types.js.map +1 -1
  266. package/dist/validity.d.cts +2 -2
  267. package/dist/validity.d.ts +2 -2
  268. package/dist/version.cjs +1 -1
  269. package/dist/version.cjs.map +1 -1
  270. package/dist/version.js +1 -1
  271. package/dist/version.js.map +1 -1
  272. package/dist/versioning/codes.cjs +18 -0
  273. package/dist/versioning/codes.cjs.map +1 -1
  274. package/dist/versioning/codes.js +18 -0
  275. package/dist/versioning/codes.js.map +1 -1
  276. package/dist/versioning/registry.cjs +4 -2
  277. package/dist/versioning/registry.cjs.map +1 -1
  278. package/dist/versioning/registry.js +4 -2
  279. package/dist/versioning/registry.js.map +1 -1
  280. package/package.json +39 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAwB1D,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,MACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAwBP,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,MACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
@@ -1,5 +1,6 @@
1
1
  // src/features/pagination/codes.ts
2
2
  import { defineDiagnostic } from "../../diagnostics/catalog.js";
3
+ import "../../diagnostics/process-guard.js";
3
4
  var ACCEPTED = {
4
5
  pageIndex: "an integer \u2265 0",
5
6
  pageSize: "an integer > 0"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAwB1D,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,EACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAwBP,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,EACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
@@ -25,6 +25,7 @@ __export(pagination_exports, {
25
25
  module.exports = __toCommonJS(pagination_exports);
26
26
  var import_options = require("../../catalog/options.cjs");
27
27
  var import_catalog = require("../../diagnostics/catalog.cjs");
28
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
28
29
  var import_redact = require("../../diagnostics/redact.cjs");
29
30
  var import_define = require("../../feature/define.cjs");
30
31
  var import_define2 = require("../../intercept/define.cjs");
@@ -90,8 +91,8 @@ var pagination = (opts = {}) => {
90
91
  let missingReported = false;
91
92
  const rowCount = () => {
92
93
  if (mode !== "server") return table.getRowModel().filteredCount;
93
- const n = internals.rowCount();
94
- if (n !== void 0 || internals.hasFetcher()) return n;
94
+ const n2 = internals.rowCount();
95
+ if (n2 !== void 0 || internals.hasFetcher()) return n2;
95
96
  const rows = table.getRowModel().totalCount;
96
97
  if (!missingReported) {
97
98
  missingReported = true;
@@ -102,17 +103,17 @@ var pagination = (opts = {}) => {
102
103
  function sanitize(s, prev) {
103
104
  const fallbackSize = isSize(prev?.pageSize) ? prev.pageSize : initialSize;
104
105
  if (s === null || typeof s !== "object") {
105
- report("state", "pageIndex", s, "the pagination state was reset to its initial value");
106
+ report("state", "pageIndex", s, "page-reset");
106
107
  return { pageIndex: 0, pageSize: initialSize };
107
108
  }
108
109
  const { pageIndex, pageSize } = s;
109
110
  let fixed2;
110
111
  if (!isIndex(pageIndex)) {
111
- report("state", "pageIndex", pageIndex, "pageIndex was replaced by 0");
112
+ report("state", "pageIndex", pageIndex, "page-index-0");
112
113
  fixed2 = { ...s, pageIndex: 0 };
113
114
  }
114
115
  if (!isSize(pageSize)) {
115
- report("state", "pageSize", pageSize, `pageSize was replaced by ${fallbackSize}`);
116
+ report("state", "pageSize", pageSize, `page-size|${fallbackSize}`);
116
117
  fixed2 = { ...fixed2 ?? s, pageSize: fallbackSize };
117
118
  }
118
119
  return fixed2;
@@ -120,9 +121,8 @@ var pagination = (opts = {}) => {
120
121
  function report(source, field, value, fallback) {
121
122
  ctx.emit("DK_INVALID_PAGINATION", { source, field, received: received(value), fallback });
122
123
  }
123
- function current() {
124
+ function current(count = rowCount()) {
124
125
  const s = ctx.getState();
125
- const count = rowCount();
126
126
  const index = count === void 0 ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count);
127
127
  return { s, count, index };
128
128
  }
@@ -152,21 +152,24 @@ var pagination = (opts = {}) => {
152
152
  if (badSize) report("option", "pageSize", opts.pageSize, `pageSize ${initialSize} was used`);
153
153
  const fixed = sanitize(ctx.getState(), void 0);
154
154
  if (fixed) ctx.setState(fixed);
155
- table.subscribe(() => {
156
- const { s, index } = current();
157
- if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index });
158
- });
155
+ const clamp = (count) => {
156
+ const { s, index } = current(count);
157
+ if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index });
158
+ };
159
+ const n = mode === "server" ? internals.rowCount() : table.getOptions().data?.length;
160
+ if (n) clamp(n);
161
+ table.subscribe(() => clamp());
159
162
  return {
160
163
  setPageIndex(index) {
161
164
  if (!Number.isInteger(index)) {
162
- report("setPageIndex", "pageIndex", index, "the call was ignored");
165
+ report("setPageIndex", "pageIndex", index, "call-ignored");
163
166
  return;
164
167
  }
165
168
  goTo(index);
166
169
  },
167
170
  setPageSize(size) {
168
171
  if (!isSize(size)) {
169
- report("setPageSize", "pageSize", size, "the call was ignored");
172
+ report("setPageSize", "pageSize", size, "call-ignored");
170
173
  return;
171
174
  }
172
175
  const { s, index } = current();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'the pagination state was reset to its initial value')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'pageIndex was replaced by 0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `pageSize was replaced by ${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown. An unknown count (a server count not\n * fetched yet) clamps nothing.\n */\n function current() {\n const s = ctx.getState()\n const count = rowCount()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => {\n const { s, index } = current()\n if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n })\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'the call was ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'the call was ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["import_define","fixed"]}
1
+ {"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'page-reset')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'page-index-0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `page-size|${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown (for `count` rows, default: the current\n * count). An unknown count (a server count not fetched yet) clamps nothing.\n */\n function current(count = rowCount()) {\n const s = ctx.getState()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n // ... and clamped to the last page, so the state never says \"page 100 of 3\" (#19). Client:\n // the paged rows are a subset of `data`, whose length bounds the last page without running\n // the pipeline here (restored filters leaving fewer rows: the listener below); no data yet\n // clamps nothing. Server: a known `rowCount`.\n const clamp = (count?: number) => {\n const { s, index } = current(count)\n if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n }\n const n =\n mode === 'server' ? internals.rowCount() : (table.getOptions().data as unknown[])?.length\n if (n) clamp(n)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => clamp())\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'call-ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'call-ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["import_define","n","fixed"]}
@@ -1,6 +1,7 @@
1
1
  // src/features/pagination/pagination.ts
2
2
  import { defineOptions } from "../../catalog/options.js";
3
3
  import { keepDiagnostics } from "../../diagnostics/catalog.js";
4
+ import "../../diagnostics/process-guard.js";
4
5
  import { describeValue } from "../../diagnostics/redact.js";
5
6
  import { defineFeature } from "../../feature/define.js";
6
7
  import { defineInterceptor } from "../../intercept/define.js";
@@ -66,8 +67,8 @@ var pagination = (opts = {}) => {
66
67
  let missingReported = false;
67
68
  const rowCount = () => {
68
69
  if (mode !== "server") return table.getRowModel().filteredCount;
69
- const n = internals.rowCount();
70
- if (n !== void 0 || internals.hasFetcher()) return n;
70
+ const n2 = internals.rowCount();
71
+ if (n2 !== void 0 || internals.hasFetcher()) return n2;
71
72
  const rows = table.getRowModel().totalCount;
72
73
  if (!missingReported) {
73
74
  missingReported = true;
@@ -78,17 +79,17 @@ var pagination = (opts = {}) => {
78
79
  function sanitize(s, prev) {
79
80
  const fallbackSize = isSize(prev?.pageSize) ? prev.pageSize : initialSize;
80
81
  if (s === null || typeof s !== "object") {
81
- report("state", "pageIndex", s, "the pagination state was reset to its initial value");
82
+ report("state", "pageIndex", s, "page-reset");
82
83
  return { pageIndex: 0, pageSize: initialSize };
83
84
  }
84
85
  const { pageIndex, pageSize } = s;
85
86
  let fixed2;
86
87
  if (!isIndex(pageIndex)) {
87
- report("state", "pageIndex", pageIndex, "pageIndex was replaced by 0");
88
+ report("state", "pageIndex", pageIndex, "page-index-0");
88
89
  fixed2 = { ...s, pageIndex: 0 };
89
90
  }
90
91
  if (!isSize(pageSize)) {
91
- report("state", "pageSize", pageSize, `pageSize was replaced by ${fallbackSize}`);
92
+ report("state", "pageSize", pageSize, `page-size|${fallbackSize}`);
92
93
  fixed2 = { ...fixed2 ?? s, pageSize: fallbackSize };
93
94
  }
94
95
  return fixed2;
@@ -96,9 +97,8 @@ var pagination = (opts = {}) => {
96
97
  function report(source, field, value, fallback) {
97
98
  ctx.emit("DK_INVALID_PAGINATION", { source, field, received: received(value), fallback });
98
99
  }
99
- function current() {
100
+ function current(count = rowCount()) {
100
101
  const s = ctx.getState();
101
- const count = rowCount();
102
102
  const index = count === void 0 ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count);
103
103
  return { s, count, index };
104
104
  }
@@ -128,21 +128,24 @@ var pagination = (opts = {}) => {
128
128
  if (badSize) report("option", "pageSize", opts.pageSize, `pageSize ${initialSize} was used`);
129
129
  const fixed = sanitize(ctx.getState(), void 0);
130
130
  if (fixed) ctx.setState(fixed);
131
- table.subscribe(() => {
132
- const { s, index } = current();
133
- if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index });
134
- });
131
+ const clamp = (count) => {
132
+ const { s, index } = current(count);
133
+ if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index });
134
+ };
135
+ const n = mode === "server" ? internals.rowCount() : table.getOptions().data?.length;
136
+ if (n) clamp(n);
137
+ table.subscribe(() => clamp());
135
138
  return {
136
139
  setPageIndex(index) {
137
140
  if (!Number.isInteger(index)) {
138
- report("setPageIndex", "pageIndex", index, "the call was ignored");
141
+ report("setPageIndex", "pageIndex", index, "call-ignored");
139
142
  return;
140
143
  }
141
144
  goTo(index);
142
145
  },
143
146
  setPageSize(size) {
144
147
  if (!isSize(size)) {
145
- report("setPageSize", "pageSize", size, "the call was ignored");
148
+ report("setPageSize", "pageSize", size, "call-ignored");
146
149
  return;
147
150
  }
148
151
  const { s, index } = current();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'the pagination state was reset to its initial value')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'pageIndex was replaced by 0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `pageSize was replaced by ${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown. An unknown count (a server count not\n * fetched yet) clamps nothing.\n */\n function current() {\n const s = ctx.getState()\n const count = rowCount()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => {\n const { s, index } = current()\n if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n })\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'the call was ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'the call was ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["fixed"]}
1
+ {"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'page-reset')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'page-index-0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `page-size|${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown (for `count` rows, default: the current\n * count). An unknown count (a server count not fetched yet) clamps nothing.\n */\n function current(count = rowCount()) {\n const s = ctx.getState()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n // ... and clamped to the last page, so the state never says \"page 100 of 3\" (#19). Client:\n // the paged rows are a subset of `data`, whose length bounds the last page without running\n // the pipeline here (restored filters leaving fewer rows: the listener below); no data yet\n // clamps nothing. Server: a known `rowCount`.\n const clamp = (count?: number) => {\n const { s, index } = current(count)\n if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n }\n const n =\n mode === 'server' ? internals.rowCount() : (table.getOptions().data as unknown[])?.length\n if (n) clamp(n)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => clamp())\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'call-ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'call-ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["n","fixed"]}
@@ -25,6 +25,7 @@ __export(codes_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(codes_exports);
27
27
  var import_catalog = require("../../diagnostics/catalog.cjs");
28
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
28
29
  var searchMessages = {
29
30
  DK_SEARCH_FAILED: {
30
31
  problem: (d) => d.problem === "bad-result" ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or { keys: RowKey[] }` : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ""}`,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAkBnD,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,MAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n DK_SEARCH_COLUMN_SKIPPED: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in the table's columns (`options.columns`, else the derived columns). */\n index: number\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAwBA,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,MAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
@@ -1,5 +1,6 @@
1
1
  // src/features/search/codes.ts
2
2
  import { defineDiagnostic } from "../../diagnostics/catalog.js";
3
+ import "../../diagnostics/process-guard.js";
3
4
  var searchMessages = {
4
5
  DK_SEARCH_FAILED: {
5
6
  problem: (d) => d.problem === "bad-result" ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or { keys: RowKey[] }` : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ""}`,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAkBnD,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n DK_SEARCH_COLUMN_SKIPPED: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in the table's columns (`options.columns`, else the derived columns). */\n index: number\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAwBA,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
@@ -26,10 +26,14 @@ __export(search_index_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(search_index_exports);
28
28
  var import_ex_search = require("ex-search");
29
+ var import_get_value = require("../../column/get-value.cjs");
30
+ var import_process_guard = require("../../diagnostics/process-guard.cjs");
31
+ var import_column_cache = require("../../pipeline/column-cache.cjs");
29
32
  var import_columns = require("../../pipeline/columns.cjs");
30
33
  var import_row = require("../../pipeline/row.cjs");
31
34
  var import_capability = require("../../versioning/capability.cjs");
32
35
  var CHUNK = 4096;
36
+ var CELLS = 16384;
33
37
  var hasExSearch = () => [import_ex_search.ExSearch.prototype.search, import_ex_search.ExSearch.prototype.setData, import_ex_search.Scorer.fuzzy].every(
34
38
  (f) => typeof f === "function"
35
39
  );
@@ -47,12 +51,38 @@ function probeExSearch(emit) {
47
51
  ) : (0, import_capability.hasCapability)(hasExSearch);
48
52
  }
49
53
  function numberReader(values) {
50
- for (let r = 0; r < values.length; r++) if (Number.isNaN(values[r])) return void 0;
51
- return (r) => String(values[r]);
54
+ const nullish = (0, import_column_cache.nullishOf)(values);
55
+ return (r) => {
56
+ const v = values[r];
57
+ return !Number.isNaN(v) ? String(v) : nullish?.[r] ? "" : "NaN";
58
+ };
59
+ }
60
+ function* scan(size, get) {
61
+ const over = /* @__PURE__ */ new Map();
62
+ try {
63
+ for (let r = 0; r < size; r++) {
64
+ const v = get(r);
65
+ if (v !== null && (typeof v === "object" || typeof v === "function")) return false;
66
+ if (typeof v === "string" && /[\u0300-\uffff]/.test(v)) {
67
+ const n = v.normalize("NFC");
68
+ if (n !== v) over.set(r, n);
69
+ }
70
+ if (r % CHUNK === CHUNK - 1) yield;
71
+ }
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ const read = (r) => {
76
+ const v = get(r);
77
+ return v === null || v === void 0 ? "" : String(v);
78
+ };
79
+ return over.size ? (r) => over.get(r) ?? read(r) : read;
52
80
  }
53
81
  function* buildIndex(ctx, config, useExSearch) {
54
82
  const cols = (0, import_columns.resolveColumns)(ctx.columns);
55
83
  const declared = cols.list.some((c) => c.def.search !== void 0);
84
+ const data = ctx.data;
85
+ const n = data.length;
56
86
  const keys = [];
57
87
  const reads = [];
58
88
  for (const col of cols.list) {
@@ -60,15 +90,36 @@ function* buildIndex(ctx, config, useExSearch) {
60
90
  const w = col.def.search;
61
91
  const weight = declared ? typeof w === "number" && w > 0 ? w : 0 : 1;
62
92
  if (weight === 0) continue;
63
- let read;
64
- if (!col.format) {
93
+ const path = col.format ? void 0 : col.path;
94
+ let failed = false;
95
+ const onError = () => {
96
+ failed = true;
97
+ };
98
+ let read = path ? yield* scan(n, (r) => (0, import_get_value.getValue)(data[r], path, onError)) : void 0;
99
+ if (failed || read === false && declared) read = void 0;
100
+ if (read === void 0) {
65
101
  const values = ctx.getColumnValues(col.id);
66
- if (values instanceof Float64Array) read = numberReader(values);
102
+ if (!values) continue;
103
+ yield;
104
+ const numbers = values instanceof Float64Array;
105
+ if (numbers && !col.format) read = numberReader(values);
106
+ else {
107
+ const own = numbers || declared && col.format ? void 0 : yield* scan(n, (r) => values[r]);
108
+ if (own === false && !declared) read = false;
109
+ else if (own && !col.format) read = own;
110
+ else {
111
+ const sliced = ctx.formattedSliced;
112
+ const formatted = sliced ? yield* sliced(col.id) : ctx.getFormattedValues(col.id);
113
+ if (!formatted) continue;
114
+ read = yield* scan(n, (r) => formatted[r]);
115
+ }
116
+ }
67
117
  }
68
118
  if (!read) {
69
- const formatted = ctx.getFormattedValues(col.id);
70
- if (!formatted) continue;
71
- read = (r) => formatted[r] ?? "";
119
+ if (process.env.NODE_ENV !== "production" && read === false) {
120
+ ctx.emit("DK_SEARCH_COLUMN_SKIPPED", { column: col.id, index: col.index });
121
+ }
122
+ continue;
72
123
  }
73
124
  keys.push({ name: `c${keys.length}`, weight });
74
125
  reads.push(read);
@@ -76,14 +127,16 @@ function* buildIndex(ctx, config, useExSearch) {
76
127
  }
77
128
  if (keys.length === 0) return null;
78
129
  const fold = (v) => config.caseSensitive ? v : v.toLowerCase();
130
+ const foldQuery = (q) => fold(q.normalize("NFC"));
79
131
  const at = (k, r) => fold(reads[k](r));
80
- return useExSearch ? exSearchIndex(ctx.data.length, keys, at, fold, config) : containsIndex(keys, at, fold, config);
132
+ return useExSearch ? exSearchIndex(ctx.data.length, keys, at, foldQuery, config) : containsIndex(keys, at, foldQuery, config);
81
133
  }
82
- function* chunked(rows, matchChunk) {
83
- if (rows.length <= CHUNK) return rankHits(matchChunk(rows));
134
+ function* chunked(rows, columns, matchChunk) {
135
+ const size = Math.max(256, Math.floor(CELLS / columns));
136
+ if (rows.length <= size) return rankHits(matchChunk(rows));
84
137
  const hits = [];
85
- for (let k = 0; k < rows.length; k += CHUNK) {
86
- for (const h of matchChunk(rows.subarray(k, k + CHUNK))) hits.push(h);
138
+ for (let k = 0; k < rows.length; k += size) {
139
+ for (const h of matchChunk(rows.subarray(k, k + size))) hits.push(h);
87
140
  yield;
88
141
  }
89
142
  return rankHits(hits);
@@ -145,7 +198,7 @@ function exSearchIndex(size, keys, at, fold, config) {
145
198
  return {
146
199
  match(rows, query) {
147
200
  const folded = fold(query);
148
- return chunked(rows, (chunk) => {
201
+ return chunked(rows, keys.length, (chunk) => {
149
202
  q = folded;
150
203
  view.show(chunk);
151
204
  try {
@@ -161,7 +214,7 @@ function containsIndex(keys, at, fold, config) {
161
214
  return {
162
215
  match(rows, query) {
163
216
  const q = fold(query);
164
- return chunked(rows, (chunk) => {
217
+ return chunked(rows, keys.length, (chunk) => {
165
218
  const hits = [];
166
219
  for (let j = 0; j < chunk.length; j++) {
167
220
  const r = chunk[j];