@datakable/core 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- 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-BKdS9EiO.d.cts → codes-B9bCK73-.d.cts} +46 -8
- package/dist/{codes-BKdS9EiO.d.ts → codes-B9bCK73-.d.ts} +46 -8
- 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 +10 -4
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +8 -3
- 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 +6 -5
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +7 -6
- 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-aagaYHcy.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
- package/dist/{explain-DcUZfFh5.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/define.cjs +3 -1
- package/dist/feature/define.cjs.map +1 -1
- package/dist/feature/define.js +3 -1
- package/dist/feature/define.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/feature/runtime.cjs.map +1 -1
- package/dist/feature/runtime.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 +6 -3
- package/dist/features/pagination/codes.cjs.map +1 -1
- package/dist/features/pagination/codes.js +6 -3
- package/dist/features/pagination/codes.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +19 -14
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +19 -14
- 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 +105 -48
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +106 -49
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/search/search.cjs +82 -24
- package/dist/features/search/search.cjs.map +1 -1
- package/dist/features/search/search.js +82 -24
- 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 +19 -23
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +19 -23
- 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 +16 -11
- package/dist/index.d.ts +16 -11
- 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 +7 -4
- package/dist/intercept/cell-batch.cjs.map +1 -1
- package/dist/intercept/cell-batch.js +7 -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/row.cjs +8 -7
- package/dist/pipeline/row.cjs.map +1 -1
- package/dist/pipeline/row.js +8 -7
- package/dist/pipeline/row.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-C3mXxLP8.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
- package/dist/{search-async-entry-BmQYT120.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 +6 -3
- package/dist/sorting.d.ts +6 -3
- 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
package/dist/ingest/row-key.js
CHANGED
|
@@ -58,46 +58,49 @@ function keyReader(rowKey, onThrow) {
|
|
|
58
58
|
function scan(data, rowKey, read, emit) {
|
|
59
59
|
const taken = /* @__PURE__ */ new Map();
|
|
60
60
|
const suffix = /* @__PURE__ */ new Map();
|
|
61
|
-
let fallbacks;
|
|
62
|
-
const free = (base, from) => {
|
|
63
|
-
let s = from;
|
|
64
|
-
let k = `${base}#${s++}`;
|
|
65
|
-
while (taken.has(k)) k = `${base}#${s++}`;
|
|
66
|
-
return [k, s];
|
|
67
|
-
};
|
|
68
61
|
const missing = { count: 0, rows: [], valueType: "" };
|
|
69
62
|
const dup = { count: 0, key: void 0, rows: [], shared: 0 };
|
|
63
|
+
const pending = [];
|
|
70
64
|
for (let i = 0; i < data.length; i++) {
|
|
71
65
|
const raw = read(data[i], i);
|
|
72
|
-
|
|
66
|
+
const str = String(raw);
|
|
73
67
|
if (!usable(raw)) {
|
|
74
68
|
if (raw !== THREW) {
|
|
75
69
|
if (missing.count === 0) missing.valueType = typeOf(raw);
|
|
76
70
|
if (missing.rows.length < SAMPLE) missing.rows.push(i);
|
|
77
71
|
missing.count++;
|
|
78
72
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
} else if (taken.has(raw)) {
|
|
73
|
+
pending.push([i, `#${i}`]);
|
|
74
|
+
} else if (taken.has(str)) {
|
|
82
75
|
dup.count++;
|
|
83
76
|
if (dup.key === void 0) {
|
|
84
77
|
dup.key = raw;
|
|
85
|
-
dup.rows.push(taken.get(
|
|
78
|
+
dup.rows.push(taken.get(str));
|
|
86
79
|
dup.shared = 1;
|
|
87
80
|
}
|
|
88
|
-
if (
|
|
81
|
+
if (str === String(dup.key)) {
|
|
89
82
|
dup.shared++;
|
|
90
83
|
if (dup.rows.length < SAMPLE) dup.rows.push(i);
|
|
91
84
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
key = k;
|
|
85
|
+
suffix.set(str, 1);
|
|
86
|
+
pending.push([i, str]);
|
|
95
87
|
} else {
|
|
96
|
-
taken.set(
|
|
97
|
-
|
|
88
|
+
taken.set(str, i);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const groups = suffix.size;
|
|
92
|
+
let fallbacks;
|
|
93
|
+
for (const [i, base] of pending) {
|
|
94
|
+
let key = base;
|
|
95
|
+
if (taken.has(key)) {
|
|
96
|
+
let n = suffix.get(base) ?? 1;
|
|
97
|
+
do
|
|
98
|
+
key = `${base}#${n++}`;
|
|
99
|
+
while (taken.has(key));
|
|
100
|
+
suffix.set(base, n);
|
|
98
101
|
}
|
|
99
102
|
taken.set(key, i);
|
|
100
|
-
|
|
103
|
+
fallbacks ??= /* @__PURE__ */ new Map();
|
|
101
104
|
fallbacks.set(i, key);
|
|
102
105
|
}
|
|
103
106
|
if (dup.key !== void 0 || missing.count > 0) {
|
|
@@ -111,7 +114,7 @@ function scan(data, rowKey, read, emit) {
|
|
|
111
114
|
rows: dup.rows,
|
|
112
115
|
shared: dup.shared,
|
|
113
116
|
count: dup.count,
|
|
114
|
-
groups
|
|
117
|
+
groups,
|
|
115
118
|
total: data.length,
|
|
116
119
|
unique: found.unique,
|
|
117
120
|
...found.pair === void 0 ? {} : { pair: found.pair }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/row-key.ts"],"sourcesContent":["import { compilePath, getValue } from '../column/get-value'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport type { RowKey } from '../pipeline/row'\nimport { rowKeyDiagnostics } from './row-key-codes'\nimport { findUniqueFields } from './unique-fields'\n\nkeepDiagnostics(rowKeyDiagnostics)\n\n/** Rows listed in a diagnostic's `rows` sample. */\nconst SAMPLE = 5\n\n/** `(row, index) => key` for the rows the pipeline builds. */\nexport type KeyOf = (row: unknown, index: number) => RowKey\n\nconst usable = (k: unknown): k is RowKey =>\n typeof k === 'string' || (typeof k === 'number' && !Number.isNaN(k))\n\nconst THREW: unique symbol = Symbol('threw')\n\nconst typeOf = (v: unknown): string => {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n return typeof v === 'number' ? 'number(NaN)' : typeof v\n}\n\n/**\n * Resolves `rowKey` (§5.2) over **all** rows, once per (data, rowKey) reference, so keys do not\n * change with filtering, sorting or paging:\n *\n * - a repeated key: the first row keeps it, later rows get `key#1`, `key#2`, …\n * (`DK_DUPLICATE_ROW_KEY`, whose `fix` names fields that are unique across all rows);\n * - a missing / `null` / non-string-or-number key: `#index` (`DK_MISSING_ROW_KEY`);\n * - a throwing `rowKey` function: `#index`, passed to `onThrow` (reported by the runner as\n * `DK_CALLBACK_FAILED`, as in T09).\n *\n * Only the rows that need a fallback are remembered; the returned reader re-reads the key of the\n * (visible) rows it is asked for, so a clean data set keeps no per-row memory. Without `rowKey`,\n * the key is the row index.\n */\nexport function resolveRowKeys(\n data: readonly unknown[],\n rowKey: unknown,\n emit: Emit,\n onThrow: (index: number, error: unknown) => void,\n): KeyOf {\n const read = keyReader(rowKey, onThrow)\n if (read === undefined) return (_row, index) => index\n const fallbacks = scan(data, rowKey, read, emit)\n return (row, index) => {\n const f = fallbacks?.get(index)\n if (f !== undefined) return f\n const k = read(row, index)\n return usable(k) ? k : `#${index}`\n }\n}\n\ntype Read = (row: unknown, index: number) => unknown\n\nfunction keyReader(\n rowKey: unknown,\n onThrow: (index: number, error: unknown) => void,\n): Read | undefined {\n let read: Read\n if (typeof rowKey === 'function') {\n const fn = rowKey as (row: unknown) => unknown\n read = (row, index) => {\n try {\n return fn(row)\n } catch (e) {\n onThrow(index, e)\n return THREW\n }\n }\n } else if (typeof rowKey === 'string') {\n const path = compilePath(rowKey)\n let cur = 0\n let threw = false\n const onError = (e: unknown) => {\n threw = true\n onThrow(cur, e)\n }\n read = (row, index) => {\n cur = index\n const k = getValue(row, path, onError)\n if (!threw) return k\n threw = false\n return THREW\n }\n } else {\n return undefined\n }\n return read\n}\n\n/**\n * One pass over every row
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/row-key.ts"],"sourcesContent":["import { compilePath, getValue } from '../column/get-value'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport type { RowKey } from '../pipeline/row'\nimport { rowKeyDiagnostics } from './row-key-codes'\nimport { findUniqueFields } from './unique-fields'\n\nkeepDiagnostics(rowKeyDiagnostics)\n\n/** Rows listed in a diagnostic's `rows` sample. */\nconst SAMPLE = 5\n\n/** `(row, index) => key` for the rows the pipeline builds. */\nexport type KeyOf = (row: unknown, index: number) => RowKey\n\nconst usable = (k: unknown): k is RowKey =>\n typeof k === 'string' || (typeof k === 'number' && !Number.isNaN(k))\n\nconst THREW: unique symbol = Symbol('threw')\n\nconst typeOf = (v: unknown): string => {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n return typeof v === 'number' ? 'number(NaN)' : typeof v\n}\n\n/**\n * Resolves `rowKey` (§5.2) over **all** rows, once per (data, rowKey) reference, so keys do not\n * change with filtering, sorting or paging:\n *\n * - a repeated key: the first row keeps it, later rows get `key#1`, `key#2`, …\n * (`DK_DUPLICATE_ROW_KEY`, whose `fix` names fields that are unique across all rows). Keys are\n * compared as strings (`1` repeats `'1'`), as React keys and `explainRow` compare them;\n * - a missing / `null` / non-string-or-number key: `#index` (`DK_MISSING_ROW_KEY`);\n * - a throwing `rowKey` function: `#index`, passed to `onThrow` (reported by the runner as\n * `DK_CALLBACK_FAILED`, as in T09).\n *\n * Only the rows that need a fallback are remembered; the returned reader re-reads the key of the\n * (visible) rows it is asked for, so a clean data set keeps no per-row memory. Without `rowKey`,\n * the key is the row index.\n */\nexport function resolveRowKeys(\n data: readonly unknown[],\n rowKey: unknown,\n emit: Emit,\n onThrow: (index: number, error: unknown) => void,\n): KeyOf {\n const read = keyReader(rowKey, onThrow)\n if (read === undefined) return (_row, index) => index\n const fallbacks = scan(data, rowKey, read, emit)\n return (row, index) => {\n const f = fallbacks?.get(index)\n if (f !== undefined) return f\n const k = read(row, index)\n return usable(k) ? k : `#${index}`\n }\n}\n\ntype Read = (row: unknown, index: number) => unknown\n\nfunction keyReader(\n rowKey: unknown,\n onThrow: (index: number, error: unknown) => void,\n): Read | undefined {\n let read: Read\n if (typeof rowKey === 'function') {\n const fn = rowKey as (row: unknown) => unknown\n read = (row, index) => {\n try {\n return fn(row)\n } catch (e) {\n onThrow(index, e)\n return THREW\n }\n }\n } else if (typeof rowKey === 'string') {\n const path = compilePath(rowKey)\n let cur = 0\n let threw = false\n const onError = (e: unknown) => {\n threw = true\n onThrow(cur, e)\n }\n read = (row, index) => {\n cur = index\n const k = getValue(row, path, onError)\n if (!threw) return k\n threw = false\n return THREW\n }\n } else {\n return undefined\n }\n return read\n}\n\n/**\n * One pass over every row, then one over the rows that need a fallback key: returns those keys\n * and emits the key diagnostics. Kept apart from the returned reader so the scan's maps are not\n * captured by it.\n */\nfunction scan(\n data: readonly unknown[],\n rowKey: unknown,\n read: Read,\n emit: Emit,\n): Map<number, RowKey> | undefined {\n /** Every key handed out so far (raw keys and fallbacks), as a string → the row that holds it. */\n const taken = new Map<string, number>()\n /** Repeated raw key, as a string → the next `#n` to try. */\n const suffix = new Map<string, number>()\n const missing = { count: 0, rows: [] as number[], valueType: '' }\n const dup = { count: 0, key: undefined as RowKey | undefined, rows: [] as number[], shared: 0 }\n /** Rows that need a fallback: [index, the base it is built on (`#index` or the repeated key)]. */\n const pending: [number, string][] = []\n\n for (let i = 0; i < data.length; i++) {\n const raw = read(data[i], i)\n const str = String(raw)\n if (!usable(raw)) {\n if (raw !== THREW) {\n if (missing.count === 0) missing.valueType = typeOf(raw)\n if (missing.rows.length < SAMPLE) missing.rows.push(i)\n missing.count++\n }\n pending.push([i, `#${i}`])\n } else if (taken.has(str)) {\n dup.count++\n if (dup.key === undefined) {\n dup.key = raw\n dup.rows.push(taken.get(str) as number)\n dup.shared = 1\n }\n if (str === String(dup.key)) {\n dup.shared++\n if (dup.rows.length < SAMPLE) dup.rows.push(i)\n }\n suffix.set(str, 1)\n pending.push([i, str])\n } else {\n taken.set(str, i)\n }\n }\n const groups = suffix.size\n\n // Fallbacks only once every real key is taken, so none takes the key of a later row: `base#n`\n // for the first free `n` (a missing row's `#index` itself when free).\n let fallbacks: Map<number, RowKey> | undefined\n for (const [i, base] of pending) {\n let key = base\n if (taken.has(key)) {\n let n = suffix.get(base) ?? 1\n do key = `${base}#${n++}`\n while (taken.has(key))\n suffix.set(base, n)\n }\n taken.set(key, i)\n fallbacks ??= new Map()\n fallbacks.set(i, key)\n }\n\n if (dup.key !== undefined || missing.count > 0) {\n // Error path only: the fix is computed from the data (a scan per candidate field).\n const path = typeof rowKey === 'string' ? rowKey : undefined\n const at = path === undefined ? {} : { path }\n if (dup.key !== undefined) {\n const found = findUniqueFields(data, path)\n emit('DK_DUPLICATE_ROW_KEY', {\n ...at,\n key: dup.key,\n rows: dup.rows,\n shared: dup.shared,\n count: dup.count,\n groups,\n total: data.length,\n unique: found.unique,\n ...(found.pair === undefined ? {} : { pair: found.pair }),\n })\n }\n if (missing.count > 0) {\n emit('DK_MISSING_ROW_KEY', {\n ...at,\n rows: missing.rows,\n count: missing.count,\n total: data.length,\n valueType: missing.valueType,\n unique: findUniqueFields(data, path, undefined).unique,\n })\n }\n }\n\n return fallbacks\n}\n"],"mappings":"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","names":[]}
|
|
@@ -25,6 +25,7 @@ __export(schema_codes_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(schema_codes_exports);
|
|
27
27
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
28
29
|
var rowList = (rows, count) => {
|
|
29
30
|
const list = rows.map((r) => `#${r}`).join(", ");
|
|
30
31
|
return count > rows.length ? `${list}, \u2026` : list;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/schema-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\n/** `options.invalidRows` (§5.1). */\nexport type InvalidRowsMode = 'drop' | 'flag' | 'throw'\n\n/** An issue path matched to a column (`DK_SCHEMA_INVALID_ROW.columns`). */\nexport interface SchemaIssueColumn {\n /** The issue path, dot-joined (`address.city`; `''` for the row itself). */\n readonly path: string\n /** The column whose key is or contains the path, if one does. */\n readonly column?: string\n /** That column's position in `options.columns`. */\n readonly index?: number\n /** Invalid rows with an issue at this path. */\n readonly count: number\n}\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SCHEMA_INVALID_ROW: {\n mode: InvalidRowsMode\n /** Rows with issues. */\n count: number\n /** Rows validated. */\n total: number\n /** A sample of the invalid rows' indices in `options.data` (first few, in order). */\n rows: readonly number[]\n /** The first issue of each sampled row (redacted: `scrubSchemaIssue`). */\n issues: readonly unknown[]\n /** Issue paths with their column, most frequent first (at most a few). */\n columns: readonly SchemaIssueColumn[]\n }\n DK_SCHEMA_CRASHED: {\n mode: InvalidRowsMode\n /** Rows for which `validate` threw or rejected. */\n count: number\n rows: readonly number[]\n /** `error.name` of the first failure; the error itself is `context.cause`. */\n errorName?: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst plural = (n: number, one: string) => `${n} ${one}${n === 1 ? '' : 's'}`\n\nconst handled = (mode: InvalidRowsMode, rows: string) => {\n switch (mode) {\n case 'drop':\n return `${rows} removed from the table (getInvalidRows() lists them)`\n case 'flag':\n return `${rows} kept with row.valid === false and row.issues (cell / invalidCell must handle them)`\n case 'throw':\n return 'DatakableSchemaError was thrown; the previous data was kept'\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const schemaMessages: DiagnosticMessages<'DK_SCHEMA_INVALID_ROW' | 'DK_SCHEMA_CRASHED'> = {\n DK_SCHEMA_INVALID_ROW: {\n problem: (d) =>\n `${d.count} of ${plural(d.total, 'row')} failed schema validation ` +\n `(rows ${rowList(d.rows, d.count)})`,\n impact: (d) => handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: (d) => {\n const out: string[] = []\n for (const c of d.columns) {\n const at = c.path === '' ? 'the row itself' : `\"${c.path}\"`\n const col =\n c.column === undefined\n ? 'no column shows it'\n : `column \"${c.column}\" (columns[${c.index}])`\n out.push(\n `${plural(c.count, 'row')} fail at ${at}, ${col}: fix the data source there, ` +\n `or relax the schema at ${at}`,\n )\n }\n if (d.mode === 'drop') {\n out.push(\n \"use invalidRows: 'flag' to keep and show the invalid rows instead of dropping them\",\n )\n }\n if (d.mode === 'throw') {\n out.push(\"use invalidRows: 'drop' or 'flag' to keep the table usable with invalid rows\")\n }\n out.push('inspect them with table.getInvalidRows() (index, raw value and issues per row)')\n return out\n },\n },\n DK_SCHEMA_CRASHED: {\n problem: (d) =>\n `the schema threw${d.errorName ? ` ${d.errorName}` : ''} instead of returning issues ` +\n `for ${plural(d.count, 'row')} (rows ${rowList(d.rows, d.count)})`,\n impact: (d) =>\n `each such row got a synthetic issue and was treated as invalid: ` +\n handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: () => [\n 'find the throwing refinement / transform in the schema: the original error is ' +\n 'context.cause (passed to onDiagnostic)',\n 'make custom checks return an issue (e.g. ctx.addIssue / a refine predicate) instead of ' +\n 'throwing on unexpected input',\n ],\n },\n}\n\n/** Codes owned by the schema ingest (§5.1, §8.3). */\nexport const schemaDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_INVALID_ROW',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n redact: { issues: 'schemaIssues' },\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_INVALID_ROW,\n ),\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_CRASHED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_CRASHED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/schema-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\n/** `options.invalidRows` (§5.1). */\nexport type InvalidRowsMode = 'drop' | 'flag' | 'throw'\n\n/** An issue path matched to a column (`DK_SCHEMA_INVALID_ROW.columns`). */\nexport interface SchemaIssueColumn {\n /** The issue path, dot-joined (`address.city`; `''` for the row itself). */\n readonly path: string\n /** The column whose key is or contains the path, if one does. */\n readonly column?: string\n /** That column's position in `options.columns`. */\n readonly index?: number\n /** Invalid rows with an issue at this path. */\n readonly count: number\n}\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SCHEMA_INVALID_ROW: {\n mode: InvalidRowsMode\n /** Rows with issues. */\n count: number\n /** Rows validated. */\n total: number\n /** A sample of the invalid rows' indices in `options.data` (first few, in order). */\n rows: readonly number[]\n /** The first issue of each sampled row (redacted: `scrubSchemaIssue`). */\n issues: readonly unknown[]\n /** Issue paths with their column, most frequent first (at most a few). */\n columns: readonly SchemaIssueColumn[]\n }\n DK_SCHEMA_CRASHED: {\n mode: InvalidRowsMode\n /** Rows for which `validate` threw or rejected. */\n count: number\n rows: readonly number[]\n /** `error.name` of the first failure; the error itself is `context.cause`. */\n errorName?: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst plural = (n: number, one: string) => `${n} ${one}${n === 1 ? '' : 's'}`\n\nconst handled = (mode: InvalidRowsMode, rows: string) => {\n switch (mode) {\n case 'drop':\n return `${rows} removed from the table (getInvalidRows() lists them)`\n case 'flag':\n return `${rows} kept with row.valid === false and row.issues (cell / invalidCell must handle them)`\n case 'throw':\n return 'DatakableSchemaError was thrown; the previous data was kept'\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const schemaMessages: DiagnosticMessages<'DK_SCHEMA_INVALID_ROW' | 'DK_SCHEMA_CRASHED'> = {\n DK_SCHEMA_INVALID_ROW: {\n problem: (d) =>\n `${d.count} of ${plural(d.total, 'row')} failed schema validation ` +\n `(rows ${rowList(d.rows, d.count)})`,\n impact: (d) => handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: (d) => {\n const out: string[] = []\n for (const c of d.columns) {\n const at = c.path === '' ? 'the row itself' : `\"${c.path}\"`\n const col =\n c.column === undefined\n ? 'no column shows it'\n : `column \"${c.column}\" (columns[${c.index}])`\n out.push(\n `${plural(c.count, 'row')} fail at ${at}, ${col}: fix the data source there, ` +\n `or relax the schema at ${at}`,\n )\n }\n if (d.mode === 'drop') {\n out.push(\n \"use invalidRows: 'flag' to keep and show the invalid rows instead of dropping them\",\n )\n }\n if (d.mode === 'throw') {\n out.push(\"use invalidRows: 'drop' or 'flag' to keep the table usable with invalid rows\")\n }\n out.push('inspect them with table.getInvalidRows() (index, raw value and issues per row)')\n return out\n },\n },\n DK_SCHEMA_CRASHED: {\n problem: (d) =>\n `the schema threw${d.errorName ? ` ${d.errorName}` : ''} instead of returning issues ` +\n `for ${plural(d.count, 'row')} (rows ${rowList(d.rows, d.count)})`,\n impact: (d) =>\n `each such row got a synthetic issue and was treated as invalid: ` +\n handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: () => [\n 'find the throwing refinement / transform in the schema: the original error is ' +\n 'context.cause (passed to onDiagnostic)',\n 'make custom checks return an issue (e.g. ctx.addIssue / a refine predicate) instead of ' +\n 'throwing on unexpected input',\n ],\n },\n}\n\n/** Codes owned by the schema ingest (§5.1, §8.3). */\nexport const schemaDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_INVALID_ROW',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n redact: { issues: 'schemaIssues' },\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_INVALID_ROW,\n ),\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_CRASHED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_CRASHED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AA2CP,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,GAAG,IAAI,aAAQ;AAC9C;AAEA,IAAM,SAAS,CAAC,GAAW,QAAgB,GAAG,CAAC,IAAI,GAAG,GAAG,MAAM,IAAI,KAAK,GAAG;AAE3E,IAAM,UAAU,CAAC,MAAuB,SAAiB;AACvD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAGO,IAAM,iBAAoF;AAAA,EAC/F,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,GAAG,EAAE,KAAK,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC,mCAC9B,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IACnC,QAAQ,CAAC,MAAM,QAAQ,EAAE,MAAM,EAAE,UAAU,IAAI,iBAAiB,iBAAiB;AAAA,IACjF,KAAK,CAAC,MAAM;AACV,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,EAAE,SAAS;AACzB,cAAM,KAAK,EAAE,SAAS,KAAK,mBAAmB,IAAI,EAAE,IAAI;AACxD,cAAM,MACJ,EAAE,WAAW,SACT,uBACA,WAAW,EAAE,MAAM,cAAc,EAAE,KAAK;AAC9C,YAAI;AAAA,UACF,GAAG,OAAO,EAAE,OAAO,KAAK,CAAC,YAAY,EAAE,KAAK,GAAG,uDACnB,EAAE;AAAA,QAChC;AAAA,MACF;AACA,UAAI,EAAE,SAAS,QAAQ;AACrB,YAAI;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,UAAI,EAAE,SAAS,SAAS;AACtB,YAAI,KAAK,8EAA8E;AAAA,MACzF;AACA,UAAI,KAAK,gFAAgF;AACzF,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,mBAAmB;AAAA,IACjB,SAAS,CAAC,MACR,mBAAmB,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE,oCAChD,OAAO,EAAE,OAAO,KAAK,CAAC,UAAU,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IACjE,QAAQ,CAAC,MACP,qEACA,QAAQ,EAAE,MAAM,EAAE,UAAU,IAAI,iBAAiB,iBAAiB;AAAA,IACpE,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,MAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAC/C,QAAQ,EAAE,QAAQ,eAAe;AAAA,IACnC;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,IACjD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/ingest/schema-codes.ts
|
|
2
2
|
import { defineDiagnostic } from "../diagnostics/catalog.js";
|
|
3
|
+
import "../diagnostics/process-guard.js";
|
|
3
4
|
var rowList = (rows, count) => {
|
|
4
5
|
const list = rows.map((r) => `#${r}`).join(", ");
|
|
5
6
|
return count > rows.length ? `${list}, \u2026` : list;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/schema-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\n/** `options.invalidRows` (§5.1). */\nexport type InvalidRowsMode = 'drop' | 'flag' | 'throw'\n\n/** An issue path matched to a column (`DK_SCHEMA_INVALID_ROW.columns`). */\nexport interface SchemaIssueColumn {\n /** The issue path, dot-joined (`address.city`; `''` for the row itself). */\n readonly path: string\n /** The column whose key is or contains the path, if one does. */\n readonly column?: string\n /** That column's position in `options.columns`. */\n readonly index?: number\n /** Invalid rows with an issue at this path. */\n readonly count: number\n}\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SCHEMA_INVALID_ROW: {\n mode: InvalidRowsMode\n /** Rows with issues. */\n count: number\n /** Rows validated. */\n total: number\n /** A sample of the invalid rows' indices in `options.data` (first few, in order). */\n rows: readonly number[]\n /** The first issue of each sampled row (redacted: `scrubSchemaIssue`). */\n issues: readonly unknown[]\n /** Issue paths with their column, most frequent first (at most a few). */\n columns: readonly SchemaIssueColumn[]\n }\n DK_SCHEMA_CRASHED: {\n mode: InvalidRowsMode\n /** Rows for which `validate` threw or rejected. */\n count: number\n rows: readonly number[]\n /** `error.name` of the first failure; the error itself is `context.cause`. */\n errorName?: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst plural = (n: number, one: string) => `${n} ${one}${n === 1 ? '' : 's'}`\n\nconst handled = (mode: InvalidRowsMode, rows: string) => {\n switch (mode) {\n case 'drop':\n return `${rows} removed from the table (getInvalidRows() lists them)`\n case 'flag':\n return `${rows} kept with row.valid === false and row.issues (cell / invalidCell must handle them)`\n case 'throw':\n return 'DatakableSchemaError was thrown; the previous data was kept'\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const schemaMessages: DiagnosticMessages<'DK_SCHEMA_INVALID_ROW' | 'DK_SCHEMA_CRASHED'> = {\n DK_SCHEMA_INVALID_ROW: {\n problem: (d) =>\n `${d.count} of ${plural(d.total, 'row')} failed schema validation ` +\n `(rows ${rowList(d.rows, d.count)})`,\n impact: (d) => handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: (d) => {\n const out: string[] = []\n for (const c of d.columns) {\n const at = c.path === '' ? 'the row itself' : `\"${c.path}\"`\n const col =\n c.column === undefined\n ? 'no column shows it'\n : `column \"${c.column}\" (columns[${c.index}])`\n out.push(\n `${plural(c.count, 'row')} fail at ${at}, ${col}: fix the data source there, ` +\n `or relax the schema at ${at}`,\n )\n }\n if (d.mode === 'drop') {\n out.push(\n \"use invalidRows: 'flag' to keep and show the invalid rows instead of dropping them\",\n )\n }\n if (d.mode === 'throw') {\n out.push(\"use invalidRows: 'drop' or 'flag' to keep the table usable with invalid rows\")\n }\n out.push('inspect them with table.getInvalidRows() (index, raw value and issues per row)')\n return out\n },\n },\n DK_SCHEMA_CRASHED: {\n problem: (d) =>\n `the schema threw${d.errorName ? ` ${d.errorName}` : ''} instead of returning issues ` +\n `for ${plural(d.count, 'row')} (rows ${rowList(d.rows, d.count)})`,\n impact: (d) =>\n `each such row got a synthetic issue and was treated as invalid: ` +\n handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: () => [\n 'find the throwing refinement / transform in the schema: the original error is ' +\n 'context.cause (passed to onDiagnostic)',\n 'make custom checks return an issue (e.g. ctx.addIssue / a refine predicate) instead of ' +\n 'throwing on unexpected input',\n ],\n },\n}\n\n/** Codes owned by the schema ingest (§5.1, §8.3). */\nexport const schemaDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_INVALID_ROW',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n redact: { issues: 'schemaIssues' },\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_INVALID_ROW,\n ),\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_CRASHED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_CRASHED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/schema-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\n/** `options.invalidRows` (§5.1). */\nexport type InvalidRowsMode = 'drop' | 'flag' | 'throw'\n\n/** An issue path matched to a column (`DK_SCHEMA_INVALID_ROW.columns`). */\nexport interface SchemaIssueColumn {\n /** The issue path, dot-joined (`address.city`; `''` for the row itself). */\n readonly path: string\n /** The column whose key is or contains the path, if one does. */\n readonly column?: string\n /** That column's position in `options.columns`. */\n readonly index?: number\n /** Invalid rows with an issue at this path. */\n readonly count: number\n}\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SCHEMA_INVALID_ROW: {\n mode: InvalidRowsMode\n /** Rows with issues. */\n count: number\n /** Rows validated. */\n total: number\n /** A sample of the invalid rows' indices in `options.data` (first few, in order). */\n rows: readonly number[]\n /** The first issue of each sampled row (redacted: `scrubSchemaIssue`). */\n issues: readonly unknown[]\n /** Issue paths with their column, most frequent first (at most a few). */\n columns: readonly SchemaIssueColumn[]\n }\n DK_SCHEMA_CRASHED: {\n mode: InvalidRowsMode\n /** Rows for which `validate` threw or rejected. */\n count: number\n rows: readonly number[]\n /** `error.name` of the first failure; the error itself is `context.cause`. */\n errorName?: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst plural = (n: number, one: string) => `${n} ${one}${n === 1 ? '' : 's'}`\n\nconst handled = (mode: InvalidRowsMode, rows: string) => {\n switch (mode) {\n case 'drop':\n return `${rows} removed from the table (getInvalidRows() lists them)`\n case 'flag':\n return `${rows} kept with row.valid === false and row.issues (cell / invalidCell must handle them)`\n case 'throw':\n return 'DatakableSchemaError was thrown; the previous data was kept'\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const schemaMessages: DiagnosticMessages<'DK_SCHEMA_INVALID_ROW' | 'DK_SCHEMA_CRASHED'> = {\n DK_SCHEMA_INVALID_ROW: {\n problem: (d) =>\n `${d.count} of ${plural(d.total, 'row')} failed schema validation ` +\n `(rows ${rowList(d.rows, d.count)})`,\n impact: (d) => handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: (d) => {\n const out: string[] = []\n for (const c of d.columns) {\n const at = c.path === '' ? 'the row itself' : `\"${c.path}\"`\n const col =\n c.column === undefined\n ? 'no column shows it'\n : `column \"${c.column}\" (columns[${c.index}])`\n out.push(\n `${plural(c.count, 'row')} fail at ${at}, ${col}: fix the data source there, ` +\n `or relax the schema at ${at}`,\n )\n }\n if (d.mode === 'drop') {\n out.push(\n \"use invalidRows: 'flag' to keep and show the invalid rows instead of dropping them\",\n )\n }\n if (d.mode === 'throw') {\n out.push(\"use invalidRows: 'drop' or 'flag' to keep the table usable with invalid rows\")\n }\n out.push('inspect them with table.getInvalidRows() (index, raw value and issues per row)')\n return out\n },\n },\n DK_SCHEMA_CRASHED: {\n problem: (d) =>\n `the schema threw${d.errorName ? ` ${d.errorName}` : ''} instead of returning issues ` +\n `for ${plural(d.count, 'row')} (rows ${rowList(d.rows, d.count)})`,\n impact: (d) =>\n `each such row got a synthetic issue and was treated as invalid: ` +\n handled(d.mode, d.count === 1 ? 'that row was' : 'those rows were'),\n fix: () => [\n 'find the throwing refinement / transform in the schema: the original error is ' +\n 'context.cause (passed to onDiagnostic)',\n 'make custom checks return an issue (e.g. ctx.addIssue / a refine predicate) instead of ' +\n 'throwing on unexpected input',\n ],\n },\n}\n\n/** Codes owned by the schema ingest (§5.1, §8.3). */\nexport const schemaDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_INVALID_ROW',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n redact: { issues: 'schemaIssues' },\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_INVALID_ROW,\n ),\n defineDiagnostic(\n {\n code: 'DK_SCHEMA_CRASHED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'schema' }),\n },\n (process.env.NODE_ENV !== \"production\") && schemaMessages.DK_SCHEMA_CRASHED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AA2CP,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,GAAG,IAAI,aAAQ;AAC9C;AAEA,IAAM,SAAS,CAAC,GAAW,QAAgB,GAAG,CAAC,IAAI,GAAG,GAAG,MAAM,IAAI,KAAK,GAAG;AAE3E,IAAM,UAAU,CAAC,MAAuB,SAAiB;AACvD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAGO,IAAM,iBAAoF;AAAA,EAC/F,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,GAAG,EAAE,KAAK,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC,mCAC9B,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IACnC,QAAQ,CAAC,MAAM,QAAQ,EAAE,MAAM,EAAE,UAAU,IAAI,iBAAiB,iBAAiB;AAAA,IACjF,KAAK,CAAC,MAAM;AACV,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,EAAE,SAAS;AACzB,cAAM,KAAK,EAAE,SAAS,KAAK,mBAAmB,IAAI,EAAE,IAAI;AACxD,cAAM,MACJ,EAAE,WAAW,SACT,uBACA,WAAW,EAAE,MAAM,cAAc,EAAE,KAAK;AAC9C,YAAI;AAAA,UACF,GAAG,OAAO,EAAE,OAAO,KAAK,CAAC,YAAY,EAAE,KAAK,GAAG,uDACnB,EAAE;AAAA,QAChC;AAAA,MACF;AACA,UAAI,EAAE,SAAS,QAAQ;AACrB,YAAI;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,UAAI,EAAE,SAAS,SAAS;AACtB,YAAI,KAAK,8EAA8E;AAAA,MACzF;AACA,UAAI,KAAK,gFAAgF;AACzF,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,mBAAmB;AAAA,IACjB,SAAS,CAAC,MACR,mBAAmB,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE,oCAChD,OAAO,EAAE,OAAO,KAAK,CAAC,UAAU,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IACjE,QAAQ,CAAC,MACP,qEACA,QAAQ,EAAE,MAAM,EAAE,UAAU,IAAI,iBAAiB,iBAAiB;AAAA,IACpE,KAAK,MAAM;AAAA,MACT;AAAA,MAEA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAC/C,QAAQ,EAAE,QAAQ,eAAe;AAAA,IACnC;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,IACjD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
|
|
@@ -53,8 +53,8 @@ function allDistinct(n, keyAt) {
|
|
|
53
53
|
const seen = /* @__PURE__ */ new Set();
|
|
54
54
|
for (let i = 0; i < n; i++) {
|
|
55
55
|
const v = keyAt(i);
|
|
56
|
-
if (!isKey(v) || seen.has(v)) return false;
|
|
57
|
-
seen.add(v);
|
|
56
|
+
if (!isKey(v) || seen.has(String(v))) return false;
|
|
57
|
+
seen.add(String(v));
|
|
58
58
|
}
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/unique-fields.ts"],"sourcesContent":["import { type CompiledPath, compilePath, getValue } from '../column/get-value'\n\n/** Depth cap of a candidate path (as `Path<T>`, §4.1). */\nconst MAX_DEPTH = 4\n/** Bound on the candidates checked (each is one pass over the data). */\nconst MAX_CANDIDATES = 64\n/** Unique fields reported in a `fix`. */\nconst MAX_UNIQUE = 3\n\nexport interface UniqueFields {\n /** Paths whose value is a string / number in every row, never repeated (row-0 order). */\n unique: string[]\n /** With no unique field: a field `f` for which `${row.<rowKey>}-${row.<f>}` is unique. */\n pair?: string\n}\n\nconst isKey = (v: unknown): v is string | number =>\n typeof v === 'string' || (typeof v === 'number' && !Number.isNaN(v))\n\nconst isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Primitive leaf paths of `row` (depth ≤ 4; arrays and Dates are not walked). A field must be in\n * every row to be unique, so row 0's paths are the only candidates.\n */\nfunction candidates(row: unknown, skip: string | undefined): string[] {\n const out: string[] = []\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.length >= MAX_CANDIDATES) return\n const path = prefix ? `${prefix}.${k}` : k\n let v: unknown\n try {\n v = obj[k]\n } catch {\n continue\n }\n if (isKey(v)) {\n if (path !== skip) out.push(path)\n } else if (isPlain(v) && depth < MAX_DEPTH) walk(v, path, depth + 1)\n }\n }\n if (isPlain(row)) walk(row, '', 1)\n return out\n}\n\n/** `true` when `keyAt(i)` is a usable key for every row and never repeats. */\nfunction allDistinct(n: number, keyAt: (i: number) => unknown): boolean {\n const seen = new Set<
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/unique-fields.ts"],"sourcesContent":["import { type CompiledPath, compilePath, getValue } from '../column/get-value'\n\n/** Depth cap of a candidate path (as `Path<T>`, §4.1). */\nconst MAX_DEPTH = 4\n/** Bound on the candidates checked (each is one pass over the data). */\nconst MAX_CANDIDATES = 64\n/** Unique fields reported in a `fix`. */\nconst MAX_UNIQUE = 3\n\nexport interface UniqueFields {\n /** Paths whose value is a string / number in every row, never repeated (row-0 order). */\n unique: string[]\n /** With no unique field: a field `f` for which `${row.<rowKey>}-${row.<f>}` is unique. */\n pair?: string\n}\n\nconst isKey = (v: unknown): v is string | number =>\n typeof v === 'string' || (typeof v === 'number' && !Number.isNaN(v))\n\nconst isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Primitive leaf paths of `row` (depth ≤ 4; arrays and Dates are not walked). A field must be in\n * every row to be unique, so row 0's paths are the only candidates.\n */\nfunction candidates(row: unknown, skip: string | undefined): string[] {\n const out: string[] = []\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.length >= MAX_CANDIDATES) return\n const path = prefix ? `${prefix}.${k}` : k\n let v: unknown\n try {\n v = obj[k]\n } catch {\n continue\n }\n if (isKey(v)) {\n if (path !== skip) out.push(path)\n } else if (isPlain(v) && depth < MAX_DEPTH) walk(v, path, depth + 1)\n }\n }\n if (isPlain(row)) walk(row, '', 1)\n return out\n}\n\n/** `true` when `keyAt(i)` is a usable key for every row and never repeats (as a string). */\nfunction allDistinct(n: number, keyAt: (i: number) => unknown): boolean {\n const seen = new Set<string>()\n for (let i = 0; i < n; i++) {\n const v = keyAt(i)\n if (!isKey(v) || seen.has(String(v))) return false\n seen.add(String(v))\n }\n return true\n}\n\n/**\n * The fields that would make a good `rowKey`, computed by scanning every row (§5.2: the `fix` of\n * `DK_DUPLICATE_ROW_KEY` / `DK_MISSING_ROW_KEY`). Runs only when a key problem was found. With\n * `pairWith` (the `rowKey` path) and no unique field, also looks for a composite key, checked with\n * exactly the string the suggested `(row) => \\`${row.a}-${row.b}\\`` builds.\n */\nexport function findUniqueFields(\n data: readonly unknown[],\n rowKeyPath: string | undefined,\n pairWith = rowKeyPath,\n): UniqueFields {\n if (data.length === 0) return { unique: [] }\n const paths = candidates(data[0], rowKeyPath)\n const compiled = paths.map(compilePath)\n const at = (path: CompiledPath) => (i: number) => getValue(data[i], path)\n const unique: string[] = []\n for (let c = 0; c < paths.length && unique.length < MAX_UNIQUE; c++) {\n if (allDistinct(data.length, at(compiled[c] as CompiledPath))) unique.push(paths[c] as string)\n }\n if (unique.length > 0 || pairWith === undefined) return { unique }\n const key = at(compilePath(pairWith))\n for (let c = 0; c < paths.length; c++) {\n const other = at(compiled[c] as CompiledPath)\n const ok = allDistinct(data.length, (i) => {\n const a = key(i)\n const b = other(i)\n return isKey(a) && isKey(b) ? `${a}-${b}` : undefined\n })\n if (ok) return { unique, pair: paths[c] as string }\n }\n return { unique }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAyD;AAGzD,IAAM,YAAY;AAElB,IAAM,iBAAiB;AAEvB,IAAM,aAAa;AASnB,IAAM,QAAQ,CAAC,MACb,OAAO,MAAM,YAAa,OAAO,MAAM,YAAY,CAAC,OAAO,MAAM,CAAC;AAEpE,IAAM,UAAU,CAAC,MACf,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,aAAa;AAM7E,SAAS,WAAW,KAAc,MAAoC;AACpE,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,KAA8B,QAAgB,UAAkB;AAC5E,eAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,UAAI,IAAI,UAAU,eAAgB;AAClC,YAAM,OAAO,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK;AACzC,UAAI;AACJ,UAAI;AACF,YAAI,IAAI,CAAC;AAAA,MACX,QAAQ;AACN;AAAA,MACF;AACA,UAAI,MAAM,CAAC,GAAG;AACZ,YAAI,SAAS,KAAM,KAAI,KAAK,IAAI;AAAA,MAClC,WAAW,QAAQ,CAAC,KAAK,QAAQ,UAAW,MAAK,GAAG,MAAM,QAAQ,CAAC;AAAA,IACrE;AAAA,EACF;AACA,MAAI,QAAQ,GAAG,EAAG,MAAK,KAAK,IAAI,CAAC;AACjC,SAAO;AACT;AAGA,SAAS,YAAY,GAAW,OAAwC;AACtE,QAAM,OAAO,oBAAI,IAAY;AAC7B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,OAAO,CAAC,CAAC,EAAG,QAAO;AAC7C,SAAK,IAAI,OAAO,CAAC,CAAC;AAAA,EACpB;AACA,SAAO;AACT;AAQO,SAAS,iBACd,MACA,YACA,WAAW,YACG;AACd,MAAI,KAAK,WAAW,EAAG,QAAO,EAAE,QAAQ,CAAC,EAAE;AAC3C,QAAM,QAAQ,WAAW,KAAK,CAAC,GAAG,UAAU;AAC5C,QAAM,WAAW,MAAM,IAAI,4BAAW;AACtC,QAAM,KAAK,CAAC,SAAuB,CAAC,UAAc,2BAAS,KAAK,CAAC,GAAG,IAAI;AACxE,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,MAAM,UAAU,OAAO,SAAS,YAAY,KAAK;AACnE,QAAI,YAAY,KAAK,QAAQ,GAAG,SAAS,CAAC,CAAiB,CAAC,EAAG,QAAO,KAAK,MAAM,CAAC,CAAW;AAAA,EAC/F;AACA,MAAI,OAAO,SAAS,KAAK,aAAa,OAAW,QAAO,EAAE,OAAO;AACjE,QAAM,MAAM,OAAG,8BAAY,QAAQ,CAAC;AACpC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,GAAG,SAAS,CAAC,CAAiB;AAC5C,UAAM,KAAK,YAAY,KAAK,QAAQ,CAAC,MAAM;AACzC,YAAM,IAAI,IAAI,CAAC;AACf,YAAM,IAAI,MAAM,CAAC;AACjB,aAAO,MAAM,CAAC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK;AAAA,IAC9C,CAAC;AACD,QAAI,GAAI,QAAO,EAAE,QAAQ,MAAM,MAAM,CAAC,EAAY;AAAA,EACpD;AACA,SAAO,EAAE,OAAO;AAClB;","names":[]}
|
|
@@ -29,8 +29,8 @@ function allDistinct(n, keyAt) {
|
|
|
29
29
|
const seen = /* @__PURE__ */ new Set();
|
|
30
30
|
for (let i = 0; i < n; i++) {
|
|
31
31
|
const v = keyAt(i);
|
|
32
|
-
if (!isKey(v) || seen.has(v)) return false;
|
|
33
|
-
seen.add(v);
|
|
32
|
+
if (!isKey(v) || seen.has(String(v))) return false;
|
|
33
|
+
seen.add(String(v));
|
|
34
34
|
}
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/unique-fields.ts"],"sourcesContent":["import { type CompiledPath, compilePath, getValue } from '../column/get-value'\n\n/** Depth cap of a candidate path (as `Path<T>`, §4.1). */\nconst MAX_DEPTH = 4\n/** Bound on the candidates checked (each is one pass over the data). */\nconst MAX_CANDIDATES = 64\n/** Unique fields reported in a `fix`. */\nconst MAX_UNIQUE = 3\n\nexport interface UniqueFields {\n /** Paths whose value is a string / number in every row, never repeated (row-0 order). */\n unique: string[]\n /** With no unique field: a field `f` for which `${row.<rowKey>}-${row.<f>}` is unique. */\n pair?: string\n}\n\nconst isKey = (v: unknown): v is string | number =>\n typeof v === 'string' || (typeof v === 'number' && !Number.isNaN(v))\n\nconst isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Primitive leaf paths of `row` (depth ≤ 4; arrays and Dates are not walked). A field must be in\n * every row to be unique, so row 0's paths are the only candidates.\n */\nfunction candidates(row: unknown, skip: string | undefined): string[] {\n const out: string[] = []\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.length >= MAX_CANDIDATES) return\n const path = prefix ? `${prefix}.${k}` : k\n let v: unknown\n try {\n v = obj[k]\n } catch {\n continue\n }\n if (isKey(v)) {\n if (path !== skip) out.push(path)\n } else if (isPlain(v) && depth < MAX_DEPTH) walk(v, path, depth + 1)\n }\n }\n if (isPlain(row)) walk(row, '', 1)\n return out\n}\n\n/** `true` when `keyAt(i)` is a usable key for every row and never repeats. */\nfunction allDistinct(n: number, keyAt: (i: number) => unknown): boolean {\n const seen = new Set<
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/unique-fields.ts"],"sourcesContent":["import { type CompiledPath, compilePath, getValue } from '../column/get-value'\n\n/** Depth cap of a candidate path (as `Path<T>`, §4.1). */\nconst MAX_DEPTH = 4\n/** Bound on the candidates checked (each is one pass over the data). */\nconst MAX_CANDIDATES = 64\n/** Unique fields reported in a `fix`. */\nconst MAX_UNIQUE = 3\n\nexport interface UniqueFields {\n /** Paths whose value is a string / number in every row, never repeated (row-0 order). */\n unique: string[]\n /** With no unique field: a field `f` for which `${row.<rowKey>}-${row.<f>}` is unique. */\n pair?: string\n}\n\nconst isKey = (v: unknown): v is string | number =>\n typeof v === 'string' || (typeof v === 'number' && !Number.isNaN(v))\n\nconst isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Primitive leaf paths of `row` (depth ≤ 4; arrays and Dates are not walked). A field must be in\n * every row to be unique, so row 0's paths are the only candidates.\n */\nfunction candidates(row: unknown, skip: string | undefined): string[] {\n const out: string[] = []\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.length >= MAX_CANDIDATES) return\n const path = prefix ? `${prefix}.${k}` : k\n let v: unknown\n try {\n v = obj[k]\n } catch {\n continue\n }\n if (isKey(v)) {\n if (path !== skip) out.push(path)\n } else if (isPlain(v) && depth < MAX_DEPTH) walk(v, path, depth + 1)\n }\n }\n if (isPlain(row)) walk(row, '', 1)\n return out\n}\n\n/** `true` when `keyAt(i)` is a usable key for every row and never repeats (as a string). */\nfunction allDistinct(n: number, keyAt: (i: number) => unknown): boolean {\n const seen = new Set<string>()\n for (let i = 0; i < n; i++) {\n const v = keyAt(i)\n if (!isKey(v) || seen.has(String(v))) return false\n seen.add(String(v))\n }\n return true\n}\n\n/**\n * The fields that would make a good `rowKey`, computed by scanning every row (§5.2: the `fix` of\n * `DK_DUPLICATE_ROW_KEY` / `DK_MISSING_ROW_KEY`). Runs only when a key problem was found. With\n * `pairWith` (the `rowKey` path) and no unique field, also looks for a composite key, checked with\n * exactly the string the suggested `(row) => \\`${row.a}-${row.b}\\`` builds.\n */\nexport function findUniqueFields(\n data: readonly unknown[],\n rowKeyPath: string | undefined,\n pairWith = rowKeyPath,\n): UniqueFields {\n if (data.length === 0) return { unique: [] }\n const paths = candidates(data[0], rowKeyPath)\n const compiled = paths.map(compilePath)\n const at = (path: CompiledPath) => (i: number) => getValue(data[i], path)\n const unique: string[] = []\n for (let c = 0; c < paths.length && unique.length < MAX_UNIQUE; c++) {\n if (allDistinct(data.length, at(compiled[c] as CompiledPath))) unique.push(paths[c] as string)\n }\n if (unique.length > 0 || pairWith === undefined) return { unique }\n const key = at(compilePath(pairWith))\n for (let c = 0; c < paths.length; c++) {\n const other = at(compiled[c] as CompiledPath)\n const ok = allDistinct(data.length, (i) => {\n const a = key(i)\n const b = other(i)\n return isKey(a) && isKey(b) ? `${a}-${b}` : undefined\n })\n if (ok) return { unique, pair: paths[c] as string }\n }\n return { unique }\n}\n"],"mappings":";AAAA,SAA4B,aAAa,gBAAgB;AAGzD,IAAM,YAAY;AAElB,IAAM,iBAAiB;AAEvB,IAAM,aAAa;AASnB,IAAM,QAAQ,CAAC,MACb,OAAO,MAAM,YAAa,OAAO,MAAM,YAAY,CAAC,OAAO,MAAM,CAAC;AAEpE,IAAM,UAAU,CAAC,MACf,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,aAAa;AAM7E,SAAS,WAAW,KAAc,MAAoC;AACpE,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,KAA8B,QAAgB,UAAkB;AAC5E,eAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,UAAI,IAAI,UAAU,eAAgB;AAClC,YAAM,OAAO,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK;AACzC,UAAI;AACJ,UAAI;AACF,YAAI,IAAI,CAAC;AAAA,MACX,QAAQ;AACN;AAAA,MACF;AACA,UAAI,MAAM,CAAC,GAAG;AACZ,YAAI,SAAS,KAAM,KAAI,KAAK,IAAI;AAAA,MAClC,WAAW,QAAQ,CAAC,KAAK,QAAQ,UAAW,MAAK,GAAG,MAAM,QAAQ,CAAC;AAAA,IACrE;AAAA,EACF;AACA,MAAI,QAAQ,GAAG,EAAG,MAAK,KAAK,IAAI,CAAC;AACjC,SAAO;AACT;AAGA,SAAS,YAAY,GAAW,OAAwC;AACtE,QAAM,OAAO,oBAAI,IAAY;AAC7B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,OAAO,CAAC,CAAC,EAAG,QAAO;AAC7C,SAAK,IAAI,OAAO,CAAC,CAAC;AAAA,EACpB;AACA,SAAO;AACT;AAQO,SAAS,iBACd,MACA,YACA,WAAW,YACG;AACd,MAAI,KAAK,WAAW,EAAG,QAAO,EAAE,QAAQ,CAAC,EAAE;AAC3C,QAAM,QAAQ,WAAW,KAAK,CAAC,GAAG,UAAU;AAC5C,QAAM,WAAW,MAAM,IAAI,WAAW;AACtC,QAAM,KAAK,CAAC,SAAuB,CAAC,MAAc,SAAS,KAAK,CAAC,GAAG,IAAI;AACxE,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,MAAM,UAAU,OAAO,SAAS,YAAY,KAAK;AACnE,QAAI,YAAY,KAAK,QAAQ,GAAG,SAAS,CAAC,CAAiB,CAAC,EAAG,QAAO,KAAK,MAAM,CAAC,CAAW;AAAA,EAC/F;AACA,MAAI,OAAO,SAAS,KAAK,aAAa,OAAW,QAAO,EAAE,OAAO;AACjE,QAAM,MAAM,GAAG,YAAY,QAAQ,CAAC;AACpC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,GAAG,SAAS,CAAC,CAAiB;AAC5C,UAAM,KAAK,YAAY,KAAK,QAAQ,CAAC,MAAM;AACzC,YAAM,IAAI,IAAI,CAAC;AACf,YAAM,IAAI,MAAM,CAAC;AACjB,aAAO,MAAM,CAAC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK;AAAA,IAC9C,CAAC;AACD,QAAI,GAAI,QAAO,EAAE,QAAQ,MAAM,MAAM,CAAC,EAAY;AAAA,EACpD;AACA,SAAO,EAAE,OAAO;AAClB;","names":[]}
|
|
@@ -25,6 +25,8 @@ __export(boundary_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(boundary_exports);
|
|
26
26
|
var import_build = require("../diagnostics/build.cjs");
|
|
27
27
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
29
|
+
var import_prose = require("../diagnostics/prose.cjs");
|
|
28
30
|
var import_redact = require("../diagnostics/redact.cjs");
|
|
29
31
|
var import_classify = require("./classify.cjs");
|
|
30
32
|
var import_codes = require("./codes.cjs");
|
|
@@ -40,7 +42,7 @@ function recover(ctx, error) {
|
|
|
40
42
|
if (!silenced.has(ctx.emit)) {
|
|
41
43
|
silenced.add(ctx.emit);
|
|
42
44
|
console.error(
|
|
43
|
-
`[DataKable] ${ctx.op} failed; ${fallback.describe}. Later failures are silent.`,
|
|
45
|
+
`[DataKable] ${ctx.op} failed; ${(0, import_prose.expandProse)(fallback.describe, process.env.NODE_ENV !== "production")}. Later failures are silent.`,
|
|
44
46
|
process.env.NODE_ENV !== "production" ? error : (0, import_redact.scrubError)(error)
|
|
45
47
|
);
|
|
46
48
|
}
|
|
@@ -69,7 +71,7 @@ function recover(ctx, error) {
|
|
|
69
71
|
"DK_INTERNAL",
|
|
70
72
|
{
|
|
71
73
|
op: ctx.op,
|
|
72
|
-
problem: "
|
|
74
|
+
problem: "escaped",
|
|
73
75
|
message: error.message,
|
|
74
76
|
...slot,
|
|
75
77
|
errorName: error.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/intercept/boundary.ts"],"sourcesContent":["import { DatakableError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { scrubError } from '../diagnostics/redact'\nimport type { EmitOptions } from '../diagnostics/types'\nimport { type CallbackError, classifyError, errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\nimport type { InterceptorContext } from './context'\nimport type { Interceptor } from './define'\nimport { FAILED, getFallback } from './fallbacks'\nimport type { OpName } from './operations'\n\nkeepDiagnostics(boundaryDiagnostics)\n\n/** Emitters (one per table) that already printed their silent `diagnostic:emit` failure. */\nconst silenced = new WeakSet<object>()\n\nconst isThenable = (v: unknown): v is PromiseLike<unknown> =>\n v != null && typeof (v as { then?: unknown }).then === 'function'\n\n/** catch → classify → emit → registry fallback (§3.4). */\nfunction recover(ctx: InterceptorContext, error: unknown): unknown {\n // Config errors, `strict: true` rethrows and core invariants are meant to reach the caller.\n if (error instanceof DatakableError) throw error\n ctx.meta.set(FAILED, error)\n const fallback = getFallback(ctx.op)\n if (fallback.silent) {\n if (!silenced.has(ctx.emit)) {\n silenced.add(ctx.emit)\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable] ${ctx.op} failed; ${fallback.describe}. Later failures are silent.`,\n (process.env.NODE_ENV !== \"production\") ? error : scrubError(error),\n )\n }\n return fallback.value(ctx as never)\n }\n const opts: EmitOptions = { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase }\n const slot = ctx.where ? { slot: ctx.where } : {}\n const code = classifyError(error)\n if (code === 'DK_CALLBACK_FAILED') {\n const e = error as CallbackError\n const name = errorName(e.cause)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: e.slot,\n ...(e.op === undefined ? {} : { op: e.op }),\n ...(e.row === undefined ? {} : { rows: [e.row] }),\n count: 1,\n fallback: fallback.describe,\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...opts, cause: e.cause },\n )\n } else if (code === 'DK_INTERNAL') {\n ctx.emit(\n 'DK_INTERNAL',\n {\n op: ctx.op,\n problem: '
|
|
1
|
+
{"version":3,"sources":["../../src/intercept/boundary.ts"],"sourcesContent":["import { DatakableError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { expandProse } from '../diagnostics/prose'\nimport { scrubError } from '../diagnostics/redact'\nimport type { EmitOptions } from '../diagnostics/types'\nimport { type CallbackError, classifyError, errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\nimport type { InterceptorContext } from './context'\nimport type { Interceptor } from './define'\nimport { FAILED, getFallback } from './fallbacks'\nimport type { OpName } from './operations'\n\nkeepDiagnostics(boundaryDiagnostics)\n\n/** Emitters (one per table) that already printed their silent `diagnostic:emit` failure. */\nconst silenced = new WeakSet<object>()\n\nconst isThenable = (v: unknown): v is PromiseLike<unknown> =>\n v != null && typeof (v as { then?: unknown }).then === 'function'\n\n/** catch → classify → emit → registry fallback (§3.4). */\nfunction recover(ctx: InterceptorContext, error: unknown): unknown {\n // Config errors, `strict: true` rethrows and core invariants are meant to reach the caller.\n if (error instanceof DatakableError) throw error\n ctx.meta.set(FAILED, error)\n const fallback = getFallback(ctx.op)\n if (fallback.silent) {\n if (!silenced.has(ctx.emit)) {\n silenced.add(ctx.emit)\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable] ${ctx.op} failed; ${expandProse(fallback.describe, (process.env.NODE_ENV !== \"production\"))}. Later failures are silent.`,\n (process.env.NODE_ENV !== \"production\") ? error : scrubError(error),\n )\n }\n return fallback.value(ctx as never)\n }\n const opts: EmitOptions = { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase }\n const slot = ctx.where ? { slot: ctx.where } : {}\n const code = classifyError(error)\n if (code === 'DK_CALLBACK_FAILED') {\n const e = error as CallbackError\n const name = errorName(e.cause)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: e.slot,\n ...(e.op === undefined ? {} : { op: e.op }),\n ...(e.row === undefined ? {} : { rows: [e.row] }),\n count: 1,\n fallback: fallback.describe,\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...opts, cause: e.cause },\n )\n } else if (code === 'DK_INTERNAL') {\n ctx.emit(\n 'DK_INTERNAL',\n {\n op: ctx.op,\n problem: 'escaped',\n message: (error as Error).message,\n ...slot,\n errorName: (error as Error).name,\n fallback: fallback.describe,\n },\n { ...opts, cause: error },\n )\n } else {\n const name = errorName(error)\n ctx.emit(\n 'DK_UNKNOWN',\n {\n op: ctx.op,\n ...slot,\n ...(name === undefined ? {} : { errorName: name }),\n fallback: fallback.describe,\n },\n { ...opts, cause: error },\n )\n }\n return fallback.value(ctx as never)\n}\n\n/**\n * The outermost, non-removable interceptor (§3.4, §8.6). Install it once per table with\n * `runtime.installBoundary(errorBoundary)`. Reports through `ctx.emit`, so its sinks are the table's\n * diagnostics sinks. Hot-safe: the success path is one `try` and a thenable check, no allocation.\n */\nexport const errorBoundary: Interceptor<OpName> = {\n name: 'errorBoundary',\n on: '*',\n hot: true,\n intercept(ctx, next) {\n let out: unknown\n try {\n out = next()\n } catch (e) {\n return recover(ctx, e) as never\n }\n if (isThenable(out)) {\n return Promise.resolve(out).then(undefined, (e: unknown) => recover(ctx, e)) as never\n }\n return out as never\n },\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/intercept/boundary.ts
|
|
2
2
|
import { DatakableError } from "../diagnostics/build.js";
|
|
3
3
|
import { keepDiagnostics } from "../diagnostics/catalog.js";
|
|
4
|
+
import "../diagnostics/process-guard.js";
|
|
5
|
+
import { expandProse } from "../diagnostics/prose.js";
|
|
4
6
|
import { scrubError } from "../diagnostics/redact.js";
|
|
5
7
|
import { classifyError, errorName } from "./classify.js";
|
|
6
8
|
import { boundaryDiagnostics } from "./codes.js";
|
|
@@ -16,7 +18,7 @@ function recover(ctx, error) {
|
|
|
16
18
|
if (!silenced.has(ctx.emit)) {
|
|
17
19
|
silenced.add(ctx.emit);
|
|
18
20
|
console.error(
|
|
19
|
-
`[DataKable] ${ctx.op} failed; ${fallback.describe}. Later failures are silent.`,
|
|
21
|
+
`[DataKable] ${ctx.op} failed; ${expandProse(fallback.describe, process.env.NODE_ENV !== "production")}. Later failures are silent.`,
|
|
20
22
|
process.env.NODE_ENV !== "production" ? error : scrubError(error)
|
|
21
23
|
);
|
|
22
24
|
}
|
|
@@ -45,7 +47,7 @@ function recover(ctx, error) {
|
|
|
45
47
|
"DK_INTERNAL",
|
|
46
48
|
{
|
|
47
49
|
op: ctx.op,
|
|
48
|
-
problem: "
|
|
50
|
+
problem: "escaped",
|
|
49
51
|
message: error.message,
|
|
50
52
|
...slot,
|
|
51
53
|
errorName: error.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/intercept/boundary.ts"],"sourcesContent":["import { DatakableError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { scrubError } from '../diagnostics/redact'\nimport type { EmitOptions } from '../diagnostics/types'\nimport { type CallbackError, classifyError, errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\nimport type { InterceptorContext } from './context'\nimport type { Interceptor } from './define'\nimport { FAILED, getFallback } from './fallbacks'\nimport type { OpName } from './operations'\n\nkeepDiagnostics(boundaryDiagnostics)\n\n/** Emitters (one per table) that already printed their silent `diagnostic:emit` failure. */\nconst silenced = new WeakSet<object>()\n\nconst isThenable = (v: unknown): v is PromiseLike<unknown> =>\n v != null && typeof (v as { then?: unknown }).then === 'function'\n\n/** catch → classify → emit → registry fallback (§3.4). */\nfunction recover(ctx: InterceptorContext, error: unknown): unknown {\n // Config errors, `strict: true` rethrows and core invariants are meant to reach the caller.\n if (error instanceof DatakableError) throw error\n ctx.meta.set(FAILED, error)\n const fallback = getFallback(ctx.op)\n if (fallback.silent) {\n if (!silenced.has(ctx.emit)) {\n silenced.add(ctx.emit)\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable] ${ctx.op} failed; ${fallback.describe}. Later failures are silent.`,\n (process.env.NODE_ENV !== \"production\") ? error : scrubError(error),\n )\n }\n return fallback.value(ctx as never)\n }\n const opts: EmitOptions = { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase }\n const slot = ctx.where ? { slot: ctx.where } : {}\n const code = classifyError(error)\n if (code === 'DK_CALLBACK_FAILED') {\n const e = error as CallbackError\n const name = errorName(e.cause)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: e.slot,\n ...(e.op === undefined ? {} : { op: e.op }),\n ...(e.row === undefined ? {} : { rows: [e.row] }),\n count: 1,\n fallback: fallback.describe,\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...opts, cause: e.cause },\n )\n } else if (code === 'DK_INTERNAL') {\n ctx.emit(\n 'DK_INTERNAL',\n {\n op: ctx.op,\n problem: '
|
|
1
|
+
{"version":3,"sources":["../../src/intercept/boundary.ts"],"sourcesContent":["import { DatakableError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { expandProse } from '../diagnostics/prose'\nimport { scrubError } from '../diagnostics/redact'\nimport type { EmitOptions } from '../diagnostics/types'\nimport { type CallbackError, classifyError, errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\nimport type { InterceptorContext } from './context'\nimport type { Interceptor } from './define'\nimport { FAILED, getFallback } from './fallbacks'\nimport type { OpName } from './operations'\n\nkeepDiagnostics(boundaryDiagnostics)\n\n/** Emitters (one per table) that already printed their silent `diagnostic:emit` failure. */\nconst silenced = new WeakSet<object>()\n\nconst isThenable = (v: unknown): v is PromiseLike<unknown> =>\n v != null && typeof (v as { then?: unknown }).then === 'function'\n\n/** catch → classify → emit → registry fallback (§3.4). */\nfunction recover(ctx: InterceptorContext, error: unknown): unknown {\n // Config errors, `strict: true` rethrows and core invariants are meant to reach the caller.\n if (error instanceof DatakableError) throw error\n ctx.meta.set(FAILED, error)\n const fallback = getFallback(ctx.op)\n if (fallback.silent) {\n if (!silenced.has(ctx.emit)) {\n silenced.add(ctx.emit)\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable] ${ctx.op} failed; ${expandProse(fallback.describe, (process.env.NODE_ENV !== \"production\"))}. Later failures are silent.`,\n (process.env.NODE_ENV !== \"production\") ? error : scrubError(error),\n )\n }\n return fallback.value(ctx as never)\n }\n const opts: EmitOptions = { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase }\n const slot = ctx.where ? { slot: ctx.where } : {}\n const code = classifyError(error)\n if (code === 'DK_CALLBACK_FAILED') {\n const e = error as CallbackError\n const name = errorName(e.cause)\n ctx.emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: e.slot,\n ...(e.op === undefined ? {} : { op: e.op }),\n ...(e.row === undefined ? {} : { rows: [e.row] }),\n count: 1,\n fallback: fallback.describe,\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...opts, cause: e.cause },\n )\n } else if (code === 'DK_INTERNAL') {\n ctx.emit(\n 'DK_INTERNAL',\n {\n op: ctx.op,\n problem: 'escaped',\n message: (error as Error).message,\n ...slot,\n errorName: (error as Error).name,\n fallback: fallback.describe,\n },\n { ...opts, cause: error },\n )\n } else {\n const name = errorName(error)\n ctx.emit(\n 'DK_UNKNOWN',\n {\n op: ctx.op,\n ...slot,\n ...(name === undefined ? {} : { errorName: name }),\n fallback: fallback.describe,\n },\n { ...opts, cause: error },\n )\n }\n return fallback.value(ctx as never)\n}\n\n/**\n * The outermost, non-removable interceptor (§3.4, §8.6). Install it once per table with\n * `runtime.installBoundary(errorBoundary)`. Reports through `ctx.emit`, so its sinks are the table's\n * diagnostics sinks. Hot-safe: the success path is one `try` and a thenable check, no allocation.\n */\nexport const errorBoundary: Interceptor<OpName> = {\n name: 'errorBoundary',\n on: '*',\n hot: true,\n intercept(ctx, next) {\n let out: unknown\n try {\n out = next()\n } catch (e) {\n return recover(ctx, e) as never\n }\n if (isThenable(out)) {\n return Promise.resolve(out).then(undefined, (e: unknown) => recover(ctx, e)) as never\n }\n return out as never\n },\n}\n"],"mappings":"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","names":[]}
|
|
@@ -25,7 +25,10 @@ __export(cell_batch_exports, {
|
|
|
25
25
|
runCells: () => runCells
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(cell_batch_exports);
|
|
28
|
+
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
29
|
var import_classify = require("./classify.cjs");
|
|
30
|
+
var import_codes = require("./codes.cjs");
|
|
31
|
+
(0, import_catalog.keepDiagnostics)(import_codes.boundaryDiagnostics);
|
|
29
32
|
var CELL_FALLBACK = {
|
|
30
33
|
accessor: void 0,
|
|
31
34
|
format: "",
|
|
@@ -33,10 +36,10 @@ var CELL_FALLBACK = {
|
|
|
33
36
|
sortBy: void 0
|
|
34
37
|
};
|
|
35
38
|
var CELL_FALLBACK_TEXT = {
|
|
36
|
-
accessor: "
|
|
37
|
-
format: "
|
|
38
|
-
filterFn: "
|
|
39
|
-
sortBy: "
|
|
39
|
+
accessor: "cells-undefined",
|
|
40
|
+
format: "cells-empty",
|
|
41
|
+
filterFn: "rows-unmatched",
|
|
42
|
+
sortBy: "rows-last"
|
|
40
43
|
};
|
|
41
44
|
var CellFailures = class {
|
|
42
45
|
constructor(sample = 5) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/intercept/cell-batch.ts"],"sourcesContent":["import type { Emit, EmitOptions } from '../diagnostics/types'\nimport { errorName } from './classify'\n\n/** The cell slots of `column:batch`. */\nexport type CellSlot = 'accessor' | 'format' | 'filterFn' | 'sortBy'\n\n/**\n * Per failed cell (§3.4): `''` (format) / `undefined` (accessor) / non-matching (filter fn) /\n * `undefined`, sorted last as a missing value (sortBy).\n */\nexport const CELL_FALLBACK = {\n accessor: undefined,\n format: '',\n filterFn: false,\n sortBy: undefined,\n} as const\n\nconst CELL_FALLBACK_TEXT: Record<CellSlot, string> = {\n accessor: '
|
|
1
|
+
{"version":3,"sources":["../../src/intercept/cell-batch.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit, EmitOptions } from '../diagnostics/types'\nimport { errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\n\n// CellFailures emits DK_CALLBACK_FAILED: keep its definition wherever this module is bundled (a\n// feature entry alone brings the feature runtime, and with it the column cache, #5).\nkeepDiagnostics(boundaryDiagnostics)\n\n/** The cell slots of `column:batch`. */\nexport type CellSlot = 'accessor' | 'format' | 'filterFn' | 'sortBy'\n\n/**\n * Per failed cell (§3.4): `''` (format) / `undefined` (accessor) / non-matching (filter fn) /\n * `undefined`, sorted last as a missing value (sortBy).\n */\nexport const CELL_FALLBACK = {\n accessor: undefined,\n format: '',\n filterFn: false,\n sortBy: undefined,\n} as const\n\n/** Prose keys (diagnostics/prose.ts) of each slot's fallback. */\nconst CELL_FALLBACK_TEXT: Record<CellSlot, string> = {\n accessor: 'cells-undefined',\n format: 'cells-empty',\n filterFn: 'rows-unmatched',\n sortBy: 'rows-last',\n}\n\n/**\n * Failures of one column's cells in one pipeline run, recorded as numbers (§3.4): a count, the\n * first `sample` row indices and the first error. No per-cell allocation; `where` and text are\n * built only in {@link CellFailures.report}. Reuse one instance per column across runs.\n */\nexport class CellFailures {\n count = 0\n private first: unknown\n private readonly rows: Uint32Array\n\n constructor(sample = 5) {\n this.rows = new Uint32Array(sample)\n }\n\n add(row: number, error: unknown): void {\n if (this.count === 0) this.first = error\n if (this.count < this.rows.length) this.rows[this.count] = row\n this.count++\n }\n\n /** The recorded sample of failing row indices (a view; valid until the next reset). */\n sample(): Uint32Array {\n return this.rows.subarray(0, Math.min(this.count, this.rows.length))\n }\n\n reset(): void {\n this.count = 0\n this.first = undefined\n }\n\n /**\n * Emits one `DK_CALLBACK_FAILED` for this column (if any cell failed), then resets. `where` is the\n * slot path `columns[<index>].<slot>`; `field` replaces the slot name in that path when the\n * failing column field differs (`key` for a key column whose getter threw).\n */\n report(\n emit: Emit,\n columnIndex: number,\n columnId: string,\n slot: CellSlot,\n options?: EmitOptions,\n field: string = slot,\n ): void {\n if (this.count === 0) return\n const name = errorName(this.first)\n emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `columns[${columnIndex}].${field}`,\n column: columnId,\n rows: Array.from(this.sample()),\n count: this.count,\n fallback: CELL_FALLBACK_TEXT[slot],\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...options, cause: this.first },\n )\n this.reset()\n }\n}\n\n/**\n * The body of a `column:batch`: runs `cell` for each row in `rows` and writes `out[row]`. A throwing\n * cell gets `fallback` and is recorded in `failures` — the loop never throws per cell and never\n * dispatches an interceptor per cell. Call `failures.report(...)` once after the loop.\n */\nexport function runCells<R>(\n rows: Uint32Array,\n cell: (row: number) => R,\n out: { [row: number]: R },\n fallback: R,\n failures: CellFailures,\n): void {\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i] as number\n try {\n out[row] = cell(row)\n } catch (e) {\n out[row] = fallback\n failures.add(row, e)\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAgC;AAEhC,sBAA0B;AAC1B,mBAAoC;AAAA,IAIpC,gCAAgB,gCAAmB;AAS5B,IAAM,gBAAgB;AAAA,EAC3B,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AACV;AAGA,IAAM,qBAA+C;AAAA,EACnD,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AACV;AAOO,IAAM,eAAN,MAAmB;AAAA,EAKxB,YAAY,SAAS,GAAG;AAJxB,iBAAQ;AAKN,SAAK,OAAO,IAAI,YAAY,MAAM;AAAA,EACpC;AAAA,EAEA,IAAI,KAAa,OAAsB;AACrC,QAAI,KAAK,UAAU,EAAG,MAAK,QAAQ;AACnC,QAAI,KAAK,QAAQ,KAAK,KAAK,OAAQ,MAAK,KAAK,KAAK,KAAK,IAAI;AAC3D,SAAK;AAAA,EACP;AAAA;AAAA,EAGA,SAAsB;AACpB,WAAO,KAAK,KAAK,SAAS,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,CAAC;AAAA,EACrE;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ;AACb,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OACE,MACA,aACA,UACA,MACA,SACA,QAAgB,MACV;AACN,QAAI,KAAK,UAAU,EAAG;AACtB,UAAM,WAAO,2BAAU,KAAK,KAAK;AACjC;AAAA,MACE;AAAA,MACA;AAAA,QACE,MAAM,WAAW,WAAW,KAAK,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM,MAAM,KAAK,KAAK,OAAO,CAAC;AAAA,QAC9B,OAAO,KAAK;AAAA,QACZ,UAAU,mBAAmB,IAAI;AAAA,QACjC,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK;AAAA,MAClD;AAAA,MACA,EAAE,GAAG,SAAS,OAAO,KAAK,MAAM;AAAA,IAClC;AACA,SAAK,MAAM;AAAA,EACb;AACF;AAOO,SAAS,SACd,MACA,MACA,KACA,UACA,UACM;AACN,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI;AACF,UAAI,GAAG,IAAI,KAAK,GAAG;AAAA,IACrB,SAAS,GAAG;AACV,UAAI,GAAG,IAAI;AACX,eAAS,IAAI,KAAK,CAAC;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// src/intercept/cell-batch.ts
|
|
2
|
+
import { keepDiagnostics } from "../diagnostics/catalog.js";
|
|
2
3
|
import { errorName } from "./classify.js";
|
|
4
|
+
import { boundaryDiagnostics } from "./codes.js";
|
|
5
|
+
keepDiagnostics(boundaryDiagnostics);
|
|
3
6
|
var CELL_FALLBACK = {
|
|
4
7
|
accessor: void 0,
|
|
5
8
|
format: "",
|
|
@@ -7,10 +10,10 @@ var CELL_FALLBACK = {
|
|
|
7
10
|
sortBy: void 0
|
|
8
11
|
};
|
|
9
12
|
var CELL_FALLBACK_TEXT = {
|
|
10
|
-
accessor: "
|
|
11
|
-
format: "
|
|
12
|
-
filterFn: "
|
|
13
|
-
sortBy: "
|
|
13
|
+
accessor: "cells-undefined",
|
|
14
|
+
format: "cells-empty",
|
|
15
|
+
filterFn: "rows-unmatched",
|
|
16
|
+
sortBy: "rows-last"
|
|
14
17
|
};
|
|
15
18
|
var CellFailures = class {
|
|
16
19
|
constructor(sample = 5) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/intercept/cell-batch.ts"],"sourcesContent":["import type { Emit, EmitOptions } from '../diagnostics/types'\nimport { errorName } from './classify'\n\n/** The cell slots of `column:batch`. */\nexport type CellSlot = 'accessor' | 'format' | 'filterFn' | 'sortBy'\n\n/**\n * Per failed cell (§3.4): `''` (format) / `undefined` (accessor) / non-matching (filter fn) /\n * `undefined`, sorted last as a missing value (sortBy).\n */\nexport const CELL_FALLBACK = {\n accessor: undefined,\n format: '',\n filterFn: false,\n sortBy: undefined,\n} as const\n\nconst CELL_FALLBACK_TEXT: Record<CellSlot, string> = {\n accessor: '
|
|
1
|
+
{"version":3,"sources":["../../src/intercept/cell-batch.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit, EmitOptions } from '../diagnostics/types'\nimport { errorName } from './classify'\nimport { boundaryDiagnostics } from './codes'\n\n// CellFailures emits DK_CALLBACK_FAILED: keep its definition wherever this module is bundled (a\n// feature entry alone brings the feature runtime, and with it the column cache, #5).\nkeepDiagnostics(boundaryDiagnostics)\n\n/** The cell slots of `column:batch`. */\nexport type CellSlot = 'accessor' | 'format' | 'filterFn' | 'sortBy'\n\n/**\n * Per failed cell (§3.4): `''` (format) / `undefined` (accessor) / non-matching (filter fn) /\n * `undefined`, sorted last as a missing value (sortBy).\n */\nexport const CELL_FALLBACK = {\n accessor: undefined,\n format: '',\n filterFn: false,\n sortBy: undefined,\n} as const\n\n/** Prose keys (diagnostics/prose.ts) of each slot's fallback. */\nconst CELL_FALLBACK_TEXT: Record<CellSlot, string> = {\n accessor: 'cells-undefined',\n format: 'cells-empty',\n filterFn: 'rows-unmatched',\n sortBy: 'rows-last',\n}\n\n/**\n * Failures of one column's cells in one pipeline run, recorded as numbers (§3.4): a count, the\n * first `sample` row indices and the first error. No per-cell allocation; `where` and text are\n * built only in {@link CellFailures.report}. Reuse one instance per column across runs.\n */\nexport class CellFailures {\n count = 0\n private first: unknown\n private readonly rows: Uint32Array\n\n constructor(sample = 5) {\n this.rows = new Uint32Array(sample)\n }\n\n add(row: number, error: unknown): void {\n if (this.count === 0) this.first = error\n if (this.count < this.rows.length) this.rows[this.count] = row\n this.count++\n }\n\n /** The recorded sample of failing row indices (a view; valid until the next reset). */\n sample(): Uint32Array {\n return this.rows.subarray(0, Math.min(this.count, this.rows.length))\n }\n\n reset(): void {\n this.count = 0\n this.first = undefined\n }\n\n /**\n * Emits one `DK_CALLBACK_FAILED` for this column (if any cell failed), then resets. `where` is the\n * slot path `columns[<index>].<slot>`; `field` replaces the slot name in that path when the\n * failing column field differs (`key` for a key column whose getter threw).\n */\n report(\n emit: Emit,\n columnIndex: number,\n columnId: string,\n slot: CellSlot,\n options?: EmitOptions,\n field: string = slot,\n ): void {\n if (this.count === 0) return\n const name = errorName(this.first)\n emit(\n 'DK_CALLBACK_FAILED',\n {\n slot: `columns[${columnIndex}].${field}`,\n column: columnId,\n rows: Array.from(this.sample()),\n count: this.count,\n fallback: CELL_FALLBACK_TEXT[slot],\n ...(name === undefined ? {} : { errorName: name }),\n },\n { ...options, cause: this.first },\n )\n this.reset()\n }\n}\n\n/**\n * The body of a `column:batch`: runs `cell` for each row in `rows` and writes `out[row]`. A throwing\n * cell gets `fallback` and is recorded in `failures` — the loop never throws per cell and never\n * dispatches an interceptor per cell. Call `failures.report(...)` once after the loop.\n */\nexport function runCells<R>(\n rows: Uint32Array,\n cell: (row: number) => R,\n out: { [row: number]: R },\n fallback: R,\n failures: CellFailures,\n): void {\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i] as number\n try {\n out[row] = cell(row)\n } catch (e) {\n out[row] = fallback\n failures.add(row, e)\n }\n }\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAEhC,SAAS,iBAAiB;AAC1B,SAAS,2BAA2B;AAIpC,gBAAgB,mBAAmB;AAS5B,IAAM,gBAAgB;AAAA,EAC3B,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AACV;AAGA,IAAM,qBAA+C;AAAA,EACnD,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AACV;AAOO,IAAM,eAAN,MAAmB;AAAA,EAKxB,YAAY,SAAS,GAAG;AAJxB,iBAAQ;AAKN,SAAK,OAAO,IAAI,YAAY,MAAM;AAAA,EACpC;AAAA,EAEA,IAAI,KAAa,OAAsB;AACrC,QAAI,KAAK,UAAU,EAAG,MAAK,QAAQ;AACnC,QAAI,KAAK,QAAQ,KAAK,KAAK,OAAQ,MAAK,KAAK,KAAK,KAAK,IAAI;AAC3D,SAAK;AAAA,EACP;AAAA;AAAA,EAGA,SAAsB;AACpB,WAAO,KAAK,KAAK,SAAS,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,CAAC;AAAA,EACrE;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ;AACb,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OACE,MACA,aACA,UACA,MACA,SACA,QAAgB,MACV;AACN,QAAI,KAAK,UAAU,EAAG;AACtB,UAAM,OAAO,UAAU,KAAK,KAAK;AACjC;AAAA,MACE;AAAA,MACA;AAAA,QACE,MAAM,WAAW,WAAW,KAAK,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM,MAAM,KAAK,KAAK,OAAO,CAAC;AAAA,QAC9B,OAAO,KAAK;AAAA,QACZ,UAAU,mBAAmB,IAAI;AAAA,QACjC,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK;AAAA,MAClD;AAAA,MACA,EAAE,GAAG,SAAS,OAAO,KAAK,MAAM;AAAA,IAClC;AACA,SAAK,MAAM;AAAA,EACb;AACF;AAOO,SAAS,SACd,MACA,MACA,KACA,UACA,UACM;AACN,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI;AACF,UAAI,GAAG,IAAI,KAAK,GAAG;AAAA,IACrB,SAAS,GAAG;AACV,UAAI,GAAG,IAAI;AACX,eAAS,IAAI,KAAK,CAAC;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
|
package/dist/intercept/codes.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(codes_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(codes_exports);
|
|
27
27
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
28
29
|
var rowList = (rows, count) => {
|
|
29
30
|
if (!rows || rows.length === 0) return "";
|
|
30
31
|
const list = rows.map((r) => `#${r}`).join(", ");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/intercept/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_CALLBACK_FAILED: {\n /** The callback's option path, e.g. `columns[2].format`, `fetcher`, `searchFn`. */\n slot: string\n /**\n * A callback passed to a method rather than set as an option: the method (`table.setState`);\n * `slot` is then the argument (`updater`).\n */\n op?: string\n /** Column id, for cell slots. */\n column?: string\n /** A sample of the failing row indices (first few, in order). */\n rows?: readonly number[]\n /** How many calls failed (cells in a column batch; 1 for a single call). */\n count: number\n /** The slot fallback that was applied. */\n fallback: string\n /** `error.name` only; the full error is `context.cause`. */\n errorName?: string\n }\n DK_UNKNOWN: {\n op: string\n slot?: string\n errorName?: string\n fallback: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[] | undefined, count: number) => {\n if (!rows || rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return ` (${count > rows.length ? `first: ${list}, …` : `rows ${list}`})`\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const boundaryMessages: DiagnosticMessages<'DK_CALLBACK_FAILED' | 'DK_UNKNOWN'> = {\n DK_CALLBACK_FAILED: {\n problem: (d) =>\n `${d.op === undefined ? '' : `${d.op}() `}${d.slot}${d.column === undefined ? '' : ` (column \"${d.column}\")`} threw` +\n `${d.errorName ? ` ${d.errorName}` : ''} for ` +\n `${d.count === 1 ? '1 row' : `${d.count} rows`}${rowList(d.rows, d.count)}`,\n impact: (d) => `${d.fallback}; the rest of the table is unaffected`,\n fix: (d) => [\n `make ${d.op === undefined ? d.slot : `the ${d.slot} passed to ${d.op}()`} total: guard null / undefined / unexpected types and return a fallback ` +\n 'instead of throwing',\n d.rows && d.rows.length > 0\n ? `reproduce with table.explainRow() on row #${d.rows[0]}; the original error is context.cause`\n : 'the original error is context.cause (passed to onDiagnostic)',\n ],\n },\n DK_UNKNOWN: {\n problem: (d) =>\n `${d.errorName ?? 'a non-Error value'} was thrown during ${d.op}` +\n `${d.slot ? ` at ${d.slot}` : ''} from code DataKable could not classify`,\n impact: (d) => `${d.fallback}; the table keeps working`,\n fix: (d) => [\n 'inspect context.cause (passed to onDiagnostic) for the original error and its stack',\n `if your code runs inside ${d.op} (a callback, interceptor or plugin), catch errors there ` +\n 'and return a fallback',\n 'if the stack points into DataKable, report it as a bug',\n ],\n },\n}\n\n/** Codes owned by the errorBoundary (spec §3.4, §8.3, §8.6). `DK_INTERNAL` lives in diagnostics/codes.ts. */\nexport const boundaryDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_CALLBACK_FAILED',\n level: 'error',\n where: (d) =>\n d.op === undefined\n ? { kind: 'option', path: d.slot }\n : { kind: 'operation', op: d.op, slot: d.slot },\n },\n (process.env.NODE_ENV !== \"production\") && boundaryMessages.DK_CALLBACK_FAILED,\n ),\n defineDiagnostic(\n {\n code: 'DK_UNKNOWN',\n level: 'error',\n where: (d) =>\n d.slot ? { kind: 'operation', op: d.op, slot: d.slot } : { kind: 'operation', op: d.op },\n },\n (process.env.NODE_ENV !== \"production\") && boundaryMessages.DK_UNKNOWN,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;
|
|
1
|
+
{"version":3,"sources":["../../src/intercept/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_CALLBACK_FAILED: {\n /** The callback's option path, e.g. `columns[2].format`, `fetcher`, `searchFn`. */\n slot: string\n /**\n * A callback passed to a method rather than set as an option: the method (`table.setState`);\n * `slot` is then the argument (`updater`).\n */\n op?: string\n /** Column id, for cell slots. */\n column?: string\n /** A sample of the failing row indices (first few, in order). */\n rows?: readonly number[]\n /** How many calls failed (cells in a column batch; 1 for a single call). */\n count: number\n /** The slot fallback that was applied. */\n fallback: string\n /** `error.name` only; the full error is `context.cause`. */\n errorName?: string\n }\n DK_UNKNOWN: {\n op: string\n slot?: string\n errorName?: string\n fallback: string\n }\n }\n}\n\nconst rowList = (rows: readonly number[] | undefined, count: number) => {\n if (!rows || rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return ` (${count > rows.length ? `first: ${list}, …` : `rows ${list}`})`\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const boundaryMessages: DiagnosticMessages<'DK_CALLBACK_FAILED' | 'DK_UNKNOWN'> = {\n DK_CALLBACK_FAILED: {\n problem: (d) =>\n `${d.op === undefined ? '' : `${d.op}() `}${d.slot}${d.column === undefined ? '' : ` (column \"${d.column}\")`} threw` +\n `${d.errorName ? ` ${d.errorName}` : ''} for ` +\n `${d.count === 1 ? '1 row' : `${d.count} rows`}${rowList(d.rows, d.count)}`,\n impact: (d) => `${d.fallback}; the rest of the table is unaffected`,\n fix: (d) => [\n `make ${d.op === undefined ? d.slot : `the ${d.slot} passed to ${d.op}()`} total: guard null / undefined / unexpected types and return a fallback ` +\n 'instead of throwing',\n d.rows && d.rows.length > 0\n ? `reproduce with table.explainRow() on row #${d.rows[0]}; the original error is context.cause`\n : 'the original error is context.cause (passed to onDiagnostic)',\n ],\n },\n DK_UNKNOWN: {\n problem: (d) =>\n `${d.errorName ?? 'a non-Error value'} was thrown during ${d.op}` +\n `${d.slot ? ` at ${d.slot}` : ''} from code DataKable could not classify`,\n impact: (d) => `${d.fallback}; the table keeps working`,\n fix: (d) => [\n 'inspect context.cause (passed to onDiagnostic) for the original error and its stack',\n `if your code runs inside ${d.op} (a callback, interceptor or plugin), catch errors there ` +\n 'and return a fallback',\n 'if the stack points into DataKable, report it as a bug',\n ],\n },\n}\n\n/** Codes owned by the errorBoundary (spec §3.4, §8.3, §8.6). `DK_INTERNAL` lives in diagnostics/codes.ts. */\nexport const boundaryDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_CALLBACK_FAILED',\n level: 'error',\n where: (d) =>\n d.op === undefined\n ? { kind: 'option', path: d.slot }\n : { kind: 'operation', op: d.op, slot: d.slot },\n },\n (process.env.NODE_ENV !== \"production\") && boundaryMessages.DK_CALLBACK_FAILED,\n ),\n defineDiagnostic(\n {\n code: 'DK_UNKNOWN',\n level: 'error',\n where: (d) =>\n d.slot ? { kind: 'operation', op: d.op, slot: d.slot } : { kind: 'operation', op: d.op },\n },\n (process.env.NODE_ENV !== \"production\") && boundaryMessages.DK_UNKNOWN,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAgCP,IAAM,UAAU,CAAC,MAAqC,UAAkB;AACtE,MAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;AACvC,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,KAAK,QAAQ,KAAK,SAAS,UAAU,IAAI,aAAQ,QAAQ,IAAI,EAAE;AACxE;AAGO,IAAM,mBAA4E;AAAA,EACvF,oBAAoB;AAAA,IAClB,SAAS,CAAC,MACR,GAAG,EAAE,OAAO,SAAY,KAAK,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,IAAI,GAAG,EAAE,WAAW,SAAY,KAAK,aAAa,EAAE,MAAM,IAAI,SACzG,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE,QACpC,EAAE,UAAU,IAAI,UAAU,GAAG,EAAE,KAAK,OAAO,GAAG,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC3E,QAAQ,CAAC,MAAM,GAAG,EAAE,QAAQ;AAAA,IAC5B,KAAK,CAAC,MAAM;AAAA,MACV,QAAQ,EAAE,OAAO,SAAY,EAAE,OAAO,OAAO,EAAE,IAAI,cAAc,EAAE,EAAE,IAAI;AAAA,MAEzE,EAAE,QAAQ,EAAE,KAAK,SAAS,IACtB,6CAA6C,EAAE,KAAK,CAAC,CAAC,0CACtD;AAAA,IACN;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,SAAS,CAAC,MACR,GAAG,EAAE,aAAa,mBAAmB,sBAAsB,EAAE,EAAE,GAC5D,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK,EAAE;AAAA,IAClC,QAAQ,CAAC,MAAM,GAAG,EAAE,QAAQ;AAAA,IAC5B,KAAK,CAAC,MAAM;AAAA,MACV;AAAA,MACA,4BAA4B,EAAE,EAAE;AAAA,MAEhC;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,sBAAsB;AAAA,MACjC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,OAAO,SACL,EAAE,MAAM,UAAU,MAAM,EAAE,KAAK,IAC/B,EAAE,MAAM,aAAa,IAAI,EAAE,IAAI,MAAM,EAAE,KAAK;AAAA,IACpD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,iBAAiB;AAAA,EAC9D;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,OAAO,EAAE,MAAM,aAAa,IAAI,EAAE,IAAI,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,IAAI,EAAE,GAAG;AAAA,IAC3F;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,iBAAiB;AAAA,EAC9D;AACF;","names":[]}
|
package/dist/intercept/codes.js
CHANGED