@datakable/core 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/{codes-AX8XkMNY.d.cts → codes-4sYQv_7J.d.cts} +36 -10
  2. package/dist/{codes-AX8XkMNY.d.ts → codes-4sYQv_7J.d.ts} +36 -10
  3. package/dist/column-filters.d.cts +2 -2
  4. package/dist/column-filters.d.ts +2 -2
  5. package/dist/diagnostics/build.cjs +1 -1
  6. package/dist/diagnostics/build.cjs.map +1 -1
  7. package/dist/diagnostics/build.js +2 -2
  8. package/dist/diagnostics/build.js.map +1 -1
  9. package/dist/diagnostics/console.cjs +15 -4
  10. package/dist/diagnostics/console.cjs.map +1 -1
  11. package/dist/diagnostics/console.js +15 -4
  12. package/dist/diagnostics/console.js.map +1 -1
  13. package/dist/diagnostics/prose.cjs +10 -1
  14. package/dist/diagnostics/prose.cjs.map +1 -1
  15. package/dist/diagnostics/prose.js +9 -1
  16. package/dist/diagnostics/prose.js.map +1 -1
  17. package/dist/{explain-pKAnnxEW.d.cts → explain-DmwN_RG9.d.cts} +11 -4
  18. package/dist/{explain-BVE3ln3G.d.ts → explain-Dr3SmMNY.d.ts} +11 -4
  19. package/dist/feature/install.cjs +2 -1
  20. package/dist/feature/install.cjs.map +1 -1
  21. package/dist/feature/install.js +2 -1
  22. package/dist/feature/install.js.map +1 -1
  23. package/dist/feature/slice-guard.cjs +76 -0
  24. package/dist/feature/slice-guard.cjs.map +1 -0
  25. package/dist/feature/slice-guard.js +50 -0
  26. package/dist/feature/slice-guard.js.map +1 -0
  27. package/dist/features/column-filters/codes.cjs +2 -19
  28. package/dist/features/column-filters/codes.cjs.map +1 -1
  29. package/dist/features/column-filters/codes.js +2 -19
  30. package/dist/features/column-filters/codes.js.map +1 -1
  31. package/dist/features/column-filters/column-filters.cjs +37 -22
  32. package/dist/features/column-filters/column-filters.cjs.map +1 -1
  33. package/dist/features/column-filters/column-filters.js +37 -22
  34. package/dist/features/column-filters/column-filters.js.map +1 -1
  35. package/dist/features/column-filters/match.cjs +6 -2
  36. package/dist/features/column-filters/match.cjs.map +1 -1
  37. package/dist/features/column-filters/match.js +6 -2
  38. package/dist/features/column-filters/match.js.map +1 -1
  39. package/dist/features/pagination/pagination.cjs +2 -2
  40. package/dist/features/pagination/pagination.cjs.map +1 -1
  41. package/dist/features/pagination/pagination.js +2 -2
  42. package/dist/features/pagination/pagination.js.map +1 -1
  43. package/dist/features/search/codes.cjs +18 -2
  44. package/dist/features/search/codes.cjs.map +1 -1
  45. package/dist/features/search/codes.js +16 -1
  46. package/dist/features/search/codes.js.map +1 -1
  47. package/dist/features/search/search-engine.cjs +24 -5
  48. package/dist/features/search/search-engine.cjs.map +1 -1
  49. package/dist/features/search/search-engine.js +25 -6
  50. package/dist/features/search/search-engine.js.map +1 -1
  51. package/dist/features/search/search-index.cjs +23 -44
  52. package/dist/features/search/search-index.cjs.map +1 -1
  53. package/dist/features/search/search-index.js +24 -45
  54. package/dist/features/search/search-index.js.map +1 -1
  55. package/dist/features/sorting/ranks.cjs +117 -50
  56. package/dist/features/sorting/ranks.cjs.map +1 -1
  57. package/dist/features/sorting/ranks.js +117 -50
  58. package/dist/features/sorting/ranks.js.map +1 -1
  59. package/dist/features/sorting/sorting.cjs +87 -32
  60. package/dist/features/sorting/sorting.cjs.map +1 -1
  61. package/dist/features/sorting/sorting.js +87 -32
  62. package/dist/features/sorting/sorting.js.map +1 -1
  63. package/dist/index.d.cts +26 -17
  64. package/dist/index.d.ts +26 -17
  65. package/dist/intercept/runtime.cjs +3 -2
  66. package/dist/intercept/runtime.cjs.map +1 -1
  67. package/dist/intercept/runtime.js +3 -2
  68. package/dist/intercept/runtime.js.map +1 -1
  69. package/dist/messages/index.cjs +2 -0
  70. package/dist/messages/index.cjs.map +1 -1
  71. package/dist/messages/index.js +2 -0
  72. package/dist/messages/index.js.map +1 -1
  73. package/dist/pagination.d.cts +2 -2
  74. package/dist/pagination.d.ts +2 -2
  75. package/dist/pipeline/column-cache.cjs +6 -2
  76. package/dist/pipeline/column-cache.cjs.map +1 -1
  77. package/dist/pipeline/column-cache.js +6 -2
  78. package/dist/pipeline/column-cache.js.map +1 -1
  79. package/dist/pipeline/release.cjs +34 -0
  80. package/dist/pipeline/release.cjs.map +1 -0
  81. package/dist/pipeline/release.js +8 -0
  82. package/dist/pipeline/release.js.map +1 -0
  83. package/dist/pipeline/runner.cjs +15 -1
  84. package/dist/pipeline/runner.cjs.map +1 -1
  85. package/dist/pipeline/runner.js +15 -1
  86. package/dist/pipeline/runner.js.map +1 -1
  87. package/dist/schema.d.cts +2 -2
  88. package/dist/schema.d.ts +2 -2
  89. package/dist/{search-async-entry-ByMJbHjc.d.cts → search-async-entry-BiemO3JT.d.cts} +2 -2
  90. package/dist/{search-async-entry-CFQODE08.d.ts → search-async-entry-GRj0E06n.d.ts} +2 -2
  91. package/dist/search-async.d.cts +3 -3
  92. package/dist/search-async.d.ts +3 -3
  93. package/dist/search.d.cts +4 -4
  94. package/dist/search.d.ts +4 -4
  95. package/dist/server.d.cts +3 -3
  96. package/dist/server.d.ts +3 -3
  97. package/dist/sorting.d.cts +2 -2
  98. package/dist/sorting.d.ts +2 -2
  99. package/dist/source/async-control.cjs +2 -1
  100. package/dist/source/async-control.cjs.map +1 -1
  101. package/dist/source/async-control.js +2 -1
  102. package/dist/source/async-control.js.map +1 -1
  103. package/dist/source/fetcher.cjs.map +1 -1
  104. package/dist/source/fetcher.js.map +1 -1
  105. package/dist/source/server.cjs +58 -5
  106. package/dist/source/server.cjs.map +1 -1
  107. package/dist/source/server.js +58 -5
  108. package/dist/source/server.js.map +1 -1
  109. package/dist/table/create-table.cjs +33 -20
  110. package/dist/table/create-table.cjs.map +1 -1
  111. package/dist/table/create-table.js +33 -20
  112. package/dist/table/create-table.js.map +1 -1
  113. package/dist/table/index.cjs +3 -0
  114. package/dist/table/index.cjs.map +1 -1
  115. package/dist/table/index.js +5 -1
  116. package/dist/table/index.js.map +1 -1
  117. package/dist/table/state-codes.cjs +72 -0
  118. package/dist/table/state-codes.cjs.map +1 -0
  119. package/dist/table/state-codes.js +45 -0
  120. package/dist/table/state-codes.js.map +1 -0
  121. package/dist/table/types.cjs.map +1 -1
  122. package/dist/table/types.js.map +1 -1
  123. package/dist/validity.d.cts +2 -2
  124. package/dist/validity.d.ts +2 -2
  125. package/dist/version.cjs +1 -1
  126. package/dist/version.cjs.map +1 -1
  127. package/dist/version.js +1 -1
  128. package/dist/version.js.map +1 -1
  129. package/package.json +3 -3
  130. package/dist/ingest/test-schemas.cjs +0 -73
  131. package/dist/ingest/test-schemas.cjs.map +0 -1
  132. package/dist/ingest/test-schemas.js +0 -46
  133. package/dist/ingest/test-schemas.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/sorting/ranks.ts"],"sourcesContent":["import { RANK, typeRank } from './compare'\n\n/** Comparisons / rows between two `yield`s of the jobs below (§9.1). */\nconst CHUNK = 4096\n/** Run length sorted natively before the merge passes. */\nconst RUN = 512\n\n/**\n * Sorts `0..n-1` by `cmp` as a time-sliced job (§9.1): native sorts of short runs, then bottom-up\n * merge passes that `yield` every {@link CHUNK} comparisons. Stable.\n */\nfunction* sortPositions(\n n: number,\n cmp: (a: number, b: number) => number,\n): Generator<undefined, Uint32Array, undefined> {\n let a = new Uint32Array(n)\n for (let i = 0; i < n; i++) a[i] = i\n for (let lo = 0; lo < n; lo += RUN) {\n a.subarray(lo, Math.min(lo + RUN, n)).sort(cmp)\n yield\n }\n let b = new Uint32Array(n)\n let ops = 0\n for (let width = RUN; width < n; width *= 2) {\n for (let lo = 0; lo < n; lo += 2 * width) {\n const mid = Math.min(lo + width, n)\n const hi = Math.min(lo + 2 * width, n)\n let i = lo\n let j = mid\n let k = lo\n while (i < mid && j < hi) {\n const x = a[i] as number\n const y = a[j] as number\n if (cmp(y, x) < 0) {\n b[k++] = y\n j++\n } else {\n b[k++] = x\n i++\n }\n if (++ops === CHUNK) {\n ops = 0\n yield\n }\n }\n while (i < mid) b[k++] = a[i++] as number\n while (j < hi) b[k++] = a[j++] as number\n }\n const t = a\n a = b\n b = t\n }\n return a\n}\n\n/**\n * Collation ranks of a string column (§6.4, §9.1): per row, the dense rank of its string among\n * the column's distinct strings in `collator` order (strings the collator finds equal share a\n * rank), `NaN` for a missing value. Sorting by these numbers gives exactly the order of\n * `collator.compare`, with integer keys (counting / radix sort) instead of a collator call per\n * comparison. A time-sliced job; `null` when a present value is not a string.\n */\nexport function* collationRanks(\n values: readonly unknown[],\n collator: Intl.Collator,\n): Generator<undefined, Float64Array | null, undefined> {\n const n = values.length\n const ids = new Map<string, number>()\n const distinct: string[] = []\n /** Per row: its string's position in `distinct`, -1 when missing. */\n const slot = new Int32Array(n)\n for (let r = 0; r < n; r++) {\n const v = values[r]\n if (typeof v === 'string') {\n let id = ids.get(v)\n if (id === undefined) {\n id = distinct.length\n ids.set(v, id)\n distinct.push(v)\n }\n slot[r] = id\n } else if (typeRank(v) === RANK.missing) slot[r] = -1\n else return null\n if (r % CHUNK === CHUNK - 1) yield\n }\n const order = yield* sortPositions(distinct.length, (a, b) =>\n collator.compare(distinct[a] as string, distinct[b] as string),\n )\n const rankOf = new Float64Array(distinct.length)\n let rank = 0\n for (let k = 0; k < order.length; k++) {\n const id = order[k] as number\n const s = distinct[id] as string\n if (k > 0 && collator.compare(distinct[order[k - 1] as number] as string, s) !== 0) rank++\n rankOf[id] = rank\n if (k % CHUNK === CHUNK - 1) yield\n }\n const out = new Float64Array(n)\n for (let r = 0; r < n; r++) {\n const id = slot[r] as number\n out[r] = id < 0 ? Number.NaN : (rankOf[id] as number)\n }\n return out\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/features/sorting/ranks.ts"],"sourcesContent":["import { RANK, typeRank } from './compare'\n\n/** Comparisons / rows between two `yield`s of the jobs below (§9.1). */\nconst CHUNK = 4096\n/**\n * Run length sorted natively before the merge passes (#97, was 512): each doubling saves a merge\n * pass, and one native run of collator calls stays within a slice even under a 4x CPU throttle.\n */\nconst RUN = 1024\n\n/**\n * Sorts `0..n-1` by `cmp` as a time-sliced job (§9.1): native sorts of `run`s, then bottom-up\n * merge passes that `yield` every {@link CHUNK} comparisons. Stable. Also returns `eq`:\n * `eq[k] = 1` when `order[k]` compares equal to `order[k - 1]`, from the comparisons the merges\n * make anyway (#97). Inside a native run it costs a second pass only when that sort saw a 0.\n */\nfunction* sortPositions(\n n: number,\n cmp: (a: number, b: number) => number,\n run: number,\n): Generator<undefined, { order: Uint32Array; eq: Uint8Array }, undefined> {\n let a = new Uint32Array(n)\n let ea = new Uint8Array(n)\n for (let i = 0; i < n; i++) a[i] = i\n for (let lo = 0; lo < n; lo += run) {\n const hi = Math.min(lo + run, n)\n let zero = false\n a.subarray(lo, hi).sort((x, y) => {\n const c = cmp(x, y)\n if (c === 0) zero = true\n return c\n })\n // Neighbours can only be equal if the sort saw a 0 (it must, to order them).\n if (zero)\n for (let k = lo + 1; k < hi; k++) ea[k] = cmp(a[k - 1] as number, a[k] as number) ? 0 : 1\n yield\n }\n let b = new Uint32Array(n)\n let eb = new Uint8Array(n)\n let ops = 0\n for (let width = run; width < n; width *= 2) {\n for (let lo = 0; lo < n; lo += 2 * width) {\n const mid = Math.min(lo + width, n)\n const hi = Math.min(lo + 2 * width, n)\n let i = lo\n let j = mid\n // The run of the last output (-1: none yet) and whether it equals the other run's head.\n let prev = -1\n let same = 0\n for (let k = lo; k < hi; k++) {\n let c = 1\n let src: number\n if (i < mid && j < hi) {\n c = cmp(a[j] as number, a[i] as number)\n src = c < 0 ? 1 : 0\n if (++ops === CHUNK) {\n ops = 0\n yield\n }\n } else src = i < mid ? 0 : 1\n const p = src ? j++ : i++\n b[k] = a[p] as number\n eb[k] = prev === src ? (ea[p] as number) : prev < 0 ? 0 : same\n same = src === 0 && c === 0 ? 1 : 0\n prev = src\n }\n }\n const t = a\n a = b\n b = t\n const e = ea\n ea = eb\n eb = e\n }\n return { order: a, eq: ea }\n}\n\n/**\n * What the next {@link collationRanks} of the same column starts from (#95): the values it ranked,\n * their ranks (the `ranks` it returned, shared) and the distinct strings in rank order (one per\n * rank, shared with the data). No string → id map: a row whose string is unchanged at its position\n * (or at a shifted one) keeps its rank, any other string is found by binary search over `reps`.\n * Never mutated (a job builds a new one), so a cancelled job leaves it usable.\n */\nexport interface RankState {\n readonly values: readonly unknown[]\n readonly ranks: ArrayLike<number>\n readonly reps: readonly string[]\n}\n\nexport interface Ranked {\n readonly ranks: Float64Array\n readonly state: RankState\n}\n\n/**\n * Collation ranks of a string column (§6.4, §9.1): per row, the dense rank of its string among\n * the column's distinct strings in `collator` order (strings the collator finds equal share a\n * rank), `NaN` for a missing value. Sorting by these numbers gives exactly the order of\n * `collator.compare`, with integer keys (counting / radix sort) instead of a collator call per\n * comparison. A time-sliced job; `null` when a present value is not a string.\n *\n * Incremental across data versions (#95): a string ranked in `prev` (the state of an earlier array\n * of the same column) keeps that rank as its sort key. It is read from the same row of `prev` when\n * the string is the same there (or at the row offset of the last match, for inserted / removed\n * rows), else found by binary search over the ranked strings; a new string is placed in the gap\n * below the first greater one. Only new strings are sorted (two in the same gap call the\n * collator): a one-row edit costs O(log d) collator calls (and a sort of numbers), not a cold\n * sort. The new state holds only the strings present now.\n */\nexport function* collationRanks(\n values: readonly unknown[],\n collator: Intl.Collator,\n prev: RankState = { values: [], ranks: [], reps: [] },\n): Generator<undefined, Ranked | null, undefined> {\n const n = values.length\n const { values: was, ranks: wasRank, reps } = prev\n const m = reps.length\n /** Per row: its rank in `prev`, `m + id` for the new string `fresh[id]`, NaN when missing. */\n const out = new Float64Array(n)\n /** Ranks of `prev` still present (the rest are dropped from the new state). */\n const used = new Uint8Array(m)\n // New strings only (a cold job: every distinct string), dropped with the job.\n const freshIds = new Map<string, number>()\n const fresh: string[] = []\n /** Per new string: `g - 0.5`, between ranks g-1 and g of `prev`. */\n const gap: number[] = []\n /** Per rank of `prev`: one row holding it, built on the first lookup that finds one. */\n let rowOf: Int32Array | undefined\n /** Offset to `prev`'s row of the last match: rows inserted / removed above shift it. */\n let shift = 0\n let ops = 0\n for (let r = 0; r < n; r++) {\n const v = values[r]\n if (typeof v === 'string') {\n let k: number | undefined\n const q = r + shift\n // Out of range: `undefined`, never a string.\n if (was[q] === v) k = wasRank[q] as number\n else {\n const id = freshIds.get(v)\n if (id !== undefined) k = m + id\n else {\n let lo = 0\n let hi = m\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n const c = collator.compare(v, reps[mid] as string)\n ops++\n if (c === 0) {\n k = mid\n break\n }\n if (c < 0) hi = mid\n else lo = mid + 1\n }\n if (k === undefined) {\n k = m + fresh.length\n freshIds.set(v, fresh.length)\n fresh.push(v)\n gap.push(lo - 0.5)\n } else {\n if (!rowOf) {\n rowOf = new Int32Array(m)\n for (let p = 0; p < was.length; p++) {\n const x = wasRank[p] as number\n if (!Number.isNaN(x)) rowOf[x] = p\n }\n }\n shift = (rowOf[k] as number) - r\n }\n }\n }\n if (k < m) used[k] = 1\n out[r] = k\n } else if (typeRank(v) === RANK.missing) out[r] = Number.NaN\n else return null\n if (++ops >= CHUNK) {\n ops = 0\n yield\n }\n }\n // The same strings as `prev` (a new array of equal content, rows moved): the same ranks.\n if (!fresh.length && !used.includes(0)) return { ranks: out, state: { values, ranks: out, reps } }\n\n const f = fresh.length\n const { order, eq } = yield* sortPositions(\n f,\n // By gap; two new strings in the same gap (cold: all of them) go to the collator.\n (a, b) =>\n (gap[a] as number) - (gap[b] as number) ||\n collator.compare(fresh[a] as string, fresh[b] as string),\n // Comparing numbers is far cheaper: longer native runs, fewer merge passes.\n m ? 8 * RUN : RUN,\n )\n // Merge the kept ranks of `prev` and the new strings (each before the rank above its gap). No\n // collator call: one pass, like the rank lookup below.\n const remap = new Float64Array(m + f)\n const kept: string[] = []\n let rank = -1\n let j = 0\n for (let g = 0; g <= m; g++) {\n while (j < f && (gap[order[j] as number] as number) < g) {\n const id = order[j] as number\n if (!eq[j]) kept[++rank] = fresh[id] as string\n remap[m + id] = rank\n j++\n }\n if (g < m && used[g]) {\n kept[++rank] = reps[g] as string\n remap[g] = rank\n }\n }\n for (let r = 0; r < n; r++) {\n const k = out[r] as number\n if (!Number.isNaN(k)) out[r] = remap[k] as number\n }\n return { ranks: out, state: { values, ranks: out, reps: kept } }\n}\n"],"mappings":"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","names":[]}
@@ -1,85 +1,152 @@
1
1
  // src/features/sorting/ranks.ts
2
2
  import { RANK, typeRank } from "./compare.js";
3
3
  var CHUNK = 4096;
4
- var RUN = 512;
5
- function* sortPositions(n, cmp) {
4
+ var RUN = 1024;
5
+ function* sortPositions(n, cmp, run) {
6
6
  let a = new Uint32Array(n);
7
+ let ea = new Uint8Array(n);
7
8
  for (let i = 0; i < n; i++) a[i] = i;
8
- for (let lo = 0; lo < n; lo += RUN) {
9
- a.subarray(lo, Math.min(lo + RUN, n)).sort(cmp);
9
+ for (let lo = 0; lo < n; lo += run) {
10
+ const hi = Math.min(lo + run, n);
11
+ let zero = false;
12
+ a.subarray(lo, hi).sort((x, y) => {
13
+ const c = cmp(x, y);
14
+ if (c === 0) zero = true;
15
+ return c;
16
+ });
17
+ if (zero)
18
+ for (let k = lo + 1; k < hi; k++) ea[k] = cmp(a[k - 1], a[k]) ? 0 : 1;
10
19
  yield;
11
20
  }
12
21
  let b = new Uint32Array(n);
22
+ let eb = new Uint8Array(n);
13
23
  let ops = 0;
14
- for (let width = RUN; width < n; width *= 2) {
24
+ for (let width = run; width < n; width *= 2) {
15
25
  for (let lo = 0; lo < n; lo += 2 * width) {
16
26
  const mid = Math.min(lo + width, n);
17
27
  const hi = Math.min(lo + 2 * width, n);
18
28
  let i = lo;
19
29
  let j = mid;
20
- let k = lo;
21
- while (i < mid && j < hi) {
22
- const x = a[i];
23
- const y = a[j];
24
- if (cmp(y, x) < 0) {
25
- b[k++] = y;
26
- j++;
27
- } else {
28
- b[k++] = x;
29
- i++;
30
- }
31
- if (++ops === CHUNK) {
32
- ops = 0;
33
- yield;
34
- }
30
+ let prev = -1;
31
+ let same = 0;
32
+ for (let k = lo; k < hi; k++) {
33
+ let c = 1;
34
+ let src;
35
+ if (i < mid && j < hi) {
36
+ c = cmp(a[j], a[i]);
37
+ src = c < 0 ? 1 : 0;
38
+ if (++ops === CHUNK) {
39
+ ops = 0;
40
+ yield;
41
+ }
42
+ } else src = i < mid ? 0 : 1;
43
+ const p = src ? j++ : i++;
44
+ b[k] = a[p];
45
+ eb[k] = prev === src ? ea[p] : prev < 0 ? 0 : same;
46
+ same = src === 0 && c === 0 ? 1 : 0;
47
+ prev = src;
35
48
  }
36
- while (i < mid) b[k++] = a[i++];
37
- while (j < hi) b[k++] = a[j++];
38
49
  }
39
50
  const t = a;
40
51
  a = b;
41
52
  b = t;
53
+ const e = ea;
54
+ ea = eb;
55
+ eb = e;
42
56
  }
43
- return a;
57
+ return { order: a, eq: ea };
44
58
  }
45
- function* collationRanks(values, collator) {
59
+ function* collationRanks(values, collator, prev = { values: [], ranks: [], reps: [] }) {
46
60
  const n = values.length;
47
- const ids = /* @__PURE__ */ new Map();
48
- const distinct = [];
49
- const slot = new Int32Array(n);
61
+ const { values: was, ranks: wasRank, reps } = prev;
62
+ const m = reps.length;
63
+ const out = new Float64Array(n);
64
+ const used = new Uint8Array(m);
65
+ const freshIds = /* @__PURE__ */ new Map();
66
+ const fresh = [];
67
+ const gap = [];
68
+ let rowOf;
69
+ let shift = 0;
70
+ let ops = 0;
50
71
  for (let r = 0; r < n; r++) {
51
72
  const v = values[r];
52
73
  if (typeof v === "string") {
53
- let id = ids.get(v);
54
- if (id === void 0) {
55
- id = distinct.length;
56
- ids.set(v, id);
57
- distinct.push(v);
74
+ let k;
75
+ const q = r + shift;
76
+ if (was[q] === v) k = wasRank[q];
77
+ else {
78
+ const id = freshIds.get(v);
79
+ if (id !== void 0) k = m + id;
80
+ else {
81
+ let lo = 0;
82
+ let hi = m;
83
+ while (lo < hi) {
84
+ const mid = lo + hi >>> 1;
85
+ const c = collator.compare(v, reps[mid]);
86
+ ops++;
87
+ if (c === 0) {
88
+ k = mid;
89
+ break;
90
+ }
91
+ if (c < 0) hi = mid;
92
+ else lo = mid + 1;
93
+ }
94
+ if (k === void 0) {
95
+ k = m + fresh.length;
96
+ freshIds.set(v, fresh.length);
97
+ fresh.push(v);
98
+ gap.push(lo - 0.5);
99
+ } else {
100
+ if (!rowOf) {
101
+ rowOf = new Int32Array(m);
102
+ for (let p = 0; p < was.length; p++) {
103
+ const x = wasRank[p];
104
+ if (!Number.isNaN(x)) rowOf[x] = p;
105
+ }
106
+ }
107
+ shift = rowOf[k] - r;
108
+ }
109
+ }
58
110
  }
59
- slot[r] = id;
60
- } else if (typeRank(v) === RANK.missing) slot[r] = -1;
111
+ if (k < m) used[k] = 1;
112
+ out[r] = k;
113
+ } else if (typeRank(v) === RANK.missing) out[r] = Number.NaN;
61
114
  else return null;
62
- if (r % CHUNK === CHUNK - 1) yield;
115
+ if (++ops >= CHUNK) {
116
+ ops = 0;
117
+ yield;
118
+ }
63
119
  }
64
- const order = yield* sortPositions(
65
- distinct.length,
66
- (a, b) => collator.compare(distinct[a], distinct[b])
120
+ if (!fresh.length && !used.includes(0)) return { ranks: out, state: { values, ranks: out, reps } };
121
+ const f = fresh.length;
122
+ const { order, eq } = yield* sortPositions(
123
+ f,
124
+ // By gap; two new strings in the same gap (cold: all of them) go to the collator.
125
+ (a, b) => gap[a] - gap[b] || collator.compare(fresh[a], fresh[b]),
126
+ // Comparing numbers is far cheaper: longer native runs, fewer merge passes.
127
+ m ? 8 * RUN : RUN
67
128
  );
68
- const rankOf = new Float64Array(distinct.length);
69
- let rank = 0;
70
- for (let k = 0; k < order.length; k++) {
71
- const id = order[k];
72
- const s = distinct[id];
73
- if (k > 0 && collator.compare(distinct[order[k - 1]], s) !== 0) rank++;
74
- rankOf[id] = rank;
75
- if (k % CHUNK === CHUNK - 1) yield;
129
+ const remap = new Float64Array(m + f);
130
+ const kept = [];
131
+ let rank = -1;
132
+ let j = 0;
133
+ for (let g = 0; g <= m; g++) {
134
+ while (j < f && gap[order[j]] < g) {
135
+ const id = order[j];
136
+ if (!eq[j]) kept[++rank] = fresh[id];
137
+ remap[m + id] = rank;
138
+ j++;
139
+ }
140
+ if (g < m && used[g]) {
141
+ kept[++rank] = reps[g];
142
+ remap[g] = rank;
143
+ }
76
144
  }
77
- const out = new Float64Array(n);
78
145
  for (let r = 0; r < n; r++) {
79
- const id = slot[r];
80
- out[r] = id < 0 ? Number.NaN : rankOf[id];
146
+ const k = out[r];
147
+ if (!Number.isNaN(k)) out[r] = remap[k];
81
148
  }
82
- return out;
149
+ return { ranks: out, state: { values, ranks: out, reps: kept } };
83
150
  }
84
151
  export {
85
152
  collationRanks
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/sorting/ranks.ts"],"sourcesContent":["import { RANK, typeRank } from './compare'\n\n/** Comparisons / rows between two `yield`s of the jobs below (§9.1). */\nconst CHUNK = 4096\n/** Run length sorted natively before the merge passes. */\nconst RUN = 512\n\n/**\n * Sorts `0..n-1` by `cmp` as a time-sliced job (§9.1): native sorts of short runs, then bottom-up\n * merge passes that `yield` every {@link CHUNK} comparisons. Stable.\n */\nfunction* sortPositions(\n n: number,\n cmp: (a: number, b: number) => number,\n): Generator<undefined, Uint32Array, undefined> {\n let a = new Uint32Array(n)\n for (let i = 0; i < n; i++) a[i] = i\n for (let lo = 0; lo < n; lo += RUN) {\n a.subarray(lo, Math.min(lo + RUN, n)).sort(cmp)\n yield\n }\n let b = new Uint32Array(n)\n let ops = 0\n for (let width = RUN; width < n; width *= 2) {\n for (let lo = 0; lo < n; lo += 2 * width) {\n const mid = Math.min(lo + width, n)\n const hi = Math.min(lo + 2 * width, n)\n let i = lo\n let j = mid\n let k = lo\n while (i < mid && j < hi) {\n const x = a[i] as number\n const y = a[j] as number\n if (cmp(y, x) < 0) {\n b[k++] = y\n j++\n } else {\n b[k++] = x\n i++\n }\n if (++ops === CHUNK) {\n ops = 0\n yield\n }\n }\n while (i < mid) b[k++] = a[i++] as number\n while (j < hi) b[k++] = a[j++] as number\n }\n const t = a\n a = b\n b = t\n }\n return a\n}\n\n/**\n * Collation ranks of a string column (§6.4, §9.1): per row, the dense rank of its string among\n * the column's distinct strings in `collator` order (strings the collator finds equal share a\n * rank), `NaN` for a missing value. Sorting by these numbers gives exactly the order of\n * `collator.compare`, with integer keys (counting / radix sort) instead of a collator call per\n * comparison. A time-sliced job; `null` when a present value is not a string.\n */\nexport function* collationRanks(\n values: readonly unknown[],\n collator: Intl.Collator,\n): Generator<undefined, Float64Array | null, undefined> {\n const n = values.length\n const ids = new Map<string, number>()\n const distinct: string[] = []\n /** Per row: its string's position in `distinct`, -1 when missing. */\n const slot = new Int32Array(n)\n for (let r = 0; r < n; r++) {\n const v = values[r]\n if (typeof v === 'string') {\n let id = ids.get(v)\n if (id === undefined) {\n id = distinct.length\n ids.set(v, id)\n distinct.push(v)\n }\n slot[r] = id\n } else if (typeRank(v) === RANK.missing) slot[r] = -1\n else return null\n if (r % CHUNK === CHUNK - 1) yield\n }\n const order = yield* sortPositions(distinct.length, (a, b) =>\n collator.compare(distinct[a] as string, distinct[b] as string),\n )\n const rankOf = new Float64Array(distinct.length)\n let rank = 0\n for (let k = 0; k < order.length; k++) {\n const id = order[k] as number\n const s = distinct[id] as string\n if (k > 0 && collator.compare(distinct[order[k - 1] as number] as string, s) !== 0) rank++\n rankOf[id] = rank\n if (k % CHUNK === CHUNK - 1) yield\n }\n const out = new Float64Array(n)\n for (let r = 0; r < n; r++) {\n const id = slot[r] as number\n out[r] = id < 0 ? Number.NaN : (rankOf[id] as number)\n }\n return out\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/features/sorting/ranks.ts"],"sourcesContent":["import { RANK, typeRank } from './compare'\n\n/** Comparisons / rows between two `yield`s of the jobs below (§9.1). */\nconst CHUNK = 4096\n/**\n * Run length sorted natively before the merge passes (#97, was 512): each doubling saves a merge\n * pass, and one native run of collator calls stays within a slice even under a 4x CPU throttle.\n */\nconst RUN = 1024\n\n/**\n * Sorts `0..n-1` by `cmp` as a time-sliced job (§9.1): native sorts of `run`s, then bottom-up\n * merge passes that `yield` every {@link CHUNK} comparisons. Stable. Also returns `eq`:\n * `eq[k] = 1` when `order[k]` compares equal to `order[k - 1]`, from the comparisons the merges\n * make anyway (#97). Inside a native run it costs a second pass only when that sort saw a 0.\n */\nfunction* sortPositions(\n n: number,\n cmp: (a: number, b: number) => number,\n run: number,\n): Generator<undefined, { order: Uint32Array; eq: Uint8Array }, undefined> {\n let a = new Uint32Array(n)\n let ea = new Uint8Array(n)\n for (let i = 0; i < n; i++) a[i] = i\n for (let lo = 0; lo < n; lo += run) {\n const hi = Math.min(lo + run, n)\n let zero = false\n a.subarray(lo, hi).sort((x, y) => {\n const c = cmp(x, y)\n if (c === 0) zero = true\n return c\n })\n // Neighbours can only be equal if the sort saw a 0 (it must, to order them).\n if (zero)\n for (let k = lo + 1; k < hi; k++) ea[k] = cmp(a[k - 1] as number, a[k] as number) ? 0 : 1\n yield\n }\n let b = new Uint32Array(n)\n let eb = new Uint8Array(n)\n let ops = 0\n for (let width = run; width < n; width *= 2) {\n for (let lo = 0; lo < n; lo += 2 * width) {\n const mid = Math.min(lo + width, n)\n const hi = Math.min(lo + 2 * width, n)\n let i = lo\n let j = mid\n // The run of the last output (-1: none yet) and whether it equals the other run's head.\n let prev = -1\n let same = 0\n for (let k = lo; k < hi; k++) {\n let c = 1\n let src: number\n if (i < mid && j < hi) {\n c = cmp(a[j] as number, a[i] as number)\n src = c < 0 ? 1 : 0\n if (++ops === CHUNK) {\n ops = 0\n yield\n }\n } else src = i < mid ? 0 : 1\n const p = src ? j++ : i++\n b[k] = a[p] as number\n eb[k] = prev === src ? (ea[p] as number) : prev < 0 ? 0 : same\n same = src === 0 && c === 0 ? 1 : 0\n prev = src\n }\n }\n const t = a\n a = b\n b = t\n const e = ea\n ea = eb\n eb = e\n }\n return { order: a, eq: ea }\n}\n\n/**\n * What the next {@link collationRanks} of the same column starts from (#95): the values it ranked,\n * their ranks (the `ranks` it returned, shared) and the distinct strings in rank order (one per\n * rank, shared with the data). No string → id map: a row whose string is unchanged at its position\n * (or at a shifted one) keeps its rank, any other string is found by binary search over `reps`.\n * Never mutated (a job builds a new one), so a cancelled job leaves it usable.\n */\nexport interface RankState {\n readonly values: readonly unknown[]\n readonly ranks: ArrayLike<number>\n readonly reps: readonly string[]\n}\n\nexport interface Ranked {\n readonly ranks: Float64Array\n readonly state: RankState\n}\n\n/**\n * Collation ranks of a string column (§6.4, §9.1): per row, the dense rank of its string among\n * the column's distinct strings in `collator` order (strings the collator finds equal share a\n * rank), `NaN` for a missing value. Sorting by these numbers gives exactly the order of\n * `collator.compare`, with integer keys (counting / radix sort) instead of a collator call per\n * comparison. A time-sliced job; `null` when a present value is not a string.\n *\n * Incremental across data versions (#95): a string ranked in `prev` (the state of an earlier array\n * of the same column) keeps that rank as its sort key. It is read from the same row of `prev` when\n * the string is the same there (or at the row offset of the last match, for inserted / removed\n * rows), else found by binary search over the ranked strings; a new string is placed in the gap\n * below the first greater one. Only new strings are sorted (two in the same gap call the\n * collator): a one-row edit costs O(log d) collator calls (and a sort of numbers), not a cold\n * sort. The new state holds only the strings present now.\n */\nexport function* collationRanks(\n values: readonly unknown[],\n collator: Intl.Collator,\n prev: RankState = { values: [], ranks: [], reps: [] },\n): Generator<undefined, Ranked | null, undefined> {\n const n = values.length\n const { values: was, ranks: wasRank, reps } = prev\n const m = reps.length\n /** Per row: its rank in `prev`, `m + id` for the new string `fresh[id]`, NaN when missing. */\n const out = new Float64Array(n)\n /** Ranks of `prev` still present (the rest are dropped from the new state). */\n const used = new Uint8Array(m)\n // New strings only (a cold job: every distinct string), dropped with the job.\n const freshIds = new Map<string, number>()\n const fresh: string[] = []\n /** Per new string: `g - 0.5`, between ranks g-1 and g of `prev`. */\n const gap: number[] = []\n /** Per rank of `prev`: one row holding it, built on the first lookup that finds one. */\n let rowOf: Int32Array | undefined\n /** Offset to `prev`'s row of the last match: rows inserted / removed above shift it. */\n let shift = 0\n let ops = 0\n for (let r = 0; r < n; r++) {\n const v = values[r]\n if (typeof v === 'string') {\n let k: number | undefined\n const q = r + shift\n // Out of range: `undefined`, never a string.\n if (was[q] === v) k = wasRank[q] as number\n else {\n const id = freshIds.get(v)\n if (id !== undefined) k = m + id\n else {\n let lo = 0\n let hi = m\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n const c = collator.compare(v, reps[mid] as string)\n ops++\n if (c === 0) {\n k = mid\n break\n }\n if (c < 0) hi = mid\n else lo = mid + 1\n }\n if (k === undefined) {\n k = m + fresh.length\n freshIds.set(v, fresh.length)\n fresh.push(v)\n gap.push(lo - 0.5)\n } else {\n if (!rowOf) {\n rowOf = new Int32Array(m)\n for (let p = 0; p < was.length; p++) {\n const x = wasRank[p] as number\n if (!Number.isNaN(x)) rowOf[x] = p\n }\n }\n shift = (rowOf[k] as number) - r\n }\n }\n }\n if (k < m) used[k] = 1\n out[r] = k\n } else if (typeRank(v) === RANK.missing) out[r] = Number.NaN\n else return null\n if (++ops >= CHUNK) {\n ops = 0\n yield\n }\n }\n // The same strings as `prev` (a new array of equal content, rows moved): the same ranks.\n if (!fresh.length && !used.includes(0)) return { ranks: out, state: { values, ranks: out, reps } }\n\n const f = fresh.length\n const { order, eq } = yield* sortPositions(\n f,\n // By gap; two new strings in the same gap (cold: all of them) go to the collator.\n (a, b) =>\n (gap[a] as number) - (gap[b] as number) ||\n collator.compare(fresh[a] as string, fresh[b] as string),\n // Comparing numbers is far cheaper: longer native runs, fewer merge passes.\n m ? 8 * RUN : RUN,\n )\n // Merge the kept ranks of `prev` and the new strings (each before the rank above its gap). No\n // collator call: one pass, like the rank lookup below.\n const remap = new Float64Array(m + f)\n const kept: string[] = []\n let rank = -1\n let j = 0\n for (let g = 0; g <= m; g++) {\n while (j < f && (gap[order[j] as number] as number) < g) {\n const id = order[j] as number\n if (!eq[j]) kept[++rank] = fresh[id] as string\n remap[m + id] = rank\n j++\n }\n if (g < m && used[g]) {\n kept[++rank] = reps[g] as string\n remap[g] = rank\n }\n }\n for (let r = 0; r < n; r++) {\n const k = out[r] as number\n if (!Number.isNaN(k)) out[r] = remap[k] as number\n }\n return { ranks: out, state: { values, ranks: out, reps: kept } }\n}\n"],"mappings":"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","names":[]}
@@ -29,13 +29,17 @@ var import_slots = require("../../catalog/slots.cjs");
29
29
  var import_get_value = require("../../column/get-value.cjs");
30
30
  var import_catalog = require("../../diagnostics/catalog.cjs");
31
31
  var import_process_guard = require("../../diagnostics/process-guard.cjs");
32
+ var import_prose = require("../../diagnostics/prose.cjs");
33
+ var import_redact = require("../../diagnostics/redact.cjs");
32
34
  var import_define = require("../../feature/define.cjs");
35
+ var import_slice_guard = require("../../feature/slice-guard.cjs");
33
36
  var import_cell_batch = require("../../intercept/cell-batch.cjs");
34
37
  var import_classify = require("../../intercept/classify.cjs");
35
38
  var import_codes2 = require("../../intercept/codes.cjs");
36
39
  var import_columns = require("../../pipeline/columns.cjs");
37
40
  var import_env_codes = require("../../pipeline/env-codes.cjs");
38
41
  var import_indices = require("../../pipeline/indices.cjs");
42
+ var import_release = require("../../pipeline/release.cjs");
39
43
  var import_slice = require("../../pipeline/slice.cjs");
40
44
  var import_mode = require("../../source/mode.cjs");
41
45
  var import_capability = require("../../versioning/capability.cjs");
@@ -50,7 +54,30 @@ var import_strategy = require("./strategy.cjs");
50
54
  import_codes.optionDiagnostics,
51
55
  import_env_codes.envDiagnostics
52
56
  );
53
- var setSort = (ctx, by) => ctx.setState((s) => JSON.stringify(s.by) === JSON.stringify(by) ? s : { by: by.slice() });
57
+ var isEntry = (e) => e?.id != null;
58
+ var byOf = (s) => s?.by;
59
+ var entriesOf = (s) => {
60
+ const by = byOf(s);
61
+ return Array.isArray(by) ? by.filter(isEntry) : [];
62
+ };
63
+ function fixSlice(s) {
64
+ const raw = byOf(s);
65
+ const by = entriesOf(s);
66
+ if (Array.isArray(raw) && raw.length === by.length) return void 0;
67
+ const bad = Array.isArray(raw) ? raw.findIndex((e) => !isEntry(e)) : -1;
68
+ return [
69
+ { by },
70
+ bad < 0 ? (0, import_redact.describeValue)(raw ?? s) : `by[${bad}]: ${(0, import_redact.describeValue)(raw[bad])}`,
71
+ `{ by: [${by.length} valid] }`
72
+ ];
73
+ }
74
+ var setSort = (ctx, by) => {
75
+ try {
76
+ if (JSON.stringify(byOf(ctx.getState())) === JSON.stringify(by)) return;
77
+ } catch {
78
+ }
79
+ ctx.setState({ by: Array.isArray(by) ? by.slice() : by });
80
+ };
54
81
  var validLocale = (v) => {
55
82
  try {
56
83
  Intl.getCanonicalLocales(v);
@@ -113,6 +140,7 @@ var SortFnFailed = class {
113
140
  }
114
141
  };
115
142
  var sorting = (opts = {}) => {
143
+ const guard = process.env.NODE_ENV !== "production" ? (0, import_slice_guard.sliceGuard)("sorting", fixSlice) : void 0;
116
144
  let collator;
117
145
  const getCollator = (emit) => {
118
146
  if (collator) return collator;
@@ -135,19 +163,31 @@ var sorting = (opts = {}) => {
135
163
  }
136
164
  return collator;
137
165
  };
138
- const rankKeys = /* @__PURE__ */ new WeakMap();
139
- function ranksFor(ctx, values) {
166
+ let rankKeys = /* @__PURE__ */ new WeakMap();
167
+ let history = /* @__PURE__ */ new Map();
168
+ function keepHistory(by) {
169
+ for (const id of history.keys()) {
170
+ if (!by.some((e) => String(e.id) === id)) history.delete(id);
171
+ }
172
+ }
173
+ function ranksFor(ctx, id, values) {
140
174
  let key = rankKeys.get(values);
141
175
  if (!key) {
142
176
  key = {};
143
177
  rankKeys.set(values, key);
144
178
  }
145
179
  const c = getCollator(ctx.emit);
146
- return (0, import_slice.sliced)(ctx, key, () => (0, import_ranks.collationRanks)(values, c));
180
+ const into = history;
181
+ return (0, import_slice.sliced)(ctx, key, function* () {
182
+ const r = yield* (0, import_ranks.collationRanks)(values, c, into.get(id));
183
+ if (!r) return null;
184
+ into.set(id, r.state);
185
+ return r.ranks;
186
+ });
147
187
  }
148
188
  let shown;
149
- const sortByCache = /* @__PURE__ */ new WeakMap();
150
- const pathCache = /* @__PURE__ */ new WeakMap();
189
+ let sortByCache = /* @__PURE__ */ new WeakMap();
190
+ let pathCache = /* @__PURE__ */ new WeakMap();
151
191
  let exsorted;
152
192
  const sortByFailures = new import_cell_batch.CellFailures();
153
193
  function sortByValues(ctx, col, sortBy) {
@@ -193,7 +233,7 @@ var sorting = (opts = {}) => {
193
233
  byId.set(id, out);
194
234
  return out;
195
235
  }
196
- return (0, import_define.defineFeature)({
236
+ const feature = (0, import_define.defineFeature)({
197
237
  name: "sorting",
198
238
  stage: "sort",
199
239
  mode: opts.mode ?? "client",
@@ -202,32 +242,37 @@ var sorting = (opts = {}) => {
202
242
  process.env.NODE_ENV !== "production" ? sortingOptions : {},
203
243
  opts
204
244
  ),
245
+ ...guard ? { interceptors: [guard.interceptor] } : void 0,
205
246
  initialState: () => ({ by: [] }),
206
247
  // After destroy(): still a declared value (`aria-sort` reads it during teardown renders).
207
248
  destroyedResult: { getSortDirection: () => false },
208
- createApi: (ctx) => ({
209
- toggleSort: (id, options) => ctx.setState((s) => {
210
- const by = s.by;
211
- const i = by.findIndex((e) => e.id === id);
212
- const cur = by[i];
213
- const next = !cur ? { id, desc: false } : cur.desc ? void 0 : { id, desc: true };
214
- if (!options?.multi) return { by: next ? [next] : [] };
215
- if (!cur) return { by: [...by, next] };
216
- const out = by.slice();
217
- if (next) out[i] = next;
218
- else out.splice(i, 1);
219
- return { by: out };
220
- }),
221
- setSorting: (by) => setSort(ctx, by),
222
- clearSorting: () => setSort(ctx, []),
223
- getSortDirection: (id) => {
224
- const e = ctx.getState().by.find((x) => x.id === id);
225
- return e ? e.desc ? "desc" : "asc" : false;
226
- }
227
- }),
249
+ createApi: (ctx) => {
250
+ guard?.install(ctx);
251
+ return {
252
+ toggleSort: (id, options) => {
253
+ const by = entriesOf(ctx.getState());
254
+ const i = by.findIndex((e) => e.id === id);
255
+ const cur = by[i];
256
+ const next = !cur ? { id, desc: false } : cur.desc ? void 0 : { id, desc: true };
257
+ if (!options?.multi) return ctx.setState({ by: next ? [next] : [] });
258
+ if (!cur) return ctx.setState({ by: [...by, next] });
259
+ const out = by.slice();
260
+ if (next) out[i] = next;
261
+ else out.splice(i, 1);
262
+ ctx.setState({ by: out });
263
+ },
264
+ setSorting: (by) => setSort(ctx, by),
265
+ clearSorting: () => setSort(ctx, []),
266
+ getSortDirection: (id) => {
267
+ const e = entriesOf(ctx.getState()).find((x) => x.id === id);
268
+ return e ? e.desc ? "desc" : "asc" : false;
269
+ }
270
+ };
271
+ },
228
272
  rowModel: (indices, state, ctx) => {
229
- const by = state?.by;
230
- if (!by || by.length === 0 || indices.length < 2) {
273
+ const by = entriesOf(state);
274
+ keepHistory(by);
275
+ if (by.length === 0 || indices.length < 2) {
231
276
  shown = void 0;
232
277
  return indices;
233
278
  }
@@ -239,7 +284,7 @@ var sorting = (opts = {}) => {
239
284
  const sortBy = col && fnField(col.def, "sortBy");
240
285
  const values = sortBy ? sortByValues(ctx, col, sortBy) : ctx.getColumnValues(id) ?? pathValues(ctx, id);
241
286
  const sortFn = col && !sortBy ? fnField(col.def, "sortFn") : void 0;
242
- return { col, desc: e.desc === true, values, sortFn, ranked: false };
287
+ return { id, col, desc: e.desc === true, values, sortFn, ranked: false };
243
288
  });
244
289
  if (process.env.NODE_ENV !== "production") {
245
290
  for (const e of entries) {
@@ -260,7 +305,7 @@ var sorting = (opts = {}) => {
260
305
  fromColumn ? { path: `columns[${primary.col?.index}].sortAlgorithm`, reason: reason2, once: false } : { path: "algorithm", feature: "sorting", reason: reason2, once: false }
261
306
  );
262
307
  if (typeof requested !== "function" && !ALGORITHMS.includes(requested)) {
263
- noEffect(`not-algorithm|${JSON.stringify(requested)}`);
308
+ noEffect(`not-algorithm|${(0, import_prose.proseArg)(JSON.stringify(requested))}`);
264
309
  requested = "auto";
265
310
  }
266
311
  if (requested !== "native" && exsorted === void 0) {
@@ -280,7 +325,7 @@ var sorting = (opts = {}) => {
280
325
  let computing = false;
281
326
  for (const e of entries) {
282
327
  if (e.sortFn || e.values instanceof Float64Array) continue;
283
- const ranks = ranksFor(ctx, e.values);
328
+ const ranks = ranksFor(ctx, e.id, e.values);
284
329
  if (ranks === void 0) computing = true;
285
330
  else if (ranks) {
286
331
  e.values = ranks;
@@ -434,6 +479,16 @@ var sorting = (opts = {}) => {
434
479
  }
435
480
  }
436
481
  });
482
+ const internal = feature;
483
+ internal[import_release.RELEASE] = () => {
484
+ rankKeys = /* @__PURE__ */ new WeakMap();
485
+ history = /* @__PURE__ */ new Map();
486
+ shown = void 0;
487
+ sortByCache = /* @__PURE__ */ new WeakMap();
488
+ pathCache = /* @__PURE__ */ new WeakMap();
489
+ };
490
+ if (process.env.NODE_ENV !== "production") internal[import_release.PROBE] = () => ({ rankKeys, history, shown, sortByCache, pathCache });
491
+ return feature;
437
492
  };
438
493
  // Annotate the CommonJS export names for ESM import in node:
439
494
  0 && (module.exports = {