@datakable/core 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/{codes-B9bCK73-.d.cts → codes-AX8XkMNY.d.cts} +6 -2
  3. package/dist/{codes-B9bCK73-.d.ts → codes-AX8XkMNY.d.ts} +6 -2
  4. package/dist/column-filters.d.cts +2 -2
  5. package/dist/column-filters.d.ts +2 -2
  6. package/dist/diagnostics/explain.cjs.map +1 -1
  7. package/dist/diagnostics/explain.js.map +1 -1
  8. package/dist/diagnostics/hub.cjs +7 -1
  9. package/dist/diagnostics/hub.cjs.map +1 -1
  10. package/dist/diagnostics/hub.js +7 -1
  11. package/dist/diagnostics/hub.js.map +1 -1
  12. package/dist/diagnostics/redact.cjs +1 -1
  13. package/dist/diagnostics/redact.cjs.map +1 -1
  14. package/dist/diagnostics/redact.js +1 -1
  15. package/dist/diagnostics/redact.js.map +1 -1
  16. package/dist/diagnostics/types.cjs.map +1 -1
  17. package/dist/{explain-Q8Cg0cZy.d.cts → explain-BVE3ln3G.d.ts} +3 -3
  18. package/dist/{explain-i0OATi_W.d.ts → explain-pKAnnxEW.d.cts} +3 -3
  19. package/dist/index.d.cts +6 -6
  20. package/dist/index.d.ts +6 -6
  21. package/dist/pagination.d.cts +2 -2
  22. package/dist/pagination.d.ts +2 -2
  23. package/dist/schema.d.cts +2 -2
  24. package/dist/schema.d.ts +2 -2
  25. package/dist/{search-async-entry-CuLYPfY_.d.cts → search-async-entry-ByMJbHjc.d.cts} +2 -2
  26. package/dist/{search-async-entry-Cx7xpuVZ.d.ts → search-async-entry-CFQODE08.d.ts} +2 -2
  27. package/dist/search-async.d.cts +3 -3
  28. package/dist/search-async.d.ts +3 -3
  29. package/dist/search.d.cts +4 -4
  30. package/dist/search.d.ts +4 -4
  31. package/dist/server.d.cts +3 -3
  32. package/dist/server.d.ts +3 -3
  33. package/dist/sorting.d.cts +2 -2
  34. package/dist/sorting.d.ts +2 -2
  35. package/dist/table/types.cjs.map +1 -1
  36. package/dist/table/types.js.map +1 -1
  37. package/dist/validity.d.cts +2 -2
  38. package/dist/validity.d.ts +2 -2
  39. package/dist/version.cjs +1 -1
  40. package/dist/version.cjs.map +1 -1
  41. package/dist/version.js +1 -1
  42. package/dist/version.js.map +1 -1
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -404,8 +404,9 @@ for (const d of table.getDiagnostics()) console.log(d.code, d.where, d.fix)
404
404
  - **Redaction** (`diagnostics.redact`): `'safe'` logs paths, indices, counts, value types and
405
405
  hashed row keys (production default); `'keys'` adds raw row keys (development default); `'full'`
406
406
  adds row values. A custom `redact(path, value)` can rewrite any field. Columns with
407
- `sensitive: true` are never logged at any level, and fetcher errors are logged by name only (the
408
- original goes to `onDiagnostic` as `context.cause`).
407
+ `sensitive: true` are never logged at any level (a sensitive `rowKey` is logged as
408
+ `[sensitive]`, not hashed), and fetcher errors are logged by name only (the original goes to
409
+ `onDiagnostic` as `context.cause`; `getDiagnostics()` leaves it out).
409
410
  - **Production text:** production builds keep `code`, `where`, `context` and a docs URL and drop
410
411
  the `problem` / `impact` / `fix` text. To keep it:
411
412
 
@@ -55,7 +55,11 @@ interface DiagnosticContext {
55
55
  };
56
56
  /** e.g. `DK_SCHEMA_INVALID_ROW#run57` */
57
57
  causedBy?: string;
58
- /** Original error (ES2022 cause). Reaches `onDiagnostic` only; `toJSON` and the console leave it out. */
58
+ /**
59
+ * Original error (ES2022 cause). Reaches code you install (`onDiagnostic`, `diagnostic:emit`
60
+ * interceptors, the `redact` seam) and, under `strict`, the thrown `DatakableError`'s `cause`;
61
+ * `getDiagnostics()`, `toJSON` and the console leave it out.
62
+ */
59
63
  cause?: unknown;
60
64
  }
61
65
  interface Diagnostic {
@@ -119,7 +123,7 @@ interface RedactionOptions {
119
123
  redact?: RedactFn;
120
124
  /** Ids / keys of `sensitive: true` columns: never logged at any level. */
121
125
  sensitiveColumns?: () => Iterable<string>;
122
- /** The `rowKey` path is, is inside or contains a sensitive column: keys hashed at every level. */
126
+ /** The `rowKey` path is, is inside or contains a sensitive column: keys logged as `[sensitive]` at every level. */
123
127
  rowKeySensitive?: () => boolean;
124
128
  }
125
129
  /**
@@ -55,7 +55,11 @@ interface DiagnosticContext {
55
55
  };
56
56
  /** e.g. `DK_SCHEMA_INVALID_ROW#run57` */
57
57
  causedBy?: string;
58
- /** Original error (ES2022 cause). Reaches `onDiagnostic` only; `toJSON` and the console leave it out. */
58
+ /**
59
+ * Original error (ES2022 cause). Reaches code you install (`onDiagnostic`, `diagnostic:emit`
60
+ * interceptors, the `redact` seam) and, under `strict`, the thrown `DatakableError`'s `cause`;
61
+ * `getDiagnostics()`, `toJSON` and the console leave it out.
62
+ */
59
63
  cause?: unknown;
60
64
  }
