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