@datakable/core 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/catalog/codes.cjs +12 -8
- package/dist/catalog/codes.cjs.map +1 -1
- package/dist/catalog/codes.js +12 -8
- package/dist/catalog/codes.js.map +1 -1
- package/dist/catalog/slots.cjs +10 -6
- package/dist/catalog/slots.cjs.map +1 -1
- package/dist/catalog/slots.js +10 -6
- package/dist/catalog/slots.js.map +1 -1
- package/dist/{codes-BKdS9EiO.d.cts → codes-B9bCK73-.d.cts} +46 -8
- package/dist/{codes-BKdS9EiO.d.ts → codes-B9bCK73-.d.ts} +46 -8
- package/dist/column/column-def.cjs.map +1 -1
- package/dist/column/get-value.cjs +6 -1
- package/dist/column/get-value.cjs.map +1 -1
- package/dist/column/get-value.js +6 -1
- package/dist/column/get-value.js.map +1 -1
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +10 -4
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +8 -3
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/catalog.cjs +11 -5
- package/dist/diagnostics/catalog.cjs.map +1 -1
- package/dist/diagnostics/catalog.js +10 -5
- package/dist/diagnostics/catalog.js.map +1 -1
- package/dist/diagnostics/codes.cjs +12 -8
- package/dist/diagnostics/codes.cjs.map +1 -1
- package/dist/diagnostics/codes.js +12 -8
- package/dist/diagnostics/codes.js.map +1 -1
- package/dist/diagnostics/console.cjs +13 -6
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +13 -6
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/env.cjs +1 -0
- package/dist/diagnostics/env.cjs.map +1 -1
- package/dist/diagnostics/env.js +1 -0
- package/dist/diagnostics/env.js.map +1 -1
- package/dist/diagnostics/explain-text.cjs +2 -1
- package/dist/diagnostics/explain-text.cjs.map +1 -1
- package/dist/diagnostics/explain-text.js +2 -1
- package/dist/diagnostics/explain-text.js.map +1 -1
- package/dist/diagnostics/explain.cjs +3 -2
- package/dist/diagnostics/explain.cjs.map +1 -1
- package/dist/diagnostics/explain.js +3 -2
- package/dist/diagnostics/explain.js.map +1 -1
- package/dist/diagnostics/hub.cjs +6 -5
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +7 -6
- package/dist/diagnostics/hub.js.map +1 -1
- package/dist/diagnostics/process-guard.cjs +26 -0
- package/dist/diagnostics/process-guard.cjs.map +1 -0
- package/dist/diagnostics/process-guard.js +9 -0
- package/dist/diagnostics/process-guard.js.map +1 -0
- package/dist/diagnostics/prose.cjs +120 -0
- package/dist/diagnostics/prose.cjs.map +1 -0
- package/dist/diagnostics/prose.js +93 -0
- package/dist/diagnostics/prose.js.map +1 -0
- package/dist/diagnostics/redact.cjs +7 -4
- package/dist/diagnostics/redact.cjs.map +1 -1
- package/dist/diagnostics/redact.js +6 -4
- package/dist/diagnostics/redact.js.map +1 -1
- package/dist/diagnostics/trace-store.cjs +6 -0
- package/dist/diagnostics/trace-store.cjs.map +1 -1
- package/dist/diagnostics/trace-store.js +4 -0
- package/dist/diagnostics/trace-store.js.map +1 -1
- package/dist/diagnostics/tracer.cjs +74 -13
- package/dist/diagnostics/tracer.cjs.map +1 -1
- package/dist/diagnostics/tracer.js +75 -14
- package/dist/diagnostics/tracer.js.map +1 -1
- package/dist/diagnostics/types.cjs.map +1 -1
- package/dist/{explain-aagaYHcy.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
- package/dist/{explain-DcUZfFh5.d.ts → explain-i0OATi_W.d.ts} +13 -4
- package/dist/feature/codes.cjs +1 -0
- package/dist/feature/codes.cjs.map +1 -1
- package/dist/feature/codes.js +1 -0
- package/dist/feature/codes.js.map +1 -1
- package/dist/feature/define.cjs +3 -1
- package/dist/feature/define.cjs.map +1 -1
- package/dist/feature/define.js +3 -1
- package/dist/feature/define.js.map +1 -1
- package/dist/feature/install.cjs +4 -3
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +4 -3
- package/dist/feature/install.js.map +1 -1
- package/dist/feature/runtime.cjs.map +1 -1
- package/dist/feature/runtime.js.map +1 -1
- package/dist/features/column-filters/codes.cjs +1 -0
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +1 -0
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +4 -2
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +4 -2
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +5 -5
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +5 -5
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/codes.cjs +6 -3
- package/dist/features/pagination/codes.cjs.map +1 -1
- package/dist/features/pagination/codes.js +6 -3
- package/dist/features/pagination/codes.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +19 -14
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +19 -14
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +1 -0
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +1 -0
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-index.cjs +105 -48
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +106 -49
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/search/search.cjs +82 -24
- package/dist/features/search/search.cjs.map +1 -1
- package/dist/features/search/search.js +82 -24
- package/dist/features/search/search.js.map +1 -1
- package/dist/features/search/skipped-codes.cjs +45 -0
- package/dist/features/search/skipped-codes.cjs.map +1 -0
- package/dist/features/search/skipped-codes.js +20 -0
- package/dist/features/search/skipped-codes.js.map +1 -0
- package/dist/features/sorting/codes.cjs +1 -0
- package/dist/features/sorting/codes.cjs.map +1 -1
- package/dist/features/sorting/codes.js +1 -0
- package/dist/features/sorting/codes.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +19 -23
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +19 -23
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/features/sorting/strategy.cjs +4 -4
- package/dist/features/sorting/strategy.cjs.map +1 -1
- package/dist/features/sorting/strategy.js +4 -4
- package/dist/features/sorting/strategy.js.map +1 -1
- package/dist/features/validity/codes.cjs +1 -0
- package/dist/features/validity/codes.cjs.map +1 -1
- package/dist/features/validity/codes.js +1 -0
- package/dist/features/validity/codes.js.map +1 -1
- package/dist/features/validity/validity.cjs +3 -3
- package/dist/features/validity/validity.cjs.map +1 -1
- package/dist/features/validity/validity.js +3 -3
- package/dist/features/validity/validity.js.map +1 -1
- package/dist/index.d.cts +16 -11
- package/dist/index.d.ts +16 -11
- package/dist/ingest/data-check-codes.cjs +4 -2
- package/dist/ingest/data-check-codes.cjs.map +1 -1
- package/dist/ingest/data-check-codes.js +4 -2
- package/dist/ingest/data-check-codes.js.map +1 -1
- package/dist/ingest/data-checks.cjs +1 -1
- package/dist/ingest/data-checks.cjs.map +1 -1
- package/dist/ingest/data-checks.js +1 -1
- package/dist/ingest/data-checks.js.map +1 -1
- package/dist/ingest/row-key-codes.cjs +1 -0
- package/dist/ingest/row-key-codes.cjs.map +1 -1
- package/dist/ingest/row-key-codes.js +1 -0
- package/dist/ingest/row-key-codes.js.map +1 -1
- package/dist/ingest/row-key.cjs +23 -20
- package/dist/ingest/row-key.cjs.map +1 -1
- package/dist/ingest/row-key.js +23 -20
- package/dist/ingest/row-key.js.map +1 -1
- package/dist/ingest/schema-codes.cjs +1 -0
- package/dist/ingest/schema-codes.cjs.map +1 -1
- package/dist/ingest/schema-codes.js +1 -0
- package/dist/ingest/schema-codes.js.map +1 -1
- package/dist/ingest/unique-fields.cjs +2 -2
- package/dist/ingest/unique-fields.cjs.map +1 -1
- package/dist/ingest/unique-fields.js +2 -2
- package/dist/ingest/unique-fields.js.map +1 -1
- package/dist/intercept/boundary.cjs +4 -2
- package/dist/intercept/boundary.cjs.map +1 -1
- package/dist/intercept/boundary.js +4 -2
- package/dist/intercept/boundary.js.map +1 -1
- package/dist/intercept/cell-batch.cjs +7 -4
- package/dist/intercept/cell-batch.cjs.map +1 -1
- package/dist/intercept/cell-batch.js +7 -4
- package/dist/intercept/cell-batch.js.map +1 -1
- package/dist/intercept/codes.cjs +1 -0
- package/dist/intercept/codes.cjs.map +1 -1
- package/dist/intercept/codes.js +1 -0
- package/dist/intercept/codes.js.map +1 -1
- package/dist/intercept/fallbacks.cjs +12 -20
- package/dist/intercept/fallbacks.cjs.map +1 -1
- package/dist/intercept/fallbacks.js +12 -20
- package/dist/intercept/fallbacks.js.map +1 -1
- package/dist/intercept/runtime.cjs +7 -9
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +7 -9
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +4 -1
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +4 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/codes.cjs +1 -0
- package/dist/pipeline/codes.cjs.map +1 -1
- package/dist/pipeline/codes.js +1 -0
- package/dist/pipeline/codes.js.map +1 -1
- package/dist/pipeline/column-cache.cjs +94 -76
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +92 -75
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/dev-checks.cjs +1 -0
- package/dist/pipeline/dev-checks.cjs.map +1 -1
- package/dist/pipeline/dev-checks.js +1 -0
- package/dist/pipeline/dev-checks.js.map +1 -1
- package/dist/pipeline/env-codes.cjs +3 -2
- package/dist/pipeline/env-codes.cjs.map +1 -1
- package/dist/pipeline/env-codes.js +3 -2
- package/dist/pipeline/env-codes.js.map +1 -1
- package/dist/pipeline/perf-codes.cjs +1 -0
- package/dist/pipeline/perf-codes.cjs.map +1 -1
- package/dist/pipeline/perf-codes.js +1 -0
- package/dist/pipeline/perf-codes.js.map +1 -1
- package/dist/pipeline/row.cjs +8 -7
- package/dist/pipeline/row.cjs.map +1 -1
- package/dist/pipeline/row.js +8 -7
- package/dist/pipeline/row.js.map +1 -1
- package/dist/pipeline/runner.cjs +17 -9
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +17 -9
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/slice.cjs +3 -1
- package/dist/pipeline/slice.cjs.map +1 -1
- package/dist/pipeline/slice.js +3 -1
- package/dist/pipeline/slice.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-C3mXxLP8.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
- package/dist/{search-async-entry-BmQYT120.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +8 -6
- package/dist/search.d.ts +8 -6
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +6 -3
- package/dist/sorting.d.ts +6 -3
- package/dist/source/codes.cjs +1 -0
- package/dist/source/codes.cjs.map +1 -1
- package/dist/source/codes.js +1 -0
- package/dist/source/codes.js.map +1 -1
- package/dist/source/fetch-codes.cjs +1 -0
- package/dist/source/fetch-codes.cjs.map +1 -1
- package/dist/source/fetch-codes.js +1 -0
- package/dist/source/fetch-codes.js.map +1 -1
- package/dist/table/codes.cjs +1 -0
- package/dist/table/codes.cjs.map +1 -1
- package/dist/table/codes.js +1 -0
- package/dist/table/codes.js.map +1 -1
- package/dist/table/create-table.cjs +39 -9
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +39 -9
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/data-mutation.cjs +1 -0
- package/dist/table/data-mutation.cjs.map +1 -1
- package/dist/table/data-mutation.js +1 -0
- package/dist/table/data-mutation.js.map +1 -1
- package/dist/table/internals.cjs.map +1 -1
- package/dist/table/internals.js.map +1 -1
- package/dist/table/ssr-codes.cjs +1 -0
- package/dist/table/ssr-codes.cjs.map +1 -1
- package/dist/table/ssr-codes.js +1 -0
- package/dist/table/ssr-codes.js.map +1 -1
- package/dist/table/ssr.cjs +4 -1
- package/dist/table/ssr.cjs.map +1 -1
- package/dist/table/ssr.js +4 -1
- package/dist/table/ssr.js.map +1 -1
- package/dist/table/state-feedback.cjs +19 -0
- package/dist/table/state-feedback.cjs.map +1 -1
- package/dist/table/state-feedback.js +19 -0
- package/dist/table/state-feedback.js.map +1 -1
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/versioning/codes.cjs +18 -0
- package/dist/versioning/codes.cjs.map +1 -1
- package/dist/versioning/codes.js +18 -0
- package/dist/versioning/codes.js.map +1 -1
- package/dist/versioning/registry.cjs +4 -2
- package/dist/versioning/registry.cjs.map +1 -1
- package/dist/versioning/registry.js +4 -2
- package/dist/versioning/registry.js.map +1 -1
- package/package.json +39 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-
|
|
2
|
-
export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-
|
|
3
|
-
import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-
|
|
4
|
-
export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-
|
|
1
|
+
import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-B9bCK73-.js';
|
|
2
|
+
export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-B9bCK73-.js';
|
|
3
|
+
import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-i0OATi_W.js';
|
|
4
|
+
export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-i0OATi_W.js';
|
|
5
5
|
import { ColumnFiltersTypes } from './column-filters.js';
|
|
6
6
|
export { ColumnFilter, ColumnFiltersApi, ColumnFiltersOptions, ColumnFiltersState, ContainsFilter, CustomFilter, EqualsFilter, InFilter, RangeFilter, RangeValue, columnFilters } from './column-filters.js';
|
|
7
7
|
import { PaginationState, PaginationApi } from './pagination.js';
|
|
8
8
|
export { PaginationOptions, pagination } from './pagination.js';
|
|
9
9
|
import { SearchApi } from './search.js';
|
|
10
10
|
export { SearchOptions, SearchTyping, SearchWait, search } from './search.js';
|
|
11
|
-
import { S as SearchState } from './search-async-entry-
|
|
12
|
-
export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-
|
|
11
|
+
import { S as SearchState } from './search-async-entry-Cx7xpuVZ.js';
|
|
12
|
+
export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-Cx7xpuVZ.js';
|
|
13
13
|
import { SortingTypes } from './sorting.js';
|
|
14
14
|
export { SortAlgorithmOption, SortDirection, SortEntry, SortingApi, SortingOptions, SortingState, compareValues, sorting, typeRank } from './sorting.js';
|
|
15
15
|
import { ValidityState, ValidityApi } from './validity.js';
|
|
@@ -37,7 +37,8 @@ type CompiledPath = readonly string[];
|
|
|
37
37
|
declare function compilePath(path: string): CompiledPath;
|
|
38
38
|
/**
|
|
39
39
|
* Reads the value at `path` of `row`, defensively (§4.3): a `null`, `undefined` or non-object
|
|
40
|
-
* (primitive, function) part-way through yields `undefined`. Arrays are walked by index.
|
|
40
|
+
* (primitive, function) part-way through yields `undefined`. Arrays are walked by index. An
|
|
41
|
+
* inherited method or `__proto__` (`constructor`, `toString`, `map`) yields `undefined`. Never
|
|
41
42
|
* throws: a throwing getter or Proxy trap yields `undefined` and is passed to `onError`.
|
|
42
43
|
*
|
|
43
44
|
* Hot paths should pass a {@link compilePath} result so the walk allocates nothing per cell.
|
|
@@ -110,6 +111,7 @@ interface DiagnosticsOptions {
|
|
|
110
111
|
max: number;
|
|
111
112
|
windowMs: number;
|
|
112
113
|
};
|
|
114
|
+
/** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */
|
|
113
115
|
now?: () => number;
|
|
114
116
|
}
|
|
115
117
|
interface Diagnostics {
|
|
@@ -141,12 +143,12 @@ declare function createDiagnostics(options?: DiagnosticsOptions): Diagnostics;
|
|
|
141
143
|
*/
|
|
142
144
|
declare function toSafeJson(value: unknown): unknown;
|
|
143
145
|
|
|
144
|
-
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch';
|
|
146
|
+
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch' | 'column:batch';
|
|
145
147
|
/**
|
|
146
148
|
* The `tracer` interceptor (§3.4): timings and per-step counts of the last pipeline run, read by
|
|
147
149
|
* `table.explain()`. `createTable` installs it in development; in production it is off (§8.2) and
|
|
148
150
|
* opt-in with `createTable({ interceptors: [tracer()] })`. Keeps the last run only: counts, never
|
|
149
|
-
* rows.
|
|
151
|
+
* rows. Time-sliced work (§9.1) is added to the step that waited on it (#61).
|
|
150
152
|
*/
|
|
151
153
|
declare function tracer(): Interceptor<Traced>;
|
|
152
154
|
|
|
@@ -157,7 +159,8 @@ interface FixedFeatureDefinition<Name extends string, S, A> extends Omit<Feature
|
|
|
157
159
|
rowModel?: (indices: Uint32Array, state: S, ctx: RowModelContext<AnyRow>) => Uint32Array;
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
160
|
-
* Defines a feature (§3.3).
|
|
162
|
+
* Defines a feature (§3.3). Returns `definition` as is (it exists for typing) and installs the
|
|
163
|
+
* feature runtime.
|
|
161
164
|
*
|
|
162
165
|
* Row-typed feature (all built-ins): declare the types interface and pass both type arguments.
|
|
163
166
|
*
|
|
@@ -311,7 +314,9 @@ declare function createTable<T, S extends TableState = never, const Fs extends r
|
|
|
311
314
|
* starts no async work. The fetcher's first request and a `searchFn` for an initial query wait for
|
|
312
315
|
* `startTable(table)`; meanwhile the row model has `status: 'pending'`. Nothing is started, so a
|
|
313
316
|
* server render leaves no request, timer or microtask chain behind. On the server (no
|
|
314
|
-
* `document`), each deferred source reports `DK_SSR_ASYNC_DEFERRED` (info)
|
|
317
|
+
* `document`), each deferred source reports `DK_SSR_ASYNC_DEFERRED` (info), printed once per process
|
|
318
|
+
* in development for a table name and source (#38: a dev server renders a page several times per
|
|
319
|
+
* request).
|
|
315
320
|
*
|
|
316
321
|
* `@datakable/react`'s `useDataTable` creates its table this way and starts it after the commit;
|
|
317
322
|
* effects never run on the server.
|
|
@@ -25,6 +25,7 @@ __export(data_check_codes_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(data_check_codes_exports);
|
|
27
27
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
28
29
|
var rowList = (rows, count) => {
|
|
29
30
|
const list = rows.map((r) => `#${r}`).join(", ");
|
|
30
31
|
return count > rows.length ? `${list}, \u2026` : list;
|
|
@@ -32,11 +33,12 @@ var rowList = (rows, count) => {
|
|
|
32
33
|
var firstRows = (n) => n === 1 ? "the first row" : `the first ${n} rows`;
|
|
33
34
|
var dataCheckMessages = {
|
|
34
35
|
DK_COLUMN_PATH_NOT_FOUND: {
|
|
35
|
-
problem: (d) => `columns[${d.index}] key "${d.column}" does not exist in any of ${
|
|
36
|
+
problem: (d) => `columns[${d.index}] key "${d.column}" does not exist in ` + (d.sampled === 1 ? "the first row" : `any of the first ${d.sampled} rows`) + (d.suggestions[0] === void 0 ? "" : ` (did you mean "${d.suggestions[0]}"?)`),
|
|
36
37
|
impact: () => "the column reads undefined in every row and renders ''",
|
|
37
38
|
fix: (d) => [
|
|
38
39
|
...d.suggestions.slice(0, 3).map((s) => `columns[${d.index}]: { key: '${s}' }`),
|
|
39
|
-
|
|
40
|
+
// "or" only after a suggested key; with none, this is the first (best) fix.
|
|
41
|
+
`${d.suggestions.length ? "or " : ""}compute the value: columns[${d.index}]: { id: '${d.column}', accessor: (row) => \u2026 }`,
|
|
40
42
|
"or, if the field only appears in later rows, ignore this (only the first rows are checked)"
|
|
41
43
|
]
|
|
42
44
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/data-check-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_COLUMN_PATH_NOT_FOUND: {\n /** Column id (its `key` path). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** How many rows were checked (the first rows of `data`). */\n sampled: number\n /** Existing paths close to `column`, best first. */\n suggestions: string[]\n }\n DK_COLUMN_PATH_PARTIAL: {\n column: string\n index: number\n sampled: number\n /** Sampled rows that lack the path. */\n count: number\n /** The first few of those row indices, in order. */\n rows: number[]\n }\n DK_INCONSISTENT_ROW_SHAPE: {\n sampled: number\n /** Keys missing from some sampled rows, with how many rows lack each (first few keys). */\n keys: { key: string; missing: number }[]\n /** How many keys differ in total (`keys` may be cut). */\n keyCount: number\n /** The first few row indices that lack at least one key. */\n rows: number[]\n /** How many sampled rows lack at least one key. */\n rowCount: number\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst firstRows = (n: number) => (n === 1 ? 'the first row' : `the first ${n} rows`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const dataCheckMessages: DiagnosticMessages<\n 'DK_COLUMN_PATH_NOT_FOUND' | 'DK_COLUMN_PATH_PARTIAL' | 'DK_INCONSISTENT_ROW_SHAPE'\n> = {\n DK_COLUMN_PATH_NOT_FOUND: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" does not exist in any of ${
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/data-check-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_COLUMN_PATH_NOT_FOUND: {\n /** Column id (its `key` path). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** How many rows were checked (the first rows of `data`). */\n sampled: number\n /** Existing paths close to `column`, best first. */\n suggestions: string[]\n }\n DK_COLUMN_PATH_PARTIAL: {\n column: string\n index: number\n sampled: number\n /** Sampled rows that lack the path. */\n count: number\n /** The first few of those row indices, in order. */\n rows: number[]\n }\n DK_INCONSISTENT_ROW_SHAPE: {\n sampled: number\n /** Keys missing from some sampled rows, with how many rows lack each (first few keys). */\n keys: { key: string; missing: number }[]\n /** How many keys differ in total (`keys` may be cut). */\n keyCount: number\n /** The first few row indices that lack at least one key. */\n rows: number[]\n /** How many sampled rows lack at least one key. */\n rowCount: number\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst firstRows = (n: number) => (n === 1 ? 'the first row' : `the first ${n} rows`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const dataCheckMessages: DiagnosticMessages<\n 'DK_COLUMN_PATH_NOT_FOUND' | 'DK_COLUMN_PATH_PARTIAL' | 'DK_INCONSISTENT_ROW_SHAPE'\n> = {\n DK_COLUMN_PATH_NOT_FOUND: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" does not exist in ` +\n (d.sampled === 1 ? 'the first row' : `any of the first ${d.sampled} rows`) +\n (d.suggestions[0] === undefined ? '' : ` (did you mean \"${d.suggestions[0]}\"?)`),\n impact: () => \"the column reads undefined in every row and renders ''\",\n fix: (d) => [\n ...d.suggestions.slice(0, 3).map((s) => `columns[${d.index}]: { key: '${s}' }`),\n // \"or\" only after a suggested key; with none, this is the first (best) fix.\n `${d.suggestions.length ? 'or ' : ''}compute the value: columns[${d.index}]: ` +\n `{ id: '${d.column}', accessor: (row) => … }`,\n 'or, if the field only appears in later rows, ignore this (only the first rows are checked)',\n ],\n },\n DK_COLUMN_PATH_PARTIAL: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" is missing in ${d.count} of ${firstRows(d.sampled)} ` +\n `(${rowList(d.rows, d.count)})`,\n impact: () => \"those cells read undefined and render '' with the default formatter\",\n fix: (d) => [\n `show a placeholder: columns[${d.index}].format: (v) => v ?? '—'`,\n 'or fill the field in every row before passing data, if it is required',\n ],\n },\n DK_INCONSISTENT_ROW_SHAPE: {\n problem: (d) =>\n `rows have differing keys in ${firstRows(d.sampled)}: ` +\n d.keys.map((k) => `\"${k.key}\" missing in ${k.missing}`).join(', ') +\n (d.keyCount > d.keys.length ? `, and ${d.keyCount - d.keys.length} more` : '') +\n ` (rows ${rowList(d.rows, d.rowCount)})`,\n impact: () => \"zero-config columns are the union of the keys; missing cells render ''\",\n fix: (d) => [\n `pass columns explicitly: columns: [${d.keys\n .slice(0, 3)\n .map((k) => `{ key: '${k.key}' }`)\n .join(', ')}, …]`,\n 'or normalise the rows so every row has the same keys',\n ],\n },\n}\n\n/** Codes owned by the ingest data checks (§5.3, §8.3). */\nexport const dataCheckDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_COLUMN_PATH_NOT_FOUND',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_COLUMN_PATH_NOT_FOUND,\n ),\n defineDiagnostic(\n {\n code: 'DK_COLUMN_PATH_PARTIAL',\n level: 'info',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_COLUMN_PATH_PARTIAL,\n ),\n defineDiagnostic(\n {\n code: 'DK_INCONSISTENT_ROW_SHAPE',\n level: 'info',\n where: () => ({ kind: 'option', path: 'columns' }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_INCONSISTENT_ROW_SHAPE,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAqCP,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,GAAG,IAAI,aAAQ;AAC9C;AAEA,IAAM,YAAY,CAAC,MAAe,MAAM,IAAI,kBAAkB,aAAa,CAAC;AAGrE,IAAM,oBAET;AAAA,EACF,0BAA0B;AAAA,IACxB,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,0BACnC,EAAE,YAAY,IAAI,kBAAkB,oBAAoB,EAAE,OAAO,YACjE,EAAE,YAAY,CAAC,MAAM,SAAY,KAAK,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAAA,IAC5E,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,GAAG,EAAE,YAAY,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,WAAW,EAAE,KAAK,cAAc,CAAC,KAAK;AAAA;AAAA,MAE9E,GAAG,EAAE,YAAY,SAAS,QAAQ,EAAE,8BAA8B,EAAE,KAAK,aAC7D,EAAE,MAAM;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EACA,wBAAwB;AAAA,IACtB,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,EAAE,KAAK,OAAO,UAAU,EAAE,OAAO,CAAC,KACrF,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC9B,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,+BAA+B,EAAE,KAAK;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA,EACA,2BAA2B;AAAA,IACzB,SAAS,CAAC,MACR,+BAA+B,UAAU,EAAE,OAAO,CAAC,OACnD,EAAE,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,gBAAgB,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,KAChE,EAAE,WAAW,EAAE,KAAK,SAAS,SAAS,EAAE,WAAW,EAAE,KAAK,MAAM,UAAU,MAC3E,UAAU,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,IACvC,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,sCAAsC,EAAE,KACrC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,WAAW,EAAE,GAAG,KAAK,EAChC,KAAK,IAAI,CAAC;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,uBAAuB;AAAA,MAClC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,UAAU;AAAA,IAClD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AACF;","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/ingest/data-check-codes.ts
|
|
2
2
|
import { defineDiagnostic } from "../diagnostics/catalog.js";
|
|
3
|
+
import "../diagnostics/process-guard.js";
|
|
3
4
|
var rowList = (rows, count) => {
|
|
4
5
|
const list = rows.map((r) => `#${r}`).join(", ");
|
|
5
6
|
return count > rows.length ? `${list}, \u2026` : list;
|
|
@@ -7,11 +8,12 @@ var rowList = (rows, count) => {
|
|
|
7
8
|
var firstRows = (n) => n === 1 ? "the first row" : `the first ${n} rows`;
|
|
8
9
|
var dataCheckMessages = {
|
|
9
10
|
DK_COLUMN_PATH_NOT_FOUND: {
|
|
10
|
-
problem: (d) => `columns[${d.index}] key "${d.column}" does not exist in any of ${
|
|
11
|
+
problem: (d) => `columns[${d.index}] key "${d.column}" does not exist in ` + (d.sampled === 1 ? "the first row" : `any of the first ${d.sampled} rows`) + (d.suggestions[0] === void 0 ? "" : ` (did you mean "${d.suggestions[0]}"?)`),
|
|
11
12
|
impact: () => "the column reads undefined in every row and renders ''",
|
|
12
13
|
fix: (d) => [
|
|
13
14
|
...d.suggestions.slice(0, 3).map((s) => `columns[${d.index}]: { key: '${s}' }`),
|
|
14
|
-
|
|
15
|
+
// "or" only after a suggested key; with none, this is the first (best) fix.
|
|
16
|
+
`${d.suggestions.length ? "or " : ""}compute the value: columns[${d.index}]: { id: '${d.column}', accessor: (row) => \u2026 }`,
|
|
15
17
|
"or, if the field only appears in later rows, ignore this (only the first rows are checked)"
|
|
16
18
|
]
|
|
17
19
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/data-check-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_COLUMN_PATH_NOT_FOUND: {\n /** Column id (its `key` path). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** How many rows were checked (the first rows of `data`). */\n sampled: number\n /** Existing paths close to `column`, best first. */\n suggestions: string[]\n }\n DK_COLUMN_PATH_PARTIAL: {\n column: string\n index: number\n sampled: number\n /** Sampled rows that lack the path. */\n count: number\n /** The first few of those row indices, in order. */\n rows: number[]\n }\n DK_INCONSISTENT_ROW_SHAPE: {\n sampled: number\n /** Keys missing from some sampled rows, with how many rows lack each (first few keys). */\n keys: { key: string; missing: number }[]\n /** How many keys differ in total (`keys` may be cut). */\n keyCount: number\n /** The first few row indices that lack at least one key. */\n rows: number[]\n /** How many sampled rows lack at least one key. */\n rowCount: number\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst firstRows = (n: number) => (n === 1 ? 'the first row' : `the first ${n} rows`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const dataCheckMessages: DiagnosticMessages<\n 'DK_COLUMN_PATH_NOT_FOUND' | 'DK_COLUMN_PATH_PARTIAL' | 'DK_INCONSISTENT_ROW_SHAPE'\n> = {\n DK_COLUMN_PATH_NOT_FOUND: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" does not exist in any of ${
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/data-check-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_COLUMN_PATH_NOT_FOUND: {\n /** Column id (its `key` path). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** How many rows were checked (the first rows of `data`). */\n sampled: number\n /** Existing paths close to `column`, best first. */\n suggestions: string[]\n }\n DK_COLUMN_PATH_PARTIAL: {\n column: string\n index: number\n sampled: number\n /** Sampled rows that lack the path. */\n count: number\n /** The first few of those row indices, in order. */\n rows: number[]\n }\n DK_INCONSISTENT_ROW_SHAPE: {\n sampled: number\n /** Keys missing from some sampled rows, with how many rows lack each (first few keys). */\n keys: { key: string; missing: number }[]\n /** How many keys differ in total (`keys` may be cut). */\n keyCount: number\n /** The first few row indices that lack at least one key. */\n rows: number[]\n /** How many sampled rows lack at least one key. */\n rowCount: number\n }\n }\n}\n\nconst rowList = (rows: readonly number[], count: number) => {\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? `${list}, …` : list\n}\n\nconst firstRows = (n: number) => (n === 1 ? 'the first row' : `the first ${n} rows`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const dataCheckMessages: DiagnosticMessages<\n 'DK_COLUMN_PATH_NOT_FOUND' | 'DK_COLUMN_PATH_PARTIAL' | 'DK_INCONSISTENT_ROW_SHAPE'\n> = {\n DK_COLUMN_PATH_NOT_FOUND: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" does not exist in ` +\n (d.sampled === 1 ? 'the first row' : `any of the first ${d.sampled} rows`) +\n (d.suggestions[0] === undefined ? '' : ` (did you mean \"${d.suggestions[0]}\"?)`),\n impact: () => \"the column reads undefined in every row and renders ''\",\n fix: (d) => [\n ...d.suggestions.slice(0, 3).map((s) => `columns[${d.index}]: { key: '${s}' }`),\n // \"or\" only after a suggested key; with none, this is the first (best) fix.\n `${d.suggestions.length ? 'or ' : ''}compute the value: columns[${d.index}]: ` +\n `{ id: '${d.column}', accessor: (row) => … }`,\n 'or, if the field only appears in later rows, ignore this (only the first rows are checked)',\n ],\n },\n DK_COLUMN_PATH_PARTIAL: {\n problem: (d) =>\n `columns[${d.index}] key \"${d.column}\" is missing in ${d.count} of ${firstRows(d.sampled)} ` +\n `(${rowList(d.rows, d.count)})`,\n impact: () => \"those cells read undefined and render '' with the default formatter\",\n fix: (d) => [\n `show a placeholder: columns[${d.index}].format: (v) => v ?? '—'`,\n 'or fill the field in every row before passing data, if it is required',\n ],\n },\n DK_INCONSISTENT_ROW_SHAPE: {\n problem: (d) =>\n `rows have differing keys in ${firstRows(d.sampled)}: ` +\n d.keys.map((k) => `\"${k.key}\" missing in ${k.missing}`).join(', ') +\n (d.keyCount > d.keys.length ? `, and ${d.keyCount - d.keys.length} more` : '') +\n ` (rows ${rowList(d.rows, d.rowCount)})`,\n impact: () => \"zero-config columns are the union of the keys; missing cells render ''\",\n fix: (d) => [\n `pass columns explicitly: columns: [${d.keys\n .slice(0, 3)\n .map((k) => `{ key: '${k.key}' }`)\n .join(', ')}, …]`,\n 'or normalise the rows so every row has the same keys',\n ],\n },\n}\n\n/** Codes owned by the ingest data checks (§5.3, §8.3). */\nexport const dataCheckDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_COLUMN_PATH_NOT_FOUND',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_COLUMN_PATH_NOT_FOUND,\n ),\n defineDiagnostic(\n {\n code: 'DK_COLUMN_PATH_PARTIAL',\n level: 'info',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_COLUMN_PATH_PARTIAL,\n ),\n defineDiagnostic(\n {\n code: 'DK_INCONSISTENT_ROW_SHAPE',\n level: 'info',\n where: () => ({ kind: 'option', path: 'columns' }),\n },\n (process.env.NODE_ENV !== \"production\") && dataCheckMessages.DK_INCONSISTENT_ROW_SHAPE,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAqCP,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,GAAG,IAAI,aAAQ;AAC9C;AAEA,IAAM,YAAY,CAAC,MAAe,MAAM,IAAI,kBAAkB,aAAa,CAAC;AAGrE,IAAM,oBAET;AAAA,EACF,0BAA0B;AAAA,IACxB,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,0BACnC,EAAE,YAAY,IAAI,kBAAkB,oBAAoB,EAAE,OAAO,YACjE,EAAE,YAAY,CAAC,MAAM,SAAY,KAAK,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAAA,IAC5E,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,GAAG,EAAE,YAAY,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,WAAW,EAAE,KAAK,cAAc,CAAC,KAAK;AAAA;AAAA,MAE9E,GAAG,EAAE,YAAY,SAAS,QAAQ,EAAE,8BAA8B,EAAE,KAAK,aAC7D,EAAE,MAAM;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EACA,wBAAwB;AAAA,IACtB,SAAS,CAAC,MACR,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,EAAE,KAAK,OAAO,UAAU,EAAE,OAAO,CAAC,KACrF,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC9B,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,+BAA+B,EAAE,KAAK;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA,EACA,2BAA2B;AAAA,IACzB,SAAS,CAAC,MACR,+BAA+B,UAAU,EAAE,OAAO,CAAC,OACnD,EAAE,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,gBAAgB,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,KAChE,EAAE,WAAW,EAAE,KAAK,SAAS,SAAS,EAAE,WAAW,EAAE,KAAK,MAAM,UAAU,MAC3E,UAAU,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,IACvC,QAAQ,MAAM;AAAA,IACd,KAAK,CAAC,MAAM;AAAA,MACV,sCAAsC,EAAE,KACrC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,WAAW,EAAE,GAAG,KAAK,EAChC,KAAK,IAAI,CAAC;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,uBAAuB;AAAA,EAClC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,UAAU;AAAA,IAClD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,kBAAkB;AAAA,EAC/D;AACF;","names":[]}
|
|
@@ -42,7 +42,7 @@ function probe(row, segs) {
|
|
|
42
42
|
if (cur === null || cur === void 0) return PRESENT;
|
|
43
43
|
if (typeof cur !== "object") return ABSENT;
|
|
44
44
|
const seg = segs[i];
|
|
45
|
-
if (!(seg in cur)) return ABSENT;
|
|
45
|
+
if (!(seg in cur) || seg in Object.prototype && !Object.hasOwn(cur, seg)) return ABSENT;
|
|
46
46
|
if (i < segs.length - 1) cur = cur[seg];
|
|
47
47
|
}
|
|
48
48
|
return PRESENT;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/data-checks.ts"],"sourcesContent":["import { suggest } from '../catalog/suggest'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport { type ResolvedColumn, resolveColumns } from '../pipeline/columns'\nimport { dataCheckDiagnostics } from './data-check-codes'\nimport { forSampledRows, INGEST_SAMPLE } from './derive-columns'\n\nkeepDiagnostics(dataCheckDiagnostics)\n\n/** Sample row indices kept per diagnostic. */\nconst MAX_ROWS = 5\n/** Differing keys listed by DK_INCONSISTENT_ROW_SHAPE. */\nconst MAX_KEYS = 10\n/** Did-you-mean candidates: path depth (§4.1 caps `Path<T>` at 4) and count. */\nconst MAX_DEPTH = 4\nconst MAX_CANDIDATES = 1000\n\nconst PRESENT = 0\nconst ABSENT = 1\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Whether `path` exists in `row`, like `getValue` reads it: a missing key or a primitive / function\n * part-way is absent; a `null` / `undefined` part-way is present (nullable paths, §4.1). Reads only\n * the intermediate values (the last segment is tested with `in`). May throw (getter, Proxy trap).\n */\nfunction probe(row: object, segs: readonly string[]): number {\n let cur: unknown = row\n for (let i = 0; i < segs.length; i++) {\n if (cur === null || cur === undefined) return PRESENT\n if (typeof cur !== 'object') return ABSENT\n const seg = segs[i] as string\n if (!(seg in cur)) return ABSENT\n if (i < segs.length - 1) cur = (cur as Record<string, unknown>)[seg]\n }\n return PRESENT\n}\n\n/** `a` and `b` differ by one swap of adjacent characters (case-insensitive). */\nfunction isTransposition(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let i = 0\n while (i < a.length && a[i] === b[i]) i++\n return (\n i < a.length - 1 && a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2)\n )\n}\n\n/**\n * Did-you-mean for a column path: `suggest` (Levenshtein) plus adjacent transpositions, which\n * Levenshtein counts as 2 edits and so misses in short names (`nmae` → `name`). Transpositions\n * come first.\n */\nfunction suggestPath(path: string, candidates: Set<string>): string[] {\n const p = path.toLowerCase()\n const out: string[] = []\n for (const c of candidates) if (isTransposition(p, c.toLowerCase())) out.push(c)\n for (const c of suggest(path, candidates)) if (!out.includes(c)) out.push(c)\n return out\n}\n\n/** The dot-paths of the sampled rows (depth ≤ 4; arrays, `Date`, functions are leaves). */\nfunction candidatePaths(data: readonly unknown[], sample: number): Set<string> {\n const out = new Set<string>()\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.size >= MAX_CANDIDATES) return\n const p = prefix + k\n out.add(p)\n if (depth < MAX_DEPTH) {\n const v = obj[k]\n if (isRecord(v)) walk(v, `${p}.`, depth + 1)\n }\n }\n }\n // An unreadable row is skipped part-way (forSampledRows catches).\n forSampledRows(data, sample, (row) => walk(row, '', 1))\n return out\n}\n\nexport interface DataChecks {\n /**\n * Runs the §5.3 checks for `data` against `columns` (explicit columns: path checks; omitted:\n * shape check). Memoised on both references; reads only the first rows. Never throws.\n */\n check(data: readonly unknown[], columns: readonly unknown[] | undefined): void\n}\n\n/**\n * The ingest data checks (§5.3): `DK_COLUMN_PATH_NOT_FOUND` (with did-you-mean),\n * `DK_COLUMN_PATH_PARTIAL` and, for zero-config tables, `DK_INCONSISTENT_ROW_SHAPE`. Every check\n * reads only the first `sample` rows, so the cost does not grow with the data.\n */\nexport function createDataChecks(emit: Emit, sample: number = INGEST_SAMPLE): DataChecks {\n let lastData: readonly unknown[] | undefined\n let lastColumns: readonly unknown[] | undefined\n\n function checkPaths(data: readonly unknown[], columns: readonly unknown[]) {\n let candidates: Set<string> | undefined\n for (const col of resolveColumns(columns).list) {\n if (!col.path) continue\n checkPath(data, col, () => {\n candidates ??= candidatePaths(data, sample)\n return candidates\n })\n }\n }\n\n function checkPath(data: readonly unknown[], col: ResolvedColumn, paths: () => Set<string>) {\n const segs = col.path as readonly string[]\n let sampled = 0\n let count = 0\n const rows: number[] = []\n // A row whose probe throws is not counted (forSampledRows skips it).\n forSampledRows(data, sample, (row, index) => {\n const r = probe(row, segs)\n sampled++\n if (r === ABSENT) {\n count++\n if (rows.length < MAX_ROWS) rows.push(index)\n }\n })\n if (count === 0) return\n if (count === sampled) {\n const suggestions = suggestPath(col.id, paths())\n emit('DK_COLUMN_PATH_NOT_FOUND', { column: col.id, index: col.index, sampled, suggestions })\n } else {\n emit('DK_COLUMN_PATH_PARTIAL', { column: col.id, index: col.index, sampled, count, rows })\n }\n }\n\n function checkShape(data: readonly unknown[]) {\n const rowKeys: { index: number; keys: string[] }[] = []\n const present = new Map<string, number>()\n forSampledRows(data, sample, (row, index) => {\n const keys = Object.keys(row)\n rowKeys.push({ index, keys })\n for (const k of keys) present.set(k, (present.get(k) ?? 0) + 1)\n })\n const sampled = rowKeys.length\n const differing: { key: string; missing: number }[] = []\n for (const [key, n] of present) if (n < sampled) differing.push({ key, missing: sampled - n })\n if (differing.length === 0) return\n const rows: number[] = []\n let rowCount = 0\n for (const r of rowKeys) {\n if (r.keys.length === present.size) continue\n rowCount++\n if (rows.length < MAX_ROWS) rows.push(r.index)\n }\n emit('DK_INCONSISTENT_ROW_SHAPE', {\n sampled,\n keys: differing.slice(0, MAX_KEYS),\n keyCount: differing.length,\n rows,\n rowCount,\n })\n }\n\n return {\n check(data, columns) {\n if (data === lastData && columns === lastColumns) return\n lastData = data\n lastColumns = columns\n if (data.length === 0) return\n if (columns) checkPaths(data, columns)\n else checkShape(data)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,qBAAgC;AAEhC,qBAAoD;AACpD,8BAAqC;AACrC,4BAA8C;AAAA,IAE9C,gCAAgB,4CAAoB;AAGpC,IAAM,WAAW;AAEjB,IAAM,WAAW;AAEjB,IAAM,YAAY;AAClB,IAAM,iBAAiB;AAEvB,IAAM,UAAU;AAChB,IAAM,SAAS;AAEf,IAAM,WAAW,CAAC,MAChB,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,aAAa;AAO7E,SAAS,MAAM,KAAa,MAAiC;AAC3D,MAAI,MAAe;AACnB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,QAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,UAAM,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/data-checks.ts"],"sourcesContent":["import { suggest } from '../catalog/suggest'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport { type ResolvedColumn, resolveColumns } from '../pipeline/columns'\nimport { dataCheckDiagnostics } from './data-check-codes'\nimport { forSampledRows, INGEST_SAMPLE } from './derive-columns'\n\nkeepDiagnostics(dataCheckDiagnostics)\n\n/** Sample row indices kept per diagnostic. */\nconst MAX_ROWS = 5\n/** Differing keys listed by DK_INCONSISTENT_ROW_SHAPE. */\nconst MAX_KEYS = 10\n/** Did-you-mean candidates: path depth (§4.1 caps `Path<T>` at 4) and count. */\nconst MAX_DEPTH = 4\nconst MAX_CANDIDATES = 1000\n\nconst PRESENT = 0\nconst ABSENT = 1\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Whether `path` exists in `row`, like `getValue` reads it: a missing key or a primitive / function\n * part-way is absent; a `null` / `undefined` part-way is present (nullable paths, §4.1). Reads only\n * the intermediate values (the last segment is tested with `in`). May throw (getter, Proxy trap).\n */\nfunction probe(row: object, segs: readonly string[]): number {\n let cur: unknown = row\n for (let i = 0; i < segs.length; i++) {\n if (cur === null || cur === undefined) return PRESENT\n if (typeof cur !== 'object') return ABSENT\n const seg = segs[i] as string\n // An inherited Object.prototype member is not data (`getValue` reads `undefined`, #42).\n if (!(seg in cur) || (seg in Object.prototype && !Object.hasOwn(cur, seg))) return ABSENT\n if (i < segs.length - 1) cur = (cur as Record<string, unknown>)[seg]\n }\n return PRESENT\n}\n\n/** `a` and `b` differ by one swap of adjacent characters (case-insensitive). */\nfunction isTransposition(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let i = 0\n while (i < a.length && a[i] === b[i]) i++\n return (\n i < a.length - 1 && a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2)\n )\n}\n\n/**\n * Did-you-mean for a column path: `suggest` (Levenshtein) plus adjacent transpositions, which\n * Levenshtein counts as 2 edits and so misses in short names (`nmae` → `name`). Transpositions\n * come first.\n */\nfunction suggestPath(path: string, candidates: Set<string>): string[] {\n const p = path.toLowerCase()\n const out: string[] = []\n for (const c of candidates) if (isTransposition(p, c.toLowerCase())) out.push(c)\n for (const c of suggest(path, candidates)) if (!out.includes(c)) out.push(c)\n return out\n}\n\n/** The dot-paths of the sampled rows (depth ≤ 4; arrays, `Date`, functions are leaves). */\nfunction candidatePaths(data: readonly unknown[], sample: number): Set<string> {\n const out = new Set<string>()\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.size >= MAX_CANDIDATES) return\n const p = prefix + k\n out.add(p)\n if (depth < MAX_DEPTH) {\n const v = obj[k]\n if (isRecord(v)) walk(v, `${p}.`, depth + 1)\n }\n }\n }\n // An unreadable row is skipped part-way (forSampledRows catches).\n forSampledRows(data, sample, (row) => walk(row, '', 1))\n return out\n}\n\nexport interface DataChecks {\n /**\n * Runs the §5.3 checks for `data` against `columns` (explicit columns: path checks; omitted:\n * shape check). Memoised on both references; reads only the first rows. Never throws.\n */\n check(data: readonly unknown[], columns: readonly unknown[] | undefined): void\n}\n\n/**\n * The ingest data checks (§5.3): `DK_COLUMN_PATH_NOT_FOUND` (with did-you-mean),\n * `DK_COLUMN_PATH_PARTIAL` and, for zero-config tables, `DK_INCONSISTENT_ROW_SHAPE`. Every check\n * reads only the first `sample` rows, so the cost does not grow with the data.\n */\nexport function createDataChecks(emit: Emit, sample: number = INGEST_SAMPLE): DataChecks {\n let lastData: readonly unknown[] | undefined\n let lastColumns: readonly unknown[] | undefined\n\n function checkPaths(data: readonly unknown[], columns: readonly unknown[]) {\n let candidates: Set<string> | undefined\n for (const col of resolveColumns(columns).list) {\n if (!col.path) continue\n checkPath(data, col, () => {\n candidates ??= candidatePaths(data, sample)\n return candidates\n })\n }\n }\n\n function checkPath(data: readonly unknown[], col: ResolvedColumn, paths: () => Set<string>) {\n const segs = col.path as readonly string[]\n let sampled = 0\n let count = 0\n const rows: number[] = []\n // A row whose probe throws is not counted (forSampledRows skips it).\n forSampledRows(data, sample, (row, index) => {\n const r = probe(row, segs)\n sampled++\n if (r === ABSENT) {\n count++\n if (rows.length < MAX_ROWS) rows.push(index)\n }\n })\n if (count === 0) return\n if (count === sampled) {\n const suggestions = suggestPath(col.id, paths())\n emit('DK_COLUMN_PATH_NOT_FOUND', { column: col.id, index: col.index, sampled, suggestions })\n } else {\n emit('DK_COLUMN_PATH_PARTIAL', { column: col.id, index: col.index, sampled, count, rows })\n }\n }\n\n function checkShape(data: readonly unknown[]) {\n const rowKeys: { index: number; keys: string[] }[] = []\n const present = new Map<string, number>()\n forSampledRows(data, sample, (row, index) => {\n const keys = Object.keys(row)\n rowKeys.push({ index, keys })\n for (const k of keys) present.set(k, (present.get(k) ?? 0) + 1)\n })\n const sampled = rowKeys.length\n const differing: { key: string; missing: number }[] = []\n for (const [key, n] of present) if (n < sampled) differing.push({ key, missing: sampled - n })\n if (differing.length === 0) return\n const rows: number[] = []\n let rowCount = 0\n for (const r of rowKeys) {\n if (r.keys.length === present.size) continue\n rowCount++\n if (rows.length < MAX_ROWS) rows.push(r.index)\n }\n emit('DK_INCONSISTENT_ROW_SHAPE', {\n sampled,\n keys: differing.slice(0, MAX_KEYS),\n keyCount: differing.length,\n rows,\n rowCount,\n })\n }\n\n return {\n check(data, columns) {\n if (data === lastData && columns === lastColumns) return\n lastData = data\n lastColumns = columns\n if (data.length === 0) return\n if (columns) checkPaths(data, columns)\n else checkShape(data)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -18,7 +18,7 @@ function probe(row, segs) {
|
|
|
18
18
|
if (cur === null || cur === void 0) return PRESENT;
|
|
19
19
|
if (typeof cur !== "object") return ABSENT;
|
|
20
20
|
const seg = segs[i];
|
|
21
|
-
if (!(seg in cur)) return ABSENT;
|
|
21
|
+
if (!(seg in cur) || seg in Object.prototype && !Object.hasOwn(cur, seg)) return ABSENT;
|
|
22
22
|
if (i < segs.length - 1) cur = cur[seg];
|
|
23
23
|
}
|
|
24
24
|
return PRESENT;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/data-checks.ts"],"sourcesContent":["import { suggest } from '../catalog/suggest'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport { type ResolvedColumn, resolveColumns } from '../pipeline/columns'\nimport { dataCheckDiagnostics } from './data-check-codes'\nimport { forSampledRows, INGEST_SAMPLE } from './derive-columns'\n\nkeepDiagnostics(dataCheckDiagnostics)\n\n/** Sample row indices kept per diagnostic. */\nconst MAX_ROWS = 5\n/** Differing keys listed by DK_INCONSISTENT_ROW_SHAPE. */\nconst MAX_KEYS = 10\n/** Did-you-mean candidates: path depth (§4.1 caps `Path<T>` at 4) and count. */\nconst MAX_DEPTH = 4\nconst MAX_CANDIDATES = 1000\n\nconst PRESENT = 0\nconst ABSENT = 1\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Whether `path` exists in `row`, like `getValue` reads it: a missing key or a primitive / function\n * part-way is absent; a `null` / `undefined` part-way is present (nullable paths, §4.1). Reads only\n * the intermediate values (the last segment is tested with `in`). May throw (getter, Proxy trap).\n */\nfunction probe(row: object, segs: readonly string[]): number {\n let cur: unknown = row\n for (let i = 0; i < segs.length; i++) {\n if (cur === null || cur === undefined) return PRESENT\n if (typeof cur !== 'object') return ABSENT\n const seg = segs[i] as string\n if (!(seg in cur)) return ABSENT\n if (i < segs.length - 1) cur = (cur as Record<string, unknown>)[seg]\n }\n return PRESENT\n}\n\n/** `a` and `b` differ by one swap of adjacent characters (case-insensitive). */\nfunction isTransposition(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let i = 0\n while (i < a.length && a[i] === b[i]) i++\n return (\n i < a.length - 1 && a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2)\n )\n}\n\n/**\n * Did-you-mean for a column path: `suggest` (Levenshtein) plus adjacent transpositions, which\n * Levenshtein counts as 2 edits and so misses in short names (`nmae` → `name`). Transpositions\n * come first.\n */\nfunction suggestPath(path: string, candidates: Set<string>): string[] {\n const p = path.toLowerCase()\n const out: string[] = []\n for (const c of candidates) if (isTransposition(p, c.toLowerCase())) out.push(c)\n for (const c of suggest(path, candidates)) if (!out.includes(c)) out.push(c)\n return out\n}\n\n/** The dot-paths of the sampled rows (depth ≤ 4; arrays, `Date`, functions are leaves). */\nfunction candidatePaths(data: readonly unknown[], sample: number): Set<string> {\n const out = new Set<string>()\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.size >= MAX_CANDIDATES) return\n const p = prefix + k\n out.add(p)\n if (depth < MAX_DEPTH) {\n const v = obj[k]\n if (isRecord(v)) walk(v, `${p}.`, depth + 1)\n }\n }\n }\n // An unreadable row is skipped part-way (forSampledRows catches).\n forSampledRows(data, sample, (row) => walk(row, '', 1))\n return out\n}\n\nexport interface DataChecks {\n /**\n * Runs the §5.3 checks for `data` against `columns` (explicit columns: path checks; omitted:\n * shape check). Memoised on both references; reads only the first rows. Never throws.\n */\n check(data: readonly unknown[], columns: readonly unknown[] | undefined): void\n}\n\n/**\n * The ingest data checks (§5.3): `DK_COLUMN_PATH_NOT_FOUND` (with did-you-mean),\n * `DK_COLUMN_PATH_PARTIAL` and, for zero-config tables, `DK_INCONSISTENT_ROW_SHAPE`. Every check\n * reads only the first `sample` rows, so the cost does not grow with the data.\n */\nexport function createDataChecks(emit: Emit, sample: number = INGEST_SAMPLE): DataChecks {\n let lastData: readonly unknown[] | undefined\n let lastColumns: readonly unknown[] | undefined\n\n function checkPaths(data: readonly unknown[], columns: readonly unknown[]) {\n let candidates: Set<string> | undefined\n for (const col of resolveColumns(columns).list) {\n if (!col.path) continue\n checkPath(data, col, () => {\n candidates ??= candidatePaths(data, sample)\n return candidates\n })\n }\n }\n\n function checkPath(data: readonly unknown[], col: ResolvedColumn, paths: () => Set<string>) {\n const segs = col.path as readonly string[]\n let sampled = 0\n let count = 0\n const rows: number[] = []\n // A row whose probe throws is not counted (forSampledRows skips it).\n forSampledRows(data, sample, (row, index) => {\n const r = probe(row, segs)\n sampled++\n if (r === ABSENT) {\n count++\n if (rows.length < MAX_ROWS) rows.push(index)\n }\n })\n if (count === 0) return\n if (count === sampled) {\n const suggestions = suggestPath(col.id, paths())\n emit('DK_COLUMN_PATH_NOT_FOUND', { column: col.id, index: col.index, sampled, suggestions })\n } else {\n emit('DK_COLUMN_PATH_PARTIAL', { column: col.id, index: col.index, sampled, count, rows })\n }\n }\n\n function checkShape(data: readonly unknown[]) {\n const rowKeys: { index: number; keys: string[] }[] = []\n const present = new Map<string, number>()\n forSampledRows(data, sample, (row, index) => {\n const keys = Object.keys(row)\n rowKeys.push({ index, keys })\n for (const k of keys) present.set(k, (present.get(k) ?? 0) + 1)\n })\n const sampled = rowKeys.length\n const differing: { key: string; missing: number }[] = []\n for (const [key, n] of present) if (n < sampled) differing.push({ key, missing: sampled - n })\n if (differing.length === 0) return\n const rows: number[] = []\n let rowCount = 0\n for (const r of rowKeys) {\n if (r.keys.length === present.size) continue\n rowCount++\n if (rows.length < MAX_ROWS) rows.push(r.index)\n }\n emit('DK_INCONSISTENT_ROW_SHAPE', {\n sampled,\n keys: differing.slice(0, MAX_KEYS),\n keyCount: differing.length,\n rows,\n rowCount,\n })\n }\n\n return {\n check(data, columns) {\n if (data === lastData && columns === lastColumns) return\n lastData = data\n lastColumns = columns\n if (data.length === 0) return\n if (columns) checkPaths(data, columns)\n else checkShape(data)\n },\n }\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAEhC,SAA8B,sBAAsB;AACpD,SAAS,4BAA4B;AACrC,SAAS,gBAAgB,qBAAqB;AAE9C,gBAAgB,oBAAoB;AAGpC,IAAM,WAAW;AAEjB,IAAM,WAAW;AAEjB,IAAM,YAAY;AAClB,IAAM,iBAAiB;AAEvB,IAAM,UAAU;AAChB,IAAM,SAAS;AAEf,IAAM,WAAW,CAAC,MAChB,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,aAAa;AAO7E,SAAS,MAAM,KAAa,MAAiC;AAC3D,MAAI,MAAe;AACnB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,QAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,UAAM,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/data-checks.ts"],"sourcesContent":["import { suggest } from '../catalog/suggest'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport { type ResolvedColumn, resolveColumns } from '../pipeline/columns'\nimport { dataCheckDiagnostics } from './data-check-codes'\nimport { forSampledRows, INGEST_SAMPLE } from './derive-columns'\n\nkeepDiagnostics(dataCheckDiagnostics)\n\n/** Sample row indices kept per diagnostic. */\nconst MAX_ROWS = 5\n/** Differing keys listed by DK_INCONSISTENT_ROW_SHAPE. */\nconst MAX_KEYS = 10\n/** Did-you-mean candidates: path depth (§4.1 caps `Path<T>` at 4) and count. */\nconst MAX_DEPTH = 4\nconst MAX_CANDIDATES = 1000\n\nconst PRESENT = 0\nconst ABSENT = 1\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)\n\n/**\n * Whether `path` exists in `row`, like `getValue` reads it: a missing key or a primitive / function\n * part-way is absent; a `null` / `undefined` part-way is present (nullable paths, §4.1). Reads only\n * the intermediate values (the last segment is tested with `in`). May throw (getter, Proxy trap).\n */\nfunction probe(row: object, segs: readonly string[]): number {\n let cur: unknown = row\n for (let i = 0; i < segs.length; i++) {\n if (cur === null || cur === undefined) return PRESENT\n if (typeof cur !== 'object') return ABSENT\n const seg = segs[i] as string\n // An inherited Object.prototype member is not data (`getValue` reads `undefined`, #42).\n if (!(seg in cur) || (seg in Object.prototype && !Object.hasOwn(cur, seg))) return ABSENT\n if (i < segs.length - 1) cur = (cur as Record<string, unknown>)[seg]\n }\n return PRESENT\n}\n\n/** `a` and `b` differ by one swap of adjacent characters (case-insensitive). */\nfunction isTransposition(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let i = 0\n while (i < a.length && a[i] === b[i]) i++\n return (\n i < a.length - 1 && a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2)\n )\n}\n\n/**\n * Did-you-mean for a column path: `suggest` (Levenshtein) plus adjacent transpositions, which\n * Levenshtein counts as 2 edits and so misses in short names (`nmae` → `name`). Transpositions\n * come first.\n */\nfunction suggestPath(path: string, candidates: Set<string>): string[] {\n const p = path.toLowerCase()\n const out: string[] = []\n for (const c of candidates) if (isTransposition(p, c.toLowerCase())) out.push(c)\n for (const c of suggest(path, candidates)) if (!out.includes(c)) out.push(c)\n return out\n}\n\n/** The dot-paths of the sampled rows (depth ≤ 4; arrays, `Date`, functions are leaves). */\nfunction candidatePaths(data: readonly unknown[], sample: number): Set<string> {\n const out = new Set<string>()\n const walk = (obj: Record<string, unknown>, prefix: string, depth: number) => {\n for (const k of Object.keys(obj)) {\n if (out.size >= MAX_CANDIDATES) return\n const p = prefix + k\n out.add(p)\n if (depth < MAX_DEPTH) {\n const v = obj[k]\n if (isRecord(v)) walk(v, `${p}.`, depth + 1)\n }\n }\n }\n // An unreadable row is skipped part-way (forSampledRows catches).\n forSampledRows(data, sample, (row) => walk(row, '', 1))\n return out\n}\n\nexport interface DataChecks {\n /**\n * Runs the §5.3 checks for `data` against `columns` (explicit columns: path checks; omitted:\n * shape check). Memoised on both references; reads only the first rows. Never throws.\n */\n check(data: readonly unknown[], columns: readonly unknown[] | undefined): void\n}\n\n/**\n * The ingest data checks (§5.3): `DK_COLUMN_PATH_NOT_FOUND` (with did-you-mean),\n * `DK_COLUMN_PATH_PARTIAL` and, for zero-config tables, `DK_INCONSISTENT_ROW_SHAPE`. Every check\n * reads only the first `sample` rows, so the cost does not grow with the data.\n */\nexport function createDataChecks(emit: Emit, sample: number = INGEST_SAMPLE): DataChecks {\n let lastData: readonly unknown[] | undefined\n let lastColumns: readonly unknown[] | undefined\n\n function checkPaths(data: readonly unknown[], columns: readonly unknown[]) {\n let candidates: Set<string> | undefined\n for (const col of resolveColumns(columns).list) {\n if (!col.path) continue\n checkPath(data, col, () => {\n candidates ??= candidatePaths(data, sample)\n return candidates\n })\n }\n }\n\n function checkPath(data: readonly unknown[], col: ResolvedColumn, paths: () => Set<string>) {\n const segs = col.path as readonly string[]\n let sampled = 0\n let count = 0\n const rows: number[] = []\n // A row whose probe throws is not counted (forSampledRows skips it).\n forSampledRows(data, sample, (row, index) => {\n const r = probe(row, segs)\n sampled++\n if (r === ABSENT) {\n count++\n if (rows.length < MAX_ROWS) rows.push(index)\n }\n })\n if (count === 0) return\n if (count === sampled) {\n const suggestions = suggestPath(col.id, paths())\n emit('DK_COLUMN_PATH_NOT_FOUND', { column: col.id, index: col.index, sampled, suggestions })\n } else {\n emit('DK_COLUMN_PATH_PARTIAL', { column: col.id, index: col.index, sampled, count, rows })\n }\n }\n\n function checkShape(data: readonly unknown[]) {\n const rowKeys: { index: number; keys: string[] }[] = []\n const present = new Map<string, number>()\n forSampledRows(data, sample, (row, index) => {\n const keys = Object.keys(row)\n rowKeys.push({ index, keys })\n for (const k of keys) present.set(k, (present.get(k) ?? 0) + 1)\n })\n const sampled = rowKeys.length\n const differing: { key: string; missing: number }[] = []\n for (const [key, n] of present) if (n < sampled) differing.push({ key, missing: sampled - n })\n if (differing.length === 0) return\n const rows: number[] = []\n let rowCount = 0\n for (const r of rowKeys) {\n if (r.keys.length === present.size) continue\n rowCount++\n if (rows.length < MAX_ROWS) rows.push(r.index)\n }\n emit('DK_INCONSISTENT_ROW_SHAPE', {\n sampled,\n keys: differing.slice(0, MAX_KEYS),\n keyCount: differing.length,\n rows,\n rowCount,\n })\n }\n\n return {\n check(data, columns) {\n if (data === lastData && columns === lastColumns) return\n lastData = data\n lastColumns = columns\n if (data.length === 0) return\n if (columns) checkPaths(data, columns)\n else checkShape(data)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -25,6 +25,7 @@ __export(row_key_codes_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(row_key_codes_exports);
|
|
27
27
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
28
29
|
var n = (x) => x.toLocaleString("en-US");
|
|
29
30
|
var rowList = (rows, total) => rows.map((r) => `#${r}`).join(", ") + (total > rows.length ? ", \u2026" : "");
|
|
30
31
|
var uniqueFixes = (unique, total) => unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/row-key-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_DUPLICATE_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** The first repeated key value (hashed under `'safe'`). */\n key: string | number\n /** A sample of the rows sharing `key` (first few, in order; the first keeps its key). */\n rows: readonly number[]\n /** How many rows share `key`. */\n shared: number\n /** How many rows got a fallback key (all repeated values). */\n count: number\n /** How many distinct key values repeat. */\n groups: number\n /** `data.length`. */\n total: number\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n /** No unique field: a field that, with the `rowKey` path, makes a unique composite key. */\n pair?: string\n }\n DK_MISSING_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** A sample of the rows without a usable key (first few, in order). */\n rows: readonly number[]\n count: number\n /** `data.length`. */\n total: number\n /** The type of the first unusable value: `undefined`, `null`, `object`, `number(NaN)`, … */\n valueType: string\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n }\n }\n}\n\nconst n = (x: number) => x.toLocaleString('en-US')\nconst rowList = (rows: readonly number[], total: number) =>\n rows.map((r) => `#${r}`).join(', ') + (total > rows.length ? ', …' : '')\nconst uniqueFixes = (unique: readonly string[], total: number) =>\n unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const rowKeyMessages: DiagnosticMessages<'DK_DUPLICATE_ROW_KEY' | 'DK_MISSING_ROW_KEY'> = {\n DK_DUPLICATE_ROW_KEY: {\n problem: (d) => {\n const head =\n d.path === undefined\n ? 'the rowKey function returns repeated keys'\n : `rowKey \"${d.path}\" is not unique`\n const more = d.groups > 1 ? `${n(d.groups)} values are repeated; ` : ''\n const what = d.path === undefined ? `key ${d.key}` : `${d.path} = ${d.key}`\n return `${head}: ${more}rows ${rowList(d.rows, d.shared)} share ${what}`\n },\n impact: (d) => {\n const shown = Math.min(d.shared - 1, 2)\n const keys = Array.from({ length: shown }, (_, i) => `${d.key}#${i + 1}`).join(', ')\n const rows = d.count === 1 ? '1 row renders' : `${n(d.count)} rows render`\n return `${rows} with fallback keys (${keys}${d.count > shown ? ', …' : ''}) — updates may hit the wrong row`\n },\n fix: (d) => {\n const fixes = uniqueFixes(d.unique, d.total)\n if (d.pair !== undefined && d.path !== undefined) {\n fixes.push(\n `rowKey: (row) => \\`\\${row.${d.path}}-\\${row.${d.pair}}\\` (unique in all ${n(d.total)} rows)`,\n )\n }\n if (fixes.length === 0) {\n fixes.push(\n `no field is unique across all ${n(d.total)} rows: add a unique id to each row, ` +\n 'or remove the duplicate rows at the source',\n )\n }\n if (d.path === undefined) fixes.push('or make the rowKey function return a unique value')\n return fixes\n },\n },\n DK_MISSING_ROW_KEY: {\n problem: (d) => {\n const rows = `rows ${rowList(d.rows, d.count)}`\n if (d.path === undefined) {\n return (\n `the rowKey function returned no usable key for ${d.count === 1 ? '1 row' : `${n(d.count)} rows`} ` +\n `(${rows}; the first is ${d.valueType})`\n )\n }\n const has = d.count === 1 ? '1 row has' : `${n(d.count)} rows have`\n return `${has} no usable key at \"${d.path}\" (${rows}; the first is ${d.valueType})`\n },\n impact: () =>\n \"those rows use the fallback key '#<index>', which changes when rows are added or removed\",\n fix: (d) => [\n ...uniqueFixes(d.unique, d.total),\n d.path === undefined\n ? 'return a string or a number for every row from the rowKey function'\n : `give every row a string or a number at \"${d.path}\"`,\n ],\n },\n}\n\n/** Codes owned by row-key resolution (§5.2, §8.3). */\nexport const rowKeyDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_DUPLICATE_ROW_KEY',\n level: 'error',\n redact: { key: 'rowKey' },\n where: (d) => ({ kind: 'row', index: d.rows[1] ?? d.rows[0] ?? 0, key: String(d.key) }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_DUPLICATE_ROW_KEY,\n ),\n defineDiagnostic(\n {\n code: 'DK_MISSING_ROW_KEY',\n level: 'error',\n where: (d) => ({ kind: 'row', index: d.rows[0] ?? 0 }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_MISSING_ROW_KEY,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/row-key-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_DUPLICATE_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** The first repeated key value (hashed under `'safe'`). */\n key: string | number\n /** A sample of the rows sharing `key` (first few, in order; the first keeps its key). */\n rows: readonly number[]\n /** How many rows share `key`. */\n shared: number\n /** How many rows got a fallback key (all repeated values). */\n count: number\n /** How many distinct key values repeat. */\n groups: number\n /** `data.length`. */\n total: number\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n /** No unique field: a field that, with the `rowKey` path, makes a unique composite key. */\n pair?: string\n }\n DK_MISSING_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** A sample of the rows without a usable key (first few, in order). */\n rows: readonly number[]\n count: number\n /** `data.length`. */\n total: number\n /** The type of the first unusable value: `undefined`, `null`, `object`, `number(NaN)`, … */\n valueType: string\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n }\n }\n}\n\nconst n = (x: number) => x.toLocaleString('en-US')\nconst rowList = (rows: readonly number[], total: number) =>\n rows.map((r) => `#${r}`).join(', ') + (total > rows.length ? ', …' : '')\nconst uniqueFixes = (unique: readonly string[], total: number) =>\n unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const rowKeyMessages: DiagnosticMessages<'DK_DUPLICATE_ROW_KEY' | 'DK_MISSING_ROW_KEY'> = {\n DK_DUPLICATE_ROW_KEY: {\n problem: (d) => {\n const head =\n d.path === undefined\n ? 'the rowKey function returns repeated keys'\n : `rowKey \"${d.path}\" is not unique`\n const more = d.groups > 1 ? `${n(d.groups)} values are repeated; ` : ''\n const what = d.path === undefined ? `key ${d.key}` : `${d.path} = ${d.key}`\n return `${head}: ${more}rows ${rowList(d.rows, d.shared)} share ${what}`\n },\n impact: (d) => {\n const shown = Math.min(d.shared - 1, 2)\n const keys = Array.from({ length: shown }, (_, i) => `${d.key}#${i + 1}`).join(', ')\n const rows = d.count === 1 ? '1 row renders' : `${n(d.count)} rows render`\n return `${rows} with fallback keys (${keys}${d.count > shown ? ', …' : ''}) — updates may hit the wrong row`\n },\n fix: (d) => {\n const fixes = uniqueFixes(d.unique, d.total)\n if (d.pair !== undefined && d.path !== undefined) {\n fixes.push(\n `rowKey: (row) => \\`\\${row.${d.path}}-\\${row.${d.pair}}\\` (unique in all ${n(d.total)} rows)`,\n )\n }\n if (fixes.length === 0) {\n fixes.push(\n `no field is unique across all ${n(d.total)} rows: add a unique id to each row, ` +\n 'or remove the duplicate rows at the source',\n )\n }\n if (d.path === undefined) fixes.push('or make the rowKey function return a unique value')\n return fixes\n },\n },\n DK_MISSING_ROW_KEY: {\n problem: (d) => {\n const rows = `rows ${rowList(d.rows, d.count)}`\n if (d.path === undefined) {\n return (\n `the rowKey function returned no usable key for ${d.count === 1 ? '1 row' : `${n(d.count)} rows`} ` +\n `(${rows}; the first is ${d.valueType})`\n )\n }\n const has = d.count === 1 ? '1 row has' : `${n(d.count)} rows have`\n return `${has} no usable key at \"${d.path}\" (${rows}; the first is ${d.valueType})`\n },\n impact: () =>\n \"those rows use the fallback key '#<index>', which changes when rows are added or removed\",\n fix: (d) => [\n ...uniqueFixes(d.unique, d.total),\n d.path === undefined\n ? 'return a string or a number for every row from the rowKey function'\n : `give every row a string or a number at \"${d.path}\"`,\n ],\n },\n}\n\n/** Codes owned by row-key resolution (§5.2, §8.3). */\nexport const rowKeyDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_DUPLICATE_ROW_KEY',\n level: 'error',\n redact: { key: 'rowKey' },\n where: (d) => ({ kind: 'row', index: d.rows[1] ?? d.rows[0] ?? 0, key: String(d.key) }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_DUPLICATE_ROW_KEY,\n ),\n defineDiagnostic(\n {\n code: 'DK_MISSING_ROW_KEY',\n level: 'error',\n where: (d) => ({ kind: 'row', index: d.rows[0] ?? 0 }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_MISSING_ROW_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/ingest/row-key-codes.ts
|
|
2
2
|
import { defineDiagnostic } from "../diagnostics/catalog.js";
|
|
3
|
+
import "../diagnostics/process-guard.js";
|
|
3
4
|
var n = (x) => x.toLocaleString("en-US");
|
|
4
5
|
var rowList = (rows, total) => rows.map((r) => `#${r}`).join(", ") + (total > rows.length ? ", \u2026" : "");
|
|
5
6
|
var uniqueFixes = (unique, total) => unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/row-key-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_DUPLICATE_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** The first repeated key value (hashed under `'safe'`). */\n key: string | number\n /** A sample of the rows sharing `key` (first few, in order; the first keeps its key). */\n rows: readonly number[]\n /** How many rows share `key`. */\n shared: number\n /** How many rows got a fallback key (all repeated values). */\n count: number\n /** How many distinct key values repeat. */\n groups: number\n /** `data.length`. */\n total: number\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n /** No unique field: a field that, with the `rowKey` path, makes a unique composite key. */\n pair?: string\n }\n DK_MISSING_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** A sample of the rows without a usable key (first few, in order). */\n rows: readonly number[]\n count: number\n /** `data.length`. */\n total: number\n /** The type of the first unusable value: `undefined`, `null`, `object`, `number(NaN)`, … */\n valueType: string\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n }\n }\n}\n\nconst n = (x: number) => x.toLocaleString('en-US')\nconst rowList = (rows: readonly number[], total: number) =>\n rows.map((r) => `#${r}`).join(', ') + (total > rows.length ? ', …' : '')\nconst uniqueFixes = (unique: readonly string[], total: number) =>\n unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const rowKeyMessages: DiagnosticMessages<'DK_DUPLICATE_ROW_KEY' | 'DK_MISSING_ROW_KEY'> = {\n DK_DUPLICATE_ROW_KEY: {\n problem: (d) => {\n const head =\n d.path === undefined\n ? 'the rowKey function returns repeated keys'\n : `rowKey \"${d.path}\" is not unique`\n const more = d.groups > 1 ? `${n(d.groups)} values are repeated; ` : ''\n const what = d.path === undefined ? `key ${d.key}` : `${d.path} = ${d.key}`\n return `${head}: ${more}rows ${rowList(d.rows, d.shared)} share ${what}`\n },\n impact: (d) => {\n const shown = Math.min(d.shared - 1, 2)\n const keys = Array.from({ length: shown }, (_, i) => `${d.key}#${i + 1}`).join(', ')\n const rows = d.count === 1 ? '1 row renders' : `${n(d.count)} rows render`\n return `${rows} with fallback keys (${keys}${d.count > shown ? ', …' : ''}) — updates may hit the wrong row`\n },\n fix: (d) => {\n const fixes = uniqueFixes(d.unique, d.total)\n if (d.pair !== undefined && d.path !== undefined) {\n fixes.push(\n `rowKey: (row) => \\`\\${row.${d.path}}-\\${row.${d.pair}}\\` (unique in all ${n(d.total)} rows)`,\n )\n }\n if (fixes.length === 0) {\n fixes.push(\n `no field is unique across all ${n(d.total)} rows: add a unique id to each row, ` +\n 'or remove the duplicate rows at the source',\n )\n }\n if (d.path === undefined) fixes.push('or make the rowKey function return a unique value')\n return fixes\n },\n },\n DK_MISSING_ROW_KEY: {\n problem: (d) => {\n const rows = `rows ${rowList(d.rows, d.count)}`\n if (d.path === undefined) {\n return (\n `the rowKey function returned no usable key for ${d.count === 1 ? '1 row' : `${n(d.count)} rows`} ` +\n `(${rows}; the first is ${d.valueType})`\n )\n }\n const has = d.count === 1 ? '1 row has' : `${n(d.count)} rows have`\n return `${has} no usable key at \"${d.path}\" (${rows}; the first is ${d.valueType})`\n },\n impact: () =>\n \"those rows use the fallback key '#<index>', which changes when rows are added or removed\",\n fix: (d) => [\n ...uniqueFixes(d.unique, d.total),\n d.path === undefined\n ? 'return a string or a number for every row from the rowKey function'\n : `give every row a string or a number at \"${d.path}\"`,\n ],\n },\n}\n\n/** Codes owned by row-key resolution (§5.2, §8.3). */\nexport const rowKeyDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_DUPLICATE_ROW_KEY',\n level: 'error',\n redact: { key: 'rowKey' },\n where: (d) => ({ kind: 'row', index: d.rows[1] ?? d.rows[0] ?? 0, key: String(d.key) }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_DUPLICATE_ROW_KEY,\n ),\n defineDiagnostic(\n {\n code: 'DK_MISSING_ROW_KEY',\n level: 'error',\n where: (d) => ({ kind: 'row', index: d.rows[0] ?? 0 }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_MISSING_ROW_KEY,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/row-key-codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\n\ndeclare module '../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_DUPLICATE_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** The first repeated key value (hashed under `'safe'`). */\n key: string | number\n /** A sample of the rows sharing `key` (first few, in order; the first keeps its key). */\n rows: readonly number[]\n /** How many rows share `key`. */\n shared: number\n /** How many rows got a fallback key (all repeated values). */\n count: number\n /** How many distinct key values repeat. */\n groups: number\n /** `data.length`. */\n total: number\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n /** No unique field: a field that, with the `rowKey` path, makes a unique composite key. */\n pair?: string\n }\n DK_MISSING_ROW_KEY: {\n /** The `rowKey` path; absent for a `rowKey` function. */\n path?: string\n /** A sample of the rows without a usable key (first few, in order). */\n rows: readonly number[]\n count: number\n /** `data.length`. */\n total: number\n /** The type of the first unusable value: `undefined`, `null`, `object`, `number(NaN)`, … */\n valueType: string\n /** Fields unique across all rows (computed by a scan). */\n unique: readonly string[]\n }\n }\n}\n\nconst n = (x: number) => x.toLocaleString('en-US')\nconst rowList = (rows: readonly number[], total: number) =>\n rows.map((r) => `#${r}`).join(', ') + (total > rows.length ? ', …' : '')\nconst uniqueFixes = (unique: readonly string[], total: number) =>\n unique.map((f) => `rowKey: '${f}' (unique in all ${n(total)} rows)`)\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const rowKeyMessages: DiagnosticMessages<'DK_DUPLICATE_ROW_KEY' | 'DK_MISSING_ROW_KEY'> = {\n DK_DUPLICATE_ROW_KEY: {\n problem: (d) => {\n const head =\n d.path === undefined\n ? 'the rowKey function returns repeated keys'\n : `rowKey \"${d.path}\" is not unique`\n const more = d.groups > 1 ? `${n(d.groups)} values are repeated; ` : ''\n const what = d.path === undefined ? `key ${d.key}` : `${d.path} = ${d.key}`\n return `${head}: ${more}rows ${rowList(d.rows, d.shared)} share ${what}`\n },\n impact: (d) => {\n const shown = Math.min(d.shared - 1, 2)\n const keys = Array.from({ length: shown }, (_, i) => `${d.key}#${i + 1}`).join(', ')\n const rows = d.count === 1 ? '1 row renders' : `${n(d.count)} rows render`\n return `${rows} with fallback keys (${keys}${d.count > shown ? ', …' : ''}) — updates may hit the wrong row`\n },\n fix: (d) => {\n const fixes = uniqueFixes(d.unique, d.total)\n if (d.pair !== undefined && d.path !== undefined) {\n fixes.push(\n `rowKey: (row) => \\`\\${row.${d.path}}-\\${row.${d.pair}}\\` (unique in all ${n(d.total)} rows)`,\n )\n }\n if (fixes.length === 0) {\n fixes.push(\n `no field is unique across all ${n(d.total)} rows: add a unique id to each row, ` +\n 'or remove the duplicate rows at the source',\n )\n }\n if (d.path === undefined) fixes.push('or make the rowKey function return a unique value')\n return fixes\n },\n },\n DK_MISSING_ROW_KEY: {\n problem: (d) => {\n const rows = `rows ${rowList(d.rows, d.count)}`\n if (d.path === undefined) {\n return (\n `the rowKey function returned no usable key for ${d.count === 1 ? '1 row' : `${n(d.count)} rows`} ` +\n `(${rows}; the first is ${d.valueType})`\n )\n }\n const has = d.count === 1 ? '1 row has' : `${n(d.count)} rows have`\n return `${has} no usable key at \"${d.path}\" (${rows}; the first is ${d.valueType})`\n },\n impact: () =>\n \"those rows use the fallback key '#<index>', which changes when rows are added or removed\",\n fix: (d) => [\n ...uniqueFixes(d.unique, d.total),\n d.path === undefined\n ? 'return a string or a number for every row from the rowKey function'\n : `give every row a string or a number at \"${d.path}\"`,\n ],\n },\n}\n\n/** Codes owned by row-key resolution (§5.2, §8.3). */\nexport const rowKeyDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_DUPLICATE_ROW_KEY',\n level: 'error',\n redact: { key: 'rowKey' },\n where: (d) => ({ kind: 'row', index: d.rows[1] ?? d.rows[0] ?? 0, key: String(d.key) }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_DUPLICATE_ROW_KEY,\n ),\n defineDiagnostic(\n {\n code: 'DK_MISSING_ROW_KEY',\n level: 'error',\n where: (d) => ({ kind: 'row', index: d.rows[0] ?? 0 }),\n },\n (process.env.NODE_ENV !== \"production\") && rowKeyMessages.DK_MISSING_ROW_KEY,\n ),\n]\n"],"mappings":"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","names":[]}
|
package/dist/ingest/row-key.cjs
CHANGED
|
@@ -82,46 +82,49 @@ function keyReader(rowKey, onThrow) {
|
|
|
82
82
|
function scan(data, rowKey, read, emit) {
|
|
83
83
|
const taken = /* @__PURE__ */ new Map();
|
|
84
84
|
const suffix = /* @__PURE__ */ new Map();
|
|
85
|
-
let fallbacks;
|
|
86
|
-
const free = (base, from) => {
|
|
87
|
-
let s = from;
|
|
88
|
-
let k = `${base}#${s++}`;
|
|
89
|
-
while (taken.has(k)) k = `${base}#${s++}`;
|
|
90
|
-
return [k, s];
|
|
91
|
-
};
|
|
92
85
|
const missing = { count: 0, rows: [], valueType: "" };
|
|
93
86
|
const dup = { count: 0, key: void 0, rows: [], shared: 0 };
|
|
87
|
+
const pending = [];
|
|
94
88
|
for (let i = 0; i < data.length; i++) {
|
|
95
89
|
const raw = read(data[i], i);
|
|
96
|
-
|
|
90
|
+
const str = String(raw);
|
|
97
91
|
if (!usable(raw)) {
|
|
98
92
|
if (raw !== THREW) {
|
|
99
93
|
if (missing.count === 0) missing.valueType = typeOf(raw);
|
|
100
94
|
if (missing.rows.length < SAMPLE) missing.rows.push(i);
|
|
101
95
|
missing.count++;
|
|
102
96
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} else if (taken.has(raw)) {
|
|
97
|
+
pending.push([i, `#${i}`]);
|
|
98
|
+
} else if (taken.has(str)) {
|
|
106
99
|
dup.count++;
|
|
107
100
|
if (dup.key === void 0) {
|
|
108
101
|
dup.key = raw;
|
|
109
|
-
dup.rows.push(taken.get(
|
|
102
|
+
dup.rows.push(taken.get(str));
|
|
110
103
|
dup.shared = 1;
|
|
111
104
|
}
|
|
112
|
-
if (
|
|
105
|
+
if (str === String(dup.key)) {
|
|
113
106
|
dup.shared++;
|
|
114
107
|
if (dup.rows.length < SAMPLE) dup.rows.push(i);
|
|
115
108
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
key = k;
|
|
109
|
+
suffix.set(str, 1);
|
|
110
|
+
pending.push([i, str]);
|
|
119
111
|
} else {
|
|
120
|
-
taken.set(
|
|
121
|
-
|
|
112
|
+
taken.set(str, i);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const groups = suffix.size;
|
|
116
|
+
let fallbacks;
|
|
117
|
+
for (const [i, base] of pending) {
|
|
118
|
+
let key = base;
|
|
119
|
+
if (taken.has(key)) {
|
|
120
|
+
let n = suffix.get(base) ?? 1;
|
|
121
|
+
do
|
|
122
|
+
key = `${base}#${n++}`;
|
|
123
|
+
while (taken.has(key));
|
|
124
|
+
suffix.set(base, n);
|
|
122
125
|
}
|
|
123
126
|
taken.set(key, i);
|
|
124
|
-
|
|
127
|
+
fallbacks ??= /* @__PURE__ */ new Map();
|
|
125
128
|
fallbacks.set(i, key);
|
|
126
129
|
}
|
|
127
130
|
if (dup.key !== void 0 || missing.count > 0) {
|
|
@@ -135,7 +138,7 @@ function scan(data, rowKey, read, emit) {
|
|
|
135
138
|
rows: dup.rows,
|
|
136
139
|
shared: dup.shared,
|
|
137
140
|
count: dup.count,
|
|
138
|
-
groups
|
|
141
|
+
groups,
|
|
139
142
|
total: data.length,
|
|
140
143
|
unique: found.unique,
|
|
141
144
|
...found.pair === void 0 ? {} : { pair: found.pair }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ingest/row-key.ts"],"sourcesContent":["import { compilePath, getValue } from '../column/get-value'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport type { RowKey } from '../pipeline/row'\nimport { rowKeyDiagnostics } from './row-key-codes'\nimport { findUniqueFields } from './unique-fields'\n\nkeepDiagnostics(rowKeyDiagnostics)\n\n/** Rows listed in a diagnostic's `rows` sample. */\nconst SAMPLE = 5\n\n/** `(row, index) => key` for the rows the pipeline builds. */\nexport type KeyOf = (row: unknown, index: number) => RowKey\n\nconst usable = (k: unknown): k is RowKey =>\n typeof k === 'string' || (typeof k === 'number' && !Number.isNaN(k))\n\nconst THREW: unique symbol = Symbol('threw')\n\nconst typeOf = (v: unknown): string => {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n return typeof v === 'number' ? 'number(NaN)' : typeof v\n}\n\n/**\n * Resolves `rowKey` (§5.2) over **all** rows, once per (data, rowKey) reference, so keys do not\n * change with filtering, sorting or paging:\n *\n * - a repeated key: the first row keeps it, later rows get `key#1`, `key#2`, …\n * (`DK_DUPLICATE_ROW_KEY`, whose `fix` names fields that are unique across all rows);\n * - a missing / `null` / non-string-or-number key: `#index` (`DK_MISSING_ROW_KEY`);\n * - a throwing `rowKey` function: `#index`, passed to `onThrow` (reported by the runner as\n * `DK_CALLBACK_FAILED`, as in T09).\n *\n * Only the rows that need a fallback are remembered; the returned reader re-reads the key of the\n * (visible) rows it is asked for, so a clean data set keeps no per-row memory. Without `rowKey`,\n * the key is the row index.\n */\nexport function resolveRowKeys(\n data: readonly unknown[],\n rowKey: unknown,\n emit: Emit,\n onThrow: (index: number, error: unknown) => void,\n): KeyOf {\n const read = keyReader(rowKey, onThrow)\n if (read === undefined) return (_row, index) => index\n const fallbacks = scan(data, rowKey, read, emit)\n return (row, index) => {\n const f = fallbacks?.get(index)\n if (f !== undefined) return f\n const k = read(row, index)\n return usable(k) ? k : `#${index}`\n }\n}\n\ntype Read = (row: unknown, index: number) => unknown\n\nfunction keyReader(\n rowKey: unknown,\n onThrow: (index: number, error: unknown) => void,\n): Read | undefined {\n let read: Read\n if (typeof rowKey === 'function') {\n const fn = rowKey as (row: unknown) => unknown\n read = (row, index) => {\n try {\n return fn(row)\n } catch (e) {\n onThrow(index, e)\n return THREW\n }\n }\n } else if (typeof rowKey === 'string') {\n const path = compilePath(rowKey)\n let cur = 0\n let threw = false\n const onError = (e: unknown) => {\n threw = true\n onThrow(cur, e)\n }\n read = (row, index) => {\n cur = index\n const k = getValue(row, path, onError)\n if (!threw) return k\n threw = false\n return THREW\n }\n } else {\n return undefined\n }\n return read\n}\n\n/**\n * One pass over every row
|
|
1
|
+
{"version":3,"sources":["../../src/ingest/row-key.ts"],"sourcesContent":["import { compilePath, getValue } from '../column/get-value'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport type { Emit } from '../diagnostics/types'\nimport type { RowKey } from '../pipeline/row'\nimport { rowKeyDiagnostics } from './row-key-codes'\nimport { findUniqueFields } from './unique-fields'\n\nkeepDiagnostics(rowKeyDiagnostics)\n\n/** Rows listed in a diagnostic's `rows` sample. */\nconst SAMPLE = 5\n\n/** `(row, index) => key` for the rows the pipeline builds. */\nexport type KeyOf = (row: unknown, index: number) => RowKey\n\nconst usable = (k: unknown): k is RowKey =>\n typeof k === 'string' || (typeof k === 'number' && !Number.isNaN(k))\n\nconst THREW: unique symbol = Symbol('threw')\n\nconst typeOf = (v: unknown): string => {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n return typeof v === 'number' ? 'number(NaN)' : typeof v\n}\n\n/**\n * Resolves `rowKey` (§5.2) over **all** rows, once per (data, rowKey) reference, so keys do not\n * change with filtering, sorting or paging:\n *\n * - a repeated key: the first row keeps it, later rows get `key#1`, `key#2`, …\n * (`DK_DUPLICATE_ROW_KEY`, whose `fix` names fields that are unique across all rows). Keys are\n * compared as strings (`1` repeats `'1'`), as React keys and `explainRow` compare them;\n * - a missing / `null` / non-string-or-number key: `#index` (`DK_MISSING_ROW_KEY`);\n * - a throwing `rowKey` function: `#index`, passed to `onThrow` (reported by the runner as\n * `DK_CALLBACK_FAILED`, as in T09).\n *\n * Only the rows that need a fallback are remembered; the returned reader re-reads the key of the\n * (visible) rows it is asked for, so a clean data set keeps no per-row memory. Without `rowKey`,\n * the key is the row index.\n */\nexport function resolveRowKeys(\n data: readonly unknown[],\n rowKey: unknown,\n emit: Emit,\n onThrow: (index: number, error: unknown) => void,\n): KeyOf {\n const read = keyReader(rowKey, onThrow)\n if (read === undefined) return (_row, index) => index\n const fallbacks = scan(data, rowKey, read, emit)\n return (row, index) => {\n const f = fallbacks?.get(index)\n if (f !== undefined) return f\n const k = read(row, index)\n return usable(k) ? k : `#${index}`\n }\n}\n\ntype Read = (row: unknown, index: number) => unknown\n\nfunction keyReader(\n rowKey: unknown,\n onThrow: (index: number, error: unknown) => void,\n): Read | undefined {\n let read: Read\n if (typeof rowKey === 'function') {\n const fn = rowKey as (row: unknown) => unknown\n read = (row, index) => {\n try {\n return fn(row)\n } catch (e) {\n onThrow(index, e)\n return THREW\n }\n }\n } else if (typeof rowKey === 'string') {\n const path = compilePath(rowKey)\n let cur = 0\n let threw = false\n const onError = (e: unknown) => {\n threw = true\n onThrow(cur, e)\n }\n read = (row, index) => {\n cur = index\n const k = getValue(row, path, onError)\n if (!threw) return k\n threw = false\n return THREW\n }\n } else {\n return undefined\n }\n return read\n}\n\n/**\n * One pass over every row, then one over the rows that need a fallback key: returns those keys\n * and emits the key diagnostics. Kept apart from the returned reader so the scan's maps are not\n * captured by it.\n */\nfunction scan(\n data: readonly unknown[],\n rowKey: unknown,\n read: Read,\n emit: Emit,\n): Map<number, RowKey> | undefined {\n /** Every key handed out so far (raw keys and fallbacks), as a string → the row that holds it. */\n const taken = new Map<string, number>()\n /** Repeated raw key, as a string → the next `#n` to try. */\n const suffix = new Map<string, number>()\n const missing = { count: 0, rows: [] as number[], valueType: '' }\n const dup = { count: 0, key: undefined as RowKey | undefined, rows: [] as number[], shared: 0 }\n /** Rows that need a fallback: [index, the base it is built on (`#index` or the repeated key)]. */\n const pending: [number, string][] = []\n\n for (let i = 0; i < data.length; i++) {\n const raw = read(data[i], i)\n const str = String(raw)\n if (!usable(raw)) {\n if (raw !== THREW) {\n if (missing.count === 0) missing.valueType = typeOf(raw)\n if (missing.rows.length < SAMPLE) missing.rows.push(i)\n missing.count++\n }\n pending.push([i, `#${i}`])\n } else if (taken.has(str)) {\n dup.count++\n if (dup.key === undefined) {\n dup.key = raw\n dup.rows.push(taken.get(str) as number)\n dup.shared = 1\n }\n if (str === String(dup.key)) {\n dup.shared++\n if (dup.rows.length < SAMPLE) dup.rows.push(i)\n }\n suffix.set(str, 1)\n pending.push([i, str])\n } else {\n taken.set(str, i)\n }\n }\n const groups = suffix.size\n\n // Fallbacks only once every real key is taken, so none takes the key of a later row: `base#n`\n // for the first free `n` (a missing row's `#index` itself when free).\n let fallbacks: Map<number, RowKey> | undefined\n for (const [i, base] of pending) {\n let key = base\n if (taken.has(key)) {\n let n = suffix.get(base) ?? 1\n do key = `${base}#${n++}`\n while (taken.has(key))\n suffix.set(base, n)\n }\n taken.set(key, i)\n fallbacks ??= new Map()\n fallbacks.set(i, key)\n }\n\n if (dup.key !== undefined || missing.count > 0) {\n // Error path only: the fix is computed from the data (a scan per candidate field).\n const path = typeof rowKey === 'string' ? rowKey : undefined\n const at = path === undefined ? {} : { path }\n if (dup.key !== undefined) {\n const found = findUniqueFields(data, path)\n emit('DK_DUPLICATE_ROW_KEY', {\n ...at,\n key: dup.key,\n rows: dup.rows,\n shared: dup.shared,\n count: dup.count,\n groups,\n total: data.length,\n unique: found.unique,\n ...(found.pair === undefined ? {} : { pair: found.pair }),\n })\n }\n if (missing.count > 0) {\n emit('DK_MISSING_ROW_KEY', {\n ...at,\n rows: missing.rows,\n count: missing.count,\n total: data.length,\n valueType: missing.valueType,\n unique: findUniqueFields(data, path, undefined).unique,\n })\n }\n }\n\n return fallbacks\n}\n"],"mappings":"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","names":[]}
|