61
65
  interface Diagnostic {
@@ -119,7 +123,7 @@ interface RedactionOptions {
119
123
  redact?: RedactFn;
120
124
  /** Ids / keys of `sensitive: true` columns: never logged at any level. */
121
125
  sensitiveColumns?: () => Iterable<string>;
122
- /** The `rowKey` path is, is inside or contains a sensitive column: keys hashed at every level. */
126
+ /** The `rowKey` path is, is inside or contains a sensitive column: keys logged as `[sensitive]` at every level. */
123
127
  rowKeySensitive?: () => boolean;
124
128
  }
125
129
  /**
@@ -1,5 +1,5 @@
1
- export { U as columnFiltersDiagnostics } from './codes-B9bCK73-.cjs';
2
- import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-Q8Cg0cZy.cjs';
1
+ export { U as columnFiltersDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-pKAnnxEW.cjs';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.cjs';
4
4
 
5
5
  /** The value a filter compares against: the element type for array-valued cells (§6.1). */
@@ -1,5 +1,5 @@
1
- export { U as columnFiltersDiagnostics } from './codes-B9bCK73-.js';
2
- import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-i0OATi_W.js';
1
+ export { U as columnFiltersDiagnostics } from './codes-AX8XkMNY.js';
2
+ import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-BVE3ln3G.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
4
 
5
5
  /** The value a filter compares against: the element type for array-valued cells (§6.1). */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/explain.ts"],"sourcesContent":["import type { AnyFeature, RowModelContext, Stage } from '../feature/types'\nimport { resolveRowKeys } from '../ingest/row-key'\nimport type { SchemaOutcome } from '../ingest/schema'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { RowKey } from '../pipeline/row'\nimport type { RunRecord } from '../pipeline/runner'\nimport type { RowModel, RowModelStatus } from '../pipeline/types'\nimport './process-guard'\nimport type { explainDetail } from './explain-detail'\nimport { formatRowExplanation, formatTableExplanation } from './explain-text'\nimport { type RedactionOptions, redactDetails, resolveRedaction, scrubSchemaIssue } from './redact'\nimport { readTrace } from './trace-store'\nimport type { Diagnostic, Location, Trigger } from './types'\n\n/** What `createTable` gives `explain()` / `explainRow()`: read-only views of the table. */\nexport interface ExplainHost {\n readonly table: object\n /** Installed features in pipeline (stage) order. */\n readonly features: readonly AnyFeature[]\n /** The current row model (computed if data, state or columns changed, like `getRowModel()`). */\n rowModel(): RowModel<unknown>\n lastRun(): RunRecord | undefined\n /** A step context for replaying a `rowModel` on chosen rows (no diagnostics, no interceptors). */\n replay(name: string): RowModelContext\n data(): readonly unknown[]\n state(): Readonly<Record<string, unknown>>\n rowKey(): unknown\n /** `options.invalidRows` when a schema is set; `undefined` without one. */\n schemaMode(): InvalidRowsMode | undefined\n schema(): SchemaOutcome | undefined\n diagnostics(): readonly Diagnostic[]\n readonly redaction: RedactionOptions\n /** Why a step removed a row (`explainDetail`): it comes with the features (their runtime). */\n readonly detail?: typeof explainDetail\n}\n\n/** One step of {@link TableExplanation}: counts only. */\nexport interface ExplainStep {\n readonly feature: string\n readonly stage: Stage\n /** `'server'`: the server ran the step; its counts are the rows it was given. */\n readonly mode: 'client' | 'server'\n readonly input: number\n readonly output: number\n /** Served from the step memo in this run (nothing changed for it). */\n readonly cached: boolean\n /** The step threw or returned a bad output and passed its input through. */\n readonly failed: boolean\n /**\n * Duration (tracer only, development or `interceptors: [tracer()]`). A step that waited on\n * time-sliced work: the total of its runs and of the slices between them (#61).\n */\n readonly ms?: number\n /** Time-sliced work (#61): the runs {@link ms} spans; absent when the step ran in one. */\n readonly runs?: number\n}\n\n/** What `table.explain()` returns (§8.4); `String(e)` is the console summary. */\nexport interface TableExplanation {\n readonly run: number\n readonly trigger: Trigger\n readonly status: RowModelStatus\n /** Rows after ingest (after the schema dropped invalid rows). */\n readonly rows: number\n /** With a schema: its `invalidRows` mode and how many rows failed it. */\n readonly schema?: { readonly mode: InvalidRowsMode; readonly invalid: number }\n readonly steps: readonly ExplainStep[]\n /** Rows after filter and search (`getRowModel().filteredCount`). */\n readonly filteredCount: number\n /** With a pagination slice: the page shown (0-based), the page count and the page size. */\n readonly page?: { readonly index: number; readonly count: number; readonly size: number }\n /** Rows in `getRowModel().rows`. */\n readonly visible: number\n /** Error and warning diagnostics in `getDiagnostics()`. */\n readonly diagnostics: number\n /** Timings come from the tracer (it saw this run). */\n readonly traced: boolean\n /** Duration of the run (tracer only). */\n readonly ms?: number\n toString(): string\n}\n\n/**\n * `kept` / `removed` by the step, `skipped` (server mode), `failed` (passed its input through),\n * `not-reached` (an earlier step or the schema removed the row).\n */\nexport type RowStepOutcome = 'kept' | 'removed' | 'skipped' | 'failed' | 'not-reached'\n\nexport interface RowStep {\n readonly feature: string\n readonly stage: Stage\n readonly outcome: RowStepOutcome\n /** Kept: the row's position in the step output (0-based) and the output size. */\n readonly position?: number\n readonly of?: number\n /** Why the step removed the row (redacted). */\n readonly detail?: Readonly<Record<string, unknown>>\n}\n\n/** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */\nexport interface RowExplanation {\n /** The key asked for, redacted (a hash under `'safe'`). */\n readonly key: unknown\n readonly found: boolean\n /** Position in `getData()`; absent when not found or dropped by the schema. */\n readonly index?: number\n /** Position in `options.data` of a row the schema dropped. */\n readonly rawIndex?: number\n readonly schema: 'none' | 'valid' | 'flagged' | 'dropped'\n /** Schema issues of a flagged / dropped row (paths + DataKable's own text unless `'full'`). */\n readonly issues?: readonly { path: string; message: string }[]\n readonly steps: readonly RowStep[]\n /** The step that removed the row: a feature name, or `'schema'`. */\n readonly removedBy?: string\n /** The row is in `getRowModel().rows`. */\n readonly visible: boolean\n readonly run: number\n /** Rows after ingest (for \"not found\"). */\n readonly rows: number\n toString(): string\n}\n\nconst NOOP = () => {}\n\n/** Steps that take part in the row model: a stage and a `rowModel` (server mode included). */\nconst pipelineSteps = (features: readonly AnyFeature[]) =>\n features.filter((f) => f.stage !== 'none' && typeof f.rowModel === 'function')\n\nconst isServer = (f: AnyFeature) => f.mode === 'server'\n\n/** `{ pageIndex, pageSize }` of the first paginate-stage slice that has them. */\nexport function pageOf(host: ExplainHost): { index: number; size: number } | undefined {\n for (const f of host.features) {\n if (f.stage !== 'paginate') continue\n const s = host.state()[f.name] as { pageIndex?: unknown; pageSize?: unknown } | undefined\n if (typeof s?.pageIndex === 'number' && typeof s.pageSize === 'number' && s.pageSize > 0) {\n return { index: s.pageIndex, size: s.pageSize }\n }\n }\n return undefined\n}\n\nexport const pageCount = (rows: number, size: number) => Math.ceil(rows / size)\n\n/** Clamped like pagination does (the page actually shown). */\nexport const shownPage = (index: number, count: number) => Math.max(0, Math.min(index, count - 1))\n\n/** `table.explain()` (§8.4): the last run's counts, with tracer timings when it was traced. */\nexport function explainTable(host: ExplainHost): TableExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const trace = readTrace(host.table)\n const traced = trace !== undefined && last !== undefined && trace.run === last.run\n const steps: ExplainStep[] = []\n let flowing = model.totalCount\n for (const f of pipelineSteps(host.features)) {\n if (isServer(f)) {\n steps.push(step(f, 'server', flowing, flowing, false, false))\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) continue\n flowing = rec.output.length\n const t = traced ? trace.steps.get(f.name) : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(t ? { ms: t.ms, ...(t.runs ? { runs: t.runs } : {}) } : {}),\n })\n }\n const mode = host.schemaMode()\n const schema = host.schema()\n const p = pageOf(host)\n const count = p ? pageCount(model.filteredCount, p.size) : 0\n let diagnostics = 0\n for (const d of host.diagnostics()) if (d.level !== 'info') diagnostics++\n const e: TableExplanation = {\n run: last?.run ?? 0,\n trigger: last?.trigger ?? 'init',\n status: model.status,\n rows: model.totalCount,\n ...(mode === undefined ? {} : { schema: { mode, invalid: schema?.invalid.length ?? 0 } }),\n steps,\n filteredCount: model.filteredCount,\n ...(p ? { page: { index: shownPage(p.index, count), count, size: p.size } } : {}),\n visible: model.rows.length,\n diagnostics,\n traced,\n ...(traced && trace.ms !== undefined ? { ms: trace.ms } : {}),\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatTableExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n\nfunction step(\n f: AnyFeature,\n mode: 'client' | 'server',\n input: number,\n output: number,\n cached: boolean,\n failed: boolean,\n): ExplainStep {\n return { feature: f.name, stage: f.stage, mode, input, output, cached, failed }\n}\n\n/** Index of the row whose key equals `key` (`42` matches `'42'`), or -1. */\nfunction findKey(rows: readonly unknown[], rowKey: unknown, key: unknown): number {\n const keyOf = resolveRowKeys(rows, rowKey, NOOP, NOOP)\n const s = String(key)\n for (let i = 0; i < rows.length; i++) {\n const k = keyOf(rows[i], i)\n if (k === key || String(k) === s) return i\n }\n return -1\n}\n\n/**\n * `table.explainRow(key)` (§8.4): replays the row through every stage of the last run — schema,\n * then each step in pipeline order — and names the step that removed it. Survival is read from the\n * recorded step input / output (no per-row bookkeeping during normal runs); a removal by a filter\n * replays its filters on the single row to name the ones that reject it. Read-only: no state\n * change, no notification, no diagnostic. Every value it shows is redacted (§8.2).\n */\nexport function explainRow(host: ExplainHost, key: RowKey): RowExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const data = host.data()\n const r = resolveRedaction(host.redaction, (process.env.NODE_ENV !== \"production\"))\n const where: Location = { kind: 'feature', name: 'explainRow' }\n const shownKey = redactDetails({ key }, { key: 'rowKey' }, where, r).key\n const mode = host.schemaMode()\n const outcome = host.schema()\n const rowKey = host.rowKey()\n\n const index = findKey(data, rowKey, key)\n let schema: RowExplanation['schema'] = mode === undefined ? 'none' : 'valid'\n let rawIndex: number | undefined\n let issues: readonly unknown[] | undefined\n if (index >= 0) {\n issues = outcome?.flagged?.get(index)\n if (issues) schema = 'flagged'\n } else if (outcome && outcome.invalid.length > 0 && rowKey !== undefined && mode !== 'flag') {\n // Dropped by the schema: looked up among the invalid raw rows by the same key.\n const i = findKey(\n outcome.invalid.map((x) => x.raw),\n rowKey,\n key,\n )\n const inv = outcome.invalid[i]\n if (inv) {\n schema = 'dropped'\n rawIndex = inv.index\n issues = inv.issues\n }\n }\n const found = index >= 0 || rawIndex !== undefined\n let removedBy: string | undefined = schema === 'dropped' ? 'schema' : undefined\n const steps: RowStep[] = []\n for (const f of pipelineSteps(host.features)) {\n const base = { feature: f.name, stage: f.stage }\n if (!found || removedBy !== undefined) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n if (isServer(f)) {\n steps.push({ ...base, outcome: 'skipped' })\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n const position = rec.output.indexOf(index)\n if (position >= 0) {\n const of = rec.output.length\n steps.push({ ...base, outcome: rec.failed ? 'failed' : 'kept', position, of })\n continue\n }\n removedBy = f.name\n const detail = host.detail?.(host, f, rec.input, index, r, where)\n steps.push({ ...base, outcome: 'removed', ...(detail ? { detail } : {}) })\n }\n const e: RowExplanation = {\n key: shownKey,\n found,\n ...(index >= 0 ? { index } : {}),\n ...(rawIndex === undefined ? {} : { rawIndex }),\n schema,\n ...(issues ? { issues: issues.map((x, i) => scrubSchemaIssue(x, r, `issues.${i}`)) } : {}),\n steps,\n ...(removedBy === undefined ? {} : { removedBy }),\n visible: index >= 0 && model.rows.some((row) => (row as { index?: unknown }).index === index),\n run: last?.run ?? 0,\n rows: data.length,\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatRowExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/explain.ts"],"sourcesContent":["import type { AnyFeature, RowModelContext, Stage } from '../feature/types'\nimport { resolveRowKeys } from '../ingest/row-key'\nimport type { SchemaOutcome } from '../ingest/schema'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { RowKey } from '../pipeline/row'\nimport type { RunRecord } from '../pipeline/runner'\nimport type { RowModel, RowModelStatus } from '../pipeline/types'\nimport './process-guard'\nimport type { explainDetail } from './explain-detail'\nimport { formatRowExplanation, formatTableExplanation } from './explain-text'\nimport { type RedactionOptions, redactDetails, resolveRedaction, scrubSchemaIssue } from './redact'\nimport { readTrace } from './trace-store'\nimport type { Diagnostic, Location, Trigger } from './types'\n\n/** What `createTable` gives `explain()` / `explainRow()`: read-only views of the table. */\nexport interface ExplainHost {\n readonly table: object\n /** Installed features in pipeline (stage) order. */\n readonly features: readonly AnyFeature[]\n /** The current row model (computed if data, state or columns changed, like `getRowModel()`). */\n rowModel(): RowModel<unknown>\n lastRun(): RunRecord | undefined\n /** A step context for replaying a `rowModel` on chosen rows (no diagnostics, no interceptors). */\n replay(name: string): RowModelContext\n data(): readonly unknown[]\n state(): Readonly<Record<string, unknown>>\n rowKey(): unknown\n /** `options.invalidRows` when a schema is set; `undefined` without one. */\n schemaMode(): InvalidRowsMode | undefined\n schema(): SchemaOutcome | undefined\n diagnostics(): readonly Diagnostic[]\n readonly redaction: RedactionOptions\n /** Why a step removed a row (`explainDetail`): it comes with the features (their runtime). */\n readonly detail?: typeof explainDetail\n}\n\n/** One step of {@link TableExplanation}: counts only. */\nexport interface ExplainStep {\n readonly feature: string\n readonly stage: Stage\n /** `'server'`: the server ran the step; its counts are the rows it was given. */\n readonly mode: 'client' | 'server'\n readonly input: number\n readonly output: number\n /** Served from the step memo in this run (nothing changed for it). */\n readonly cached: boolean\n /** The step threw or returned a bad output and passed its input through. */\n readonly failed: boolean\n /**\n * Duration (tracer only, development or `interceptors: [tracer()]`). A step that waited on\n * time-sliced work: the total of its runs and of the slices between them (#61).\n */\n readonly ms?: number\n /** Time-sliced work (#61): the runs {@link ms} spans; absent when the step ran in one. */\n readonly runs?: number\n}\n\n/** What `table.explain()` returns (§8.4); `String(e)` is the console summary. */\nexport interface TableExplanation {\n readonly run: number\n readonly trigger: Trigger\n readonly status: RowModelStatus\n /** Rows after ingest (after the schema dropped invalid rows). */\n readonly rows: number\n /** With a schema: its `invalidRows` mode and how many rows failed it. */\n readonly schema?: { readonly mode: InvalidRowsMode; readonly invalid: number }\n readonly steps: readonly ExplainStep[]\n /** Rows after filter and search (`getRowModel().filteredCount`). */\n readonly filteredCount: number\n /** With a pagination slice: the page shown (0-based), the page count and the page size. */\n readonly page?: { readonly index: number; readonly count: number; readonly size: number }\n /** Rows in `getRowModel().rows`. */\n readonly visible: number\n /** Error and warning diagnostics in `getDiagnostics()`. */\n readonly diagnostics: number\n /** Timings come from the tracer (it saw this run). */\n readonly traced: boolean\n /** Duration of the run (tracer only). */\n readonly ms?: number\n toString(): string\n}\n\n/**\n * `kept` / `removed` by the step, `skipped` (server mode), `failed` (passed its input through),\n * `not-reached` (an earlier step or the schema removed the row).\n */\nexport type RowStepOutcome = 'kept' | 'removed' | 'skipped' | 'failed' | 'not-reached'\n\nexport interface RowStep {\n readonly feature: string\n readonly stage: Stage\n readonly outcome: RowStepOutcome\n /** Kept: the row's position in the step output (0-based) and the output size. */\n readonly position?: number\n readonly of?: number\n /** Why the step removed the row (redacted). */\n readonly detail?: Readonly<Record<string, unknown>>\n}\n\n/** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */\nexport interface RowExplanation {\n /** The key asked for, redacted (a hash under `'safe'`, `[sensitive]` for a sensitive rowKey). */\n readonly key: unknown\n readonly found: boolean\n /** Position in `getData()`; absent when not found or dropped by the schema. */\n readonly index?: number\n /** Position in `options.data` of a row the schema dropped. */\n readonly rawIndex?: number\n readonly schema: 'none' | 'valid' | 'flagged' | 'dropped'\n /** Schema issues of a flagged / dropped row (paths + DataKable's own text unless `'full'`). */\n readonly issues?: readonly { path: string; message: string }[]\n readonly steps: readonly RowStep[]\n /** The step that removed the row: a feature name, or `'schema'`. */\n readonly removedBy?: string\n /** The row is in `getRowModel().rows`. */\n readonly visible: boolean\n readonly run: number\n /** Rows after ingest (for \"not found\"). */\n readonly rows: number\n toString(): string\n}\n\nconst NOOP = () => {}\n\n/** Steps that take part in the row model: a stage and a `rowModel` (server mode included). */\nconst pipelineSteps = (features: readonly AnyFeature[]) =>\n features.filter((f) => f.stage !== 'none' && typeof f.rowModel === 'function')\n\nconst isServer = (f: AnyFeature) => f.mode === 'server'\n\n/** `{ pageIndex, pageSize }` of the first paginate-stage slice that has them. */\nexport function pageOf(host: ExplainHost): { index: number; size: number } | undefined {\n for (const f of host.features) {\n if (f.stage !== 'paginate') continue\n const s = host.state()[f.name] as { pageIndex?: unknown; pageSize?: unknown } | undefined\n if (typeof s?.pageIndex === 'number' && typeof s.pageSize === 'number' && s.pageSize > 0) {\n return { index: s.pageIndex, size: s.pageSize }\n }\n }\n return undefined\n}\n\nexport const pageCount = (rows: number, size: number) => Math.ceil(rows / size)\n\n/** Clamped like pagination does (the page actually shown). */\nexport const shownPage = (index: number, count: number) => Math.max(0, Math.min(index, count - 1))\n\n/** `table.explain()` (§8.4): the last run's counts, with tracer timings when it was traced. */\nexport function explainTable(host: ExplainHost): TableExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const trace = readTrace(host.table)\n const traced = trace !== undefined && last !== undefined && trace.run === last.run\n const steps: ExplainStep[] = []\n let flowing = model.totalCount\n for (const f of pipelineSteps(host.features)) {\n if (isServer(f)) {\n steps.push(step(f, 'server', flowing, flowing, false, false))\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) continue\n flowing = rec.output.length\n const t = traced ? trace.steps.get(f.name) : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(t ? { ms: t.ms, ...(t.runs ? { runs: t.runs } : {}) } : {}),\n })\n }\n const mode = host.schemaMode()\n const schema = host.schema()\n const p = pageOf(host)\n const count = p ? pageCount(model.filteredCount, p.size) : 0\n let diagnostics = 0\n for (const d of host.diagnostics()) if (d.level !== 'info') diagnostics++\n const e: TableExplanation = {\n run: last?.run ?? 0,\n trigger: last?.trigger ?? 'init',\n status: model.status,\n rows: model.totalCount,\n ...(mode === undefined ? {} : { schema: { mode, invalid: schema?.invalid.length ?? 0 } }),\n steps,\n filteredCount: model.filteredCount,\n ...(p ? { page: { index: shownPage(p.index, count), count, size: p.size } } : {}),\n visible: model.rows.length,\n diagnostics,\n traced,\n ...(traced && trace.ms !== undefined ? { ms: trace.ms } : {}),\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatTableExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n\nfunction step(\n f: AnyFeature,\n mode: 'client' | 'server',\n input: number,\n output: number,\n cached: boolean,\n failed: boolean,\n): ExplainStep {\n return { feature: f.name, stage: f.stage, mode, input, output, cached, failed }\n}\n\n/** Index of the row whose key equals `key` (`42` matches `'42'`), or -1. */\nfunction findKey(rows: readonly unknown[], rowKey: unknown, key: unknown): number {\n const keyOf = resolveRowKeys(rows, rowKey, NOOP, NOOP)\n const s = String(key)\n for (let i = 0; i < rows.length; i++) {\n const k = keyOf(rows[i], i)\n if (k === key || String(k) === s) return i\n }\n return -1\n}\n\n/**\n * `table.explainRow(key)` (§8.4): replays the row through every stage of the last run — schema,\n * then each step in pipeline order — and names the step that removed it. Survival is read from the\n * recorded step input / output (no per-row bookkeeping during normal runs); a removal by a filter\n * replays its filters on the single row to name the ones that reject it. Read-only: no state\n * change, no notification, no diagnostic. Every value it shows is redacted (§8.2).\n */\nexport function explainRow(host: ExplainHost, key: RowKey): RowExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const data = host.data()\n const r = resolveRedaction(host.redaction, (process.env.NODE_ENV !== \"production\"))\n const where: Location = { kind: 'feature', name: 'explainRow' }\n const shownKey = redactDetails({ key }, { key: 'rowKey' }, where, r).key\n const mode = host.schemaMode()\n const outcome = host.schema()\n const rowKey = host.rowKey()\n\n const index = findKey(data, rowKey, key)\n let schema: RowExplanation['schema'] = mode === undefined ? 'none' : 'valid'\n let rawIndex: number | undefined\n let issues: readonly unknown[] | undefined\n if (index >= 0) {\n issues = outcome?.flagged?.get(index)\n if (issues) schema = 'flagged'\n } else if (outcome && outcome.invalid.length > 0 && rowKey !== undefined && mode !== 'flag') {\n // Dropped by the schema: looked up among the invalid raw rows by the same key.\n const i = findKey(\n outcome.invalid.map((x) => x.raw),\n rowKey,\n key,\n )\n const inv = outcome.invalid[i]\n if (inv) {\n schema = 'dropped'\n rawIndex = inv.index\n issues = inv.issues\n }\n }\n const found = index >= 0 || rawIndex !== undefined\n let removedBy: string | undefined = schema === 'dropped' ? 'schema' : undefined\n const steps: RowStep[] = []\n for (const f of pipelineSteps(host.features)) {\n const base = { feature: f.name, stage: f.stage }\n if (!found || removedBy !== undefined) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n if (isServer(f)) {\n steps.push({ ...base, outcome: 'skipped' })\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n const position = rec.output.indexOf(index)\n if (position >= 0) {\n const of = rec.output.length\n steps.push({ ...base, outcome: rec.failed ? 'failed' : 'kept', position, of })\n continue\n }\n removedBy = f.name\n const detail = host.detail?.(host, f, rec.input, index, r, where)\n steps.push({ ...base, outcome: 'removed', ...(detail ? { detail } : {}) })\n }\n const e: RowExplanation = {\n key: shownKey,\n found,\n ...(index >= 0 ? { index } : {}),\n ...(rawIndex === undefined ? {} : { rawIndex }),\n schema,\n ...(issues ? { issues: issues.map((x, i) => scrubSchemaIssue(x, r, `issues.${i}`)) } : {}),\n steps,\n ...(removedBy === undefined ? {} : { removedBy }),\n visible: index >= 0 && model.rows.some((row) => (row as { index?: unknown }).index === index),\n run: last?.run ?? 0,\n rows: data.length,\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatRowExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/explain.ts"],"sourcesContent":["import type { AnyFeature, RowModelContext, Stage } from '../feature/types'\nimport { resolveRowKeys } from '../ingest/row-key'\nimport type { SchemaOutcome } from '../ingest/schema'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { RowKey } from '../pipeline/row'\nimport type { RunRecord } from '../pipeline/runner'\nimport type { RowModel, RowModelStatus } from '../pipeline/types'\nimport './process-guard'\nimport type { explainDetail } from './explain-detail'\nimport { formatRowExplanation, formatTableExplanation } from './explain-text'\nimport { type RedactionOptions, redactDetails, resolveRedaction, scrubSchemaIssue } from './redact'\nimport { readTrace } from './trace-store'\nimport type { Diagnostic, Location, Trigger } from './types'\n\n/** What `createTable` gives `explain()` / `explainRow()`: read-only views of the table. */\nexport interface ExplainHost {\n readonly table: object\n /** Installed features in pipeline (stage) order. */\n readonly features: readonly AnyFeature[]\n /** The current row model (computed if data, state or columns changed, like `getRowModel()`). */\n rowModel(): RowModel<unknown>\n lastRun(): RunRecord | undefined\n /** A step context for replaying a `rowModel` on chosen rows (no diagnostics, no interceptors). */\n replay(name: string): RowModelContext\n data(): readonly unknown[]\n state(): Readonly<Record<string, unknown>>\n rowKey(): unknown\n /** `options.invalidRows` when a schema is set; `undefined` without one. */\n schemaMode(): InvalidRowsMode | undefined\n schema(): SchemaOutcome | undefined\n diagnostics(): readonly Diagnostic[]\n readonly redaction: RedactionOptions\n /** Why a step removed a row (`explainDetail`): it comes with the features (their runtime). */\n readonly detail?: typeof explainDetail\n}\n\n/** One step of {@link TableExplanation}: counts only. */\nexport interface ExplainStep {\n readonly feature: string\n readonly stage: Stage\n /** `'server'`: the server ran the step; its counts are the rows it was given. */\n readonly mode: 'client' | 'server'\n readonly input: number\n readonly output: number\n /** Served from the step memo in this run (nothing changed for it). */\n readonly cached: boolean\n /** The step threw or returned a bad output and passed its input through. */\n readonly failed: boolean\n /**\n * Duration (tracer only, development or `interceptors: [tracer()]`). A step that waited on\n * time-sliced work: the total of its runs and of the slices between them (#61).\n */\n readonly ms?: number\n /** Time-sliced work (#61): the runs {@link ms} spans; absent when the step ran in one. */\n readonly runs?: number\n}\n\n/** What `table.explain()` returns (§8.4); `String(e)` is the console summary. */\nexport interface TableExplanation {\n readonly run: number\n readonly trigger: Trigger\n readonly status: RowModelStatus\n /** Rows after ingest (after the schema dropped invalid rows). */\n readonly rows: number\n /** With a schema: its `invalidRows` mode and how many rows failed it. */\n readonly schema?: { readonly mode: InvalidRowsMode; readonly invalid: number }\n readonly steps: readonly ExplainStep[]\n /** Rows after filter and search (`getRowModel().filteredCount`). */\n readonly filteredCount: number\n /** With a pagination slice: the page shown (0-based), the page count and the page size. */\n readonly page?: { readonly index: number; readonly count: number; readonly size: number }\n /** Rows in `getRowModel().rows`. */\n readonly visible: number\n /** Error and warning diagnostics in `getDiagnostics()`. */\n readonly diagnostics: number\n /** Timings come from the tracer (it saw this run). */\n readonly traced: boolean\n /** Duration of the run (tracer only). */\n readonly ms?: number\n toString(): string\n}\n\n/**\n * `kept` / `removed` by the step, `skipped` (server mode), `failed` (passed its input through),\n * `not-reached` (an earlier step or the schema removed the row).\n */\nexport type RowStepOutcome = 'kept' | 'removed' | 'skipped' | 'failed' | 'not-reached'\n\nexport interface RowStep {\n readonly feature: string\n readonly stage: Stage\n readonly outcome: RowStepOutcome\n /** Kept: the row's position in the step output (0-based) and the output size. */\n readonly position?: number\n readonly of?: number\n /** Why the step removed the row (redacted). */\n readonly detail?: Readonly<Record<string, unknown>>\n}\n\n/** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */\nexport interface RowExplanation {\n /** The key asked for, redacted (a hash under `'safe'`). */\n readonly key: unknown\n readonly found: boolean\n /** Position in `getData()`; absent when not found or dropped by the schema. */\n readonly index?: number\n /** Position in `options.data` of a row the schema dropped. */\n readonly rawIndex?: number\n readonly schema: 'none' | 'valid' | 'flagged' | 'dropped'\n /** Schema issues of a flagged / dropped row (paths + DataKable's own text unless `'full'`). */\n readonly issues?: readonly { path: string; message: string }[]\n readonly steps: readonly RowStep[]\n /** The step that removed the row: a feature name, or `'schema'`. */\n readonly removedBy?: string\n /** The row is in `getRowModel().rows`. */\n readonly visible: boolean\n readonly run: number\n /** Rows after ingest (for \"not found\"). */\n readonly rows: number\n toString(): string\n}\n\nconst NOOP = () => {}\n\n/** Steps that take part in the row model: a stage and a `rowModel` (server mode included). */\nconst pipelineSteps = (features: readonly AnyFeature[]) =>\n features.filter((f) => f.stage !== 'none' && typeof f.rowModel === 'function')\n\nconst isServer = (f: AnyFeature) => f.mode === 'server'\n\n/** `{ pageIndex, pageSize }` of the first paginate-stage slice that has them. */\nexport function pageOf(host: ExplainHost): { index: number; size: number } | undefined {\n for (const f of host.features) {\n if (f.stage !== 'paginate') continue\n const s = host.state()[f.name] as { pageIndex?: unknown; pageSize?: unknown } | undefined\n if (typeof s?.pageIndex === 'number' && typeof s.pageSize === 'number' && s.pageSize > 0) {\n return { index: s.pageIndex, size: s.pageSize }\n }\n }\n return undefined\n}\n\nexport const pageCount = (rows: number, size: number) => Math.ceil(rows / size)\n\n/** Clamped like pagination does (the page actually shown). */\nexport const shownPage = (index: number, count: number) => Math.max(0, Math.min(index, count - 1))\n\n/** `table.explain()` (§8.4): the last run's counts, with tracer timings when it was traced. */\nexport function explainTable(host: ExplainHost): TableExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const trace = readTrace(host.table)\n const traced = trace !== undefined && last !== undefined && trace.run === last.run\n const steps: ExplainStep[] = []\n let flowing = model.totalCount\n for (const f of pipelineSteps(host.features)) {\n if (isServer(f)) {\n steps.push(step(f, 'server', flowing, flowing, false, false))\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) continue\n flowing = rec.output.length\n const t = traced ? trace.steps.get(f.name) : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(t ? { ms: t.ms, ...(t.runs ? { runs: t.runs } : {}) } : {}),\n })\n }\n const mode = host.schemaMode()\n const schema = host.schema()\n const p = pageOf(host)\n const count = p ? pageCount(model.filteredCount, p.size) : 0\n let diagnostics = 0\n for (const d of host.diagnostics()) if (d.level !== 'info') diagnostics++\n const e: TableExplanation = {\n run: last?.run ?? 0,\n trigger: last?.trigger ?? 'init',\n status: model.status,\n rows: model.totalCount,\n ...(mode === undefined ? {} : { schema: { mode, invalid: schema?.invalid.length ?? 0 } }),\n steps,\n filteredCount: model.filteredCount,\n ...(p ? { page: { index: shownPage(p.index, count), count, size: p.size } } : {}),\n visible: model.rows.length,\n diagnostics,\n traced,\n ...(traced && trace.ms !== undefined ? { ms: trace.ms } : {}),\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatTableExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n\nfunction step(\n f: AnyFeature,\n mode: 'client' | 'server',\n input: number,\n output: number,\n cached: boolean,\n failed: boolean,\n): ExplainStep {\n return { feature: f.name, stage: f.stage, mode, input, output, cached, failed }\n}\n\n/** Index of the row whose key equals `key` (`42` matches `'42'`), or -1. */\nfunction findKey(rows: readonly unknown[], rowKey: unknown, key: unknown): number {\n const keyOf = resolveRowKeys(rows, rowKey, NOOP, NOOP)\n const s = String(key)\n for (let i = 0; i < rows.length; i++) {\n const k = keyOf(rows[i], i)\n if (k === key || String(k) === s) return i\n }\n return -1\n}\n\n/**\n * `table.explainRow(key)` (§8.4): replays the row through every stage of the last run — schema,\n * then each step in pipeline order — and names the step that removed it. Survival is read from the\n * recorded step input / output (no per-row bookkeeping during normal runs); a removal by a filter\n * replays its filters on the single row to name the ones that reject it. Read-only: no state\n * change, no notification, no diagnostic. Every value it shows is redacted (§8.2).\n */\nexport function explainRow(host: ExplainHost, key: RowKey): RowExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const data = host.data()\n const r = resolveRedaction(host.redaction, (process.env.NODE_ENV !== \"production\"))\n const where: Location = { kind: 'feature', name: 'explainRow' }\n const shownKey = redactDetails({ key }, { key: 'rowKey' }, where, r).key\n const mode = host.schemaMode()\n const outcome = host.schema()\n const rowKey = host.rowKey()\n\n const index = findKey(data, rowKey, key)\n let schema: RowExplanation['schema'] = mode === undefined ? 'none' : 'valid'\n let rawIndex: number | undefined\n let issues: readonly unknown[] | undefined\n if (index >= 0) {\n issues = outcome?.flagged?.get(index)\n if (issues) schema = 'flagged'\n } else if (outcome && outcome.invalid.length > 0 && rowKey !== undefined && mode !== 'flag') {\n // Dropped by the schema: looked up among the invalid raw rows by the same key.\n const i = findKey(\n outcome.invalid.map((x) => x.raw),\n rowKey,\n key,\n )\n const inv = outcome.invalid[i]\n if (inv) {\n schema = 'dropped'\n rawIndex = inv.index\n issues = inv.issues\n }\n }\n const found = index >= 0 || rawIndex !== undefined\n let removedBy: string | undefined = schema === 'dropped' ? 'schema' : undefined\n const steps: RowStep[] = []\n for (const f of pipelineSteps(host.features)) {\n const base = { feature: f.name, stage: f.stage }\n if (!found || removedBy !== undefined) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n if (isServer(f)) {\n steps.push({ ...base, outcome: 'skipped' })\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n const position = rec.output.indexOf(index)\n if (position >= 0) {\n const of = rec.output.length\n steps.push({ ...base, outcome: rec.failed ? 'failed' : 'kept', position, of })\n continue\n }\n removedBy = f.name\n const detail = host.detail?.(host, f, rec.input, index, r, where)\n steps.push({ ...base, outcome: 'removed', ...(detail ? { detail } : {}) })\n }\n const e: RowExplanation = {\n key: shownKey,\n found,\n ...(index >= 0 ? { index } : {}),\n ...(rawIndex === undefined ? {} : { rawIndex }),\n schema,\n ...(issues ? { issues: issues.map((x, i) => scrubSchemaIssue(x, r, `issues.${i}`)) } : {}),\n steps,\n ...(removedBy === undefined ? {} : { removedBy }),\n visible: index >= 0 && model.rows.some((row) => (row as { index?: unknown }).index === index),\n run: last?.run ?? 0,\n rows: data.length,\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatRowExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/explain.ts"],"sourcesContent":["import type { AnyFeature, RowModelContext, Stage } from '../feature/types'\nimport { resolveRowKeys } from '../ingest/row-key'\nimport type { SchemaOutcome } from '../ingest/schema'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { RowKey } from '../pipeline/row'\nimport type { RunRecord } from '../pipeline/runner'\nimport type { RowModel, RowModelStatus } from '../pipeline/types'\nimport './process-guard'\nimport type { explainDetail } from './explain-detail'\nimport { formatRowExplanation, formatTableExplanation } from './explain-text'\nimport { type RedactionOptions, redactDetails, resolveRedaction, scrubSchemaIssue } from './redact'\nimport { readTrace } from './trace-store'\nimport type { Diagnostic, Location, Trigger } from './types'\n\n/** What `createTable` gives `explain()` / `explainRow()`: read-only views of the table. */\nexport interface ExplainHost {\n readonly table: object\n /** Installed features in pipeline (stage) order. */\n readonly features: readonly AnyFeature[]\n /** The current row model (computed if data, state or columns changed, like `getRowModel()`). */\n rowModel(): RowModel<unknown>\n lastRun(): RunRecord | undefined\n /** A step context for replaying a `rowModel` on chosen rows (no diagnostics, no interceptors). */\n replay(name: string): RowModelContext\n data(): readonly unknown[]\n state(): Readonly<Record<string, unknown>>\n rowKey(): unknown\n /** `options.invalidRows` when a schema is set; `undefined` without one. */\n schemaMode(): InvalidRowsMode | undefined\n schema(): SchemaOutcome | undefined\n diagnostics(): readonly Diagnostic[]\n readonly redaction: RedactionOptions\n /** Why a step removed a row (`explainDetail`): it comes with the features (their runtime). */\n readonly detail?: typeof explainDetail\n}\n\n/** One step of {@link TableExplanation}: counts only. */\nexport interface ExplainStep {\n readonly feature: string\n readonly stage: Stage\n /** `'server'`: the server ran the step; its counts are the rows it was given. */\n readonly mode: 'client' | 'server'\n readonly input: number\n readonly output: number\n /** Served from the step memo in this run (nothing changed for it). */\n readonly cached: boolean\n /** The step threw or returned a bad output and passed its input through. */\n readonly failed: boolean\n /**\n * Duration (tracer only, development or `interceptors: [tracer()]`). A step that waited on\n * time-sliced work: the total of its runs and of the slices between them (#61).\n */\n readonly ms?: number\n /** Time-sliced work (#61): the runs {@link ms} spans; absent when the step ran in one. */\n readonly runs?: number\n}\n\n/** What `table.explain()` returns (§8.4); `String(e)` is the console summary. */\nexport interface TableExplanation {\n readonly run: number\n readonly trigger: Trigger\n readonly status: RowModelStatus\n /** Rows after ingest (after the schema dropped invalid rows). */\n readonly rows: number\n /** With a schema: its `invalidRows` mode and how many rows failed it. */\n readonly schema?: { readonly mode: InvalidRowsMode; readonly invalid: number }\n readonly steps: readonly ExplainStep[]\n /** Rows after filter and search (`getRowModel().filteredCount`). */\n readonly filteredCount: number\n /** With a pagination slice: the page shown (0-based), the page count and the page size. */\n readonly page?: { readonly index: number; readonly count: number; readonly size: number }\n /** Rows in `getRowModel().rows`. */\n readonly visible: number\n /** Error and warning diagnostics in `getDiagnostics()`. */\n readonly diagnostics: number\n /** Timings come from the tracer (it saw this run). */\n readonly traced: boolean\n /** Duration of the run (tracer only). */\n readonly ms?: number\n toString(): string\n}\n\n/**\n * `kept` / `removed` by the step, `skipped` (server mode), `failed` (passed its input through),\n * `not-reached` (an earlier step or the schema removed the row).\n */\nexport type RowStepOutcome = 'kept' | 'removed' | 'skipped' | 'failed' | 'not-reached'\n\nexport interface RowStep {\n readonly feature: string\n readonly stage: Stage\n readonly outcome: RowStepOutcome\n /** Kept: the row's position in the step output (0-based) and the output size. */\n readonly position?: number\n readonly of?: number\n /** Why the step removed the row (redacted). */\n readonly detail?: Readonly<Record<string, unknown>>\n}\n\n/** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */\nexport interface RowExplanation {\n /** The key asked for, redacted (a hash under `'safe'`, `[sensitive]` for a sensitive rowKey). */\n readonly key: unknown\n readonly found: boolean\n /** Position in `getData()`; absent when not found or dropped by the schema. */\n readonly index?: number\n /** Position in `options.data` of a row the schema dropped. */\n readonly rawIndex?: number\n readonly schema: 'none' | 'valid' | 'flagged' | 'dropped'\n /** Schema issues of a flagged / dropped row (paths + DataKable's own text unless `'full'`). */\n readonly issues?: readonly { path: string; message: string }[]\n readonly steps: readonly RowStep[]\n /** The step that removed the row: a feature name, or `'schema'`. */\n readonly removedBy?: string\n /** The row is in `getRowModel().rows`. */\n readonly visible: boolean\n readonly run: number\n /** Rows after ingest (for \"not found\"). */\n readonly rows: number\n toString(): string\n}\n\nconst NOOP = () => {}\n\n/** Steps that take part in the row model: a stage and a `rowModel` (server mode included). */\nconst pipelineSteps = (features: readonly AnyFeature[]) =>\n features.filter((f) => f.stage !== 'none' && typeof f.rowModel === 'function')\n\nconst isServer = (f: AnyFeature) => f.mode === 'server'\n\n/** `{ pageIndex, pageSize }` of the first paginate-stage slice that has them. */\nexport function pageOf(host: ExplainHost): { index: number; size: number } | undefined {\n for (const f of host.features) {\n if (f.stage !== 'paginate') continue\n const s = host.state()[f.name] as { pageIndex?: unknown; pageSize?: unknown } | undefined\n if (typeof s?.pageIndex === 'number' && typeof s.pageSize === 'number' && s.pageSize > 0) {\n return { index: s.pageIndex, size: s.pageSize }\n }\n }\n return undefined\n}\n\nexport const pageCount = (rows: number, size: number) => Math.ceil(rows / size)\n\n/** Clamped like pagination does (the page actually shown). */\nexport const shownPage = (index: number, count: number) => Math.max(0, Math.min(index, count - 1))\n\n/** `table.explain()` (§8.4): the last run's counts, with tracer timings when it was traced. */\nexport function explainTable(host: ExplainHost): TableExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const trace = readTrace(host.table)\n const traced = trace !== undefined && last !== undefined && trace.run === last.run\n const steps: ExplainStep[] = []\n let flowing = model.totalCount\n for (const f of pipelineSteps(host.features)) {\n if (isServer(f)) {\n steps.push(step(f, 'server', flowing, flowing, false, false))\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) continue\n flowing = rec.output.length\n const t = traced ? trace.steps.get(f.name) : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(t ? { ms: t.ms, ...(t.runs ? { runs: t.runs } : {}) } : {}),\n })\n }\n const mode = host.schemaMode()\n const schema = host.schema()\n const p = pageOf(host)\n const count = p ? pageCount(model.filteredCount, p.size) : 0\n let diagnostics = 0\n for (const d of host.diagnostics()) if (d.level !== 'info') diagnostics++\n const e: TableExplanation = {\n run: last?.run ?? 0,\n trigger: last?.trigger ?? 'init',\n status: model.status,\n rows: model.totalCount,\n ...(mode === undefined ? {} : { schema: { mode, invalid: schema?.invalid.length ?? 0 } }),\n steps,\n filteredCount: model.filteredCount,\n ...(p ? { page: { index: shownPage(p.index, count), count, size: p.size } } : {}),\n visible: model.rows.length,\n diagnostics,\n traced,\n ...(traced && trace.ms !== undefined ? { ms: trace.ms } : {}),\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatTableExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n\nfunction step(\n f: AnyFeature,\n mode: 'client' | 'server',\n input: number,\n output: number,\n cached: boolean,\n failed: boolean,\n): ExplainStep {\n return { feature: f.name, stage: f.stage, mode, input, output, cached, failed }\n}\n\n/** Index of the row whose key equals `key` (`42` matches `'42'`), or -1. */\nfunction findKey(rows: readonly unknown[], rowKey: unknown, key: unknown): number {\n const keyOf = resolveRowKeys(rows, rowKey, NOOP, NOOP)\n const s = String(key)\n for (let i = 0; i < rows.length; i++) {\n const k = keyOf(rows[i], i)\n if (k === key || String(k) === s) return i\n }\n return -1\n}\n\n/**\n * `table.explainRow(key)` (§8.4): replays the row through every stage of the last run — schema,\n * then each step in pipeline order — and names the step that removed it. Survival is read from the\n * recorded step input / output (no per-row bookkeeping during normal runs); a removal by a filter\n * replays its filters on the single row to name the ones that reject it. Read-only: no state\n * change, no notification, no diagnostic. Every value it shows is redacted (§8.2).\n */\nexport function explainRow(host: ExplainHost, key: RowKey): RowExplanation {\n const model = host.rowModel()\n const last = host.lastRun()\n const data = host.data()\n const r = resolveRedaction(host.redaction, (process.env.NODE_ENV !== \"production\"))\n const where: Location = { kind: 'feature', name: 'explainRow' }\n const shownKey = redactDetails({ key }, { key: 'rowKey' }, where, r).key\n const mode = host.schemaMode()\n const outcome = host.schema()\n const rowKey = host.rowKey()\n\n const index = findKey(data, rowKey, key)\n let schema: RowExplanation['schema'] = mode === undefined ? 'none' : 'valid'\n let rawIndex: number | undefined\n let issues: readonly unknown[] | undefined\n if (index >= 0) {\n issues = outcome?.flagged?.get(index)\n if (issues) schema = 'flagged'\n } else if (outcome && outcome.invalid.length > 0 && rowKey !== undefined && mode !== 'flag') {\n // Dropped by the schema: looked up among the invalid raw rows by the same key.\n const i = findKey(\n outcome.invalid.map((x) => x.raw),\n rowKey,\n key,\n )\n const inv = outcome.invalid[i]\n if (inv) {\n schema = 'dropped'\n rawIndex = inv.index\n issues = inv.issues\n }\n }\n const found = index >= 0 || rawIndex !== undefined\n let removedBy: string | undefined = schema === 'dropped' ? 'schema' : undefined\n const steps: RowStep[] = []\n for (const f of pipelineSteps(host.features)) {\n const base = { feature: f.name, stage: f.stage }\n if (!found || removedBy !== undefined) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n if (isServer(f)) {\n steps.push({ ...base, outcome: 'skipped' })\n continue\n }\n const rec = last?.steps.find((s) => s.feature === f)\n if (!rec) {\n steps.push({ ...base, outcome: 'not-reached' })\n continue\n }\n const position = rec.output.indexOf(index)\n if (position >= 0) {\n const of = rec.output.length\n steps.push({ ...base, outcome: rec.failed ? 'failed' : 'kept', position, of })\n continue\n }\n removedBy = f.name\n const detail = host.detail?.(host, f, rec.input, index, r, where)\n steps.push({ ...base, outcome: 'removed', ...(detail ? { detail } : {}) })\n }\n const e: RowExplanation = {\n key: shownKey,\n found,\n ...(index >= 0 ? { index } : {}),\n ...(rawIndex === undefined ? {} : { rawIndex }),\n schema,\n ...(issues ? { issues: issues.map((x, i) => scrubSchemaIssue(x, r, `issues.${i}`)) } : {}),\n steps,\n ...(removedBy === undefined ? {} : { removedBy }),\n visible: index >= 0 && model.rows.some((row) => (row as { index?: unknown }).index === index),\n run: last?.run ?? 0,\n rows: data.length,\n toString: () => ((process.env.NODE_ENV !== \"production\") ? formatRowExplanation(e) : JSON.stringify(e)),\n }\n return e\n}\n"],"mappings":"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","names":[]}
@@ -142,7 +142,13 @@ function createDiagnostics(options = {}) {
142
142
  return {
143
143
  table,
144
144
  emit,
145
- getDiagnostics: () => buffer.toArray(),
145
+ // The original error can quote row values (a sensitive column's accessor throwing
146
+ // `bad ssn 123-…`): it reaches onDiagnostic and strict's DatakableError only, so readers of the
147
+ // buffer get a copy without it. `occurrences` stays shared, so counts still aggregate live.
148
+ getDiagnostics: () => buffer.toArray().map((d) => {
149
+ const { cause, ...context } = d.context;
150
+ return cause === void 0 ? d : Object.setPrototypeOf({ ...d, context }, Object.getPrototypeOf(d));
151
+ }),
146
152
  attach(rt) {
147
153
  runtime = rt;
148
154
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport './process-guard'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic, quiet?: boolean): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n if (!quiet) toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic, opts.quiet)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d, opts.quiet)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport './process-guard'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic, quiet?: boolean): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n if (!quiet) toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic, opts.quiet)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d, opts.quiet)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n // The original error can quote row values (a sensitive column's accessor throwing\n // `bad ssn 123-…`): it reaches onDiagnostic and strict's DatakableError only, so readers of the\n // buffer get a copy without it. `occurrences` stays shared, so counts still aggregate live.\n getDiagnostics: () =>\n buffer.toArray().map((d) => {\n const { cause, ...context } = d.context\n return cause === undefined\n ? d\n : Object.setPrototypeOf({ ...d, context }, Object.getPrototypeOf(d))\n }),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -122,7 +122,13 @@ function createDiagnostics(options = {}) {
122
122
  return {
123
123
  table,
124
124
  emit,
125
- getDiagnostics: () => buffer.toArray(),
125
+ // The original error can quote row values (a sensitive column's accessor throwing
126
+ // `bad ssn 123-…`): it reaches onDiagnostic and strict's DatakableError only, so readers of the
127
+ // buffer get a copy without it. `occurrences` stays shared, so counts still aggregate live.
128
+ getDiagnostics: () => buffer.toArray().map((d) => {
129
+ const { cause, ...context } = d.context;
130
+ return cause === void 0 ? d : Object.setPrototypeOf({ ...d, context }, Object.getPrototypeOf(d));
131
+ }),
126
132
  attach(rt) {
127
133
  runtime = rt;
128
134
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport './process-guard'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic, quiet?: boolean): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n if (!quiet) toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic, opts.quiet)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d, opts.quiet)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport './process-guard'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic, quiet?: boolean): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n if (!quiet) toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic, opts.quiet)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d, opts.quiet)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n // The original error can quote row values (a sensitive column's accessor throwing\n // `bad ssn 123-…`): it reaches onDiagnostic and strict's DatakableError only, so readers of the\n // buffer get a copy without it. `occurrences` stays shared, so counts still aggregate live.\n getDiagnostics: () =>\n buffer.toArray().map((d) => {\n const { cause, ...context } = d.context\n return cause === undefined\n ? d\n : Object.setPrototypeOf({ ...d, context }, Object.getPrototypeOf(d))\n }),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -79,7 +79,7 @@ function custom(r, path, value) {
79
79
  }
