@datakable/core 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/catalog/codes.cjs +12 -8
- package/dist/catalog/codes.cjs.map +1 -1
- package/dist/catalog/codes.js +12 -8
- package/dist/catalog/codes.js.map +1 -1
- package/dist/catalog/slots.cjs +10 -6
- package/dist/catalog/slots.cjs.map +1 -1
- package/dist/catalog/slots.js +10 -6
- package/dist/catalog/slots.js.map +1 -1
- package/dist/{codes-OsjAR3Wz.d.cts → codes-B9bCK73-.d.cts} +30 -4
- package/dist/{codes-OsjAR3Wz.d.ts → codes-B9bCK73-.d.ts} +30 -4
- package/dist/column/column-def.cjs.map +1 -1
- package/dist/column/get-value.cjs +6 -1
- package/dist/column/get-value.cjs.map +1 -1
- package/dist/column/get-value.js +6 -1
- package/dist/column/get-value.js.map +1 -1
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +4 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +4 -1
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/catalog.cjs +11 -5
- package/dist/diagnostics/catalog.cjs.map +1 -1
- package/dist/diagnostics/catalog.js +10 -5
- package/dist/diagnostics/catalog.js.map +1 -1
- package/dist/diagnostics/codes.cjs +12 -8
- package/dist/diagnostics/codes.cjs.map +1 -1
- package/dist/diagnostics/codes.js +12 -8
- package/dist/diagnostics/codes.js.map +1 -1
- package/dist/diagnostics/console.cjs +13 -6
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +13 -6
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/env.cjs +1 -0
- package/dist/diagnostics/env.cjs.map +1 -1
- package/dist/diagnostics/env.js +1 -0
- package/dist/diagnostics/env.js.map +1 -1
- package/dist/diagnostics/explain-text.cjs +2 -1
- package/dist/diagnostics/explain-text.cjs.map +1 -1
- package/dist/diagnostics/explain-text.js +2 -1
- package/dist/diagnostics/explain-text.js.map +1 -1
- package/dist/diagnostics/explain.cjs +3 -2
- package/dist/diagnostics/explain.cjs.map +1 -1
- package/dist/diagnostics/explain.js +3 -2
- package/dist/diagnostics/explain.js.map +1 -1
- package/dist/diagnostics/hub.cjs +5 -4
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +5 -4
- package/dist/diagnostics/hub.js.map +1 -1
- package/dist/diagnostics/process-guard.cjs +26 -0
- package/dist/diagnostics/process-guard.cjs.map +1 -0
- package/dist/diagnostics/process-guard.js +9 -0
- package/dist/diagnostics/process-guard.js.map +1 -0
- package/dist/diagnostics/prose.cjs +120 -0
- package/dist/diagnostics/prose.cjs.map +1 -0
- package/dist/diagnostics/prose.js +93 -0
- package/dist/diagnostics/prose.js.map +1 -0
- package/dist/diagnostics/redact.cjs +7 -4
- package/dist/diagnostics/redact.cjs.map +1 -1
- package/dist/diagnostics/redact.js +6 -4
- package/dist/diagnostics/redact.js.map +1 -1
- package/dist/diagnostics/trace-store.cjs +6 -0
- package/dist/diagnostics/trace-store.cjs.map +1 -1
- package/dist/diagnostics/trace-store.js +4 -0
- package/dist/diagnostics/trace-store.js.map +1 -1
- package/dist/diagnostics/tracer.cjs +74 -13
- package/dist/diagnostics/tracer.cjs.map +1 -1
- package/dist/diagnostics/tracer.js +75 -14
- package/dist/diagnostics/tracer.js.map +1 -1
- package/dist/diagnostics/types.cjs.map +1 -1
- package/dist/{explain-vkD2cewE.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
- package/dist/{explain-Dn99IQt0.d.ts → explain-i0OATi_W.d.ts} +13 -4
- package/dist/feature/codes.cjs +1 -0
- package/dist/feature/codes.cjs.map +1 -1
- package/dist/feature/codes.js +1 -0
- package/dist/feature/codes.js.map +1 -1
- package/dist/feature/install.cjs +4 -3
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +4 -3
- package/dist/feature/install.js.map +1 -1
- package/dist/features/column-filters/codes.cjs +1 -0
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +1 -0
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +4 -2
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +4 -2
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +5 -5
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +5 -5
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/codes.cjs +1 -0
- package/dist/features/pagination/codes.cjs.map +1 -1
- package/dist/features/pagination/codes.js +1 -0
- package/dist/features/pagination/codes.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +16 -13
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +16 -13
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +1 -0
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +1 -0
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-index.cjs +68 -15
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +68 -15
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/search/search.cjs +41 -9
- package/dist/features/search/search.cjs.map +1 -1
- package/dist/features/search/search.js +41 -9
- package/dist/features/search/search.js.map +1 -1
- package/dist/features/search/skipped-codes.cjs +45 -0
- package/dist/features/search/skipped-codes.cjs.map +1 -0
- package/dist/features/search/skipped-codes.js +20 -0
- package/dist/features/search/skipped-codes.js.map +1 -0
- package/dist/features/sorting/codes.cjs +1 -0
- package/dist/features/sorting/codes.cjs.map +1 -1
- package/dist/features/sorting/codes.js +1 -0
- package/dist/features/sorting/codes.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +16 -20
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +16 -20
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/features/sorting/strategy.cjs +4 -4
- package/dist/features/sorting/strategy.cjs.map +1 -1
- package/dist/features/sorting/strategy.js +4 -4
- package/dist/features/sorting/strategy.js.map +1 -1
- package/dist/features/validity/codes.cjs +1 -0
- package/dist/features/validity/codes.cjs.map +1 -1
- package/dist/features/validity/codes.js +1 -0
- package/dist/features/validity/codes.js.map +1 -1
- package/dist/features/validity/validity.cjs +3 -3
- package/dist/features/validity/validity.cjs.map +1 -1
- package/dist/features/validity/validity.js +3 -3
- package/dist/features/validity/validity.js.map +1 -1
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/ingest/data-check-codes.cjs +4 -2
- package/dist/ingest/data-check-codes.cjs.map +1 -1
- package/dist/ingest/data-check-codes.js +4 -2
- package/dist/ingest/data-check-codes.js.map +1 -1
- package/dist/ingest/data-checks.cjs +1 -1
- package/dist/ingest/data-checks.cjs.map +1 -1
- package/dist/ingest/data-checks.js +1 -1
- package/dist/ingest/data-checks.js.map +1 -1
- package/dist/ingest/row-key-codes.cjs +1 -0
- package/dist/ingest/row-key-codes.cjs.map +1 -1
- package/dist/ingest/row-key-codes.js +1 -0
- package/dist/ingest/row-key-codes.js.map +1 -1
- package/dist/ingest/row-key.cjs +23 -20
- package/dist/ingest/row-key.cjs.map +1 -1
- package/dist/ingest/row-key.js +23 -20
- package/dist/ingest/row-key.js.map +1 -1
- package/dist/ingest/schema-codes.cjs +1 -0
- package/dist/ingest/schema-codes.cjs.map +1 -1
- package/dist/ingest/schema-codes.js +1 -0
- package/dist/ingest/schema-codes.js.map +1 -1
- package/dist/ingest/unique-fields.cjs +2 -2
- package/dist/ingest/unique-fields.cjs.map +1 -1
- package/dist/ingest/unique-fields.js +2 -2
- package/dist/ingest/unique-fields.js.map +1 -1
- package/dist/intercept/boundary.cjs +4 -2
- package/dist/intercept/boundary.cjs.map +1 -1
- package/dist/intercept/boundary.js +4 -2
- package/dist/intercept/boundary.js.map +1 -1
- package/dist/intercept/cell-batch.cjs +4 -4
- package/dist/intercept/cell-batch.cjs.map +1 -1
- package/dist/intercept/cell-batch.js +4 -4
- package/dist/intercept/cell-batch.js.map +1 -1
- package/dist/intercept/codes.cjs +1 -0
- package/dist/intercept/codes.cjs.map +1 -1
- package/dist/intercept/codes.js +1 -0
- package/dist/intercept/codes.js.map +1 -1
- package/dist/intercept/fallbacks.cjs +12 -20
- package/dist/intercept/fallbacks.cjs.map +1 -1
- package/dist/intercept/fallbacks.js +12 -20
- package/dist/intercept/fallbacks.js.map +1 -1
- package/dist/intercept/runtime.cjs +7 -9
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +7 -9
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +4 -1
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +4 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/codes.cjs +1 -0
- package/dist/pipeline/codes.cjs.map +1 -1
- package/dist/pipeline/codes.js +1 -0
- package/dist/pipeline/codes.js.map +1 -1
- package/dist/pipeline/column-cache.cjs +94 -76
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +92 -75
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/dev-checks.cjs +1 -0
- package/dist/pipeline/dev-checks.cjs.map +1 -1
- package/dist/pipeline/dev-checks.js +1 -0
- package/dist/pipeline/dev-checks.js.map +1 -1
- package/dist/pipeline/env-codes.cjs +3 -2
- package/dist/pipeline/env-codes.cjs.map +1 -1
- package/dist/pipeline/env-codes.js +3 -2
- package/dist/pipeline/env-codes.js.map +1 -1
- package/dist/pipeline/perf-codes.cjs +1 -0
- package/dist/pipeline/perf-codes.cjs.map +1 -1
- package/dist/pipeline/perf-codes.js +1 -0
- package/dist/pipeline/perf-codes.js.map +1 -1
- package/dist/pipeline/runner.cjs +17 -9
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +17 -9
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/slice.cjs +3 -1
- package/dist/pipeline/slice.cjs.map +1 -1
- package/dist/pipeline/slice.js +3 -1
- package/dist/pipeline/slice.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-v5JdEk5Y.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
- package/dist/{search-async-entry-DGSw2pDL.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +8 -6
- package/dist/search.d.ts +8 -6
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/codes.cjs +1 -0
- package/dist/source/codes.cjs.map +1 -1
- package/dist/source/codes.js +1 -0
- package/dist/source/codes.js.map +1 -1
- package/dist/source/fetch-codes.cjs +1 -0
- package/dist/source/fetch-codes.cjs.map +1 -1
- package/dist/source/fetch-codes.js +1 -0
- package/dist/source/fetch-codes.js.map +1 -1
- package/dist/table/codes.cjs +1 -0
- package/dist/table/codes.cjs.map +1 -1
- package/dist/table/codes.js +1 -0
- package/dist/table/codes.js.map +1 -1
- package/dist/table/create-table.cjs +39 -9
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +39 -9
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/data-mutation.cjs +1 -0
- package/dist/table/data-mutation.cjs.map +1 -1
- package/dist/table/data-mutation.js +1 -0
- package/dist/table/data-mutation.js.map +1 -1
- package/dist/table/internals.cjs.map +1 -1
- package/dist/table/internals.js.map +1 -1
- package/dist/table/ssr-codes.cjs +1 -0
- package/dist/table/ssr-codes.cjs.map +1 -1
- package/dist/table/ssr-codes.js +1 -0
- package/dist/table/ssr-codes.js.map +1 -1
- package/dist/table/ssr.cjs +4 -1
- package/dist/table/ssr.cjs.map +1 -1
- package/dist/table/ssr.js +4 -1
- package/dist/table/ssr.js.map +1 -1
- package/dist/table/state-feedback.cjs +19 -0
- package/dist/table/state-feedback.cjs.map +1 -1
- package/dist/table/state-feedback.js +19 -0
- package/dist/table/state-feedback.js.map +1 -1
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/versioning/codes.cjs +18 -0
- package/dist/versioning/codes.cjs.map +1 -1
- package/dist/versioning/codes.js +18 -0
- package/dist/versioning/codes.js.map +1 -1
- package/dist/versioning/registry.cjs +4 -2
- package/dist/versioning/registry.cjs.map +1 -1
- package/dist/versioning/registry.js +4 -2
- package/dist/versioning/registry.js.map +1 -1
- package/package.json +39 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/console.ts"],"sourcesContent":["import { formatWhere } from './build'\nimport type { Diagnostic } from './types'\n\nconst ICON = { error: '✖', warn: '⚠', info: 'ℹ' } as const\n\n/** The §8.1 console text: header + aligned body. Also usable by explain() and tests. */\nexport function formatDiagnostic(d: Diagnostic): { header: string; body: string } {\n const header = `${ICON[d.level]} [DataKable ${d.context.table}] ${d.code}: ${d.problem}`\n const lines = [` where ${formatWhere(d.where)}`]\n if (d.impact) lines.push(` impact ${d.impact}`)\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n const c = d.context\n lines.push(` context trigger=${c.trigger} run=${c.run} phase=${c.phase}`)\n return { header, body: lines.join('\\n') }\n}\n\nexport interface ConsoleSinkOptions {\n table: string\n dev: boolean\n rateLimit: { max: number; windowMs: number }\n now: () => number\n}\n\n/**\n * Default console sink (§8.2): dev prints one collapsed group per diagnostic; prod prints\n * error level only, on one line. A fixed-window rate limit protects the console; what it holds back\n * is still in getDiagnostics() and onDiagnostic.\n */\nexport function createConsoleSink(o: ConsoleSinkOptions): (d: Diagnostic) => void {\n let windowStart = Number.NEGATIVE_INFINITY\n let printed = 0\n let suppressed = 0\n // The grouped format is development-only: a production build prints one line (§8.2). `(process.env.NODE_ENV !== \"production\")` is\n // read inline so the bundler drops the grouped branch.\n return (d) => {\n if ((!(process.env.NODE_ENV !== \"production\") || !o.dev) && d.level !== 'error') return\n const t = o.now()\n if (t - windowStart >= o.rateLimit.windowMs) {\n if (suppressed > 0) {\n console.warn(\n `[DataKable ${o.table}] ${suppressed} diagnostics were not printed (rate limit); ` +\n 'read them with getDiagnostics() or onDiagnostic',\n )\n }\n windowStart = t\n printed = 0\n suppressed = 0\n }\n if (printed >= o.rateLimit.max) {\n suppressed++\n return\n }\n printed++\n
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/console.ts"],"sourcesContent":["import { formatWhere } from './build'\nimport './process-guard'\nimport type { Diagnostic } from './types'\n\nconst ICON = { error: '✖', warn: '⚠', info: 'ℹ' } as const\n\n/** The §8.1 console text: header + aligned body. Also usable by explain() and tests. */\nexport function formatDiagnostic(d: Diagnostic): { header: string; body: string } {\n const header = `${ICON[d.level]} [DataKable ${d.context.table}] ${d.code}: ${d.problem}`\n const lines = [` where ${formatWhere(d.where)}`]\n if (d.impact) lines.push(` impact ${d.impact}`)\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n const c = d.context\n lines.push(` context trigger=${c.trigger} run=${c.run} phase=${c.phase}`)\n return { header, body: lines.join('\\n') }\n}\n\nexport interface ConsoleSinkOptions {\n table: string\n dev: boolean\n rateLimit: { max: number; windowMs: number }\n now: () => number\n}\n\n/**\n * Default console sink (§8.2): dev prints one collapsed group per diagnostic; prod prints\n * error level only, on one line. A fixed-window rate limit protects the console; what it holds back\n * is still in getDiagnostics() and onDiagnostic.\n */\nexport function createConsoleSink(o: ConsoleSinkOptions): (d: Diagnostic) => void {\n let windowStart = Number.NEGATIVE_INFINITY\n let printed = 0\n let suppressed = 0\n // The grouped format is development-only: a production build prints one line (§8.2). `(process.env.NODE_ENV !== \"production\")` is\n // read inline so the bundler drops the grouped branch.\n return (d) => {\n if ((!(process.env.NODE_ENV !== \"production\") || !o.dev) && d.level !== 'error') return\n const t = o.now()\n if (t - windowStart >= o.rateLimit.windowMs) {\n if (suppressed > 0) {\n console.warn(\n `[DataKable ${o.table}] ${suppressed} diagnostics were not printed (rate limit); ` +\n 'read them with getDiagnostics() or onDiagnostic',\n )\n }\n windowStart = t\n printed = 0\n suppressed = 0\n }\n if (printed >= o.rateLimit.max) {\n suppressed++\n return\n }\n printed++\n // `(process.env.NODE_ENV !== \"production\") &&` first, not an early `if (!(process.env.NODE_ENV !== \"production\")) return`: a branch that folds to `false` is removed\n // while parsing, so a production bundle keeps no `formatDiagnostic` (code after a folded early\n // return is dropped only when printing, after tree shaking kept what it calls).\n if ((process.env.NODE_ENV !== \"production\") && o.dev) {\n const { header, body } = formatDiagnostic(d)\n const log = d.level === 'error' ? 'error' : d.level === 'warn' ? 'warn' : 'info'\n // Runtimes without console groups (the Edge Runtime: log/info/warn/error/debug only) get the\n // same text as one call.\n if (typeof console.groupCollapsed !== 'function' || typeof console.groupEnd !== 'function') {\n console[log](`${header}\\n${body}`)\n return\n }\n console.groupCollapsed(header)\n console[log](body)\n console.groupEnd()\n return\n }\n console.error(`[DataKable ${o.table}] ${d.code} at ${formatWhere(d.where)} — ${d.docs}`)\n }\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,OAAO;AAGP,IAAM,OAAO,EAAE,OAAO,UAAK,MAAM,UAAK,MAAM,SAAI;AAGzC,SAAS,iBAAiB,GAAiD;AAChF,QAAM,SAAS,GAAG,KAAK,EAAE,KAAK,CAAC,eAAe,EAAE,QAAQ,KAAK,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO;AACtF,QAAM,QAAQ,CAAC,aAAa,YAAY,EAAE,KAAK,CAAC,EAAE;AAClD,MAAI,EAAE,OAAQ,OAAM,KAAK,aAAa,EAAE,MAAM,EAAE;AAChD,IAAE,IAAI,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAM,KAAK,GAAG,MAAM,IAAI,eAAe,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE;AAAA,EACrE,CAAC;AACD,QAAM,KAAK,aAAa,EAAE,IAAI,EAAE;AAChC,QAAM,IAAI,EAAE;AACZ,QAAM,KAAK,qBAAqB,EAAE,OAAO,QAAQ,EAAE,GAAG,UAAU,EAAE,KAAK,EAAE;AACzE,SAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,IAAI,EAAE;AAC1C;AAcO,SAAS,kBAAkB,GAAgD;AAChF,MAAI,cAAc,OAAO;AACzB,MAAI,UAAU;AACd,MAAI,aAAa;AAGjB,SAAO,CAAC,MAAM;AACZ,SAAK,EAAE,QAAQ,IAAI,aAAa,iBAAiB,CAAC,EAAE,QAAQ,EAAE,UAAU,QAAS;AACjF,UAAM,IAAI,EAAE,IAAI;AAChB,QAAI,IAAI,eAAe,EAAE,UAAU,UAAU;AAC3C,UAAI,aAAa,GAAG;AAClB,gBAAQ;AAAA,UACN,cAAc,EAAE,KAAK,KAAK,UAAU;AAAA,QAEtC;AAAA,MACF;AACA,oBAAc;AACd,gBAAU;AACV,mBAAa;AAAA,IACf;AACA,QAAI,WAAW,EAAE,UAAU,KAAK;AAC9B;AACA;AAAA,IACF;AACA;AAIA,QAAK,QAAQ,IAAI,aAAa,gBAAiB,EAAE,KAAK;AACpD,YAAM,EAAE,QAAQ,KAAK,IAAI,iBAAiB,CAAC;AAC3C,YAAM,MAAM,EAAE,UAAU,UAAU,UAAU,EAAE,UAAU,SAAS,SAAS;AAG1E,UAAI,OAAO,QAAQ,mBAAmB,cAAc,OAAO,QAAQ,aAAa,YAAY;AAC1F,gBAAQ,GAAG,EAAE,GAAG,MAAM;AAAA,EAAK,IAAI,EAAE;AACjC;AAAA,MACF;AACA,cAAQ,eAAe,MAAM;AAC7B,cAAQ,GAAG,EAAE,IAAI;AACjB,cAAQ,SAAS;AACjB;AAAA,IACF;AACA,YAAQ,MAAM,cAAc,EAAE,KAAK,KAAK,EAAE,IAAI,OAAO,YAAY,EAAE,KAAK,CAAC,WAAM,EAAE,IAAI,EAAE;AAAA,EACzF;AACF;","names":[]}
|
package/dist/diagnostics/env.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(env_exports, {
|
|
|
23
23
|
DEV: () => DEV
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(env_exports);
|
|
26
|
+
var import_process_guard = require("./process-guard.cjs");
|
|
26
27
|
var DEV = process.env.NODE_ENV !== "production";
|
|
27
28
|
// Annotate the CommonJS export names for ESM import in node:
|
|
28
29
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/env.ts"],"sourcesContent":["// No @types/node in core: declare just what is read.\ndeclare const process: { env: { NODE_ENV?: string } }\n\n/**\n * Development build: text builders, dev checks and dev-only warnings are on (§8.2).\n *\n * Written as the bare `process.env.NODE_ENV` read on purpose: bundlers replace it with a string\n * literal, so `DEV` folds to a constant and every `if (DEV)` branch is removed from a production\n * bundle. A `typeof process` guard would keep it a runtime value (and `true` in browsers).\n */\nexport const DEV: boolean = process.env.NODE_ENV !== 'production'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/env.ts"],"sourcesContent":["// The helpful error when `process` is missing (#36); evaluated before the read below.\nimport './process-guard'\n\n// No @types/node in core: declare just what is read.\ndeclare const process: { env: { NODE_ENV?: string } }\n\n/**\n * Development build: text builders, dev checks and dev-only warnings are on (§8.2).\n *\n * Written as the bare `process.env.NODE_ENV` read on purpose: bundlers replace it with a string\n * literal, so `DEV` folds to a constant and every `if (DEV)` branch is removed from a production\n * bundle. A `typeof process` guard would keep it a runtime value (and `true` in browsers).\n */\nexport const DEV: boolean = process.env.NODE_ENV !== 'production'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAAO;AAYA,IAAM,MAAe,QAAQ,IAAI,aAAa;","names":[]}
|
package/dist/diagnostics/env.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/env.ts"],"sourcesContent":["// No @types/node in core: declare just what is read.\ndeclare const process: { env: { NODE_ENV?: string } }\n\n/**\n * Development build: text builders, dev checks and dev-only warnings are on (§8.2).\n *\n * Written as the bare `process.env.NODE_ENV` read on purpose: bundlers replace it with a string\n * literal, so `DEV` folds to a constant and every `if (DEV)` branch is removed from a production\n * bundle. A `typeof process` guard would keep it a runtime value (and `true` in browsers).\n */\nexport const DEV: boolean = process.env.NODE_ENV !== 'production'\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/env.ts"],"sourcesContent":["// The helpful error when `process` is missing (#36); evaluated before the read below.\nimport './process-guard'\n\n// No @types/node in core: declare just what is read.\ndeclare const process: { env: { NODE_ENV?: string } }\n\n/**\n * Development build: text builders, dev checks and dev-only warnings are on (§8.2).\n *\n * Written as the bare `process.env.NODE_ENV` read on purpose: bundlers replace it with a string\n * literal, so `DEV` folds to a constant and every `if (DEV)` branch is removed from a production\n * bundle. A `typeof process` guard would keep it a runtime value (and `true` in browsers).\n */\nexport const DEV: boolean = process.env.NODE_ENV !== 'production'\n"],"mappings":";AACA,OAAO;AAYA,IAAM,MAAe,QAAQ,IAAI,aAAa;","names":[]}
|
|
@@ -40,7 +40,8 @@ function formatTableExplanation(e) {
|
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
const flag = s.failed ? " failed" : s.cached ? " cached" : "";
|
|
43
|
-
|
|
43
|
+
const sliced = s.runs ? ` (sliced, ${s.runs} runs)` : "";
|
|
44
|
+
parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}${sliced}`);
|
|
44
45
|
}
|
|
45
46
|
if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`);
|
|
46
47
|
const open = e.diagnostics === 1 ? "1 open diagnostic" : `${e.diagnostics} open diagnostics`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/explain-text.ts"],"sourcesContent":["import type { RowExplanation, RowStep, TableExplanation } from './explain'\n\n// Console text of explain() / explainRow() (§8.4). Development only: production `toString()` is the\n// JSON of the structured result, so bundlers drop this module (§8.2 prod stripping).\n\nconst ms = (v: number | undefined) => (v === undefined ? '' : ` ${v.toFixed(1)} ms`)\n\n/** `run 57 · trigger api:setSearch · 1203 rows → schema 1198 (5 dropped) → … → page 1/2 (10)` */\nexport function formatTableExplanation(e: TableExplanation): string {\n const parts: string[] = []\n if (e.schema) {\n const { mode, invalid } = e.schema\n const before = mode === 'flag' ? e.rows : e.rows + invalid\n parts.push(`${before} rows`)\n parts.push(`schema ${e.rows} (${invalid} ${mode === 'flag' ? 'flagged' : 'dropped'})`)\n } else parts.push(`${e.rows} rows`)\n for (const s of e.steps) {\n if (s.stage === 'paginate' && e.page) continue\n if (s.mode === 'server') {\n parts.push(`${s.feature} (server)`)\n continue\n }\n const flag = s.failed ? ' failed' : s.cached ? ' cached' : ''\n parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}`)\n }\n if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`)\n const open = e.diagnostics === 1 ? '1 open diagnostic' : `${e.diagnostics} open diagnostics`\n return (\n `run ${e.run}${ms(e.ms)} · trigger ${e.trigger} · ${parts.join(' → ')} · status ${e.status}` +\n ` · ${open}`\n )\n}\n\nconst fmt = (v: unknown) => (typeof v === 'string' ? v : JSON.stringify(v))\n\n/** What a removing step did, in words. */\nfunction reason(s: RowStep): string {\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') {\n return `on page ${d.page} of ${d.pageCount}, page ${d.currentPage} is shown`\n }\n if (s.stage === 'search') {\n const q = `query (${d.queryLength} chars)`\n return d.searchFn\n ? `${q}: not in the searchFn result`\n : `${q} did not match (threshold ${d.threshold})`\n }\n if (Array.isArray(d.filters)) {\n return (d.filters as Record<string, unknown>[])\n .map((f) => `${f.column} ${f.type} ${fmt(f.value)} → ${fmt(f.cell)} fails`)\n .join('; ')\n }\n if (d.validity !== undefined) {\n return `validity '${String(d.validity)}' hides ${d.flagged ? 'flagged' : 'valid'} rows`\n }\n return 'its rowModel did not return the row'\n}\n\n/** The fix for the removing step, best first. */\nfunction fix(e: RowExplanation, s: RowStep | undefined): string {\n if (e.removedBy === 'schema') {\n return \"correct the data at the issue paths, or invalidRows: 'flag' to keep invalid rows\"\n }\n if (!s) return ''\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') return `setPageIndex(${d.page - 1})`\n if (s.stage === 'search') {\n if (d.searchFn) return 'check what searchFn returns for this query'\n const lower = Math.max(0, Math.round(((d.threshold as number) - 0.1) * 10) / 10)\n return `search({ threshold: ${lower} }) or check the query spelling`\n }\n if (Array.isArray(d.filters)) {\n const cols = (d.filters as { column: string }[]).map((f) => `'${f.column}'`)\n return `setColumnFilter(${cols[0]}, undefined) or resetColumnFilters()`\n }\n if (d.validity !== undefined) return \"setValidity('all')\"\n return `check table.getState().${s.feature}`\n}\n\nconst pad = (name: string, width: number) => name.padEnd(width)\n\n/** `✓ schema valid` / `✗ search query (8 chars) … ← removed here` / `fix …` */\nexport function formatRowExplanation(e: RowExplanation): string {\n const key = fmt(e.key)\n if (!e.found) {\n return (\n `row ${key}: not found in the data (${e.rows} rows, run ${e.run})\\n` +\n ' fix check the key against rowKey (without rowKey the key is the row index)'\n )\n }\n const width = Math.max(8, ...e.steps.map((s) => s.feature.length)) + 1\n const where = e.index === undefined ? `options.data[${e.rawIndex}]` : `data index ${e.index}`\n const lines = [`row ${key} · ${where} · run ${e.run}${e.visible ? ' · visible' : ''}`]\n const issues = e.issues?.map((i) => `${i.path || '(root)'}: ${i.message}`).join('; ')\n if (e.schema === 'none') lines.push(` – ${pad('schema', width)}no schema`)\n else if (e.schema === 'valid') lines.push(` ✓ ${pad('schema', width)}valid`)\n else if (e.schema === 'flagged')\n lines.push(` ✓ ${pad('schema', width)}flagged (kept): ${issues}`)\n else lines.push(` ✗ ${pad('schema', width)}invalid: ${issues} ← removed here`)\n let removing: RowStep | undefined\n for (const s of e.steps) {\n const name = pad(s.feature, width)\n if (s.outcome === 'removed') {\n removing = s\n lines.push(` ✗ ${name}${reason(s)} ← removed here`)\n } else if (s.outcome === 'kept' || s.outcome === 'failed') {\n const at = `position ${(s.position ?? 0) + 1} of ${s.of}`\n lines.push(\n s.outcome === 'kept'\n ? ` ✓ ${name}${at}`\n : ` ! ${name}failed in this run (input passed through); ${at}`,\n )\n } else if (s.outcome === 'skipped') lines.push(` – ${name}skipped (server mode)`)\n else lines.push(` · ${name}not reached`)\n }\n const f = fix(e, removing)\n if (f) lines.push(` ${pad('fix', width + 2)}${f}`)\n return lines.join('\\n')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,IAAM,KAAK,CAAC,MAA2B,MAAM,SAAY,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AAGvE,SAAS,uBAAuB,GAA6B;AAClE,QAAM,QAAkB,CAAC;AACzB,MAAI,EAAE,QAAQ;AACZ,UAAM,EAAE,MAAM,QAAQ,IAAI,EAAE;AAC5B,UAAM,SAAS,SAAS,SAAS,EAAE,OAAO,EAAE,OAAO;AACnD,UAAM,KAAK,GAAG,MAAM,OAAO;AAC3B,UAAM,KAAK,UAAU,EAAE,IAAI,KAAK,OAAO,IAAI,SAAS,SAAS,YAAY,SAAS,GAAG;AAAA,EACvF,MAAO,OAAM,KAAK,GAAG,EAAE,IAAI,OAAO;AAClC,aAAW,KAAK,EAAE,OAAO;AACvB,QAAI,EAAE,UAAU,cAAc,EAAE,KAAM;AACtC,QAAI,EAAE,SAAS,UAAU;AACvB,YAAM,KAAK,GAAG,EAAE,OAAO,WAAW;AAClC;AAAA,IACF;AACA,UAAM,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,YAAY;AAC3D,UAAM,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/explain-text.ts"],"sourcesContent":["import type { RowExplanation, RowStep, TableExplanation } from './explain'\n\n// Console text of explain() / explainRow() (§8.4). Development only: production `toString()` is the\n// JSON of the structured result, so bundlers drop this module (§8.2 prod stripping).\n\nconst ms = (v: number | undefined) => (v === undefined ? '' : ` ${v.toFixed(1)} ms`)\n\n/** `run 57 · trigger api:setSearch · 1203 rows → schema 1198 (5 dropped) → … → page 1/2 (10)` */\nexport function formatTableExplanation(e: TableExplanation): string {\n const parts: string[] = []\n if (e.schema) {\n const { mode, invalid } = e.schema\n const before = mode === 'flag' ? e.rows : e.rows + invalid\n parts.push(`${before} rows`)\n parts.push(`schema ${e.rows} (${invalid} ${mode === 'flag' ? 'flagged' : 'dropped'})`)\n } else parts.push(`${e.rows} rows`)\n for (const s of e.steps) {\n if (s.stage === 'paginate' && e.page) continue\n if (s.mode === 'server') {\n parts.push(`${s.feature} (server)`)\n continue\n }\n const flag = s.failed ? ' failed' : s.cached ? ' cached' : ''\n const sliced = s.runs ? ` (sliced, ${s.runs} runs)` : ''\n parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}${sliced}`)\n }\n if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`)\n const open = e.diagnostics === 1 ? '1 open diagnostic' : `${e.diagnostics} open diagnostics`\n return (\n `run ${e.run}${ms(e.ms)} · trigger ${e.trigger} · ${parts.join(' → ')} · status ${e.status}` +\n ` · ${open}`\n )\n}\n\nconst fmt = (v: unknown) => (typeof v === 'string' ? v : JSON.stringify(v))\n\n/** What a removing step did, in words. */\nfunction reason(s: RowStep): string {\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') {\n return `on page ${d.page} of ${d.pageCount}, page ${d.currentPage} is shown`\n }\n if (s.stage === 'search') {\n const q = `query (${d.queryLength} chars)`\n return d.searchFn\n ? `${q}: not in the searchFn result`\n : `${q} did not match (threshold ${d.threshold})`\n }\n if (Array.isArray(d.filters)) {\n return (d.filters as Record<string, unknown>[])\n .map((f) => `${f.column} ${f.type} ${fmt(f.value)} → ${fmt(f.cell)} fails`)\n .join('; ')\n }\n if (d.validity !== undefined) {\n return `validity '${String(d.validity)}' hides ${d.flagged ? 'flagged' : 'valid'} rows`\n }\n return 'its rowModel did not return the row'\n}\n\n/** The fix for the removing step, best first. */\nfunction fix(e: RowExplanation, s: RowStep | undefined): string {\n if (e.removedBy === 'schema') {\n return \"correct the data at the issue paths, or invalidRows: 'flag' to keep invalid rows\"\n }\n if (!s) return ''\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') return `setPageIndex(${d.page - 1})`\n if (s.stage === 'search') {\n if (d.searchFn) return 'check what searchFn returns for this query'\n const lower = Math.max(0, Math.round(((d.threshold as number) - 0.1) * 10) / 10)\n return `search({ threshold: ${lower} }) or check the query spelling`\n }\n if (Array.isArray(d.filters)) {\n const cols = (d.filters as { column: string }[]).map((f) => `'${f.column}'`)\n return `setColumnFilter(${cols[0]}, undefined) or resetColumnFilters()`\n }\n if (d.validity !== undefined) return \"setValidity('all')\"\n return `check table.getState().${s.feature}`\n}\n\nconst pad = (name: string, width: number) => name.padEnd(width)\n\n/** `✓ schema valid` / `✗ search query (8 chars) … ← removed here` / `fix …` */\nexport function formatRowExplanation(e: RowExplanation): string {\n const key = fmt(e.key)\n if (!e.found) {\n return (\n `row ${key}: not found in the data (${e.rows} rows, run ${e.run})\\n` +\n ' fix check the key against rowKey (without rowKey the key is the row index)'\n )\n }\n const width = Math.max(8, ...e.steps.map((s) => s.feature.length)) + 1\n const where = e.index === undefined ? `options.data[${e.rawIndex}]` : `data index ${e.index}`\n const lines = [`row ${key} · ${where} · run ${e.run}${e.visible ? ' · visible' : ''}`]\n const issues = e.issues?.map((i) => `${i.path || '(root)'}: ${i.message}`).join('; ')\n if (e.schema === 'none') lines.push(` – ${pad('schema', width)}no schema`)\n else if (e.schema === 'valid') lines.push(` ✓ ${pad('schema', width)}valid`)\n else if (e.schema === 'flagged')\n lines.push(` ✓ ${pad('schema', width)}flagged (kept): ${issues}`)\n else lines.push(` ✗ ${pad('schema', width)}invalid: ${issues} ← removed here`)\n let removing: RowStep | undefined\n for (const s of e.steps) {\n const name = pad(s.feature, width)\n if (s.outcome === 'removed') {\n removing = s\n lines.push(` ✗ ${name}${reason(s)} ← removed here`)\n } else if (s.outcome === 'kept' || s.outcome === 'failed') {\n const at = `position ${(s.position ?? 0) + 1} of ${s.of}`\n lines.push(\n s.outcome === 'kept'\n ? ` ✓ ${name}${at}`\n : ` ! ${name}failed in this run (input passed through); ${at}`,\n )\n } else if (s.outcome === 'skipped') lines.push(` – ${name}skipped (server mode)`)\n else lines.push(` · ${name}not reached`)\n }\n const f = fix(e, removing)\n if (f) lines.push(` ${pad('fix', width + 2)}${f}`)\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -15,7 +15,8 @@ function formatTableExplanation(e) {
|
|
|
15
15
|
continue;
|
|
16
16
|
}
|
|
17
17
|
const flag = s.failed ? " failed" : s.cached ? " cached" : "";
|
|
18
|
-
|
|
18
|
+
const sliced = s.runs ? ` (sliced, ${s.runs} runs)` : "";
|
|
19
|
+
parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}${sliced}`);
|
|
19
20
|
}
|
|
20
21
|
if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`);
|
|
21
22
|
const open = e.diagnostics === 1 ? "1 open diagnostic" : `${e.diagnostics} open diagnostics`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/explain-text.ts"],"sourcesContent":["import type { RowExplanation, RowStep, TableExplanation } from './explain'\n\n// Console text of explain() / explainRow() (§8.4). Development only: production `toString()` is the\n// JSON of the structured result, so bundlers drop this module (§8.2 prod stripping).\n\nconst ms = (v: number | undefined) => (v === undefined ? '' : ` ${v.toFixed(1)} ms`)\n\n/** `run 57 · trigger api:setSearch · 1203 rows → schema 1198 (5 dropped) → … → page 1/2 (10)` */\nexport function formatTableExplanation(e: TableExplanation): string {\n const parts: string[] = []\n if (e.schema) {\n const { mode, invalid } = e.schema\n const before = mode === 'flag' ? e.rows : e.rows + invalid\n parts.push(`${before} rows`)\n parts.push(`schema ${e.rows} (${invalid} ${mode === 'flag' ? 'flagged' : 'dropped'})`)\n } else parts.push(`${e.rows} rows`)\n for (const s of e.steps) {\n if (s.stage === 'paginate' && e.page) continue\n if (s.mode === 'server') {\n parts.push(`${s.feature} (server)`)\n continue\n }\n const flag = s.failed ? ' failed' : s.cached ? ' cached' : ''\n parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}`)\n }\n if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`)\n const open = e.diagnostics === 1 ? '1 open diagnostic' : `${e.diagnostics} open diagnostics`\n return (\n `run ${e.run}${ms(e.ms)} · trigger ${e.trigger} · ${parts.join(' → ')} · status ${e.status}` +\n ` · ${open}`\n )\n}\n\nconst fmt = (v: unknown) => (typeof v === 'string' ? v : JSON.stringify(v))\n\n/** What a removing step did, in words. */\nfunction reason(s: RowStep): string {\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') {\n return `on page ${d.page} of ${d.pageCount}, page ${d.currentPage} is shown`\n }\n if (s.stage === 'search') {\n const q = `query (${d.queryLength} chars)`\n return d.searchFn\n ? `${q}: not in the searchFn result`\n : `${q} did not match (threshold ${d.threshold})`\n }\n if (Array.isArray(d.filters)) {\n return (d.filters as Record<string, unknown>[])\n .map((f) => `${f.column} ${f.type} ${fmt(f.value)} → ${fmt(f.cell)} fails`)\n .join('; ')\n }\n if (d.validity !== undefined) {\n return `validity '${String(d.validity)}' hides ${d.flagged ? 'flagged' : 'valid'} rows`\n }\n return 'its rowModel did not return the row'\n}\n\n/** The fix for the removing step, best first. */\nfunction fix(e: RowExplanation, s: RowStep | undefined): string {\n if (e.removedBy === 'schema') {\n return \"correct the data at the issue paths, or invalidRows: 'flag' to keep invalid rows\"\n }\n if (!s) return ''\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') return `setPageIndex(${d.page - 1})`\n if (s.stage === 'search') {\n if (d.searchFn) return 'check what searchFn returns for this query'\n const lower = Math.max(0, Math.round(((d.threshold as number) - 0.1) * 10) / 10)\n return `search({ threshold: ${lower} }) or check the query spelling`\n }\n if (Array.isArray(d.filters)) {\n const cols = (d.filters as { column: string }[]).map((f) => `'${f.column}'`)\n return `setColumnFilter(${cols[0]}, undefined) or resetColumnFilters()`\n }\n if (d.validity !== undefined) return \"setValidity('all')\"\n return `check table.getState().${s.feature}`\n}\n\nconst pad = (name: string, width: number) => name.padEnd(width)\n\n/** `✓ schema valid` / `✗ search query (8 chars) … ← removed here` / `fix …` */\nexport function formatRowExplanation(e: RowExplanation): string {\n const key = fmt(e.key)\n if (!e.found) {\n return (\n `row ${key}: not found in the data (${e.rows} rows, run ${e.run})\\n` +\n ' fix check the key against rowKey (without rowKey the key is the row index)'\n )\n }\n const width = Math.max(8, ...e.steps.map((s) => s.feature.length)) + 1\n const where = e.index === undefined ? `options.data[${e.rawIndex}]` : `data index ${e.index}`\n const lines = [`row ${key} · ${where} · run ${e.run}${e.visible ? ' · visible' : ''}`]\n const issues = e.issues?.map((i) => `${i.path || '(root)'}: ${i.message}`).join('; ')\n if (e.schema === 'none') lines.push(` – ${pad('schema', width)}no schema`)\n else if (e.schema === 'valid') lines.push(` ✓ ${pad('schema', width)}valid`)\n else if (e.schema === 'flagged')\n lines.push(` ✓ ${pad('schema', width)}flagged (kept): ${issues}`)\n else lines.push(` ✗ ${pad('schema', width)}invalid: ${issues} ← removed here`)\n let removing: RowStep | undefined\n for (const s of e.steps) {\n const name = pad(s.feature, width)\n if (s.outcome === 'removed') {\n removing = s\n lines.push(` ✗ ${name}${reason(s)} ← removed here`)\n } else if (s.outcome === 'kept' || s.outcome === 'failed') {\n const at = `position ${(s.position ?? 0) + 1} of ${s.of}`\n lines.push(\n s.outcome === 'kept'\n ? ` ✓ ${name}${at}`\n : ` ! ${name}failed in this run (input passed through); ${at}`,\n )\n } else if (s.outcome === 'skipped') lines.push(` – ${name}skipped (server mode)`)\n else lines.push(` · ${name}not reached`)\n }\n const f = fix(e, removing)\n if (f) lines.push(` ${pad('fix', width + 2)}${f}`)\n return lines.join('\\n')\n}\n"],"mappings":";AAKA,IAAM,KAAK,CAAC,MAA2B,MAAM,SAAY,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AAGvE,SAAS,uBAAuB,GAA6B;AAClE,QAAM,QAAkB,CAAC;AACzB,MAAI,EAAE,QAAQ;AACZ,UAAM,EAAE,MAAM,QAAQ,IAAI,EAAE;AAC5B,UAAM,SAAS,SAAS,SAAS,EAAE,OAAO,EAAE,OAAO;AACnD,UAAM,KAAK,GAAG,MAAM,OAAO;AAC3B,UAAM,KAAK,UAAU,EAAE,IAAI,KAAK,OAAO,IAAI,SAAS,SAAS,YAAY,SAAS,GAAG;AAAA,EACvF,MAAO,OAAM,KAAK,GAAG,EAAE,IAAI,OAAO;AAClC,aAAW,KAAK,EAAE,OAAO;AACvB,QAAI,EAAE,UAAU,cAAc,EAAE,KAAM;AACtC,QAAI,EAAE,SAAS,UAAU;AACvB,YAAM,KAAK,GAAG,EAAE,OAAO,WAAW;AAClC;AAAA,IACF;AACA,UAAM,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,YAAY;AAC3D,UAAM,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/explain-text.ts"],"sourcesContent":["import type { RowExplanation, RowStep, TableExplanation } from './explain'\n\n// Console text of explain() / explainRow() (§8.4). Development only: production `toString()` is the\n// JSON of the structured result, so bundlers drop this module (§8.2 prod stripping).\n\nconst ms = (v: number | undefined) => (v === undefined ? '' : ` ${v.toFixed(1)} ms`)\n\n/** `run 57 · trigger api:setSearch · 1203 rows → schema 1198 (5 dropped) → … → page 1/2 (10)` */\nexport function formatTableExplanation(e: TableExplanation): string {\n const parts: string[] = []\n if (e.schema) {\n const { mode, invalid } = e.schema\n const before = mode === 'flag' ? e.rows : e.rows + invalid\n parts.push(`${before} rows`)\n parts.push(`schema ${e.rows} (${invalid} ${mode === 'flag' ? 'flagged' : 'dropped'})`)\n } else parts.push(`${e.rows} rows`)\n for (const s of e.steps) {\n if (s.stage === 'paginate' && e.page) continue\n if (s.mode === 'server') {\n parts.push(`${s.feature} (server)`)\n continue\n }\n const flag = s.failed ? ' failed' : s.cached ? ' cached' : ''\n const sliced = s.runs ? ` (sliced, ${s.runs} runs)` : ''\n parts.push(`${s.feature} ${s.output}${flag}${ms(s.ms)}${sliced}`)\n }\n if (e.page) parts.push(`page ${e.page.index + 1}/${e.page.count} (${e.visible})`)\n const open = e.diagnostics === 1 ? '1 open diagnostic' : `${e.diagnostics} open diagnostics`\n return (\n `run ${e.run}${ms(e.ms)} · trigger ${e.trigger} · ${parts.join(' → ')} · status ${e.status}` +\n ` · ${open}`\n )\n}\n\nconst fmt = (v: unknown) => (typeof v === 'string' ? v : JSON.stringify(v))\n\n/** What a removing step did, in words. */\nfunction reason(s: RowStep): string {\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') {\n return `on page ${d.page} of ${d.pageCount}, page ${d.currentPage} is shown`\n }\n if (s.stage === 'search') {\n const q = `query (${d.queryLength} chars)`\n return d.searchFn\n ? `${q}: not in the searchFn result`\n : `${q} did not match (threshold ${d.threshold})`\n }\n if (Array.isArray(d.filters)) {\n return (d.filters as Record<string, unknown>[])\n .map((f) => `${f.column} ${f.type} ${fmt(f.value)} → ${fmt(f.cell)} fails`)\n .join('; ')\n }\n if (d.validity !== undefined) {\n return `validity '${String(d.validity)}' hides ${d.flagged ? 'flagged' : 'valid'} rows`\n }\n return 'its rowModel did not return the row'\n}\n\n/** The fix for the removing step, best first. */\nfunction fix(e: RowExplanation, s: RowStep | undefined): string {\n if (e.removedBy === 'schema') {\n return \"correct the data at the issue paths, or invalidRows: 'flag' to keep invalid rows\"\n }\n if (!s) return ''\n const d = s.detail ?? {}\n if (s.stage === 'paginate' && typeof d.page === 'number') return `setPageIndex(${d.page - 1})`\n if (s.stage === 'search') {\n if (d.searchFn) return 'check what searchFn returns for this query'\n const lower = Math.max(0, Math.round(((d.threshold as number) - 0.1) * 10) / 10)\n return `search({ threshold: ${lower} }) or check the query spelling`\n }\n if (Array.isArray(d.filters)) {\n const cols = (d.filters as { column: string }[]).map((f) => `'${f.column}'`)\n return `setColumnFilter(${cols[0]}, undefined) or resetColumnFilters()`\n }\n if (d.validity !== undefined) return \"setValidity('all')\"\n return `check table.getState().${s.feature}`\n}\n\nconst pad = (name: string, width: number) => name.padEnd(width)\n\n/** `✓ schema valid` / `✗ search query (8 chars) … ← removed here` / `fix …` */\nexport function formatRowExplanation(e: RowExplanation): string {\n const key = fmt(e.key)\n if (!e.found) {\n return (\n `row ${key}: not found in the data (${e.rows} rows, run ${e.run})\\n` +\n ' fix check the key against rowKey (without rowKey the key is the row index)'\n )\n }\n const width = Math.max(8, ...e.steps.map((s) => s.feature.length)) + 1\n const where = e.index === undefined ? `options.data[${e.rawIndex}]` : `data index ${e.index}`\n const lines = [`row ${key} · ${where} · run ${e.run}${e.visible ? ' · visible' : ''}`]\n const issues = e.issues?.map((i) => `${i.path || '(root)'}: ${i.message}`).join('; ')\n if (e.schema === 'none') lines.push(` – ${pad('schema', width)}no schema`)\n else if (e.schema === 'valid') lines.push(` ✓ ${pad('schema', width)}valid`)\n else if (e.schema === 'flagged')\n lines.push(` ✓ ${pad('schema', width)}flagged (kept): ${issues}`)\n else lines.push(` ✗ ${pad('schema', width)}invalid: ${issues} ← removed here`)\n let removing: RowStep | undefined\n for (const s of e.steps) {\n const name = pad(s.feature, width)\n if (s.outcome === 'removed') {\n removing = s\n lines.push(` ✗ ${name}${reason(s)} ← removed here`)\n } else if (s.outcome === 'kept' || s.outcome === 'failed') {\n const at = `position ${(s.position ?? 0) + 1} of ${s.of}`\n lines.push(\n s.outcome === 'kept'\n ? ` ✓ ${name}${at}`\n : ` ! ${name}failed in this run (input passed through); ${at}`,\n )\n } else if (s.outcome === 'skipped') lines.push(` – ${name}skipped (server mode)`)\n else lines.push(` · ${name}not reached`)\n }\n const f = fix(e, removing)\n if (f) lines.push(` ${pad('fix', width + 2)}${f}`)\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -28,6 +28,7 @@ __export(explain_exports, {
|
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(explain_exports);
|
|
30
30
|
var import_row_key = require("../ingest/row-key.cjs");
|
|
31
|
+
var import_process_guard = require("./process-guard.cjs");
|
|
31
32
|
var import_explain_text = require("./explain-text.cjs");
|
|
32
33
|
var import_redact = require("./redact.cjs");
|
|
33
34
|
var import_trace_store = require("./trace-store.cjs");
|
|
@@ -62,10 +63,10 @@ function explainTable(host) {
|
|
|
62
63
|
const rec = last?.steps.find((s) => s.feature === f);
|
|
63
64
|
if (!rec) continue;
|
|
64
65
|
flowing = rec.output.length;
|
|
65
|
-
const
|
|
66
|
+
const t = traced ? trace.steps.get(f.name) : void 0;
|
|
66
67
|
steps.push({
|
|
67
68
|
...step(f, "client", rec.input.length, rec.output.length, rec.cached, rec.failed),
|
|
68
|
-
...ms
|
|
69
|
+
...t ? { ms: t.ms, ...t.runs ? { runs: t.runs } : {} } : {}
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
const mode = host.schemaMode();
|
|
@@ -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 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 /** Duration (tracer only, development or `interceptors: [tracer()]`). */\n readonly ms?: 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 ms = traced ? trace.steps.get(f.name)?.ms : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(ms === undefined ? {} : { ms }),\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'`). */\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,5 +1,6 @@
|
|
|
1
1
|
// src/diagnostics/explain.ts
|
|
2
2
|
import { resolveRowKeys } from "../ingest/row-key.js";
|
|
3
|
+
import "./process-guard.js";
|
|
3
4
|
import { formatRowExplanation, formatTableExplanation } from "./explain-text.js";
|
|
4
5
|
import { redactDetails, resolveRedaction, scrubSchemaIssue } from "./redact.js";
|
|
5
6
|
import { readTrace } from "./trace-store.js";
|
|
@@ -34,10 +35,10 @@ function explainTable(host) {
|
|
|
34
35
|
const rec = last?.steps.find((s) => s.feature === f);
|
|
35
36
|
if (!rec) continue;
|
|
36
37
|
flowing = rec.output.length;
|
|
37
|
-
const
|
|
38
|
+
const t = traced ? trace.steps.get(f.name) : void 0;
|
|
38
39
|
steps.push({
|
|
39
40
|
...step(f, "client", rec.input.length, rec.output.length, rec.cached, rec.failed),
|
|
40
|
-
...ms
|
|
41
|
+
...t ? { ms: t.ms, ...t.runs ? { runs: t.runs } : {} } : {}
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
const mode = host.schemaMode();
|
|
@@ -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 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 /** Duration (tracer only, development or `interceptors: [tracer()]`). */\n readonly ms?: 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 ms = traced ? trace.steps.get(f.name)?.ms : undefined\n steps.push({\n ...step(f, 'client', rec.input.length, rec.output.length, rec.cached, rec.failed),\n ...(ms === undefined ? {} : { ms }),\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'`). */\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":[]}
|
package/dist/diagnostics/hub.cjs
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(hub_exports);
|
|
|
26
26
|
var import_context = require("../intercept/context.cjs");
|
|
27
27
|
var import_build = require("./build.cjs");
|
|
28
28
|
var import_console = require("./console.cjs");
|
|
29
|
+
var import_process_guard = require("./process-guard.cjs");
|
|
29
30
|
var import_redact = require("./redact.cjs");
|
|
30
31
|
var import_ring_buffer = require("./ring-buffer.cjs");
|
|
31
32
|
var tableSeq = 0;
|
|
@@ -55,7 +56,7 @@ function createDiagnostics(options = {}) {
|
|
|
55
56
|
dev ? e : (0, import_redact.scrubError)(e)
|
|
56
57
|
);
|
|
57
58
|
};
|
|
58
|
-
function deliver(input) {
|
|
59
|
+
function deliver(input, quiet) {
|
|
59
60
|
const d = redact ? redact(input) : input;
|
|
60
61
|
if (getDataRef) {
|
|
61
62
|
const ref = getDataRef();
|
|
@@ -80,7 +81,7 @@ function createDiagnostics(options = {}) {
|
|
|
80
81
|
fail(e);
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
|
-
toConsole?.(d);
|
|
84
|
+
if (!quiet) toConsole?.(d);
|
|
84
85
|
return d;
|
|
85
86
|
}
|
|
86
87
|
const redactor = () => {
|
|
@@ -119,13 +120,13 @@ function createDiagnostics(options = {}) {
|
|
|
119
120
|
depth++;
|
|
120
121
|
try {
|
|
121
122
|
runtime.run("diagnostic:emit", ctx, (x) => {
|
|
122
|
-
out = deliver(x.input.diagnostic);
|
|
123
|
+
out = deliver(x.input.diagnostic, opts.quiet);
|
|
123
124
|
});
|
|
124
125
|
} finally {
|
|
125
126
|
depth--;
|
|
126
127
|
}
|
|
127
128
|
} else {
|
|
128
|
-
out = deliver(d);
|
|
129
|
+
out = deliver(d, opts.quiet);
|
|
129
130
|
}
|
|
130
131
|
} catch (e) {
|
|
131
132
|
if (strict && e instanceof import_build.DatakableError) throw e;
|
|
@@ -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 {\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): 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 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)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\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 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":[]}
|
package/dist/diagnostics/hub.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createContext } from "../intercept/context.js";
|
|
3
3
|
import { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from "./build.js";
|
|
4
4
|
import { createConsoleSink } from "./console.js";
|
|
5
|
+
import "./process-guard.js";
|
|
5
6
|
import {
|
|
6
7
|
DK_UNSAFE_REDACTION,
|
|
7
8
|
resolveRedaction,
|
|
@@ -35,7 +36,7 @@ function createDiagnostics(options = {}) {
|
|
|
35
36
|
dev ? e : scrubError(e)
|
|
36
37
|
);
|
|
37
38
|
};
|
|
38
|
-
function deliver(input) {
|
|
39
|
+
function deliver(input, quiet) {
|
|
39
40
|
const d = redact ? redact(input) : input;
|
|
40
41
|
if (getDataRef) {
|
|
41
42
|
const ref = getDataRef();
|
|
@@ -60,7 +61,7 @@ function createDiagnostics(options = {}) {
|
|
|
60
61
|
fail(e);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
toConsole?.(d);
|
|
64
|
+
if (!quiet) toConsole?.(d);
|
|
64
65
|
return d;
|
|
65
66
|
}
|
|
66
67
|
const redactor = () => {
|
|
@@ -99,13 +100,13 @@ function createDiagnostics(options = {}) {
|
|
|
99
100
|
depth++;
|
|
100
101
|
try {
|
|
101
102
|
runtime.run("diagnostic:emit", ctx, (x) => {
|
|
102
|
-
out = deliver(x.input.diagnostic);
|
|
103
|
+
out = deliver(x.input.diagnostic, opts.quiet);
|
|
103
104
|
});
|
|
104
105
|
} finally {
|
|
105
106
|
depth--;
|
|
106
107
|
}
|
|
107
108
|
} else {
|
|
108
|
-
out = deliver(d);
|
|
109
|
+
out = deliver(d, opts.quiet);
|
|
109
110
|
}
|
|
110
111
|
} catch (e) {
|
|
111
112
|
if (strict && e instanceof DatakableError) throw e;
|
|
@@ -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 {\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): 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 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)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\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 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":[]}
|