80
80
  }
81
81
  function redactKey(r, path, key) {
82
- if (r.rowKeySensitive) return hashRowKey(key);
82
+ if (r.rowKeySensitive) return SENSITIVE;
83
83
  const c = custom(r, path, key);
84
84
  if (c !== void 0) return c;
85
85
  return r.level === "safe" ? hashRowKey(key) : key;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/redact.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from './catalog'\nimport './process-guard'\nimport type { Location } from './types'\n\ndeclare module './catalog' {\n interface DiagnosticCatalog {\n DK_UNSAFE_REDACTION: { level: 'full' }\n }\n}\n\n/**\n * Redaction levels (§8.2). `'safe'`: paths, indices, counts, value types, hashed row keys\n * (production default). `'keys'`: + raw row keys (development default). `'full'`: + row values.\n */\nexport type RedactLevel = 'safe' | 'keys' | 'full'\n\n/**\n * Custom rule: return a string to log instead of `value`, or `undefined` for the built-in policy.\n * `path` names the field, e.g. `where.key`, `details.value`, `details.keys.2`, `details.issues.0`.\n * Sensitive columns never reach it.\n */\nexport type RedactFn = (path: string, value: unknown) => string | undefined\n\nexport interface RedactionOptions {\n /** Default `'keys'` in development, `'safe'` in production. */\n level?: RedactLevel\n redact?: RedactFn\n /** Ids / keys of `sensitive: true` columns: never logged at any level. */\n sensitiveColumns?: () => Iterable<string>\n /** The `rowKey` path is, is inside or contains a sensitive column: keys hashed at every level. */\n rowKeySensitive?: () => boolean\n}\n\n/**\n * Which detail fields of a diagnostic carry user data, declared on its `defineDiagnostic` entry.\n * Undeclared fields are logged as-is, so they must hold only DataKable-safe data (paths, ids,\n * indices, counts). Text builders receive the redacted values: treat these fields as opaque and\n * only interpolate them.\n *\n * - `rowKey` / `rowKeys`: a row key / an array of them (hashed under `'safe'`).\n * - `value` / `values`: a cell value / an array of them (type summary unless `'full'`). The column\n * is `details.column` when it is a string, else the `where` column id.\n * - `schemaIssues`: one Standard Schema issue or an array of them (`scrubSchemaIssue`).\n * - `error`: a thrown value, e.g. a fetcher error (`scrubError`).\n */\nexport type RedactField = 'rowKey' | 'rowKeys' | 'value' | 'values' | 'schemaIssues' | 'error'\n\n/** A resolved policy, built once per emit. */\nexport interface Redactor {\n level: RedactLevel\n custom: RedactFn | undefined\n sensitive: ReadonlySet<string>\n rowKeySensitive: boolean\n /** Called when the custom rule throws; the value is logged as `[redacted]`. */\n onError?: (error: unknown) => void\n}\n\nexport const SENSITIVE = '[sensitive]'\nexport const REDACTED = '[redacted]'\n\nconst LEVELS: readonly string[] = ['safe', 'keys', 'full']\nconst EMPTY: ReadonlySet<string> = new Set()\n\nexport function resolveRedaction(\n option: RedactLevel | RedactionOptions | undefined,\n dev: boolean,\n): Redactor {\n const o: RedactionOptions = typeof option === 'string' ? { level: option } : (option ?? {})\n const level = o.level ?? (dev ? 'keys' : 'safe')\n const cols = o.sensitiveColumns?.()\n return {\n // An unknown level gets the strictest policy; validating the option is optionValidator's job.\n level: LEVELS.includes(level) ? level : 'safe',\n custom: o.redact,\n sensitive: cols ? new Set(cols) : EMPTY,\n rowKeySensitive: o.rowKeySensitive?.() ?? false,\n }\n}\n\n/** Short 32-bit FNV-1a hash of `String(key)` (UTF-16 code units), as `h:xxxxxxxx`. */\nexport function hashRowKey(key: unknown): string {\n const s = String(key)\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return `h:${(h >>> 0).toString(16).padStart(8, '0')}`\n}\n\n/** The value's type, never its contents: `string(12)`, `number`, `array(3)`, `date`, … */\nexport function describeValue(v: unknown): string {\n if (v === null) return 'null'\n if (typeof v === 'string') return `string(${v.length})`\n if (typeof v === 'number') return Number.isNaN(v) ? 'number(NaN)' : 'number'\n if (Array.isArray(v)) return `array(${v.length})`\n if (v instanceof Date) return 'date'\n return typeof v\n}\n\nfunction custom(r: Redactor, path: string, value: unknown): string | undefined {\n if (!r.custom) return undefined\n try {\n return r.custom(path, value)\n } catch (e) {\n r.onError?.(e)\n return REDACTED\n }\n}\n\nfunction redactKey(r: Redactor, path: string, key: unknown): unknown {\n // A sensitive rowKey column wins over the custom rule and the level.\n if (r.rowKeySensitive) return hashRowKey(key)\n const c = custom(r, path, key)\n if (c !== undefined) return c\n return r.level === 'safe' ? hashRowKey(key) : key\n}\n\nfunction redactValue(r: Redactor, path: string, value: unknown, column?: string): unknown {\n if (column !== undefined && underSensitive(r, column)) return SENSITIVE\n const c = custom(r, path, value)\n if (c !== undefined) return c\n return r.level === 'full' ? value : describeValue(value)\n}\n\ntype IssuePath = ReadonlyArray<PropertyKey | { key: PropertyKey }>\n\n/** Joins a Standard Schema issue path: `['user', 0, { key: 'email' }]` → `user.0.email`. */\nfunction issuePath(path: IssuePath | undefined): string {\n if (!path) return ''\n return path.map((s) => String(typeof s === 'object' && s !== null ? s.key : s)).join('.')\n}\n\n/** `a` and `b` are the same path or one is inside the other (`contact` ↔ `contact.email`). */\nexport const pathsOverlap = (a: string, b: string): boolean =>\n a === b || a.startsWith(`${b}.`) || b.startsWith(`${a}.`)\n\n/** `path` is, is inside or contains a sensitive column (#45). */\nfunction underSensitive(r: Redactor, path: string): boolean {\n for (const col of r.sensitive) if (pathsOverlap(path, col)) return true\n return false\n}\n\n/**\n * A schema issue as `{ path, message }`. Validators embed received values in their messages, so\n * under `'safe'` / `'keys'` the message is DataKable's own text. So it is at every level when the\n * table has a sensitive column: any message (a root-level refinement, another field's) may embed\n * its value (#46).\n */\nexport function scrubSchemaIssue(\n issue: unknown,\n r: Redactor,\n fieldPath = 'issue',\n): { path: string; message: string } {\n const i = (typeof issue === 'object' && issue !== null ? issue : {}) as {\n message?: unknown\n path?: IssuePath\n }\n let path: string\n try {\n path = issuePath(i.path)\n } catch {\n path = ''\n }\n const own = `invalid value at ${path || '(root)'}`\n if (r.sensitive.size > 0) return { path, message: own }\n const c = custom(r, fieldPath, i.message)\n if (c !== undefined) return { path, message: c }\n return { path, message: r.level === 'full' && typeof i.message === 'string' ? i.message : own }\n}\n\n/**\n * A thrown value as `error.name` + `code` only (fetcher errors embed URLs, tokens, payloads). The\n * full error belongs in `context.cause`, which reaches `onDiagnostic` only.\n */\nexport function scrubError(e: unknown): string {\n if (typeof e !== 'object' || e === null) return typeof e\n try {\n const { name, code } = e as { name?: unknown; code?: unknown }\n const n = typeof name === 'string' && name ? name : 'object'\n return typeof code === 'string' || typeof code === 'number' ? `${n} (${code})` : n\n } catch {\n return 'object'\n }\n}\n\n/** Hashes / passes through `where.key` per the policy; other locations hold no user data. */\nexport function redactLocation(where: Location, r: Redactor): Location {\n if (where.kind !== 'row' || where.key === undefined) return where\n const key = redactKey(r, 'where.key', where.key)\n return { kind: 'row', index: where.index, key: typeof key === 'string' ? key : String(key) }\n}\n\n/** A shallow copy of `details` with the declared fields redacted; the input is never mutated. */\nexport function redactDetails(\n details: Record<string, unknown>,\n fields: Readonly<Record<string, RedactField | undefined>> | undefined,\n where: Location,\n r: Redactor,\n): Record<string, unknown> {\n if (!fields) return details\n const out: Record<string, unknown> = { ...details }\n const column =\n typeof details.column === 'string'\n ? details.column\n : where.kind === 'column'\n ? where.id\n : undefined\n for (const name of Object.keys(fields)) {\n const kind = fields[name]\n if (kind === undefined || !(name in details)) continue\n const v = details[name]\n const p = `details.${name}`\n const each = (f: (x: unknown, path: string) => unknown) =>\n Array.isArray(v) ? v.map((x, i) => f(x, `${p}.${i}`)) : f(v, p)\n switch (kind) {\n case 'rowKey':\n out[name] = redactKey(r, p, v)\n break\n case 'rowKeys':\n out[name] = each((x, path) => redactKey(r, path, x))\n break\n case 'value':\n out[name] = redactValue(r, p, v, column)\n break\n case 'values':\n out[name] = each((x, path) => redactValue(r, path, x, column))\n break\n case 'schemaIssues':\n out[name] = each((x, path) => scrubSchemaIssue(x, r, path))\n break\n case 'error':\n out[name] = custom(r, p, v) ?? scrubError(v)\n break\n }\n }\n return out\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const redactionMessages: DiagnosticMessages<'DK_UNSAFE_REDACTION'> = {\n DK_UNSAFE_REDACTION: {\n problem: (d) => `diagnostics.redact is '${d.level}' in a production build`,\n impact: () =>\n 'raw row keys and cell values (except sensitive columns) reach onDiagnostic, ' +\n 'getDiagnostics() and the console',\n fix: () => [\n \"remove diagnostics.redact in production (the default is 'safe')\",\n \"or use 'keys' to log raw row keys without cell values\",\n 'mark personal-data columns with sensitive: true so they are never logged',\n ],\n },\n}\n\n/** `'full'` in a production build (§8.2: \"warns if enabled in production\"). Emitted by the hub. */\nexport const DK_UNSAFE_REDACTION = defineDiagnostic(\n {\n code: 'DK_UNSAFE_REDACTION',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'diagnostics.redact' }),\n },\n (process.env.NODE_ENV !== \"production\") && redactionMessages.DK_UNSAFE_REDACTION,\n)\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/redact.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from './catalog'\nimport './process-guard'\nimport type { Location } from './types'\n\ndeclare module './catalog' {\n interface DiagnosticCatalog {\n DK_UNSAFE_REDACTION: { level: 'full' }\n }\n}\n\n/**\n * Redaction levels (§8.2). `'safe'`: paths, indices, counts, value types, hashed row keys\n * (production default). `'keys'`: + raw row keys (development default). `'full'`: + row values.\n */\nexport type RedactLevel = 'safe' | 'keys' | 'full'\n\n/**\n * Custom rule: return a string to log instead of `value`, or `undefined` for the built-in policy.\n * `path` names the field, e.g. `where.key`, `details.value`, `details.keys.2`, `details.issues.0`.\n * Sensitive columns never reach it.\n */\nexport type RedactFn = (path: string, value: unknown) => string | undefined\n\nexport interface RedactionOptions {\n /** Default `'keys'` in development, `'safe'` in production. */\n level?: RedactLevel\n redact?: RedactFn\n /** Ids / keys of `sensitive: true` columns: never logged at any level. */\n sensitiveColumns?: () => Iterable<string>\n /** The `rowKey` path is, is inside or contains a sensitive column: keys logged as `[sensitive]` at every level. */\n rowKeySensitive?: () => boolean\n}\n\n/**\n * Which detail fields of a diagnostic carry user data, declared on its `defineDiagnostic` entry.\n * Undeclared fields are logged as-is, so they must hold only DataKable-safe data (paths, ids,\n * indices, counts). Text builders receive the redacted values: treat these fields as opaque and\n * only interpolate them.\n *\n * - `rowKey` / `rowKeys`: a row key / an array of them (hashed under `'safe'`).\n * - `value` / `values`: a cell value / an array of them (type summary unless `'full'`). The column\n * is `details.column` when it is a string, else the `where` column id.\n * - `schemaIssues`: one Standard Schema issue or an array of them (`scrubSchemaIssue`).\n * - `error`: a thrown value, e.g. a fetcher error (`scrubError`).\n */\nexport type RedactField = 'rowKey' | 'rowKeys' | 'value' | 'values' | 'schemaIssues' | 'error'\n\n/** A resolved policy, built once per emit. */\nexport interface Redactor {\n level: RedactLevel\n custom: RedactFn | undefined\n sensitive: ReadonlySet<string>\n rowKeySensitive: boolean\n /** Called when the custom rule throws; the value is logged as `[redacted]`. */\n onError?: (error: unknown) => void\n}\n\nexport const SENSITIVE = '[sensitive]'\nexport const REDACTED = '[redacted]'\n\nconst LEVELS: readonly string[] = ['safe', 'keys', 'full']\nconst EMPTY: ReadonlySet<string> = new Set()\n\nexport function resolveRedaction(\n option: RedactLevel | RedactionOptions | undefined,\n dev: boolean,\n): Redactor {\n const o: RedactionOptions = typeof option === 'string' ? { level: option } : (option ?? {})\n const level = o.level ?? (dev ? 'keys' : 'safe')\n const cols = o.sensitiveColumns?.()\n return {\n // An unknown level gets the strictest policy; validating the option is optionValidator's job.\n level: LEVELS.includes(level) ? level : 'safe',\n custom: o.redact,\n sensitive: cols ? new Set(cols) : EMPTY,\n rowKeySensitive: o.rowKeySensitive?.() ?? false,\n }\n}\n\n/** Short 32-bit FNV-1a hash of `String(key)` (UTF-16 code units), as `h:xxxxxxxx`. */\nexport function hashRowKey(key: unknown): string {\n const s = String(key)\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return `h:${(h >>> 0).toString(16).padStart(8, '0')}`\n}\n\n/** The value's type, never its contents: `string(12)`, `number`, `array(3)`, `date`, … */\nexport function describeValue(v: unknown): string {\n if (v === null) return 'null'\n if (typeof v === 'string') return `string(${v.length})`\n if (typeof v === 'number') return Number.isNaN(v) ? 'number(NaN)' : 'number'\n if (Array.isArray(v)) return `array(${v.length})`\n if (v instanceof Date) return 'date'\n return typeof v\n}\n\nfunction custom(r: Redactor, path: string, value: unknown): string | undefined {\n if (!r.custom) return undefined\n try {\n return r.custom(path, value)\n } catch (e) {\n r.onError?.(e)\n return REDACTED\n }\n}\n\nfunction redactKey(r: Redactor, path: string, key: unknown): unknown {\n // A sensitive rowKey column wins over the custom rule and the level. No key-derived value at all:\n // an unsalted 32-bit hash of an SSN or email is reversible by brute force.\n if (r.rowKeySensitive) return SENSITIVE\n const c = custom(r, path, key)\n if (c !== undefined) return c\n return r.level === 'safe' ? hashRowKey(key) : key\n}\n\nfunction redactValue(r: Redactor, path: string, value: unknown, column?: string): unknown {\n if (column !== undefined && underSensitive(r, column)) return SENSITIVE\n const c = custom(r, path, value)\n if (c !== undefined) return c\n return r.level === 'full' ? value : describeValue(value)\n}\n\ntype IssuePath = ReadonlyArray<PropertyKey | { key: PropertyKey }>\n\n/** Joins a Standard Schema issue path: `['user', 0, { key: 'email' }]` → `user.0.email`. */\nfunction issuePath(path: IssuePath | undefined): string {\n if (!path) return ''\n return path.map((s) => String(typeof s === 'object' && s !== null ? s.key : s)).join('.')\n}\n\n/** `a` and `b` are the same path or one is inside the other (`contact` ↔ `contact.email`). */\nexport const pathsOverlap = (a: string, b: string): boolean =>\n a === b || a.startsWith(`${b}.`) || b.startsWith(`${a}.`)\n\n/** `path` is, is inside or contains a sensitive column (#45). */\nfunction underSensitive(r: Redactor, path: string): boolean {\n for (const col of r.sensitive) if (pathsOverlap(path, col)) return true\n return false\n}\n\n/**\n * A schema issue as `{ path, message }`. Validators embed received values in their messages, so\n * under `'safe'` / `'keys'` the message is DataKable's own text. So it is at every level when the\n * table has a sensitive column: any message (a root-level refinement, another field's) may embed\n * its value (#46).\n */\nexport function scrubSchemaIssue(\n issue: unknown,\n r: Redactor,\n fieldPath = 'issue',\n): { path: string; message: string } {\n const i = (typeof issue === 'object' && issue !== null ? issue : {}) as {\n message?: unknown\n path?: IssuePath\n }\n let path: string\n try {\n path = issuePath(i.path)\n } catch {\n path = ''\n }\n const own = `invalid value at ${path || '(root)'}`\n if (r.sensitive.size > 0) return { path, message: own }\n const c = custom(r, fieldPath, i.message)\n if (c !== undefined) return { path, message: c }\n return { path, message: r.level === 'full' && typeof i.message === 'string' ? i.message : own }\n}\n\n/**\n * A thrown value as `error.name` + `code` only (fetcher errors embed URLs, tokens, payloads). The\n * full error belongs in `context.cause`, which reaches `onDiagnostic` only.\n */\nexport function scrubError(e: unknown): string {\n if (typeof e !== 'object' || e === null) return typeof e\n try {\n const { name, code } = e as { name?: unknown; code?: unknown }\n const n = typeof name === 'string' && name ? name : 'object'\n return typeof code === 'string' || typeof code === 'number' ? `${n} (${code})` : n\n } catch {\n return 'object'\n }\n}\n\n/** Hashes / passes through `where.key` per the policy; other locations hold no user data. */\nexport function redactLocation(where: Location, r: Redactor): Location {\n if (where.kind !== 'row' || where.key === undefined) return where\n const key = redactKey(r, 'where.key', where.key)\n return { kind: 'row', index: where.index, key: typeof key === 'string' ? key : String(key) }\n}\n\n/** A shallow copy of `details` with the declared fields redacted; the input is never mutated. */\nexport function redactDetails(\n details: Record<string, unknown>,\n fields: Readonly<Record<string, RedactField | undefined>> | undefined,\n where: Location,\n r: Redactor,\n): Record<string, unknown> {\n if (!fields) return details\n const out: Record<string, unknown> = { ...details }\n const column =\n typeof details.column === 'string'\n ? details.column\n : where.kind === 'column'\n ? where.id\n : undefined\n for (const name of Object.keys(fields)) {\n const kind = fields[name]\n if (kind === undefined || !(name in details)) continue\n const v = details[name]\n const p = `details.${name}`\n const each = (f: (x: unknown, path: string) => unknown) =>\n Array.isArray(v) ? v.map((x, i) => f(x, `${p}.${i}`)) : f(v, p)\n switch (kind) {\n case 'rowKey':\n out[name] = redactKey(r, p, v)\n break\n case 'rowKeys':\n out[name] = each((x, path) => redactKey(r, path, x))\n break\n case 'value':\n out[name] = redactValue(r, p, v, column)\n break\n case 'values':\n out[name] = each((x, path) => redactValue(r, path, x, column))\n break\n case 'schemaIssues':\n out[name] = each((x, path) => scrubSchemaIssue(x, r, path))\n break\n case 'error':\n out[name] = custom(r, p, v) ?? scrubError(v)\n break\n }\n }\n return out\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const redactionMessages: DiagnosticMessages<'DK_UNSAFE_REDACTION'> = {\n DK_UNSAFE_REDACTION: {\n problem: (d) => `diagnostics.redact is '${d.level}' in a production build`,\n impact: () =>\n 'raw row keys and cell values (except sensitive columns) reach onDiagnostic, ' +\n 'getDiagnostics() and the console',\n fix: () => [\n \"remove diagnostics.redact in production (the default is 'safe')\",\n \"or use 'keys' to log raw row keys without cell values\",\n 'mark personal-data columns with sensitive: true so they are never logged',\n ],\n },\n}\n\n/** `'full'` in a production build (§8.2: \"warns if enabled in production\"). Emitted by the hub. */\nexport const DK_UNSAFE_REDACTION = defineDiagnostic(\n {\n code: 'DK_UNSAFE_REDACTION',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'diagnostics.redact' }),\n },\n (process.env.NODE_ENV !== \"production\") && redactionMessages.DK_UNSAFE_REDACTION,\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAwDA,IAAM,YAAY;AAClB,IAAM,WAAW;AAExB,IAAM,SAA4B,CAAC,QAAQ,QAAQ,MAAM;AACzD,IAAM,QAA6B,oBAAI,IAAI;AAEpC,SAAS,iBACd,QACA,KACU;AACV,QAAM,IAAsB,OAAO,WAAW,WAAW,EAAE,OAAO,OAAO,IAAK,UAAU,CAAC;AACzF,QAAM,QAAQ,EAAE,UAAU,MAAM,SAAS;AACzC,QAAM,OAAO,EAAE,mBAAmB;AAClC,SAAO;AAAA;AAAA,IAEL,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,IACxC,QAAQ,EAAE;AAAA,IACV,WAAW,OAAO,IAAI,IAAI,IAAI,IAAI;AAAA,IAClC,iBAAiB,EAAE,kBAAkB,KAAK;AAAA,EAC5C;AACF;AAGO,SAAS,WAAW,KAAsB;AAC/C,QAAM,IAAI,OAAO,GAAG;AACpB,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,SAAK,EAAE,WAAW,CAAC;AACnB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC7B;AACA,SAAO,MAAM,MAAM,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AACrD;AAGO,SAAS,cAAc,GAAoB;AAChD,MAAI,MAAM,KAAM,QAAO;AACvB,MAAI,OAAO,MAAM,SAAU,QAAO,UAAU,EAAE,MAAM;AACpD,MAAI,OAAO,MAAM,SAAU,QAAO,OAAO,MAAM,CAAC,IAAI,gBAAgB;AACpE,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,SAAS,EAAE,MAAM;AAC9C,MAAI,aAAa,KAAM,QAAO;AAC9B,SAAO,OAAO;AAChB;AAEA,SAAS,OAAO,GAAa,MAAc,OAAoC;AAC7E,MAAI,CAAC,EAAE,OAAQ,QAAO;AACtB,MAAI;AACF,WAAO,EAAE,OAAO,MAAM,KAAK;AAAA,EAC7B,SAAS,GAAG;AACV,MAAE,UAAU,CAAC;AACb,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,GAAa,MAAc,KAAuB;AAGnE,MAAI,EAAE,gBAAiB,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG,MAAM,GAAG;AAC7B,MAAI,MAAM,OAAW,QAAO;AAC5B,SAAO,EAAE,UAAU,SAAS,WAAW,GAAG,IAAI;AAChD;AAEA,SAAS,YAAY,GAAa,MAAc,OAAgB,QAA0B;AACxF,MAAI,WAAW,UAAa,eAAe,GAAG,MAAM,EAAG,QAAO;AAC9D,QAAM,IAAI,OAAO,GAAG,MAAM,KAAK;AAC/B,MAAI,MAAM,OAAW,QAAO;AAC5B,SAAO,EAAE,UAAU,SAAS,QAAQ,cAAc,KAAK;AACzD;AAKA,SAAS,UAAU,MAAqC;AACtD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,IAAI,CAAC,MAAM,OAAO,OAAO,MAAM,YAAY,MAAM,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AAC1F;AAGO,IAAM,eAAe,CAAC,GAAW,MACtC,MAAM,KAAK,EAAE,WAAW,GAAG,CAAC,GAAG,KAAK,EAAE,WAAW,GAAG,CAAC,GAAG;AAG1D,SAAS,eAAe,GAAa,MAAuB;AAC1D,aAAW,OAAO,EAAE,UAAW,KAAI,aAAa,MAAM,GAAG,EAAG,QAAO;AACnE,SAAO;AACT;AAQO,SAAS,iBACd,OACA,GACA,YAAY,SACuB;AACnC,QAAM,IAAK,OAAO,UAAU,YAAY,UAAU,OAAO,QAAQ,CAAC;AAIlE,MAAI;AACJ,MAAI;AACF,WAAO,UAAU,EAAE,IAAI;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,MAAM,oBAAoB,QAAQ,QAAQ;AAChD,MAAI,EAAE,UAAU,OAAO,EAAG,QAAO,EAAE,MAAM,SAAS,IAAI;AACtD,QAAM,IAAI,OAAO,GAAG,WAAW,EAAE,OAAO;AACxC,MAAI,MAAM,OAAW,QAAO,EAAE,MAAM,SAAS,EAAE;AAC/C,SAAO,EAAE,MAAM,SAAS,EAAE,UAAU,UAAU,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,IAAI;AAChG;AAMO,SAAS,WAAW,GAAoB;AAC7C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO,OAAO;AACvD,MAAI;AACF,UAAM,EAAE,MAAM,KAAK,IAAI;AACvB,UAAM,IAAI,OAAO,SAAS,YAAY,OAAO,OAAO;AACpD,WAAO,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,GAAG,CAAC,KAAK,IAAI,MAAM;AAAA,EACnF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,eAAe,OAAiB,GAAuB;AACrE,MAAI,MAAM,SAAS,SAAS,MAAM,QAAQ,OAAW,QAAO;AAC5D,QAAM,MAAM,UAAU,GAAG,aAAa,MAAM,GAAG;AAC/C,SAAO,EAAE,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG,EAAE;AAC7F;AAGO,SAAS,cACd,SACA,QACA,OACA,GACyB;AACzB,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAA+B,EAAE,GAAG,QAAQ;AAClD,QAAM,SACJ,OAAO,QAAQ,WAAW,WACtB,QAAQ,SACR,MAAM,SAAS,WACb,MAAM,KACN;AACR,aAAW,QAAQ,OAAO,KAAK,MAAM,GAAG;AACtC,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,SAAS,UAAa,EAAE,QAAQ,SAAU;AAC9C,UAAM,IAAI,QAAQ,IAAI;AACtB,UAAM,IAAI,WAAW,IAAI;AACzB,UAAM,OAAO,CAAC,MACZ,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC;AAChE,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,YAAI,IAAI,IAAI,UAAU,GAAG,GAAG,CAAC;AAC7B;AAAA,MACF,KAAK;AACH,YAAI,IAAI,IAAI,KAAK,CAAC,GAAG,SAAS,UAAU,GAAG,MAAM,CAAC,CAAC;AACnD;AAAA,MACF,KAAK;AACH,YAAI,IAAI,IAAI,YAAY,GAAG,GAAG,GAAG,MAAM;AACvC;AAAA,MACF,KAAK;AACH,YAAI,IAAI,IAAI,KAAK,CAAC,GAAG,SAAS,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D;AAAA,MACF,KAAK;AACH,YAAI,IAAI,IAAI,KAAK,CAAC,GAAG,SAAS,iBAAiB,GAAG,GAAG,IAAI,CAAC;AAC1D;AAAA,MACF,KAAK;AACH,YAAI,IAAI,IAAI,OAAO,GAAG,GAAG,CAAC,KAAK,WAAW,CAAC;AAC3C;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,oBAA+D;AAAA,EAC1E,qBAAqB;AAAA,IACnB,SAAS,CAAC,MAAM,0BAA0B,EAAE,KAAK;AAAA,IACjD,QAAQ,MACN;AAAA,IAEF,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,0BAAsB;AAAA,EACjC;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,qBAAqB;AAAA,EAC7D;AAAA,EACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAC/D;","names":[]}
@@ -44,7 +44,7 @@ function custom(r, path, value) {
44
44
  }
45
45
  }
46
46
  function redactKey(r, path, key) {
47
- if (r.rowKeySensitive) return hashRowKey(key);
47
+ if (r.rowKeySensitive) return SENSITIVE;
48
48
  const c = custom(r, path, key);
49
49
  if (c !== void 0) return c;
50
50
  return r.level === "safe" ? hashRowKey(key) : key;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/redact.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from './catalog'\nimport './process-guard'\nimport type { Location } from './types'\n\ndeclare module './catalog' {\n interface DiagnosticCatalog {\n DK_UNSAFE_REDACTION: { level: 'full' }\n }\n}\n\n/**\n * Redaction levels (§8.2). `'safe'`: paths, indices, counts, value types, hashed row keys\n * (production default). `'keys'`: + raw row keys (development default). `'full'`: + row values.\n */\nexport type RedactLevel = 'safe' | 'keys' | 'full'\n\n/**\n * Custom rule: return a string to log instead of `value`, or `undefined` for the built-in policy.\n * `path` names the field, e.g. `where.key`, `details.value`, `details.keys.2`, `details.issues.0`.\n * Sensitive columns never reach it.\n */\nexport type RedactFn = (path: string, value: unknown) => string | undefined\n\nexport interface RedactionOptions {\n /** Default `'keys'` in development, `'safe'` in production. */\n level?: RedactLevel\n redact?: RedactFn\n /** Ids / keys of `sensitive: true` columns: never logged at any level. */\n sensitiveColumns?: () => Iterable<string>\n /** The `rowKey` path is, is inside or contains a sensitive column: keys hashed at every level. */\n rowKeySensitive?: () => boolean\n}\n\n/**\n * Which detail fields of a diagnostic carry user data, declared on its `defineDiagnostic` entry.\n * Undeclared fields are logged as-is, so they must hold only DataKable-safe data (paths, ids,\n * indices, counts). Text builders receive the redacted values: treat these fields as opaque and\n * only interpolate them.\n *\n * - `rowKey` / `rowKeys`: a row key / an array of them (hashed under `'safe'`).\n * - `value` / `values`: a cell value / an array of them (type summary unless `'full'`). The column\n * is `details.column` when it is a string, else the `where` column id.\n * - `schemaIssues`: one Standard Schema issue or an array of them (`scrubSchemaIssue`).\n * - `error`: a thrown value, e.g. a fetcher error (`scrubError`).\n */\nexport type RedactField = 'rowKey' | 'rowKeys' | 'value' | 'values' | 'schemaIssues' | 'error'\n\n/** A resolved policy, built once per emit. */\nexport interface Redactor {\n level: RedactLevel\n custom: RedactFn | undefined\n sensitive: ReadonlySet<string>\n rowKeySensitive: boolean\n /** Called when the custom rule throws; the value is logged as `[redacted]`. */\n onError?: (error: unknown) => void\n}\n\nexport const SENSITIVE = '[sensitive]'\nexport const REDACTED = '[redacted]'\n\nconst LEVELS: readonly string[] = ['safe', 'keys', 'full']\nconst EMPTY: ReadonlySet<string> = new Set()\n\nexport function resolveRedaction(\n option: RedactLevel | RedactionOptions | undefined,\n dev: boolean,\n): Redactor {\n const o: RedactionOptions = typeof option === 'string' ? { level: option } : (option ?? {})\n const level = o.level ?? (dev ? 'keys' : 'safe')\n const cols = o.sensitiveColumns?.()\n return {\n // An unknown level gets the strictest policy; validating the option is optionValidator's job.\n level: LEVELS.includes(level) ? level : 'safe',\n custom: o.redact,\n sensitive: cols ? new Set(cols) : EMPTY,\n rowKeySensitive: o.rowKeySensitive?.() ?? false,\n }\n}\n\n/** Short 32-bit FNV-1a hash of `String(key)` (UTF-16 code units), as `h:xxxxxxxx`. */\nexport function hashRowKey(key: unknown): string {\n const s = String(key)\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return `h:${(h >>> 0).toString(16).padStart(8, '0')}`\n}\n\n/** The value's type, never its contents: `string(12)`, `number`, `array(3)`, `date`, … */\nexport function describeValue(v: unknown): string {\n if (v === null) return 'null'\n if (typeof v === 'string') return `string(${v.length})`\n if (typeof v === 'number') return Number.isNaN(v) ? 'number(NaN)' : 'number'\n if (Array.isArray(v)) return `array(${v.length})`\n if (v instanceof Date) return 'date'\n return typeof v\n}\n\nfunction custom(r: Redactor, path: string, value: unknown): string | undefined {\n if (!r.custom) return undefined\n try {\n return r.custom(path, value)\n } catch (e) {\n r.onError?.(e)\n return REDACTED\n }\n}\n\nfunction redactKey(r: Redactor, path: string, key: unknown): unknown {\n // A sensitive rowKey column wins over the custom rule and the level.\n if (r.rowKeySensitive) return hashRowKey(key)\n const c = custom(r, path, key)\n if (c !== undefined) return c\n return r.level === 'safe' ? hashRowKey(key) : key\n}\n\nfunction redactValue(r: Redactor, path: string, value: unknown, column?: string): unknown {\n if (column !== undefined && underSensitive(r, column)) return SENSITIVE\n const c = custom(r, path, value)\n if (c !== undefined) return c\n return r.level === 'full' ? value : describeValue(value)\n}\n\ntype IssuePath = ReadonlyArray<PropertyKey | { key: PropertyKey }>\n\n/** Joins a Standard Schema issue path: `['user', 0, { key: 'email' }]` → `user.0.email`. */\nfunction issuePath(path: IssuePath | undefined): string {\n if (!path) return ''\n return path.map((s) => String(typeof s === 'object' && s !== null ? s.key : s)).join('.')\n}\n\n/** `a` and `b` are the same path or one is inside the other (`contact` ↔ `contact.email`). */\nexport const pathsOverlap = (a: string, b: string): boolean =>\n a === b || a.startsWith(`${b}.`) || b.startsWith(`${a}.`)\n\n/** `path` is, is inside or contains a sensitive column (#45). */\nfunction underSensitive(r: Redactor, path: string): boolean {\n for (const col of r.sensitive) if (pathsOverlap(path, col)) return true\n return false\n}\n\n/**\n * A schema issue as `{ path, message }`. Validators embed received values in their messages, so\n * under `'safe'` / `'keys'` the message is DataKable's own text. So it is at every level when the\n * table has a sensitive column: any message (a root-level refinement, another field's) may embed\n * its value (#46).\n */\nexport function scrubSchemaIssue(\n issue: unknown,\n r: Redactor,\n fieldPath = 'issue',\n): { path: string; message: string } {\n const i = (typeof issue === 'object' && issue !== null ? issue : {}) as {\n message?: unknown\n path?: IssuePath\n }\n let path: string\n try {\n path = issuePath(i.path)\n } catch {\n path = ''\n }\n const own = `invalid value at ${path || '(root)'}`\n if (r.sensitive.size > 0) return { path, message: own }\n const c = custom(r, fieldPath, i.message)\n if (c !== undefined) return { path, message: c }\n return { path, message: r.level === 'full' && typeof i.message === 'string' ? i.message : own }\n}\n\n/**\n * A thrown value as `error.name` + `code` only (fetcher errors embed URLs, tokens, payloads). The\n * full error belongs in `context.cause`, which reaches `onDiagnostic` only.\n */\nexport function scrubError(e: unknown): string {\n if (typeof e !== 'object' || e === null) return typeof e\n try {\n const { name, code } = e as { name?: unknown; code?: unknown }\n const n = typeof name === 'string' && name ? name : 'object'\n return typeof code === 'string' || typeof code === 'number' ? `${n} (${code})` : n\n } catch {\n return 'object'\n }\n}\n\n/** Hashes / passes through `where.key` per the policy; other locations hold no user data. */\nexport function redactLocation(where: Location, r: Redactor): Location {\n if (where.kind !== 'row' || where.key === undefined) return where\n const key = redactKey(r, 'where.key', where.key)\n return { kind: 'row', index: where.index, key: typeof key === 'string' ? key : String(key) }\n}\n\n/** A shallow copy of `details` with the declared fields redacted; the input is never mutated. */\nexport function redactDetails(\n details: Record<string, unknown>,\n fields: Readonly<Record<string, RedactField | undefined>> | undefined,\n where: Location,\n r: Redactor,\n): Record<string, unknown> {\n if (!fields) return details\n const out: Record<string, unknown> = { ...details }\n const column =\n typeof details.column === 'string'\n ? details.column\n : where.kind === 'column'\n ? where.id\n : undefined\n for (const name of Object.keys(fields)) {\n const kind = fields[name]\n if (kind === undefined || !(name in details)) continue\n const v = details[name]\n const p = `details.${name}`\n const each = (f: (x: unknown, path: string) => unknown) =>\n Array.isArray(v) ? v.map((x, i) => f(x, `${p}.${i}`)) : f(v, p)\n switch (kind) {\n case 'rowKey':\n out[name] = redactKey(r, p, v)\n break\n case 'rowKeys':\n out[name] = each((x, path) => redactKey(r, path, x))\n break\n case 'value':\n out[name] = redactValue(r, p, v, column)\n break\n case 'values':\n out[name] = each((x, path) => redactValue(r, path, x, column))\n break\n case 'schemaIssues':\n out[name] = each((x, path) => scrubSchemaIssue(x, r, path))\n break\n case 'error':\n out[name] = custom(r, p, v) ?? scrubError(v)\n break\n }\n }\n return out\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const redactionMessages: DiagnosticMessages<'DK_UNSAFE_REDACTION'> = {\n DK_UNSAFE_REDACTION: {\n problem: (d) => `diagnostics.redact is '${d.level}' in a production build`,\n impact: () =>\n 'raw row keys and cell values (except sensitive columns) reach onDiagnostic, ' +\n 'getDiagnostics() and the console',\n fix: () => [\n \"remove diagnostics.redact in production (the default is 'safe')\",\n \"or use 'keys' to log raw row keys without cell values\",\n 'mark personal-data columns with sensitive: true so they are never logged',\n ],\n },\n}\n\n/** `'full'` in a production build (§8.2: \"warns if enabled in production\"). Emitted by the hub. */\nexport const DK_UNSAFE_REDACTION = defineDiagnostic(\n {\n code: 'DK_UNSAFE_REDACTION',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'diagnostics.redact' }),\n },\n (process.env.NODE_ENV !== \"production\") && redactionMessages.DK_UNSAFE_REDACTION,\n)\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/redact.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from './catalog'\nimport './process-guard'\nimport type { Location } from './types'\n\ndeclare module './catalog' {\n interface DiagnosticCatalog {\n DK_UNSAFE_REDACTION: { level: 'full' }\n }\n}\n\n/**\n * Redaction levels (§8.2). `'safe'`: paths, indices, counts, value types, hashed row keys\n * (production default). `'keys'`: + raw row keys (development default). `'full'`: + row values.\n */\nexport type RedactLevel = 'safe' | 'keys' | 'full'\n\n/**\n * Custom rule: return a string to log instead of `value`, or `undefined` for the built-in policy.\n * `path` names the field, e.g. `where.key`, `details.value`, `details.keys.2`, `details.issues.0`.\n * Sensitive columns never reach it.\n */\nexport type RedactFn = (path: string, value: unknown) => string | undefined\n\nexport interface RedactionOptions {\n /** Default `'keys'` in development, `'safe'` in production. */\n level?: RedactLevel\n redact?: RedactFn\n /** Ids / keys of `sensitive: true` columns: never logged at any level. */\n sensitiveColumns?: () => Iterable<string>\n /** The `rowKey` path is, is inside or contains a sensitive column: keys logged as `[sensitive]` at every level. */\n rowKeySensitive?: () => boolean\n}\n\n/**\n * Which detail fields of a diagnostic carry user data, declared on its `defineDiagnostic` entry.\n * Undeclared fields are logged as-is, so they must hold only DataKable-safe data (paths, ids,\n * indices, counts). Text builders receive the redacted values: treat these fields as opaque and\n * only interpolate them.\n *\n * - `rowKey` / `rowKeys`: a row key / an array of them (hashed under `'safe'`).\n * - `value` / `values`: a cell value / an array of them (type summary unless `'full'`). The column\n * is `details.column` when it is a string, else the `where` column id.\n * - `schemaIssues`: one Standard Schema issue or an array of them (`scrubSchemaIssue`).\n * - `error`: a thrown value, e.g. a fetcher error (`scrubError`).\n */\nexport type RedactField = 'rowKey' | 'rowKeys' | 'value' | 'values' | 'schemaIssues' | 'error'\n\n/** A resolved policy, built once per emit. */\nexport interface Redactor {\n level: RedactLevel\n custom: RedactFn | undefined\n sensitive: ReadonlySet<string>\n rowKeySensitive: boolean\n /** Called when the custom rule throws; the value is logged as `[redacted]`. */\n onError?: (error: unknown) => void\n}\n\nexport const SENSITIVE = '[sensitive]'\nexport const REDACTED = '[redacted]'\n\nconst LEVELS: readonly string[] = ['safe', 'keys', 'full']\nconst EMPTY: ReadonlySet<string> = new Set()\n\nexport function resolveRedaction(\n option: RedactLevel | RedactionOptions | undefined,\n dev: boolean,\n): Redactor {\n const o: RedactionOptions = typeof option === 'string' ? { level: option } : (option ?? {})\n const level = o.level ?? (dev ? 'keys' : 'safe')\n const cols = o.sensitiveColumns?.()\n return {\n // An unknown level gets the strictest policy; validating the option is optionValidator's job.\n level: LEVELS.includes(level) ? level : 'safe',\n custom: o.redact,\n sensitive: cols ? new Set(cols) : EMPTY,\n rowKeySensitive: o.rowKeySensitive?.() ?? false,\n }\n}\n\n/** Short 32-bit FNV-1a hash of `String(key)` (UTF-16 code units), as `h:xxxxxxxx`. */\nexport function hashRowKey(key: unknown): string {\n const s = String(key)\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return `h:${(h >>> 0).toString(16).padStart(8, '0')}`\n}\n\n/** The value's type, never its contents: `string(12)`, `number`, `array(3)`, `date`, … */\nexport function describeValue(v: unknown): string {\n if (v === null) return 'null'\n if (typeof v === 'string') return `string(${v.length})`\n if (typeof v === 'number') return Number.isNaN(v) ? 'number(NaN)' : 'number'\n if (Array.isArray(v)) return `array(${v.length})`\n if (v instanceof Date) return 'date'\n return typeof v\n}\n\nfunction custom(r: Redactor, path: string, value: unknown): string | undefined {\n if (!r.custom) return undefined\n try {\n return r.custom(path, value)\n } catch (e) {\n r.onError?.(e)\n return REDACTED\n }\n}\n\nfunction redactKey(r: Redactor, path: string, key: unknown): unknown {\n // A sensitive rowKey column wins over the custom rule and the level. No key-derived value at all:\n // an unsalted 32-bit hash of an SSN or email is reversible by brute force.\n if (r.rowKeySensitive) return SENSITIVE\n const c = custom(r, path, key)\n if (c !== undefined) return c\n return r.level === 'safe' ? hashRowKey(key) : key\n}\n\nfunction redactValue(r: Redactor, path: string, value: unknown, column?: string): unknown {\n if (column !== undefined && underSensitive(r, column)) return SENSITIVE\n const c = custom(r, path, value)\n if (c !== undefined) return c\n return r.level === 'full' ? value : describeValue(value)\n}\n\ntype IssuePath = ReadonlyArray<PropertyKey | { key: PropertyKey }>\n\n/** Joins a Standard Schema issue path: `['user', 0, { key: 'email' }]` → `user.0.email`. */\nfunction issuePath(path: IssuePath | undefined): string {\n if (!path) return ''\n return path.map((s) => String(typeof s === 'object' && s !== null ? s.key : s)).join('.')\n}\n\n/** `a` and `b` are the same path or one is inside the other (`contact` ↔ `contact.email`). */\nexport const pathsOverlap = (a: string, b: string): boolean =>\n a === b || a.startsWith(`${b}.`) || b.startsWith(`${a}.`)\n\n/** `path` is, is inside or contains a sensitive column (#45). */\nfunction underSensitive(r: Redactor, path: string): boolean {\n for (const col of r.sensitive) if (pathsOverlap(path, col)) return true\n return false\n}\n\n/**\n * A schema issue as `{ path, message }`. Validators embed received values in their messages, so\n * under `'safe'` / `'keys'` the message is DataKable's own text. So it is at every level when the\n * table has a sensitive column: any message (a root-level refinement, another field's) may embed\n * its value (#46).\n */\nexport function scrubSchemaIssue(\n issue: unknown,\n r: Redactor,\n fieldPath = 'issue',\n): { path: string; message: string } {\n const i = (typeof issue === 'object' && issue !== null ? issue : {}) as {\n message?: unknown\n path?: IssuePath\n }\n let path: string\n try {\n path = issuePath(i.path)\n } catch {\n path = ''\n }\n const own = `invalid value at ${path || '(root)'}`\n if (r.sensitive.size > 0) return { path, message: own }\n const c = custom(r, fieldPath, i.message)\n if (c !== undefined) return { path, message: c }\n return { path, message: r.level === 'full' && typeof i.message === 'string' ? i.message : own }\n}\n\n/**\n * A thrown value as `error.name` + `code` only (fetcher errors embed URLs, tokens, payloads). The\n * full error belongs in `context.cause`, which reaches `onDiagnostic` only.\n */\nexport function scrubError(e: unknown): string {\n if (typeof e !== 'object' || e === null) return typeof e\n try {\n const { name, code } = e as { name?: unknown; code?: unknown }\n const n = typeof name === 'string' && name ? name : 'object'\n return typeof code === 'string' || typeof code === 'number' ? `${n} (${code})` : n\n } catch {\n return 'object'\n }\n}\n\n/** Hashes / passes through `where.key` per the policy; other locations hold no user data. */\nexport function redactLocation(where: Location, r: Redactor): Location {\n if (where.kind !== 'row' || where.key === undefined) return where\n const key = redactKey(r, 'where.key', where.key)\n return { kind: 'row', index: where.index, key: typeof key === 'string' ? key : String(key) }\n}\n\n/** A shallow copy of `details` with the declared fields redacted; the input is never mutated. */\nexport function redactDetails(\n details: Record<string, unknown>,\n fields: Readonly<Record<string, RedactField | undefined>> | undefined,\n where: Location,\n r: Redactor,\n): Record<string, unknown> {\n if (!fields) return details\n const out: Record<string, unknown> = { ...details }\n const column =\n typeof details.column === 'string'\n ? details.column\n : where.kind === 'column'\n ? where.id\n : undefined\n for (const name of Object.keys(fields)) {\n const kind = fields[name]\n if (kind === undefined || !(name in details)) continue\n const v = details[name]\n const p = `details.${name}`\n const each = (f: (x: unknown, path: string) => unknown) =>\n Array.isArray(v) ? v.map((x, i) => f(x, `${p}.${i}`)) : f(v, p)\n switch (kind) {\n case 'rowKey':\n out[name] = redactKey(r, p, v)\n break\n case 'rowKeys':\n out[name] = each((x, path) => redactKey(r, path, x))\n break\n case 'value':\n out[name] = redactValue(r, p, v, column)\n break\n case 'values':\n out[name] = each((x, path) => redactValue(r, path, x, column))\n break\n case 'schemaIssues':\n out[name] = each((x, path) => scrubSchemaIssue(x, r, path))\n break\n case 'error':\n out[name] = custom(r, p, v) ?? scrubError(v)\n break\n }\n }\n return out\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const redactionMessages: DiagnosticMessages<'DK_UNSAFE_REDACTION'> = {\n DK_UNSAFE_REDACTION: {\n problem: (d) => `diagnostics.redact is '${d.level}' in a production build`,\n impact: () =>\n 'raw row keys and cell values (except sensitive columns) reach onDiagnostic, ' +\n 'getDiagnostics() and the console',\n fix: () => [\n \"remove diagnostics.redact in production (the default is 'safe')\",\n \"or use 'keys' to log raw row keys without cell values\",\n 'mark personal-data columns with sensitive: true so they are never logged',\n ],\n },\n}\n\n/** `'full'` in a production build (§8.2: \"warns if enabled in production\"). Emitted by the hub. */\nexport const DK_UNSAFE_REDACTION = defineDiagnostic(\n {\n code: 'DK_UNSAFE_REDACTION',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'diagnostics.redact' }),\n },\n (process.env.NODE_ENV !== \"production\") && redactionMessages.DK_UNSAFE_REDACTION,\n)\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/types.ts"],"sourcesContent":["import type { DiagnosticCode, DiagnosticDetails } from './catalog'\n\nexport type { DiagnosticCode, DiagnosticDetails } from './catalog'\n\nexport type DiagnosticLevel = 'error' | 'warn' | 'info'\n\nexport type Trigger = 'init' | 'data-change' | 'setOptions' | 'hydrate' | 'fetch' | `api:${string}`\n\nexport type Phase = 'ssr' | 'hydrate' | 'client'\n\n/**\n * Where in the developer's code or data (§8.1). `interceptor` and `operation` are additions for\n * plugin-level codes the spec table does not locate (interceptors, op registry).\n */\nexport type Location =\n | { kind: 'column'; index: number; id: string }\n | { kind: 'feature'; name: string }\n | { kind: 'option'; path: string }\n | { kind: 'row'; index: number; key?: string }\n | { kind: 'interceptor'; name: string; op: string }\n | { kind: 'operation'; op: string; slot?: string }\n\n/** A summary of table state — never raw input (§8.1). */\nexport interface DiagnosticStateSummary {\n searchLength: number\n activeFilters: string[]\n sort: string[]\n page: number\n}\n\nexport interface DiagnosticContext {\n /** `options.name`, else `table#n`. */\n table: string\n /** Dev: creation site, e.g. `UsersPage.tsx:42`. */\n createdAt?: string\n /** `@datakable/core@x.y.z` */\n package: string\n trigger: Trigger\n /** Pipeline run id — joins explain() and other diagnostics. */\n run: number\n phase: Phase\n mode: Record<string, 'client' | 'server'>\n state: DiagnosticStateSummary\n occurrences: { count: number; first: number; last: number }\n /** e.g. `DK_SCHEMA_INVALID_ROW#run57` */\n causedBy?: string\n /** Original error (ES2022 cause). Reaches `onDiagnostic` only; `toJSON` and the console leave it out. */\n cause?: unknown\n}\n\nexport interface Diagnostic {\n /** Stable public API; never reused or redefined. */\n code: DiagnosticCode\n level: DiagnosticLevel\n /** What happened, instance-specific. Empty in production unless messages are opted in. */\n problem: string\n where: Location\n /** Consequence + the fallback the system applied. */\n impact: string\n /** Concrete fixes, best first. */\n fix: string[]\n /** `docs/errors.md#<code>` (+ query params in production). */\n docs: string\n /** The catalog details. Redaction (T26) rewrites them before any sink sees them. */\n details: Record<string, unknown>\n context: DiagnosticContext\n /** `code@where` — grouping key (dedupe, Sentry). */\n fingerprint: string\n /** Safe serializer: bigint, cycles and Errors never throw; `context.cause` is left out. */\n toJSON(): object\n}\n\n/** Per-call additions to the live context. */\nexport interface EmitOptions {\n cause?: unknown\n causedBy?: string\n run?: number\n trigger?: Trigger\n phase?: Phase\n /**\n * Not printed by the default console sink (another table printed it in this process, #38). Still\n * stored and delivered to `onDiagnostic`.\n */\n quiet?: boolean\n}\n\n/** Emits a catalog diagnostic; `details` is typed by `code` (see `DiagnosticCatalog`). */\nexport type Emit = <C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n) => void\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/types.ts"],"sourcesContent":["import type { DiagnosticCode, DiagnosticDetails } from './catalog'\n\nexport type { DiagnosticCode, DiagnosticDetails } from './catalog'\n\nexport type DiagnosticLevel = 'error' | 'warn' | 'info'\n\nexport type Trigger = 'init' | 'data-change' | 'setOptions' | 'hydrate' | 'fetch' | `api:${string}`\n\nexport type Phase = 'ssr' | 'hydrate' | 'client'\n\n/**\n * Where in the developer's code or data (§8.1). `interceptor` and `operation` are additions for\n * plugin-level codes the spec table does not locate (interceptors, op registry).\n */\nexport type Location =\n | { kind: 'column'; index: number; id: string }\n | { kind: 'feature'; name: string }\n | { kind: 'option'; path: string }\n | { kind: 'row'; index: number; key?: string }\n | { kind: 'interceptor'; name: string; op: string }\n | { kind: 'operation'; op: string; slot?: string }\n\n/** A summary of table state — never raw input (§8.1). */\nexport interface DiagnosticStateSummary {\n searchLength: number\n activeFilters: string[]\n sort: string[]\n page: number\n}\n\nexport interface DiagnosticContext {\n /** `options.name`, else `table#n`. */\n table: string\n /** Dev: creation site, e.g. `UsersPage.tsx:42`. */\n createdAt?: string\n /** `@datakable/core@x.y.z` */\n package: string\n trigger: Trigger\n /** Pipeline run id — joins explain() and other diagnostics. */\n run: number\n phase: Phase\n mode: Record<string, 'client' | 'server'>\n state: DiagnosticStateSummary\n occurrences: { count: number; first: number; last: number }\n /** e.g. `DK_SCHEMA_INVALID_ROW#run57` */\n causedBy?: string\n /**\n * Original error (ES2022 cause). Reaches code you install (`onDiagnostic`, `diagnostic:emit`\n * interceptors, the `redact` seam) and, under `strict`, the thrown `DatakableError`'s `cause`;\n * `getDiagnostics()`, `toJSON` and the console leave it out.\n */\n cause?: unknown\n}\n\nexport interface Diagnostic {\n /** Stable public API; never reused or redefined. */\n code: DiagnosticCode\n level: DiagnosticLevel\n /** What happened, instance-specific. Empty in production unless messages are opted in. */\n problem: string\n where: Location\n /** Consequence + the fallback the system applied. */\n impact: string\n /** Concrete fixes, best first. */\n fix: string[]\n /** `docs/errors.md#<code>` (+ query params in production). */\n docs: string\n /** The catalog details. Redaction (T26) rewrites them before any sink sees them. */\n details: Record<string, unknown>\n context: DiagnosticContext\n /** `code@where` — grouping key (dedupe, Sentry). */\n fingerprint: string\n /** Safe serializer: bigint, cycles and Errors never throw; `context.cause` is left out. */\n toJSON(): object\n}\n\n/** Per-call additions to the live context. */\nexport interface EmitOptions {\n cause?: unknown\n causedBy?: string\n run?: number\n trigger?: Trigger\n phase?: Phase\n /**\n * Not printed by the default console sink (another table printed it in this process, #38). Still\n * stored and delivered to `onDiagnostic`.\n */\n quiet?: boolean\n}\n\n/** Emits a catalog diagnostic; `details` is typed by `code` (see `DiagnosticCatalog`). */\nexport type Emit = <C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n) => void\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -1,4 +1,4 @@
1
- import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-B9bCK73-.cjs';
1
+ import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-AX8XkMNY.js';
2
2
 
3
3
  /** Runtime types an option can accept (`'array'` is checked before `'object'`). */
4
4
  type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
@@ -639,7 +639,7 @@ interface TableDiagnosticsOptions {
639
639
  /**
640
640
  * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level
641
641
  * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never
642
- * logged; a `rowKey` path into a sensitive column is hashed at every level.
642
+ * logged; a `rowKey` path into a sensitive column is logged as `[sensitive]` (no hash) at every level.
643
643
  */
644
644
  redact?: RedactLevel | RedactFn;
645
645
  }
@@ -1010,7 +1010,7 @@ interface RowStep {
1010
1010
  }
1011
1011
  /** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */
1012
1012
  interface RowExplanation {
1013
- /** The key asked for, redacted (a hash under `'safe'`). */
1013
+ /** The key asked for, redacted (a hash under `'safe'`, `[sensitive]` for a sensitive rowKey). */
1014
1014
  readonly key: unknown;
1015
1015
  readonly found: boolean;
1016
1016
  /** Position in `getData()`; absent when not found or dropped by the schema. */
@@ -1,4 +1,4 @@
1
- import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-B9bCK73-.js';
1
+ import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-AX8XkMNY.cjs';
2
2
 
3
3
  /** Runtime types an option can accept (`'array'` is checked before `'object'`). */
4
4
  type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
@@ -639,7 +639,7 @@ interface TableDiagnosticsOptions {
639
639
  /**
640
640
  * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level
641
641
  * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never
642
- * logged; a `rowKey` path into a sensitive column is hashed at every level.
642
+ * logged; a `rowKey` path into a sensitive column is logged as `[sensitive]` (no hash) at every level.
643
643
  */
644
644
  redact?: RedactLevel | RedactFn;
645
645
  }
@@ -1010,7 +1010,7 @@ interface RowStep {
1010
1010
  }
1011
1011
  /** What `table.explainRow(key)` returns (§8.4); `String(e)` is the console report. */
1012
1012
  interface RowExplanation {
1013
- /** The key asked for, redacted (a hash under `'safe'`). */
1013
+ /** The key asked for, redacted (a hash under `'safe'`, `[sensitive]` for a sensitive rowKey). */
1014
1014
  readonly key: unknown;
1015
1015
  readonly found: boolean;
1016
1016
  /** Position in `getData()`; absent when not found or dropped by the schema. */
package/dist/index.d.cts CHANGED
@@ -1,15 +1,15 @@
1
- import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-B9bCK73-.cjs';
2
- export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-B9bCK73-.cjs';
3
- import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-Q8Cg0cZy.cjs';
4
- export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-Q8Cg0cZy.cjs';
1
+ import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-AX8XkMNY.cjs';
2
+ export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-AX8XkMNY.cjs';
3
+ import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-pKAnnxEW.cjs';
4
+ export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-pKAnnxEW.cjs';
5
5
  import { ColumnFiltersTypes } from './column-filters.cjs';
6
6
  export { ColumnFilter, ColumnFiltersApi, ColumnFiltersOptions, ColumnFiltersState, ContainsFilter, CustomFilter, EqualsFilter, InFilter, RangeFilter, RangeValue, columnFilters } from './column-filters.cjs';
7
7
  import { PaginationState, PaginationApi } from './pagination.cjs';
8
8
  export { PaginationOptions, pagination } from './pagination.cjs';
9
9
  import { SearchApi } from './search.cjs';
10
10
  export { SearchOptions, SearchTyping, SearchWait, search } from './search.cjs';
11
- import { S as SearchState } from './search-async-entry-CuLYPfY_.cjs';
12
- export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-CuLYPfY_.cjs';
11
+ import { S as SearchState } from './search-async-entry-ByMJbHjc.cjs';
12
+ export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-ByMJbHjc.cjs';
13
13
  import { SortingTypes } from './sorting.cjs';
14
14
  export { SortAlgorithmOption, SortDirection, SortEntry, SortingApi, SortingOptions, SortingState, compareValues, sorting, typeRank } from './sorting.cjs';
15
15
  import { ValidityState, ValidityApi } from './validity.cjs';
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-B9bCK73-.js';
2
- export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-B9bCK73-.js';
3
- import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-i0OATi_W.js';
4
- export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-i0OATi_W.js';
1
+ import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-AX8XkMNY.js';
2
+ export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-AX8XkMNY.js';
3
+ import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-BVE3ln3G.js';
4
+ export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-BVE3ln3G.js';
5
5
  import { ColumnFiltersTypes } from './column-filters.js';
6
6
  export { ColumnFilter, ColumnFiltersApi, ColumnFiltersOptions, ColumnFiltersState, ContainsFilter, CustomFilter, EqualsFilter, InFilter, RangeFilter, RangeValue, columnFilters } from './column-filters.js';
7
7
  import { PaginationState, PaginationApi } from './pagination.js';
8
8
  export { PaginationOptions, pagination } from './pagination.js';
9
9
  import { SearchApi } from './search.js';
10
10
  export { SearchOptions, SearchTyping, SearchWait, search } from './search.js';
11
- import { S as SearchState } from './search-async-entry-Cx7xpuVZ.js';
12
- export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-Cx7xpuVZ.js';
11
+ import { S as SearchState } from './search-async-entry-CFQODE08.js';
12
+ export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-CFQODE08.js';
13
13
  import { SortingTypes } from './sorting.js';
14
14
  export { SortAlgorithmOption, SortDirection, SortEntry, SortingApi, SortingOptions, SortingState, compareValues, sorting, typeRank } from './sorting.js';
15
15
  import { ValidityState, ValidityApi } from './validity.js';
@@ -1,5 +1,5 @@
1
- export { ai as paginationDiagnostics } from './codes-B9bCK73-.cjs';
2
- import { F as Feature, b as FixedTypes } from './explain-Q8Cg0cZy.cjs';
1
+ export { ai as paginationDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ import { F as Feature, b as FixedTypes } from './explain-pKAnnxEW.cjs';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.cjs';
4
4
 
5
5
  interface PaginationState {
@@ -1,5 +1,5 @@
1
- export { ai as paginationDiagnostics } from './codes-B9bCK73-.js';
2
- import { F as Feature, b as FixedTypes } from './explain-i0OATi_W.js';
1
+ export { ai as paginationDiagnostics } from './codes-AX8XkMNY.js';
2
+ import { F as Feature, b as FixedTypes } from './explain-BVE3ln3G.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
4
 
5
5
  interface PaginationState {
package/dist/schema.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StandardSchemaV1, e as TableSchema } from './codes-B9bCK73-.cjs';
2
- export { q as DatakableSchemaError, h as InvalidRow, I as InvalidRowsMode, C as SCHEMA_INGEST, H as SchemaIssueColumn, J as SchemaOutcome, ad as isStandardSchema, am as schemaDiagnostics } from './codes-B9bCK73-.cjs';
1
+ import { S as StandardSchemaV1, e as TableSchema } from './codes-AX8XkMNY.cjs';
2
+ export { q as DatakableSchemaError, h as InvalidRow, I as InvalidRowsMode, C as SCHEMA_INGEST, H as SchemaIssueColumn, J as SchemaOutcome, ad as isStandardSchema, am as schemaDiagnostics } from './codes-AX8XkMNY.cjs';
3
3
 
4
4
  /**
5
5
  * Opts a Standard Schema v1 (zod, valibot, arktype, …) into table validation (§5.1):
package/dist/schema.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StandardSchemaV1, e as TableSchema } from './codes-B9bCK73-.js';
2
- export { q as DatakableSchemaError, h as InvalidRow, I as InvalidRowsMode, C as SCHEMA_INGEST, H as SchemaIssueColumn, J as SchemaOutcome, ad as isStandardSchema, am as schemaDiagnostics } from './codes-B9bCK73-.js';
1
+ import { S as StandardSchemaV1, e as TableSchema } from './codes-AX8XkMNY.js';
2
+ export { q as DatakableSchemaError, h as InvalidRow, I as InvalidRowsMode, C as SCHEMA_INGEST, H as SchemaIssueColumn, J as SchemaOutcome, ad as isStandardSchema, am as schemaDiagnostics } from './codes-AX8XkMNY.js';
3
3
 
4
4
  /**
5
5
  * Opts a Standard Schema v1 (zod, valibot, arktype, …) into table validation (§5.1):
@@ -1,5 +1,5 @@
1
- import { i as RowKey } from './codes-B9bCK73-.cjs';
2
- import { R as RowModelContext, c as FeatureContext } from './explain-Q8Cg0cZy.cjs';
1
+ import { i as RowKey } from './codes-AX8XkMNY.cjs';
2
+ import { R as RowModelContext, c as FeatureContext } from './explain-pKAnnxEW.cjs';
3
3
 
4
4
  /** What `setSearch` / `flushSearch` resolve to; they never reject. */
5
5
  interface SearchOutcome {
@@ -1,5 +1,5 @@
1
- import { i as RowKey } from './codes-B9bCK73-.js';
2
- import { R as RowModelContext, c as FeatureContext } from './explain-i0OATi_W.js';
1
+ import { i as RowKey } from './codes-AX8XkMNY.js';
2
+ import { R as RowModelContext, c as FeatureContext } from './explain-BVE3ln3G.js';
3
3
 
4
4
  /** What `setSearch` / `flushSearch` resolve to; they never reject. */
5
5
  interface SearchOutcome {
@@ -1,3 +1,3 @@
1
- export { ap as searchDiagnostics } from './codes-B9bCK73-.cjs';
2
- export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, w as withSearchFn } from './search-async-entry-CuLYPfY_.cjs';
3
- import './explain-Q8Cg0cZy.cjs';
1
+ export { ap as searchDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, w as withSearchFn } from './search-async-entry-ByMJbHjc.cjs';
3
+ import './explain-pKAnnxEW.cjs';
@@ -1,3 +1,3 @@
1
- export { ap as searchDiagnostics } from './codes-B9bCK73-.js';
2
- export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, w as withSearchFn } from './search-async-entry-Cx7xpuVZ.js';
3
- import './explain-i0OATi_W.js';
1
+ export { ap as searchDiagnostics } from './codes-AX8XkMNY.js';
2
+ export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, w as withSearchFn } from './search-async-entry-CFQODE08.js';
3
+ import './explain-BVE3ln3G.js';
package/dist/search.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- export { ap as searchDiagnostics } from './codes-B9bCK73-.cjs';
2
- import { d as AnyRow, F as Feature, b as FixedTypes } from './explain-Q8Cg0cZy.cjs';
1
+ export { ap as searchDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ import { d as AnyRow, F as Feature, b as FixedTypes } from './explain-pKAnnxEW.cjs';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.cjs';
4
- import { d as SearchOutcome, A as AsyncSearchFn, S as SearchState } from './search-async-entry-CuLYPfY_.cjs';
5
- export { a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-CuLYPfY_.cjs';
4
+ import { d as SearchOutcome, A as AsyncSearchFn, S as SearchState } from './search-async-entry-ByMJbHjc.cjs';
5
+ export { a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-ByMJbHjc.cjs';
6
6
 
7
7
  interface SearchApi {
8
8
  /** Sets `input` (and `query`, with the client's immediate typing strategy). */
package/dist/search.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { ap as searchDiagnostics } from './codes-B9bCK73-.js';
2
- import { d as AnyRow, F as Feature, b as FixedTypes } from './explain-i0OATi_W.js';
1
+ export { ap as searchDiagnostics } from './codes-AX8XkMNY.js';
2
+ import { d as AnyRow, F as Feature, b as FixedTypes } from './explain-BVE3ln3G.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
- import { d as SearchOutcome, A as AsyncSearchFn, S as SearchState } from './search-async-entry-Cx7xpuVZ.js';
5
- export { a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-Cx7xpuVZ.js';
4
+ import { d as SearchOutcome, A as AsyncSearchFn, S as SearchState } from './search-async-entry-CFQODE08.js';
5
+ export { a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-CFQODE08.js';
6
6
 
7
7
  interface SearchApi {
8
8
  /** Sets `input` (and `query`, with the client's immediate typing strategy). */
package/dist/server.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as Interceptor, D as Fetcher, i as ServerFetcher } from './explain-Q8Cg0cZy.cjs';
2
- export { x as FetchOutcome, y as FetchParams, z as FetchResult, ab as SERVER_SOURCE } from './explain-Q8Cg0cZy.cjs';
3
- export { B as BadResponse, N as asyncDiagnostics, a4 as fetchDiagnostics } from './codes-B9bCK73-.cjs';
1
+ import { a as Interceptor, D as Fetcher, i as ServerFetcher } from './explain-pKAnnxEW.cjs';
2
+ export { x as FetchOutcome, y as FetchParams, z as FetchResult, ab as SERVER_SOURCE } from './explain-pKAnnxEW.cjs';
3
+ export { B as BadResponse, N as asyncDiagnostics, a4 as fetchDiagnostics } from './codes-AX8XkMNY.cjs';
4
4
 
5
5
  /** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */
6
6
  declare const DEFAULT_ASYNC_TIMEOUT = 30000;
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as Interceptor, D as Fetcher, i as ServerFetcher } from './explain-i0OATi_W.js';
2
- export { x as FetchOutcome, y as FetchParams, z as FetchResult, ab as SERVER_SOURCE } from './explain-i0OATi_W.js';
3
- export { B as BadResponse, N as asyncDiagnostics, a4 as fetchDiagnostics } from './codes-B9bCK73-.js';
1
+ import { a as Interceptor, D as Fetcher, i as ServerFetcher } from './explain-BVE3ln3G.js';
2
+ export { x as FetchOutcome, y as FetchParams, z as FetchResult, ab as SERVER_SOURCE } from './explain-BVE3ln3G.js';
3
+ export { B as BadResponse, N as asyncDiagnostics, a4 as fetchDiagnostics } from './codes-AX8XkMNY.js';
4
4
 
5
5
  /** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */
6
6
  declare const DEFAULT_ASYNC_TIMEOUT = 30000;
@@ -1,5 +1,5 @@
1
- export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-B9bCK73-.cjs';
2
- import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-Q8Cg0cZy.cjs';
1
+ export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-pKAnnxEW.cjs';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.cjs';
4
4
 
5
5
  /** The rank of `v` in {@link RANK}. */
package/dist/sorting.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-B9bCK73-.js';
2
- import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-i0OATi_W.js';
1
+ export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-AX8XkMNY.js';
2
+ import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-BVE3ln3G.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
4
 
5
5
  /** The rank of `v` in {@link RANK}. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/table/types.ts"],"sourcesContent":["import type { ColumnDef, LooseRow, Path } from '../column'\nimport type { RowExplanation, TableExplanation } from '../diagnostics/explain'\nimport type { RedactFn, RedactLevel } from '../diagnostics/redact'\nimport type { Diagnostic } from '../diagnostics/types'\nimport type { AnyFeature } from '../feature/types'\nimport type { InvalidRow } from '../ingest/schema'\nimport type { TableSchema } from '../ingest/schema-brand'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport type { RowKey } from '../pipeline/row'\nimport type { RowModel } from '../pipeline/types'\nimport type { FetchOutcome } from '../source/fetcher'\nimport type { ServerFetcher } from '../source/server-brand'\n\n/** Table state: one entry per slice (feature state is typed by `StateOf`, T08). */\nexport type TableState = Record<string, unknown>\n\n/** A partial state merged per slice, or a function of the current state returning one. */\nexport type Updater<S> = Partial<S> | ((prev: S) => Partial<S>)\n\n/** `options.state`: a slice given as `undefined` counts as not passed (owned by the store). */\nexport type ControlledState<S> = { [K in keyof S]?: S[K] | undefined }\n\n/** Called after each committed change with the new and the previously notified snapshot. */\nexport type Listener<S> = (state: S, prev: S) => void\n\n/**\n * Max consecutive re-entrant notification rounds (a listener updating state, whose listeners\n * update state, …). The update that would start the next round is dropped (`DK_UPDATE_LOOP`).\n */\nexport const UPDATE_DEPTH_LIMIT = 50\n\nexport interface TableDiagnosticsOptions {\n /**\n * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level\n * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never\n * logged; a `rowKey` path into a sensitive column is hashed at every level.\n */\n redact?: RedactLevel | RedactFn\n}\n\nexport interface TableOptions<T, S extends TableState = TableState> {\n /** Names the table in diagnostics; default `table#n`. */\n name?: string\n data?: readonly T[]\n columns?: readonly ColumnDef<T>[]\n /**\n * A Standard Schema v1 (zod, valibot, arktype, …) every row is validated with (§5.1), opted in\n * with `withSchema(UserSchema)` from `@datakable/core/schema`; the row type is its output. An\n * async schema sets `status: 'validating'` while it runs. Read once, at creation.\n */\n schema?: TableSchema<unknown, T>\n /**\n * What happens to rows that fail `schema` (§5.1): `'drop'` (default) removes them, `'flag'` keeps\n * them with `row.valid === false`, `'throw'` throws `DatakableSchemaError`. All three list them in\n * `getInvalidRows()`. Read once, at creation; no effect without `schema`.\n */\n invalidRows?: InvalidRowsMode\n /** Row identity (§5.2). Read by redaction here; key handling is the ingest's. */\n rowKey?: Path<LooseRow<T>> | ((row: T) => string | number)\n /**\n * Installed features (§3.3), in any order: the pipeline runs them by `stage`. Each adds its state\n * slice and merges its API onto the table. Read once, at creation (`setOptions` ignores it).\n */\n features?: readonly AnyFeature[]\n /** Starting values of owned slices; a feature slice given here replaces its `initialState()`. */\n initialState?: Partial<S>\n /**\n * Controlled slices (§3.2): every key with a defined value is owned by the caller, who feeds\n * changes back through `setOptions({ state })`. Slices not passed are owned by the store.\n */\n state?: ControlledState<S>\n /**\n * Receives changes to controlled slices as an updater that merges only the changed slices into\n * the caller's state: `onStateChange: (u) => setControlled(u)`. Not called for owned slices.\n */\n onStateChange?: (updater: (prev: Partial<S>) => Partial<S>) => void\n /** User / feature interceptors (§3.4). The errorBoundary is always installed outermost. */\n interceptors?: readonly Interceptor<OpName>[]\n /** User sink for every diagnostic (e.g. Sentry). */\n onDiagnostic?: (d: Diagnostic) => void\n /**\n * ms before an async request (`fetcher`, `searchFn`) is aborted with `DK_ASYNC_TIMEOUT`.\n * Default 30 000; `Infinity` disables it. Read once, at creation.\n */\n timeout?: number\n /**\n * Server source (§7.1): `fetcher({ query, filters, sort, pageIndex, pageSize, signal })` →\n * `{ rows, rowCount }`. It receives only the state of the server-mode features and runs at\n * creation and whenever that state changes (`status: 'fetching'`), through `async:request`\n * (latest wins, abort, `timeout`). Needs at least one feature with `mode: 'server'`\n * (`DK_FETCHER_WITHOUT_SERVER_MODE`). The returned rows go through `schema` and the data checks.\n * Opt in with `withFetcher(fetchUsers)` from `@datakable/core/server`: a table without a fetcher\n * does not bundle the server source (changed 2026-10-13 for §9.3).\n */\n fetcher?: ServerFetcher<T>\n /**\n * Server mode without a fetcher (§7.1): the total number of rows on the server, while `data` is\n * only the current page. Server-mode pagination counts pages from it (`DK_ROW_COUNT_MISSING`\n * when missing).\n */\n rowCount?: number\n /**\n * SSR (§7.2): the rows the server already produced for the initial state (e.g. its fetcher\n * result). Ingested at creation like `data` when `data` is not given, and counts as the result of\n * the initial async work: the fetcher's first request is skipped (a later server-state change\n * fetches), a `searchFn` is not called for the initial query, and hydration does not re-fetch.\n * With server pagination, `rowCount` gives its total. Read once, at creation.\n */\n initialData?: readonly T[]\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n diagnostics?: TableDiagnosticsOptions\n}\n\nexport interface Table<\n T,\n S extends TableState = TableState,\n M extends InvalidRowsMode = 'drop',\n /** The element type `setOptions({ data })` accepts: `unknown` (raw input) with a schema. */\n In = T,\n> {\n /** The current immutable snapshot; the same reference until a slice changes. */\n getState(): S\n setState(updater: Updater<S>): void\n /** Returns the unsubscribe function. One throwing listener never stops the others. */\n subscribe(listener: Listener<S>): () => void\n /** Merges `options` into the current ones (`table:setOptions`). */\n setOptions(\n options: Partial<Omit<TableOptions<T, S>, 'features' | 'data'>> & { data?: readonly In[] },\n ): void\n getOptions(): Readonly<TableOptions<T, S>>\n /** The ingested data (the previous data while an async ingest is pending or failed). */\n getData(): readonly T[]\n /**\n * The row model (§6.6): the visible rows (`Row` objects for the current page only), the counts\n * and the status. The same object until data, state or columns change the result.\n */\n getRowModel(): RowModel<T, M>\n /**\n * The rows that failed `schema` in the current data (§5.1), in every `invalidRows` mode:\n * `{ index, raw, issues }` with `index` into `options.data`. Empty without a schema.\n */\n getInvalidRows(): readonly InvalidRow[]\n /** Oldest first, at most 100 entries. */\n getDiagnostics(): Diagnostic[]\n /**\n * The last pipeline run (§8.4): rows per step, schema drops, the page, open diagnostics, and\n * timings when traced (the tracer runs in development). `String(table.explain())` is a one-line\n * summary. Read-only.\n */\n explain(): TableExplanation\n /**\n * Replays the row with key `key` through the schema and every step (§8.4): which step kept it\n * where, and the step that removed it, with a fix. Values are redacted like diagnostics (§8.2).\n * `String(table.explainRow(key))` is the console report. Read-only.\n */\n explainRow(key: RowKey): RowExplanation\n /**\n * Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after\n * `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.\n */\n refetch(): Promise<FetchOutcome>\n /** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */\n destroy(): void\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BO,IAAM,qBAAqB;","names":[]}
1
+ {"version":3,"sources":["../../src/table/types.ts"],"sourcesContent":["import type { ColumnDef, LooseRow, Path } from '../column'\nimport type { RowExplanation, TableExplanation } from '../diagnostics/explain'\nimport type { RedactFn, RedactLevel } from '../diagnostics/redact'\nimport type { Diagnostic } from '../diagnostics/types'\nimport type { AnyFeature } from '../feature/types'\nimport type { InvalidRow } from '../ingest/schema'\nimport type { TableSchema } from '../ingest/schema-brand'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport type { RowKey } from '../pipeline/row'\nimport type { RowModel } from '../pipeline/types'\nimport type { FetchOutcome } from '../source/fetcher'\nimport type { ServerFetcher } from '../source/server-brand'\n\n/** Table state: one entry per slice (feature state is typed by `StateOf`, T08). */\nexport type TableState = Record<string, unknown>\n\n/** A partial state merged per slice, or a function of the current state returning one. */\nexport type Updater<S> = Partial<S> | ((prev: S) => Partial<S>)\n\n/** `options.state`: a slice given as `undefined` counts as not passed (owned by the store). */\nexport type ControlledState<S> = { [K in keyof S]?: S[K] | undefined }\n\n/** Called after each committed change with the new and the previously notified snapshot. */\nexport type Listener<S> = (state: S, prev: S) => void\n\n/**\n * Max consecutive re-entrant notification rounds (a listener updating state, whose listeners\n * update state, …). The update that would start the next round is dropped (`DK_UPDATE_LOOP`).\n */\nexport const UPDATE_DEPTH_LIMIT = 50\n\nexport interface TableDiagnosticsOptions {\n /**\n * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level\n * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never\n * logged; a `rowKey` path into a sensitive column is logged as `[sensitive]` (no hash) at every level.\n */\n redact?: RedactLevel | RedactFn\n}\n\nexport interface TableOptions<T, S extends TableState = TableState> {\n /** Names the table in diagnostics; default `table#n`. */\n name?: string\n data?: readonly T[]\n columns?: readonly ColumnDef<T>[]\n /**\n * A Standard Schema v1 (zod, valibot, arktype, …) every row is validated with (§5.1), opted in\n * with `withSchema(UserSchema)` from `@datakable/core/schema`; the row type is its output. An\n * async schema sets `status: 'validating'` while it runs. Read once, at creation.\n */\n schema?: TableSchema<unknown, T>\n /**\n * What happens to rows that fail `schema` (§5.1): `'drop'` (default) removes them, `'flag'` keeps\n * them with `row.valid === false`, `'throw'` throws `DatakableSchemaError`. All three list them in\n * `getInvalidRows()`. Read once, at creation; no effect without `schema`.\n */\n invalidRows?: InvalidRowsMode\n /** Row identity (§5.2). Read by redaction here; key handling is the ingest's. */\n rowKey?: Path<LooseRow<T>> | ((row: T) => string | number)\n /**\n * Installed features (§3.3), in any order: the pipeline runs them by `stage`. Each adds its state\n * slice and merges its API onto the table. Read once, at creation (`setOptions` ignores it).\n */\n features?: readonly AnyFeature[]\n /** Starting values of owned slices; a feature slice given here replaces its `initialState()`. */\n initialState?: Partial<S>\n /**\n * Controlled slices (§3.2): every key with a defined value is owned by the caller, who feeds\n * changes back through `setOptions({ state })`. Slices not passed are owned by the store.\n */\n state?: ControlledState<S>\n /**\n * Receives changes to controlled slices as an updater that merges only the changed slices into\n * the caller's state: `onStateChange: (u) => setControlled(u)`. Not called for owned slices.\n */\n onStateChange?: (updater: (prev: Partial<S>) => Partial<S>) => void\n /** User / feature interceptors (§3.4). The errorBoundary is always installed outermost. */\n interceptors?: readonly Interceptor<OpName>[]\n /** User sink for every diagnostic (e.g. Sentry). */\n onDiagnostic?: (d: Diagnostic) => void\n /**\n * ms before an async request (`fetcher`, `searchFn`) is aborted with `DK_ASYNC_TIMEOUT`.\n * Default 30 000; `Infinity` disables it. Read once, at creation.\n */\n timeout?: number\n /**\n * Server source (§7.1): `fetcher({ query, filters, sort, pageIndex, pageSize, signal })` →\n * `{ rows, rowCount }`. It receives only the state of the server-mode features and runs at\n * creation and whenever that state changes (`status: 'fetching'`), through `async:request`\n * (latest wins, abort, `timeout`). Needs at least one feature with `mode: 'server'`\n * (`DK_FETCHER_WITHOUT_SERVER_MODE`). The returned rows go through `schema` and the data checks.\n * Opt in with `withFetcher(fetchUsers)` from `@datakable/core/server`: a table without a fetcher\n * does not bundle the server source (changed 2026-10-13 for §9.3).\n */\n fetcher?: ServerFetcher<T>\n /**\n * Server mode without a fetcher (§7.1): the total number of rows on the server, while `data` is\n * only the current page. Server-mode pagination counts pages from it (`DK_ROW_COUNT_MISSING`\n * when missing).\n */\n rowCount?: number\n /**\n * SSR (§7.2): the rows the server already produced for the initial state (e.g. its fetcher\n * result). Ingested at creation like `data` when `data` is not given, and counts as the result of\n * the initial async work: the fetcher's first request is skipped (a later server-state change\n * fetches), a `searchFn` is not called for the initial query, and hydration does not re-fetch.\n * With server pagination, `rowCount` gives its total. Read once, at creation.\n */\n initialData?: readonly T[]\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n diagnostics?: TableDiagnosticsOptions\n}\n\nexport interface Table<\n T,\n S extends TableState = TableState,\n M extends InvalidRowsMode = 'drop',\n /** The element type `setOptions({ data })` accepts: `unknown` (raw input) with a schema. */\n In = T,\n> {\n /** The current immutable snapshot; the same reference until a slice changes. */\n getState(): S\n setState(updater: Updater<S>): void\n /** Returns the unsubscribe function. One throwing listener never stops the others. */\n subscribe(listener: Listener<S>): () => void\n /** Merges `options` into the current ones (`table:setOptions`). */\n setOptions(\n options: Partial<Omit<TableOptions<T, S>, 'features' | 'data'>> & { data?: readonly In[] },\n ): void\n getOptions(): Readonly<TableOptions<T, S>>\n /** The ingested data (the previous data while an async ingest is pending or failed). */\n getData(): readonly T[]\n /**\n * The row model (§6.6): the visible rows (`Row` objects for the current page only), the counts\n * and the status. The same object until data, state or columns change the result.\n */\n getRowModel(): RowModel<T, M>\n /**\n * The rows that failed `schema` in the current data (§5.1), in every `invalidRows` mode:\n * `{ index, raw, issues }` with `index` into `options.data`. Empty without a schema.\n */\n getInvalidRows(): readonly InvalidRow[]\n /** Oldest first, at most 100 entries. */\n getDiagnostics(): Diagnostic[]\n /**\n * The last pipeline run (§8.4): rows per step, schema drops, the page, open diagnostics, and\n * timings when traced (the tracer runs in development). `String(table.explain())` is a one-line\n * summary. Read-only.\n */\n explain(): TableExplanation\n /**\n * Replays the row with key `key` through the schema and every step (§8.4): which step kept it\n * where, and the step that removed it, with a fix. Values are redacted like diagnostics (§8.2).\n * `String(table.explainRow(key))` is the console report. Read-only.\n */\n explainRow(key: RowKey): RowExplanation\n /**\n * Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after\n * `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.\n */\n refetch(): Promise<FetchOutcome>\n /** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */\n destroy(): void\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BO,IAAM,qBAAqB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/table/types.ts"],"sourcesContent":["import type { ColumnDef, LooseRow, Path } from '../column'\nimport type { RowExplanation, TableExplanation } from '../diagnostics/explain'\nimport type { RedactFn, RedactLevel } from '../diagnostics/redact'\nimport type { Diagnostic } from '../diagnostics/types'\nimport type { AnyFeature } from '../feature/types'\nimport type { InvalidRow } from '../ingest/schema'\nimport type { TableSchema } from '../ingest/schema-brand'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport type { RowKey } from '../pipeline/row'\nimport type { RowModel } from '../pipeline/types'\nimport type { FetchOutcome } from '../source/fetcher'\nimport type { ServerFetcher } from '../source/server-brand'\n\n/** Table state: one entry per slice (feature state is typed by `StateOf`, T08). */\nexport type TableState = Record<string, unknown>\n\n/** A partial state merged per slice, or a function of the current state returning one. */\nexport type Updater<S> = Partial<S> | ((prev: S) => Partial<S>)\n\n/** `options.state`: a slice given as `undefined` counts as not passed (owned by the store). */\nexport type ControlledState<S> = { [K in keyof S]?: S[K] | undefined }\n\n/** Called after each committed change with the new and the previously notified snapshot. */\nexport type Listener<S> = (state: S, prev: S) => void\n\n/**\n * Max consecutive re-entrant notification rounds (a listener updating state, whose listeners\n * update state, …). The update that would start the next round is dropped (`DK_UPDATE_LOOP`).\n */\nexport const UPDATE_DEPTH_LIMIT = 50\n\nexport interface TableDiagnosticsOptions {\n /**\n * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level\n * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never\n * logged; a `rowKey` path into a sensitive column is hashed at every level.\n */\n redact?: RedactLevel | RedactFn\n}\n\nexport interface TableOptions<T, S extends TableState = TableState> {\n /** Names the table in diagnostics; default `table#n`. */\n name?: string\n data?: readonly T[]\n columns?: readonly ColumnDef<T>[]\n /**\n * A Standard Schema v1 (zod, valibot, arktype, …) every row is validated with (§5.1), opted in\n * with `withSchema(UserSchema)` from `@datakable/core/schema`; the row type is its output. An\n * async schema sets `status: 'validating'` while it runs. Read once, at creation.\n */\n schema?: TableSchema<unknown, T>\n /**\n * What happens to rows that fail `schema` (§5.1): `'drop'` (default) removes them, `'flag'` keeps\n * them with `row.valid === false`, `'throw'` throws `DatakableSchemaError`. All three list them in\n * `getInvalidRows()`. Read once, at creation; no effect without `schema`.\n */\n invalidRows?: InvalidRowsMode\n /** Row identity (§5.2). Read by redaction here; key handling is the ingest's. */\n rowKey?: Path<LooseRow<T>> | ((row: T) => string | number)\n /**\n * Installed features (§3.3), in any order: the pipeline runs them by `stage`. Each adds its state\n * slice and merges its API onto the table. Read once, at creation (`setOptions` ignores it).\n */\n features?: readonly AnyFeature[]\n /** Starting values of owned slices; a feature slice given here replaces its `initialState()`. */\n initialState?: Partial<S>\n /**\n * Controlled slices (§3.2): every key with a defined value is owned by the caller, who feeds\n * changes back through `setOptions({ state })`. Slices not passed are owned by the store.\n */\n state?: ControlledState<S>\n /**\n * Receives changes to controlled slices as an updater that merges only the changed slices into\n * the caller's state: `onStateChange: (u) => setControlled(u)`. Not called for owned slices.\n */\n onStateChange?: (updater: (prev: Partial<S>) => Partial<S>) => void\n /** User / feature interceptors (§3.4). The errorBoundary is always installed outermost. */\n interceptors?: readonly Interceptor<OpName>[]\n /** User sink for every diagnostic (e.g. Sentry). */\n onDiagnostic?: (d: Diagnostic) => void\n /**\n * ms before an async request (`fetcher`, `searchFn`) is aborted with `DK_ASYNC_TIMEOUT`.\n * Default 30 000; `Infinity` disables it. Read once, at creation.\n */\n timeout?: number\n /**\n * Server source (§7.1): `fetcher({ query, filters, sort, pageIndex, pageSize, signal })` →\n * `{ rows, rowCount }`. It receives only the state of the server-mode features and runs at\n * creation and whenever that state changes (`status: 'fetching'`), through `async:request`\n * (latest wins, abort, `timeout`). Needs at least one feature with `mode: 'server'`\n * (`DK_FETCHER_WITHOUT_SERVER_MODE`). The returned rows go through `schema` and the data checks.\n * Opt in with `withFetcher(fetchUsers)` from `@datakable/core/server`: a table without a fetcher\n * does not bundle the server source (changed 2026-10-13 for §9.3).\n */\n fetcher?: ServerFetcher<T>\n /**\n * Server mode without a fetcher (§7.1): the total number of rows on the server, while `data` is\n * only the current page. Server-mode pagination counts pages from it (`DK_ROW_COUNT_MISSING`\n * when missing).\n */\n rowCount?: number\n /**\n * SSR (§7.2): the rows the server already produced for the initial state (e.g. its fetcher\n * result). Ingested at creation like `data` when `data` is not given, and counts as the result of\n * the initial async work: the fetcher's first request is skipped (a later server-state change\n * fetches), a `searchFn` is not called for the initial query, and hydration does not re-fetch.\n * With server pagination, `rowCount` gives its total. Read once, at creation.\n */\n initialData?: readonly T[]\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n diagnostics?: TableDiagnosticsOptions\n}\n\nexport interface Table<\n T,\n S extends TableState = TableState,\n M extends InvalidRowsMode = 'drop',\n /** The element type `setOptions({ data })` accepts: `unknown` (raw input) with a schema. */\n In = T,\n> {\n /** The current immutable snapshot; the same reference until a slice changes. */\n getState(): S\n setState(updater: Updater<S>): void\n /** Returns the unsubscribe function. One throwing listener never stops the others. */\n subscribe(listener: Listener<S>): () => void\n /** Merges `options` into the current ones (`table:setOptions`). */\n setOptions(\n options: Partial<Omit<TableOptions<T, S>, 'features' | 'data'>> & { data?: readonly In[] },\n ): void\n getOptions(): Readonly<TableOptions<T, S>>\n /** The ingested data (the previous data while an async ingest is pending or failed). */\n getData(): readonly T[]\n /**\n * The row model (§6.6): the visible rows (`Row` objects for the current page only), the counts\n * and the status. The same object until data, state or columns change the result.\n */\n getRowModel(): RowModel<T, M>\n /**\n * The rows that failed `schema` in the current data (§5.1), in every `invalidRows` mode:\n * `{ index, raw, issues }` with `index` into `options.data`. Empty without a schema.\n */\n getInvalidRows(): readonly InvalidRow[]\n /** Oldest first, at most 100 entries. */\n getDiagnostics(): Diagnostic[]\n /**\n * The last pipeline run (§8.4): rows per step, schema drops, the page, open diagnostics, and\n * timings when traced (the tracer runs in development). `String(table.explain())` is a one-line\n * summary. Read-only.\n */\n explain(): TableExplanation\n /**\n * Replays the row with key `key` through the schema and every step (§8.4): which step kept it\n * where, and the step that removed it, with a fix. Values are redacted like diagnostics (§8.2).\n * `String(table.explainRow(key))` is the console report. Read-only.\n */\n explainRow(key: RowKey): RowExplanation\n /**\n * Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after\n * `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.\n */\n refetch(): Promise<FetchOutcome>\n /** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */\n destroy(): void\n}\n"],"mappings":";AA+BO,IAAM,qBAAqB;","names":[]}
1
+ {"version":3,"sources":["../../src/table/types.ts"],"sourcesContent":["import type { ColumnDef, LooseRow, Path } from '../column'\nimport type { RowExplanation, TableExplanation } from '../diagnostics/explain'\nimport type { RedactFn, RedactLevel } from '../diagnostics/redact'\nimport type { Diagnostic } from '../diagnostics/types'\nimport type { AnyFeature } from '../feature/types'\nimport type { InvalidRow } from '../ingest/schema'\nimport type { TableSchema } from '../ingest/schema-brand'\nimport type { InvalidRowsMode } from '../ingest/schema-codes'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport type { RowKey } from '../pipeline/row'\nimport type { RowModel } from '../pipeline/types'\nimport type { FetchOutcome } from '../source/fetcher'\nimport type { ServerFetcher } from '../source/server-brand'\n\n/** Table state: one entry per slice (feature state is typed by `StateOf`, T08). */\nexport type TableState = Record<string, unknown>\n\n/** A partial state merged per slice, or a function of the current state returning one. */\nexport type Updater<S> = Partial<S> | ((prev: S) => Partial<S>)\n\n/** `options.state`: a slice given as `undefined` counts as not passed (owned by the store). */\nexport type ControlledState<S> = { [K in keyof S]?: S[K] | undefined }\n\n/** Called after each committed change with the new and the previously notified snapshot. */\nexport type Listener<S> = (state: S, prev: S) => void\n\n/**\n * Max consecutive re-entrant notification rounds (a listener updating state, whose listeners\n * update state, …). The update that would start the next round is dropped (`DK_UPDATE_LOOP`).\n */\nexport const UPDATE_DEPTH_LIMIT = 50\n\nexport interface TableDiagnosticsOptions {\n /**\n * Redaction (§8.2): a level, or a custom `redact(path, value)` on top of the default level\n * (`'keys'` in development, `'safe'` in production). Columns with `sensitive: true` are never\n * logged; a `rowKey` path into a sensitive column is logged as `[sensitive]` (no hash) at every level.\n */\n redact?: RedactLevel | RedactFn\n}\n\nexport interface TableOptions<T, S extends TableState = TableState> {\n /** Names the table in diagnostics; default `table#n`. */\n name?: string\n data?: readonly T[]\n columns?: readonly ColumnDef<T>[]\n /**\n * A Standard Schema v1 (zod, valibot, arktype, …) every row is validated with (§5.1), opted in\n * with `withSchema(UserSchema)` from `@datakable/core/schema`; the row type is its output. An\n * async schema sets `status: 'validating'` while it runs. Read once, at creation.\n */\n schema?: TableSchema<unknown, T>\n /**\n * What happens to rows that fail `schema` (§5.1): `'drop'` (default) removes them, `'flag'` keeps\n * them with `row.valid === false`, `'throw'` throws `DatakableSchemaError`. All three list them in\n * `getInvalidRows()`. Read once, at creation; no effect without `schema`.\n */\n invalidRows?: InvalidRowsMode\n /** Row identity (§5.2). Read by redaction here; key handling is the ingest's. */\n rowKey?: Path<LooseRow<T>> | ((row: T) => string | number)\n /**\n * Installed features (§3.3), in any order: the pipeline runs them by `stage`. Each adds its state\n * slice and merges its API onto the table. Read once, at creation (`setOptions` ignores it).\n */\n features?: readonly AnyFeature[]\n /** Starting values of owned slices; a feature slice given here replaces its `initialState()`. */\n initialState?: Partial<S>\n /**\n * Controlled slices (§3.2): every key with a defined value is owned by the caller, who feeds\n * changes back through `setOptions({ state })`. Slices not passed are owned by the store.\n */\n state?: ControlledState<S>\n /**\n * Receives changes to controlled slices as an updater that merges only the changed slices into\n * the caller's state: `onStateChange: (u) => setControlled(u)`. Not called for owned slices.\n */\n onStateChange?: (updater: (prev: Partial<S>) => Partial<S>) => void\n /** User / feature interceptors (§3.4). The errorBoundary is always installed outermost. */\n interceptors?: readonly Interceptor<OpName>[]\n /** User sink for every diagnostic (e.g. Sentry). */\n onDiagnostic?: (d: Diagnostic) => void\n /**\n * ms before an async request (`fetcher`, `searchFn`) is aborted with `DK_ASYNC_TIMEOUT`.\n * Default 30 000; `Infinity` disables it. Read once, at creation.\n */\n timeout?: number\n /**\n * Server source (§7.1): `fetcher({ query, filters, sort, pageIndex, pageSize, signal })` →\n * `{ rows, rowCount }`. It receives only the state of the server-mode features and runs at\n * creation and whenever that state changes (`status: 'fetching'`), through `async:request`\n * (latest wins, abort, `timeout`). Needs at least one feature with `mode: 'server'`\n * (`DK_FETCHER_WITHOUT_SERVER_MODE`). The returned rows go through `schema` and the data checks.\n * Opt in with `withFetcher(fetchUsers)` from `@datakable/core/server`: a table without a fetcher\n * does not bundle the server source (changed 2026-10-13 for §9.3).\n */\n fetcher?: ServerFetcher<T>\n /**\n * Server mode without a fetcher (§7.1): the total number of rows on the server, while `data` is\n * only the current page. Server-mode pagination counts pages from it (`DK_ROW_COUNT_MISSING`\n * when missing).\n */\n rowCount?: number\n /**\n * SSR (§7.2): the rows the server already produced for the initial state (e.g. its fetcher\n * result). Ingested at creation like `data` when `data` is not given, and counts as the result of\n * the initial async work: the fetcher's first request is skipped (a later server-state change\n * fetches), a `searchFn` is not called for the initial query, and hydration does not re-fetch.\n * With server pagination, `rowCount` gives its total. Read once, at creation.\n */\n initialData?: readonly T[]\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n diagnostics?: TableDiagnosticsOptions\n}\n\nexport interface Table<\n T,\n S extends TableState = TableState,\n M extends InvalidRowsMode = 'drop',\n /** The element type `setOptions({ data })` accepts: `unknown` (raw input) with a schema. */\n In = T,\n> {\n /** The current immutable snapshot; the same reference until a slice changes. */\n getState(): S\n setState(updater: Updater<S>): void\n /** Returns the unsubscribe function. One throwing listener never stops the others. */\n subscribe(listener: Listener<S>): () => void\n /** Merges `options` into the current ones (`table:setOptions`). */\n setOptions(\n options: Partial<Omit<TableOptions<T, S>, 'features' | 'data'>> & { data?: readonly In[] },\n ): void\n getOptions(): Readonly<TableOptions<T, S>>\n /** The ingested data (the previous data while an async ingest is pending or failed). */\n getData(): readonly T[]\n /**\n * The row model (§6.6): the visible rows (`Row` objects for the current page only), the counts\n * and the status. The same object until data, state or columns change the result.\n */\n getRowModel(): RowModel<T, M>\n /**\n * The rows that failed `schema` in the current data (§5.1), in every `invalidRows` mode:\n * `{ index, raw, issues }` with `index` into `options.data`. Empty without a schema.\n */\n getInvalidRows(): readonly InvalidRow[]\n /** Oldest first, at most 100 entries. */\n getDiagnostics(): Diagnostic[]\n /**\n * The last pipeline run (§8.4): rows per step, schema drops, the page, open diagnostics, and\n * timings when traced (the tracer runs in development). `String(table.explain())` is a one-line\n * summary. Read-only.\n */\n explain(): TableExplanation\n /**\n * Replays the row with key `key` through the schema and every step (§8.4): which step kept it\n * where, and the step that removed it, with a fix. Values are redacted like diagnostics (§8.2).\n * `String(table.explainRow(key))` is the console report. Read-only.\n */\n explainRow(key: RowKey): RowExplanation\n /**\n * Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after\n * `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.\n */\n refetch(): Promise<FetchOutcome>\n /** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */\n destroy(): void\n}\n"],"mappings":";AA+BO,IAAM,qBAAqB;","names":[]}
@@ -1,5 +1,5 @@
1
- export { aw as validityDiagnostics } from './codes-B9bCK73-.cjs';
2
- import { F as Feature, b as FixedTypes } from './explain-Q8Cg0cZy.cjs';
1
+ export { aw as validityDiagnostics } from './codes-AX8XkMNY.cjs';
2
+ import { F as Feature, b as FixedTypes } from './explain-pKAnnxEW.cjs';
3
3
 
4
4
  /** Which rows the `validity` step keeps (§6.2). */
5
5
  type ValidityState = 'all' | 'valid' | 'invalid';
@@ -1,5 +1,5 @@
1
- export { aw as validityDiagnostics } from './codes-B9bCK73-.js';
2
- import { F as Feature, b as FixedTypes } from './explain-i0OATi_W.js';
1
+ export { aw as validityDiagnostics } from './codes-AX8XkMNY.js';
2
+ import { F as Feature, b as FixedTypes } from './explain-BVE3ln3G.js';
3
3
 
4
4
  /** Which rows the `validity` step keeps (§6.2). */
5
5
  type ValidityState = 'all' | 'valid' | 'invalid';
package/dist/version.cjs CHANGED
@@ -25,7 +25,7 @@ __export(version_exports, {
25
25
  module.exports = __toCommonJS(version_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.0.1";
28
+ var version = "2.0.2";
29
29
 
30
30
  // src/version.ts
31
31
  var VERSION = version;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../package.json"],"sourcesContent":["// Read from package.json at build time (the bundler inlines the one field), so a release never\n// ships a stale hard-coded version.\nimport { version } from '../package.json'\n\n/** The `@datakable/core` version (`context.package`, docs URLs, the global version registry). */\nexport const VERSION: string = version\n","{\n \"name\": \"@datakable/core\",\n \"version\": \"2.0.1\",\n \"description\": \"Headless, framework-agnostic core of DataKable — table state, sorting, filtering and types.\",\n \"license\": \"MIT\",\n \"author\": \"Supakit Kitjanabumrungsak\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ink01101011/datakable.git\",\n \"directory\": \"packages/core\"\n },\n \"homepage\": \"https://github.com/Ink01101011/datakable#readme\",\n \"bugs\": \"https://github.com/Ink01101011/datakable/issues\",\n \"keywords\": [\n \"react\",\n \"react-table\",\n \"datatable\",\n \"data-table\",\n \"data-grid\",\n \"datagrid\",\n \"table\",\n \"typescript\",\n \"type-safe\",\n \"headless\",\n \"headless-ui\",\n \"zero-config\",\n \"sorting\",\n \"filtering\",\n \"search\",\n \"pagination\",\n \"framework-agnostic\",\n \"vanilla-js\",\n \"table-state\"\n ],\n \"type\": \"module\",\n \"sideEffects\": [\n \"./dist/messages/index.js\",\n \"./dist/messages/index.cjs\",\n \"./dist/diagnostics/process-guard.js\",\n \"./dist/diagnostics/process-guard.cjs\"\n ],\n \"engines\": {\n \"node\": \">=18.18.0\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \"column-filters\": [\n \"./dist/column-filters.d.ts\"\n ],\n \"validity\": [\n \"./dist/validity.d.ts\"\n ],\n \"search\": [\n \"./dist/search.d.ts\"\n ],\n \"sorting\": [\n \"./dist/sorting.d.ts\"\n ],\n \"pagination\": [\n \"./dist/pagination.d.ts\"\n ],\n \"messages\": [\n \"./dist/messages.d.ts\"\n ],\n \"schema\": [\n \"./dist/schema.d.ts\"\n ],\n \"search-async\": [\n \"./dist/search-async.d.ts\"\n ],\n \"server\": [\n \"./dist/server.d.ts\"\n ]\n }\n },\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./column-filters\": {\n \"import\": {\n \"types\": \"./dist/column-filters.d.ts\",\n \"default\": \"./dist/features/column-filters/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/column-filters.d.cts\",\n \"default\": \"./dist/features/column-filters/index.cjs\"\n }\n },\n \"./validity\": {\n \"import\": {\n \"types\": \"./dist/validity.d.ts\",\n \"default\": \"./dist/features/validity/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/validity.d.cts\",\n \"default\": \"./dist/features/validity/index.cjs\"\n }\n },\n \"./search\": {\n \"import\": {\n \"types\": \"./dist/search.d.ts\",\n \"default\": \"./dist/features/search/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/search.d.cts\",\n \"default\": \"./dist/features/search/index.cjs\"\n }\n },\n \"./sorting\": {\n \"import\": {\n \"types\": \"./dist/sorting.d.ts\",\n \"default\": \"./dist/features/sorting/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/sorting.d.cts\",\n \"default\": \"./dist/features/sorting/index.cjs\"\n }\n },\n \"./pagination\": {\n \"import\": {\n \"types\": \"./dist/pagination.d.ts\",\n \"default\": \"./dist/features/pagination/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/pagination.d.cts\",\n \"default\": \"./dist/features/pagination/index.cjs\"\n }\n },\n \"./messages\": {\n \"import\": {\n \"types\": \"./dist/messages.d.ts\",\n \"default\": \"./dist/messages/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/messages.d.cts\",\n \"default\": \"./dist/messages/index.cjs\"\n }\n },\n \"./schema\": {\n \"import\": {\n \"types\": \"./dist/schema.d.ts\",\n \"default\": \"./dist/ingest/schema-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/schema.d.cts\",\n \"default\": \"./dist/ingest/schema-entry.cjs\"\n }\n },\n \"./search-async\": {\n \"import\": {\n \"types\": \"./dist/search-async.d.ts\",\n \"default\": \"./dist/features/search/search-async-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/search-async.d.cts\",\n \"default\": \"./dist/features/search/search-async-entry.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/source/server-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/source/server-entry.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/postbuild.mjs\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc -p tsconfig.json\",\n \"check:pkg\": \"publint --strict && attw --pack . && node scripts/check-catalog.mjs\",\n \"check:catalog\": \"node scripts/check-catalog.mjs\"\n },\n \"dependencies\": {\n \"ex-search\": \"1.1.1\",\n \"exsorted\": \"1.2.3\",\n \"kk-debounce\": \"1.1.0\"\n },\n \"devDependencies\": {\n \"esbuild\": \"0.27.7\"\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEE,cAAW;;;ADGN,IAAM,UAAkB;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts","../package.json"],"sourcesContent":["// Read from package.json at build time (the bundler inlines the one field), so a release never\n// ships a stale hard-coded version.\nimport { version } from '../package.json'\n\n/** The `@datakable/core` version (`context.package`, docs URLs, the global version registry). */\nexport const VERSION: string = version\n","{\n \"name\": \"@datakable/core\",\n \"version\": \"2.0.2\",\n \"description\": \"Headless, framework-agnostic core of DataKable — table state, sorting, filtering and types.\",\n \"license\": \"MIT\",\n \"author\": \"Supakit Kitjanabumrungsak\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ink01101011/datakable.git\",\n \"directory\": \"packages/core\"\n },\n \"homepage\": \"https://github.com/Ink01101011/datakable#readme\",\n \"bugs\": \"https://github.com/Ink01101011/datakable/issues\",\n \"keywords\": [\n \"react\",\n \"react-table\",\n \"datatable\",\n \"data-table\",\n \"data-grid\",\n \"datagrid\",\n \"table\",\n \"typescript\",\n \"type-safe\",\n \"headless\",\n \"headless-ui\",\n \"zero-config\",\n \"sorting\",\n \"filtering\",\n \"search\",\n \"pagination\",\n \"framework-agnostic\",\n \"vanilla-js\",\n \"table-state\"\n ],\n \"type\": \"module\",\n \"sideEffects\": [\n \"./dist/messages/index.js\",\n \"./dist/messages/index.cjs\",\n \"./dist/diagnostics/process-guard.js\",\n \"./dist/diagnostics/process-guard.cjs\"\n ],\n \"engines\": {\n \"node\": \">=18.18.0\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \"column-filters\": [\n \"./dist/column-filters.d.ts\"\n ],\n \"validity\": [\n \"./dist/validity.d.ts\"\n ],\n \"search\": [\n \"./dist/search.d.ts\"\n ],\n \"sorting\": [\n \"./dist/sorting.d.ts\"\n ],\n \"pagination\": [\n \"./dist/pagination.d.ts\"\n ],\n \"messages\": [\n \"./dist/messages.d.ts\"\n ],\n \"schema\": [\n \"./dist/schema.d.ts\"\n ],\n \"search-async\": [\n \"./dist/search-async.d.ts\"\n ],\n \"server\": [\n \"./dist/server.d.ts\"\n ]\n }\n },\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./column-filters\": {\n \"import\": {\n \"types\": \"./dist/column-filters.d.ts\",\n \"default\": \"./dist/features/column-filters/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/column-filters.d.cts\",\n \"default\": \"./dist/features/column-filters/index.cjs\"\n }\n },\n \"./validity\": {\n \"import\": {\n \"types\": \"./dist/validity.d.ts\",\n \"default\": \"./dist/features/validity/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/validity.d.cts\",\n \"default\": \"./dist/features/validity/index.cjs\"\n }\n },\n \"./search\": {\n \"import\": {\n \"types\": \"./dist/search.d.ts\",\n \"default\": \"./dist/features/search/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/search.d.cts\",\n \"default\": \"./dist/features/search/index.cjs\"\n }\n },\n \"./sorting\": {\n \"import\": {\n \"types\": \"./dist/sorting.d.ts\",\n \"default\": \"./dist/features/sorting/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/sorting.d.cts\",\n \"default\": \"./dist/features/sorting/index.cjs\"\n }\n },\n \"./pagination\": {\n \"import\": {\n \"types\": \"./dist/pagination.d.ts\",\n \"default\": \"./dist/features/pagination/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/pagination.d.cts\",\n \"default\": \"./dist/features/pagination/index.cjs\"\n }\n },\n \"./messages\": {\n \"import\": {\n \"types\": \"./dist/messages.d.ts\",\n \"default\": \"./dist/messages/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/messages.d.cts\",\n \"default\": \"./dist/messages/index.cjs\"\n }\n },\n \"./schema\": {\n \"import\": {\n \"types\": \"./dist/schema.d.ts\",\n \"default\": \"./dist/ingest/schema-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/schema.d.cts\",\n \"default\": \"./dist/ingest/schema-entry.cjs\"\n }\n },\n \"./search-async\": {\n \"import\": {\n \"types\": \"./dist/search-async.d.ts\",\n \"default\": \"./dist/features/search/search-async-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/search-async.d.cts\",\n \"default\": \"./dist/features/search/search-async-entry.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/source/server-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/source/server-entry.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/postbuild.mjs\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc -p tsconfig.json\",\n \"check:pkg\": \"publint --strict && attw --pack . && node scripts/check-catalog.mjs\",\n \"check:catalog\": \"node scripts/check-catalog.mjs\"\n },\n \"dependencies\": {\n \"ex-search\": \"1.1.1\",\n \"exsorted\": \"1.2.3\",\n \"kk-debounce\": \"1.1.0\"\n },\n \"devDependencies\": {\n \"esbuild\": \"0.27.7\"\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEE,cAAW;;;ADGN,IAAM,UAAkB;","names":[]}
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.0.1";
2
+ var version = "2.0.2";
3
3
 
4
4
  // src/version.ts
5
5
  var VERSION = version;
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/version.ts"],"sourcesContent":["{\n \"name\": \"@datakable/core\",\n \"version\": \"2.0.1\",\n \"description\": \"Headless, framework-agnostic core of DataKable — table state, sorting, filtering and types.\",\n \"license\": \"MIT\",\n \"author\": \"Supakit Kitjanabumrungsak\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ink01101011/datakable.git\",\n \"directory\": \"packages/core\"\n },\n \"homepage\": \"https://github.com/Ink01101011/datakable#readme\",\n \"bugs\": \"https://github.com/Ink01101011/datakable/issues\",\n \"keywords\": [\n \"react\",\n \"react-table\",\n \"datatable\",\n \"data-table\",\n \"data-grid\",\n \"datagrid\",\n \"table\",\n \"typescript\",\n \"type-safe\",\n \"headless\",\n \"headless-ui\",\n \"zero-config\",\n \"sorting\",\n \"filtering\",\n \"search\",\n \"pagination\",\n \"framework-agnostic\",\n \"vanilla-js\",\n \"table-state\"\n ],\n \"type\": \"module\",\n \"sideEffects\": [\n \"./dist/messages/index.js\",\n \"./dist/messages/index.cjs\",\n \"./dist/diagnostics/process-guard.js\",\n \"./dist/diagnostics/process-guard.cjs\"\n ],\n \"engines\": {\n \"node\": \">=18.18.0\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \"column-filters\": [\n \"./dist/column-filters.d.ts\"\n ],\n \"validity\": [\n \"./dist/validity.d.ts\"\n ],\n \"search\": [\n \"./dist/search.d.ts\"\n ],\n \"sorting\": [\n \"./dist/sorting.d.ts\"\n ],\n \"pagination\": [\n \"./dist/pagination.d.ts\"\n ],\n \"messages\": [\n \"./dist/messages.d.ts\"\n ],\n \"schema\": [\n \"./dist/schema.d.ts\"\n ],\n \"search-async\": [\n \"./dist/search-async.d.ts\"\n ],\n \"server\": [\n \"./dist/server.d.ts\"\n ]\n }\n },\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./column-filters\": {\n \"import\": {\n \"types\": \"./dist/column-filters.d.ts\",\n \"default\": \"./dist/features/column-filters/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/column-filters.d.cts\",\n \"default\": \"./dist/features/column-filters/index.cjs\"\n }\n },\n \"./validity\": {\n \"import\": {\n \"types\": \"./dist/validity.d.ts\",\n \"default\": \"./dist/features/validity/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/validity.d.cts\",\n \"default\": \"./dist/features/validity/index.cjs\"\n }\n },\n \"./search\": {\n \"import\": {\n \"types\": \"./dist/search.d.ts\",\n \"default\": \"./dist/features/search/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/search.d.cts\",\n \"default\": \"./dist/features/search/index.cjs\"\n }\n },\n \"./sorting\": {\n \"import\": {\n \"types\": \"./dist/sorting.d.ts\",\n \"default\": \"./dist/features/sorting/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/sorting.d.cts\",\n \"default\": \"./dist/features/sorting/index.cjs\"\n }\n },\n \"./pagination\": {\n \"import\": {\n \"types\": \"./dist/pagination.d.ts\",\n \"default\": \"./dist/features/pagination/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/pagination.d.cts\",\n \"default\": \"./dist/features/pagination/index.cjs\"\n }\n },\n \"./messages\": {\n \"import\": {\n \"types\": \"./dist/messages.d.ts\",\n \"default\": \"./dist/messages/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/messages.d.cts\",\n \"default\": \"./dist/messages/index.cjs\"\n }\n },\n \"./schema\": {\n \"import\": {\n \"types\": \"./dist/schema.d.ts\",\n \"default\": \"./dist/ingest/schema-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/schema.d.cts\",\n \"default\": \"./dist/ingest/schema-entry.cjs\"\n }\n },\n \"./search-async\": {\n \"import\": {\n \"types\": \"./dist/search-async.d.ts\",\n \"default\": \"./dist/features/search/search-async-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/search-async.d.cts\",\n \"default\": \"./dist/features/search/search-async-entry.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/source/server-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/source/server-entry.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/postbuild.mjs\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc -p tsconfig.json\",\n \"check:pkg\": \"publint --strict && attw --pack . && node scripts/check-catalog.mjs\",\n \"check:catalog\": \"node scripts/check-catalog.mjs\"\n },\n \"dependencies\": {\n \"ex-search\": \"1.1.1\",\n \"exsorted\": \"1.2.3\",\n \"kk-debounce\": \"1.1.0\"\n },\n \"devDependencies\": {\n \"esbuild\": \"0.27.7\"\n }\n}\n","// Read from package.json at build time (the bundler inlines the one field), so a release never\n// ships a stale hard-coded version.\nimport { version } from '../package.json'\n\n/** The `@datakable/core` version (`context.package`, docs URLs, the global version registry). */\nexport const VERSION: string = version\n"],"mappings":";AAEE,cAAW;;;ACGN,IAAM,UAAkB;","names":[]}
1
+ {"version":3,"sources":["../package.json","../src/version.ts"],"sourcesContent":["{\n \"name\": \"@datakable/core\",\n \"version\": \"2.0.2\",\n \"description\": \"Headless, framework-agnostic core of DataKable — table state, sorting, filtering and types.\",\n \"license\": \"MIT\",\n \"author\": \"Supakit Kitjanabumrungsak\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ink01101011/datakable.git\",\n \"directory\": \"packages/core\"\n },\n \"homepage\": \"https://github.com/Ink01101011/datakable#readme\",\n \"bugs\": \"https://github.com/Ink01101011/datakable/issues\",\n \"keywords\": [\n \"react\",\n \"react-table\",\n \"datatable\",\n \"data-table\",\n \"data-grid\",\n \"datagrid\",\n \"table\",\n \"typescript\",\n \"type-safe\",\n \"headless\",\n \"headless-ui\",\n \"zero-config\",\n \"sorting\",\n \"filtering\",\n \"search\",\n \"pagination\",\n \"framework-agnostic\",\n \"vanilla-js\",\n \"table-state\"\n ],\n \"type\": \"module\",\n \"sideEffects\": [\n \"./dist/messages/index.js\",\n \"./dist/messages/index.cjs\",\n \"./dist/diagnostics/process-guard.js\",\n \"./dist/diagnostics/process-guard.cjs\"\n ],\n \"engines\": {\n \"node\": \">=18.18.0\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"typesVersions\": {\n \"*\": {\n \"column-filters\": [\n \"./dist/column-filters.d.ts\"\n ],\n \"validity\": [\n \"./dist/validity.d.ts\"\n ],\n \"search\": [\n \"./dist/search.d.ts\"\n ],\n \"sorting\": [\n \"./dist/sorting.d.ts\"\n ],\n \"pagination\": [\n \"./dist/pagination.d.ts\"\n ],\n \"messages\": [\n \"./dist/messages.d.ts\"\n ],\n \"schema\": [\n \"./dist/schema.d.ts\"\n ],\n \"search-async\": [\n \"./dist/search-async.d.ts\"\n ],\n \"server\": [\n \"./dist/server.d.ts\"\n ]\n }\n },\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./column-filters\": {\n \"import\": {\n \"types\": \"./dist/column-filters.d.ts\",\n \"default\": \"./dist/features/column-filters/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/column-filters.d.cts\",\n \"default\": \"./dist/features/column-filters/index.cjs\"\n }\n },\n \"./validity\": {\n \"import\": {\n \"types\": \"./dist/validity.d.ts\",\n \"default\": \"./dist/features/validity/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/validity.d.cts\",\n \"default\": \"./dist/features/validity/index.cjs\"\n }\n },\n \"./search\": {\n \"import\": {\n \"types\": \"./dist/search.d.ts\",\n \"default\": \"./dist/features/search/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/search.d.cts\",\n \"default\": \"./dist/features/search/index.cjs\"\n }\n },\n \"./sorting\": {\n \"import\": {\n \"types\": \"./dist/sorting.d.ts\",\n \"default\": \"./dist/features/sorting/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/sorting.d.cts\",\n \"default\": \"./dist/features/sorting/index.cjs\"\n }\n },\n \"./pagination\": {\n \"import\": {\n \"types\": \"./dist/pagination.d.ts\",\n \"default\": \"./dist/features/pagination/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/pagination.d.cts\",\n \"default\": \"./dist/features/pagination/index.cjs\"\n }\n },\n \"./messages\": {\n \"import\": {\n \"types\": \"./dist/messages.d.ts\",\n \"default\": \"./dist/messages/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/messages.d.cts\",\n \"default\": \"./dist/messages/index.cjs\"\n }\n },\n \"./schema\": {\n \"import\": {\n \"types\": \"./dist/schema.d.ts\",\n \"default\": \"./dist/ingest/schema-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/schema.d.cts\",\n \"default\": \"./dist/ingest/schema-entry.cjs\"\n }\n },\n \"./search-async\": {\n \"import\": {\n \"types\": \"./dist/search-async.d.ts\",\n \"default\": \"./dist/features/search/search-async-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/search-async.d.cts\",\n \"default\": \"./dist/features/search/search-async-entry.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/source/server-entry.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/source/server-entry.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/postbuild.mjs\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc -p tsconfig.json\",\n \"check:pkg\": \"publint --strict && attw --pack . && node scripts/check-catalog.mjs\",\n \"check:catalog\": \"node scripts/check-catalog.mjs\"\n },\n \"dependencies\": {\n \"ex-search\": \"1.1.1\",\n \"exsorted\": \"1.2.3\",\n \"kk-debounce\": \"1.1.0\"\n },\n \"devDependencies\": {\n \"esbuild\": \"0.27.7\"\n }\n}\n","// Read from package.json at build time (the bundler inlines the one field), so a release never\n// ships a stale hard-coded version.\nimport { version } from '../package.json'\n\n/** The `@datakable/core` version (`context.package`, docs URLs, the global version registry). */\nexport const VERSION: string = version\n"],"mappings":";AAEE,cAAW;;;ACGN,IAAM,UAAkB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datakable/core",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Headless, framework-agnostic core of DataKable — table state, sorting, filtering and types.",
5
5
  "license": "MIT",
6
6
  "author": "Supakit Kitjanabumrungsak",