@datakable/core 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/catalog/codes.cjs +12 -8
- package/dist/catalog/codes.cjs.map +1 -1
- package/dist/catalog/codes.js +12 -8
- package/dist/catalog/codes.js.map +1 -1
- package/dist/catalog/slots.cjs +10 -6
- package/dist/catalog/slots.cjs.map +1 -1
- package/dist/catalog/slots.js +10 -6
- package/dist/catalog/slots.js.map +1 -1
- package/dist/{codes-OsjAR3Wz.d.cts → codes-B9bCK73-.d.cts} +30 -4
- package/dist/{codes-OsjAR3Wz.d.ts → codes-B9bCK73-.d.ts} +30 -4
- package/dist/column/column-def.cjs.map +1 -1
- package/dist/column/get-value.cjs +6 -1
- package/dist/column/get-value.cjs.map +1 -1
- package/dist/column/get-value.js +6 -1
- package/dist/column/get-value.js.map +1 -1
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +4 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +4 -1
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/catalog.cjs +11 -5
- package/dist/diagnostics/catalog.cjs.map +1 -1
- package/dist/diagnostics/catalog.js +10 -5
- package/dist/diagnostics/catalog.js.map +1 -1
- package/dist/diagnostics/codes.cjs +12 -8
- package/dist/diagnostics/codes.cjs.map +1 -1
- package/dist/diagnostics/codes.js +12 -8
- package/dist/diagnostics/codes.js.map +1 -1
- package/dist/diagnostics/console.cjs +13 -6
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +13 -6
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/env.cjs +1 -0
- package/dist/diagnostics/env.cjs.map +1 -1
- package/dist/diagnostics/env.js +1 -0
- package/dist/diagnostics/env.js.map +1 -1
- package/dist/diagnostics/explain-text.cjs +2 -1
- package/dist/diagnostics/explain-text.cjs.map +1 -1
- package/dist/diagnostics/explain-text.js +2 -1
- package/dist/diagnostics/explain-text.js.map +1 -1
- package/dist/diagnostics/explain.cjs +3 -2
- package/dist/diagnostics/explain.cjs.map +1 -1
- package/dist/diagnostics/explain.js +3 -2
- package/dist/diagnostics/explain.js.map +1 -1
- package/dist/diagnostics/hub.cjs +5 -4
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +5 -4
- package/dist/diagnostics/hub.js.map +1 -1
- package/dist/diagnostics/process-guard.cjs +26 -0
- package/dist/diagnostics/process-guard.cjs.map +1 -0
- package/dist/diagnostics/process-guard.js +9 -0
- package/dist/diagnostics/process-guard.js.map +1 -0
- package/dist/diagnostics/prose.cjs +120 -0
- package/dist/diagnostics/prose.cjs.map +1 -0
- package/dist/diagnostics/prose.js +93 -0
- package/dist/diagnostics/prose.js.map +1 -0
- package/dist/diagnostics/redact.cjs +7 -4
- package/dist/diagnostics/redact.cjs.map +1 -1
- package/dist/diagnostics/redact.js +6 -4
- package/dist/diagnostics/redact.js.map +1 -1
- package/dist/diagnostics/trace-store.cjs +6 -0
- package/dist/diagnostics/trace-store.cjs.map +1 -1
- package/dist/diagnostics/trace-store.js +4 -0
- package/dist/diagnostics/trace-store.js.map +1 -1
- package/dist/diagnostics/tracer.cjs +74 -13
- package/dist/diagnostics/tracer.cjs.map +1 -1
- package/dist/diagnostics/tracer.js +75 -14
- package/dist/diagnostics/tracer.js.map +1 -1
- package/dist/diagnostics/types.cjs.map +1 -1
- package/dist/{explain-vkD2cewE.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
- package/dist/{explain-Dn99IQt0.d.ts → explain-i0OATi_W.d.ts} +13 -4
- package/dist/feature/codes.cjs +1 -0
- package/dist/feature/codes.cjs.map +1 -1
- package/dist/feature/codes.js +1 -0
- package/dist/feature/codes.js.map +1 -1
- package/dist/feature/install.cjs +4 -3
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +4 -3
- package/dist/feature/install.js.map +1 -1
- package/dist/features/column-filters/codes.cjs +1 -0
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +1 -0
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +4 -2
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +4 -2
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +5 -5
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +5 -5
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/codes.cjs +1 -0
- package/dist/features/pagination/codes.cjs.map +1 -1
- package/dist/features/pagination/codes.js +1 -0
- package/dist/features/pagination/codes.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +16 -13
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +16 -13
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +1 -0
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +1 -0
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-index.cjs +68 -15
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +68 -15
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/search/search.cjs +41 -9
- package/dist/features/search/search.cjs.map +1 -1
- package/dist/features/search/search.js +41 -9
- package/dist/features/search/search.js.map +1 -1
- package/dist/features/search/skipped-codes.cjs +45 -0
- package/dist/features/search/skipped-codes.cjs.map +1 -0
- package/dist/features/search/skipped-codes.js +20 -0
- package/dist/features/search/skipped-codes.js.map +1 -0
- package/dist/features/sorting/codes.cjs +1 -0
- package/dist/features/sorting/codes.cjs.map +1 -1
- package/dist/features/sorting/codes.js +1 -0
- package/dist/features/sorting/codes.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +16 -20
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +16 -20
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/features/sorting/strategy.cjs +4 -4
- package/dist/features/sorting/strategy.cjs.map +1 -1
- package/dist/features/sorting/strategy.js +4 -4
- package/dist/features/sorting/strategy.js.map +1 -1
- package/dist/features/validity/codes.cjs +1 -0
- package/dist/features/validity/codes.cjs.map +1 -1
- package/dist/features/validity/codes.js +1 -0
- package/dist/features/validity/codes.js.map +1 -1
- package/dist/features/validity/validity.cjs +3 -3
- package/dist/features/validity/validity.cjs.map +1 -1
- package/dist/features/validity/validity.js +3 -3
- package/dist/features/validity/validity.js.map +1 -1
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/ingest/data-check-codes.cjs +4 -2
- package/dist/ingest/data-check-codes.cjs.map +1 -1
- package/dist/ingest/data-check-codes.js +4 -2
- package/dist/ingest/data-check-codes.js.map +1 -1
- package/dist/ingest/data-checks.cjs +1 -1
- package/dist/ingest/data-checks.cjs.map +1 -1
- package/dist/ingest/data-checks.js +1 -1
- package/dist/ingest/data-checks.js.map +1 -1
- package/dist/ingest/row-key-codes.cjs +1 -0
- package/dist/ingest/row-key-codes.cjs.map +1 -1
- package/dist/ingest/row-key-codes.js +1 -0
- package/dist/ingest/row-key-codes.js.map +1 -1
- package/dist/ingest/row-key.cjs +23 -20
- package/dist/ingest/row-key.cjs.map +1 -1
- package/dist/ingest/row-key.js +23 -20
- package/dist/ingest/row-key.js.map +1 -1
- package/dist/ingest/schema-codes.cjs +1 -0
- package/dist/ingest/schema-codes.cjs.map +1 -1
- package/dist/ingest/schema-codes.js +1 -0
- package/dist/ingest/schema-codes.js.map +1 -1
- package/dist/ingest/unique-fields.cjs +2 -2
- package/dist/ingest/unique-fields.cjs.map +1 -1
- package/dist/ingest/unique-fields.js +2 -2
- package/dist/ingest/unique-fields.js.map +1 -1
- package/dist/intercept/boundary.cjs +4 -2
- package/dist/intercept/boundary.cjs.map +1 -1
- package/dist/intercept/boundary.js +4 -2
- package/dist/intercept/boundary.js.map +1 -1
- package/dist/intercept/cell-batch.cjs +4 -4
- package/dist/intercept/cell-batch.cjs.map +1 -1
- package/dist/intercept/cell-batch.js +4 -4
- package/dist/intercept/cell-batch.js.map +1 -1
- package/dist/intercept/codes.cjs +1 -0
- package/dist/intercept/codes.cjs.map +1 -1
- package/dist/intercept/codes.js +1 -0
- package/dist/intercept/codes.js.map +1 -1
- package/dist/intercept/fallbacks.cjs +12 -20
- package/dist/intercept/fallbacks.cjs.map +1 -1
- package/dist/intercept/fallbacks.js +12 -20
- package/dist/intercept/fallbacks.js.map +1 -1
- package/dist/intercept/runtime.cjs +7 -9
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +7 -9
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +4 -1
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +4 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/codes.cjs +1 -0
- package/dist/pipeline/codes.cjs.map +1 -1
- package/dist/pipeline/codes.js +1 -0
- package/dist/pipeline/codes.js.map +1 -1
- package/dist/pipeline/column-cache.cjs +94 -76
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +92 -75
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/dev-checks.cjs +1 -0
- package/dist/pipeline/dev-checks.cjs.map +1 -1
- package/dist/pipeline/dev-checks.js +1 -0
- package/dist/pipeline/dev-checks.js.map +1 -1
- package/dist/pipeline/env-codes.cjs +3 -2
- package/dist/pipeline/env-codes.cjs.map +1 -1
- package/dist/pipeline/env-codes.js +3 -2
- package/dist/pipeline/env-codes.js.map +1 -1
- package/dist/pipeline/perf-codes.cjs +1 -0
- package/dist/pipeline/perf-codes.cjs.map +1 -1
- package/dist/pipeline/perf-codes.js +1 -0
- package/dist/pipeline/perf-codes.js.map +1 -1
- package/dist/pipeline/runner.cjs +17 -9
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +17 -9
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/slice.cjs +3 -1
- package/dist/pipeline/slice.cjs.map +1 -1
- package/dist/pipeline/slice.js +3 -1
- package/dist/pipeline/slice.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-v5JdEk5Y.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
- package/dist/{search-async-entry-DGSw2pDL.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +8 -6
- package/dist/search.d.ts +8 -6
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/codes.cjs +1 -0
- package/dist/source/codes.cjs.map +1 -1
- package/dist/source/codes.js +1 -0
- package/dist/source/codes.js.map +1 -1
- package/dist/source/fetch-codes.cjs +1 -0
- package/dist/source/fetch-codes.cjs.map +1 -1
- package/dist/source/fetch-codes.js +1 -0
- package/dist/source/fetch-codes.js.map +1 -1
- package/dist/table/codes.cjs +1 -0
- package/dist/table/codes.cjs.map +1 -1
- package/dist/table/codes.js +1 -0
- package/dist/table/codes.js.map +1 -1
- package/dist/table/create-table.cjs +39 -9
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +39 -9
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/data-mutation.cjs +1 -0
- package/dist/table/data-mutation.cjs.map +1 -1
- package/dist/table/data-mutation.js +1 -0
- package/dist/table/data-mutation.js.map +1 -1
- package/dist/table/internals.cjs.map +1 -1
- package/dist/table/internals.js.map +1 -1
- package/dist/table/ssr-codes.cjs +1 -0
- package/dist/table/ssr-codes.cjs.map +1 -1
- package/dist/table/ssr-codes.js +1 -0
- package/dist/table/ssr-codes.js.map +1 -1
- package/dist/table/ssr.cjs +4 -1
- package/dist/table/ssr.cjs.map +1 -1
- package/dist/table/ssr.js +4 -1
- package/dist/table/ssr.js.map +1 -1
- package/dist/table/state-feedback.cjs +19 -0
- package/dist/table/state-feedback.cjs.map +1 -1
- package/dist/table/state-feedback.js +19 -0
- package/dist/table/state-feedback.js.map +1 -1
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/versioning/codes.cjs +18 -0
- package/dist/versioning/codes.cjs.map +1 -1
- package/dist/versioning/codes.js +18 -0
- package/dist/versioning/codes.js.map +1 -1
- package/dist/versioning/registry.cjs +4 -2
- package/dist/versioning/registry.cjs.map +1 -1
- package/dist/versioning/registry.js +4 -2
- package/dist/versioning/registry.js.map +1 -1
- package/package.json +39 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/validity/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_VALIDITY: {\n /** The API method that received the value, or `'state'` for a slice written to the store. */\n source: 'setValidity' | 'state'\n /** A short constant (`\"broken\"`) or the value's type only. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n }\n}\n\nconst ACCEPTED = \"'all' | 'valid' | 'invalid'\"\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const validityMessages: DiagnosticMessages<'DK_INVALID_VALIDITY'> = {\n DK_INVALID_VALIDITY: {\n problem: (d) =>\n d.source === 'state'\n ? `the validity state is ${d.received}; it must be ${ACCEPTED}`\n : `setValidity() received ${d.received}; it must be ${ACCEPTED}`,\n impact: (d) => d.fallback,\n fix: (d) => [\n d.source === 'state'\n ? `write validity as ${ACCEPTED} (check initialState, setState and the controlled ` +\n 'options.state)'\n : `pass ${ACCEPTED} to setValidity()`,\n ],\n },\n}\n\n/** Codes owned by the `validity` feature (§6.2). */\nexport const validityDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_VALIDITY',\n level: 'warn',\n where: (d) =>\n d.source === 'state'\n ? { kind: 'option', path: 'state.validity' }\n : { kind: 'operation', op: 'table.setValidity' },\n },\n (process.env.NODE_ENV !== \"production\") && validityMessages.DK_INVALID_VALIDITY,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../../src/features/validity/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_VALIDITY: {\n /** The API method that received the value, or `'state'` for a slice written to the store. */\n source: 'setValidity' | 'state'\n /** A short constant (`\"broken\"`) or the value's type only. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n }\n}\n\nconst ACCEPTED = \"'all' | 'valid' | 'invalid'\"\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const validityMessages: DiagnosticMessages<'DK_INVALID_VALIDITY'> = {\n DK_INVALID_VALIDITY: {\n problem: (d) =>\n d.source === 'state'\n ? `the validity state is ${d.received}; it must be ${ACCEPTED}`\n : `setValidity() received ${d.received}; it must be ${ACCEPTED}`,\n impact: (d) => d.fallback,\n fix: (d) => [\n d.source === 'state'\n ? `write validity as ${ACCEPTED} (check initialState, setState and the controlled ` +\n 'options.state)'\n : `pass ${ACCEPTED} to setValidity()`,\n ],\n },\n}\n\n/** Codes owned by the `validity` feature (§6.2). */\nexport const validityDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_VALIDITY',\n level: 'warn',\n where: (d) =>\n d.source === 'state'\n ? { kind: 'option', path: 'state.validity' }\n : { kind: 'operation', op: 'table.setValidity' },\n },\n (process.env.NODE_ENV !== \"production\") && validityMessages.DK_INVALID_VALIDITY,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAeP,IAAM,WAAW;AAGV,IAAM,mBAA8D;AAAA,EACzE,qBAAqB;AAAA,IACnB,SAAS,CAAC,MACR,EAAE,WAAW,UACT,yBAAyB,EAAE,QAAQ,gBAAgB,QAAQ,KAC3D,0BAA0B,EAAE,QAAQ,gBAAgB,QAAQ;AAAA,IAClE,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,WAAW,UACT,qBAAqB,QAAQ,qEAE7B,QAAQ,QAAQ;AAAA,IACtB;AAAA,EACF;AACF;AAGO,IAAM,sBAAsB;AAAA,EACjC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,UACT,EAAE,MAAM,UAAU,MAAM,iBAAiB,IACzC,EAAE,MAAM,aAAa,IAAI,oBAAoB;AAAA,IACrD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,iBAAiB;AAAA,EAC9D;AACF;","names":[]}
|
|
@@ -42,7 +42,7 @@ var validity = () => (0, import_define.defineFeature)({
|
|
|
42
42
|
ctx.emit("DK_INVALID_VALIDITY", {
|
|
43
43
|
source: "setValidity",
|
|
44
44
|
received: received(value),
|
|
45
|
-
fallback: "
|
|
45
|
+
fallback: "validity-ignored"
|
|
46
46
|
});
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
@@ -54,7 +54,7 @@ var validity = () => (0, import_define.defineFeature)({
|
|
|
54
54
|
ctx.emit("DK_INVALID_VALIDITY", {
|
|
55
55
|
source: "state",
|
|
56
56
|
received: received(state),
|
|
57
|
-
fallback: "
|
|
57
|
+
fallback: "validity-all"
|
|
58
58
|
});
|
|
59
59
|
return indices;
|
|
60
60
|
}
|
|
@@ -64,7 +64,7 @@ var validity = () => (0, import_define.defineFeature)({
|
|
|
64
64
|
ctx.emit("DK_OPTION_NO_EFFECT", {
|
|
65
65
|
path: "state.validity",
|
|
66
66
|
feature: NAME,
|
|
67
|
-
reason: "validity
|
|
67
|
+
reason: "validity-needs-flag",
|
|
68
68
|
once: false
|
|
69
69
|
});
|
|
70
70
|
return indices;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/validity/validity.ts"],"sourcesContent":["import { optionDiagnostics } from '../../catalog/codes'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { validityDiagnostics } from './codes'\n\nkeepDiagnostics(validityDiagnostics, optionDiagnostics)\n\n/** Which rows the `validity` step keeps (§6.2). */\nexport type ValidityState = 'all' | 'valid' | 'invalid'\n\nexport interface ValidityApi {\n /** Shows every row, only the rows that passed the schema, or only the flagged ones. */\n setValidity(value: ValidityState): void\n}\n\nconst NAME = 'validity'\n\nconst isValidity = (v: unknown): v is ValidityState =>\n v === 'all' || v === 'valid' || v === 'invalid'\n\n/** A short string as written (`\"broken\"`); anything else by its type only. */\nconst received = (v: unknown) =>\n typeof v === 'string' && v.length <= 20 ? JSON.stringify(v) : describeValue(v)\n\n/**\n * Filters rows by schema validity (§6.2, §5.1). State `'all' | 'valid' | 'invalid'` (default\n * `'all'`); stage `filter`, so it runs with `columnFilters` before search.\n *\n * - Active only with `schema` + `invalidRows: 'flag'`. In any other mode the step passes its input\n * through, and a value other than `'all'` reports `DK_OPTION_NO_EFFECT` — from the step, so\n * `setValidity`, `setState`, `initialState` and controlled state are all covered.\n * - A slice that is not one of the three values reads as `'all'` (`DK_INVALID_VALIDITY`);\n * `setValidity` with one is ignored (same code).\n */\nexport const validity = () =>\n defineFeature({\n name: NAME,\n stage: 'filter',\n initialState: (): ValidityState => 'all',\n createApi: (ctx: FeatureContext<ValidityState>): ValidityApi => ({\n setValidity(value) {\n if (!isValidity(value)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'setValidity',\n received: received(value),\n fallback: '
|
|
1
|
+
{"version":3,"sources":["../../../src/features/validity/validity.ts"],"sourcesContent":["import { optionDiagnostics } from '../../catalog/codes'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { validityDiagnostics } from './codes'\n\nkeepDiagnostics(validityDiagnostics, optionDiagnostics)\n\n/** Which rows the `validity` step keeps (§6.2). */\nexport type ValidityState = 'all' | 'valid' | 'invalid'\n\nexport interface ValidityApi {\n /** Shows every row, only the rows that passed the schema, or only the flagged ones. */\n setValidity(value: ValidityState): void\n}\n\nconst NAME = 'validity'\n\nconst isValidity = (v: unknown): v is ValidityState =>\n v === 'all' || v === 'valid' || v === 'invalid'\n\n/** A short string as written (`\"broken\"`); anything else by its type only. */\nconst received = (v: unknown) =>\n typeof v === 'string' && v.length <= 20 ? JSON.stringify(v) : describeValue(v)\n\n/**\n * Filters rows by schema validity (§6.2, §5.1). State `'all' | 'valid' | 'invalid'` (default\n * `'all'`); stage `filter`, so it runs with `columnFilters` before search.\n *\n * - Active only with `schema` + `invalidRows: 'flag'`. In any other mode the step passes its input\n * through, and a value other than `'all'` reports `DK_OPTION_NO_EFFECT` — from the step, so\n * `setValidity`, `setState`, `initialState` and controlled state are all covered.\n * - A slice that is not one of the three values reads as `'all'` (`DK_INVALID_VALIDITY`);\n * `setValidity` with one is ignored (same code).\n */\nexport const validity = () =>\n defineFeature({\n name: NAME,\n stage: 'filter',\n initialState: (): ValidityState => 'all',\n createApi: (ctx: FeatureContext<ValidityState>): ValidityApi => ({\n setValidity(value) {\n if (!isValidity(value)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'setValidity',\n received: received(value),\n fallback: 'validity-ignored',\n })\n return\n }\n if (value !== ctx.getState()) ctx.setState(value)\n },\n }),\n rowModel: (indices, state, ctx) => {\n if (!isValidity(state)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'state',\n received: received(state),\n fallback: 'validity-all',\n })\n return indices\n }\n if (state === 'all') return indices\n const flagged = ctx.flagged\n if (!flagged) {\n ctx.emit('DK_OPTION_NO_EFFECT', {\n path: 'state.validity',\n feature: NAME,\n reason: 'validity-needs-flag',\n once: false,\n })\n return indices\n }\n const keep = state === 'invalid'\n const out = new Uint32Array(indices.length)\n let n = 0\n for (let i = 0; i < indices.length; i++) {\n const r = indices[i] as number\n if ((flagged(r) !== undefined) === keep) out[n++] = r\n }\n return n === indices.length ? indices : out.subarray(0, n)\n },\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAkC;AAClC,qBAAgC;AAChC,oBAA8B;AAC9B,oBAA8B;AAE9B,IAAAA,gBAAoC;AAAA,IAEpC,gCAAgB,mCAAqB,8BAAiB;AAUtD,IAAM,OAAO;AAEb,IAAM,aAAa,CAAC,MAClB,MAAM,SAAS,MAAM,WAAW,MAAM;AAGxC,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,EAAE,UAAU,KAAK,KAAK,UAAU,CAAC,QAAI,6BAAc,CAAC;AAYxE,IAAM,WAAW,UACtB,6BAAc;AAAA,EACZ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,cAAc,MAAqB;AAAA,EACnC,WAAW,CAAC,SAAqD;AAAA,IAC/D,YAAY,OAAO;AACjB,UAAI,CAAC,WAAW,KAAK,GAAG;AACtB,YAAI,KAAK,uBAAuB;AAAA,UAC9B,QAAQ;AAAA,UACR,UAAU,SAAS,KAAK;AAAA,UACxB,UAAU;AAAA,QACZ,CAAC;AACD;AAAA,MACF;AACA,UAAI,UAAU,IAAI,SAAS,EAAG,KAAI,SAAS,KAAK;AAAA,IAClD;AAAA,EACF;AAAA,EACA,UAAU,CAAC,SAAS,OAAO,QAAQ;AACjC,QAAI,CAAC,WAAW,KAAK,GAAG;AACtB,UAAI,KAAK,uBAAuB;AAAA,QAC9B,QAAQ;AAAA,QACR,UAAU,SAAS,KAAK;AAAA,QACxB,UAAU;AAAA,MACZ,CAAC;AACD,aAAO;AAAA,IACT;AACA,QAAI,UAAU,MAAO,QAAO;AAC5B,UAAM,UAAU,IAAI;AACpB,QAAI,CAAC,SAAS;AACZ,UAAI,KAAK,uBAAuB;AAAA,QAC9B,MAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,aAAO;AAAA,IACT;AACA,UAAM,OAAO,UAAU;AACvB,UAAM,MAAM,IAAI,YAAY,QAAQ,MAAM;AAC1C,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,IAAI,QAAQ,CAAC;AACnB,UAAK,QAAQ,CAAC,MAAM,WAAe,KAAM,KAAI,GAAG,IAAI;AAAA,IACtD;AACA,WAAO,MAAM,QAAQ,SAAS,UAAU,IAAI,SAAS,GAAG,CAAC;AAAA,EAC3D;AACF,CAAC;","names":["import_codes"]}
|
|
@@ -18,7 +18,7 @@ var validity = () => defineFeature({
|
|
|
18
18
|
ctx.emit("DK_INVALID_VALIDITY", {
|
|
19
19
|
source: "setValidity",
|
|
20
20
|
received: received(value),
|
|
21
|
-
fallback: "
|
|
21
|
+
fallback: "validity-ignored"
|
|
22
22
|
});
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
@@ -30,7 +30,7 @@ var validity = () => defineFeature({
|
|
|
30
30
|
ctx.emit("DK_INVALID_VALIDITY", {
|
|
31
31
|
source: "state",
|
|
32
32
|
received: received(state),
|
|
33
|
-
fallback: "
|
|
33
|
+
fallback: "validity-all"
|
|
34
34
|
});
|
|
35
35
|
return indices;
|
|
36
36
|
}
|
|
@@ -40,7 +40,7 @@ var validity = () => defineFeature({
|
|
|
40
40
|
ctx.emit("DK_OPTION_NO_EFFECT", {
|
|
41
41
|
path: "state.validity",
|
|
42
42
|
feature: NAME,
|
|
43
|
-
reason: "validity
|
|
43
|
+
reason: "validity-needs-flag",
|
|
44
44
|
once: false
|
|
45
45
|
});
|
|
46
46
|
return indices;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/validity/validity.ts"],"sourcesContent":["import { optionDiagnostics } from '../../catalog/codes'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { validityDiagnostics } from './codes'\n\nkeepDiagnostics(validityDiagnostics, optionDiagnostics)\n\n/** Which rows the `validity` step keeps (§6.2). */\nexport type ValidityState = 'all' | 'valid' | 'invalid'\n\nexport interface ValidityApi {\n /** Shows every row, only the rows that passed the schema, or only the flagged ones. */\n setValidity(value: ValidityState): void\n}\n\nconst NAME = 'validity'\n\nconst isValidity = (v: unknown): v is ValidityState =>\n v === 'all' || v === 'valid' || v === 'invalid'\n\n/** A short string as written (`\"broken\"`); anything else by its type only. */\nconst received = (v: unknown) =>\n typeof v === 'string' && v.length <= 20 ? JSON.stringify(v) : describeValue(v)\n\n/**\n * Filters rows by schema validity (§6.2, §5.1). State `'all' | 'valid' | 'invalid'` (default\n * `'all'`); stage `filter`, so it runs with `columnFilters` before search.\n *\n * - Active only with `schema` + `invalidRows: 'flag'`. In any other mode the step passes its input\n * through, and a value other than `'all'` reports `DK_OPTION_NO_EFFECT` — from the step, so\n * `setValidity`, `setState`, `initialState` and controlled state are all covered.\n * - A slice that is not one of the three values reads as `'all'` (`DK_INVALID_VALIDITY`);\n * `setValidity` with one is ignored (same code).\n */\nexport const validity = () =>\n defineFeature({\n name: NAME,\n stage: 'filter',\n initialState: (): ValidityState => 'all',\n createApi: (ctx: FeatureContext<ValidityState>): ValidityApi => ({\n setValidity(value) {\n if (!isValidity(value)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'setValidity',\n received: received(value),\n fallback: '
|
|
1
|
+
{"version":3,"sources":["../../../src/features/validity/validity.ts"],"sourcesContent":["import { optionDiagnostics } from '../../catalog/codes'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { validityDiagnostics } from './codes'\n\nkeepDiagnostics(validityDiagnostics, optionDiagnostics)\n\n/** Which rows the `validity` step keeps (§6.2). */\nexport type ValidityState = 'all' | 'valid' | 'invalid'\n\nexport interface ValidityApi {\n /** Shows every row, only the rows that passed the schema, or only the flagged ones. */\n setValidity(value: ValidityState): void\n}\n\nconst NAME = 'validity'\n\nconst isValidity = (v: unknown): v is ValidityState =>\n v === 'all' || v === 'valid' || v === 'invalid'\n\n/** A short string as written (`\"broken\"`); anything else by its type only. */\nconst received = (v: unknown) =>\n typeof v === 'string' && v.length <= 20 ? JSON.stringify(v) : describeValue(v)\n\n/**\n * Filters rows by schema validity (§6.2, §5.1). State `'all' | 'valid' | 'invalid'` (default\n * `'all'`); stage `filter`, so it runs with `columnFilters` before search.\n *\n * - Active only with `schema` + `invalidRows: 'flag'`. In any other mode the step passes its input\n * through, and a value other than `'all'` reports `DK_OPTION_NO_EFFECT` — from the step, so\n * `setValidity`, `setState`, `initialState` and controlled state are all covered.\n * - A slice that is not one of the three values reads as `'all'` (`DK_INVALID_VALIDITY`);\n * `setValidity` with one is ignored (same code).\n */\nexport const validity = () =>\n defineFeature({\n name: NAME,\n stage: 'filter',\n initialState: (): ValidityState => 'all',\n createApi: (ctx: FeatureContext<ValidityState>): ValidityApi => ({\n setValidity(value) {\n if (!isValidity(value)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'setValidity',\n received: received(value),\n fallback: 'validity-ignored',\n })\n return\n }\n if (value !== ctx.getState()) ctx.setState(value)\n },\n }),\n rowModel: (indices, state, ctx) => {\n if (!isValidity(state)) {\n ctx.emit('DK_INVALID_VALIDITY', {\n source: 'state',\n received: received(state),\n fallback: 'validity-all',\n })\n return indices\n }\n if (state === 'all') return indices\n const flagged = ctx.flagged\n if (!flagged) {\n ctx.emit('DK_OPTION_NO_EFFECT', {\n path: 'state.validity',\n feature: NAME,\n reason: 'validity-needs-flag',\n once: false,\n })\n return indices\n }\n const keep = state === 'invalid'\n const out = new Uint32Array(indices.length)\n let n = 0\n for (let i = 0; i < indices.length; i++) {\n const r = indices[i] as number\n if ((flagged(r) !== undefined) === keep) out[n++] = r\n }\n return n === indices.length ? indices : out.subarray(0, n)\n },\n })\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAE9B,SAAS,2BAA2B;AAEpC,gBAAgB,qBAAqB,iBAAiB;AAUtD,IAAM,OAAO;AAEb,IAAM,aAAa,CAAC,MAClB,MAAM,SAAS,MAAM,WAAW,MAAM;AAGxC,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,EAAE,UAAU,KAAK,KAAK,UAAU,CAAC,IAAI,cAAc,CAAC;AAYxE,IAAM,WAAW,MACtB,cAAc;AAAA,EACZ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,cAAc,MAAqB;AAAA,EACnC,WAAW,CAAC,SAAqD;AAAA,IAC/D,YAAY,OAAO;AACjB,UAAI,CAAC,WAAW,KAAK,GAAG;AACtB,YAAI,KAAK,uBAAuB;AAAA,UAC9B,QAAQ;AAAA,UACR,UAAU,SAAS,KAAK;AAAA,UACxB,UAAU;AAAA,QACZ,CAAC;AACD;AAAA,MACF;AACA,UAAI,UAAU,IAAI,SAAS,EAAG,KAAI,SAAS,KAAK;AAAA,IAClD;AAAA,EACF;AAAA,EACA,UAAU,CAAC,SAAS,OAAO,QAAQ;AACjC,QAAI,CAAC,WAAW,KAAK,GAAG;AACtB,UAAI,KAAK,uBAAuB;AAAA,QAC9B,QAAQ;AAAA,QACR,UAAU,SAAS,KAAK;AAAA,QACxB,UAAU;AAAA,MACZ,CAAC;AACD,aAAO;AAAA,IACT;AACA,QAAI,UAAU,MAAO,QAAO;AAC5B,UAAM,UAAU,IAAI;AACpB,QAAI,CAAC,SAAS;AACZ,UAAI,KAAK,uBAAuB;AAAA,QAC9B,MAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,aAAO;AAAA,IACT;AACA,UAAM,OAAO,UAAU;AACvB,UAAM,MAAM,IAAI,YAAY,QAAQ,MAAM;AAC1C,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,IAAI,QAAQ,CAAC;AACnB,UAAK,QAAQ,CAAC,MAAM,WAAe,KAAM,KAAI,GAAG,IAAI;AAAA,IACtD;AACA,WAAO,MAAM,QAAQ,SAAS,UAAU,IAAI,SAAS,GAAG,CAAC;AAAA,EAC3D;AACF,CAAC;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { D as Diagnostic, c as DiagnosticCode, d as DiagnosticDetails, a as EmitOptions, T as Trigger, P as Phase, j as DiagnosticStateSummary, f as RedactLevel, k as RedactionOptions, l as DiagnosticDefinition, I as InvalidRowsMode, e as TableSchema, S as StandardSchemaV1, E as Emit } from './codes-
|
|
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-.cjs';
|
|
2
|
+
export { B as BadResponse, m as BuildInput, n as DK_UNSAFE_REDACTION, o as DatakableConfigError, p as DatakableError, q as DatakableSchemaError, r as DiagnosticCatalog, s as DiagnosticContext, t as DiagnosticLevel, u as DiagnosticMessages, v as DiagnosticMeta, w as DiagnosticText, x as FlaggedInvalidRow, F as FlaggedRow, h as InvalidRow, L as Location, y as REDACTED, z as RedactField, g as RedactFn, A as Redactor, b as Row, R as RowIssue, i as RowKey, C as SCHEMA_INGEST, G as SENSITIVE, H as SchemaIssueColumn, J as SchemaOutcome, K as SlotDefinition, M as SlotReturns, V as ValidRow, N as asyncDiagnostics, O as boundaryDiagnostics, Q as buildDiagnostic, U as columnFiltersDiagnostics, W as comparatorDiagnostics, X as coreDiagnostics, Y as dataCheckDiagnostics, Z as dataMutationDiagnostics, _ as defineDiagnostic, $ as defineSlot, a0 as describeReturn, a1 as describeValue, a2 as envDiagnostics, a3 as featureDiagnostics, a4 as fetchDiagnostics, a5 as formatSlot, a6 as formatWhere, a7 as getDiagnosticDefinition, a8 as getDiagnosticText, a9 as getSlot, aa as hasMessages, ab as hashRowKey, ac as installMessages, ad as isStandardSchema, ae as keepDiagnostics, af as listDiagnosticCodes, ag as matchesSlot, ah as optionDiagnostics, ai as paginationDiagnostics, aj as pipelineDiagnostics, ak as resolveRedaction, al as rowKeyDiagnostics, am as schemaDiagnostics, an as scrubError, ao as scrubSchemaIssue, ap as searchDiagnostics, aq as slotDiagnostics, ar as sortingDiagnostics, as as ssrDiagnostics, at as stepDiagnostics, au as tableDiagnostics, av as throwConfigError, aw as validityDiagnostics, ax as versioningDiagnostics } from './codes-B9bCK73-.cjs';
|
|
3
|
+
import { S as Stage, A as AnyFeature, I as InterceptorRuntime, a as Interceptor, F as Feature, b as FixedTypes, c as FeatureContext, R as RowModelContext, d as AnyRow, e as FeatureTypes, O as OpName, f as InterceptorContext, g as OpResult, T as TableState, h as TableOptions, i as ServerFetcher, j as TableStateFor, k as Table, l as ApiOf } from './explain-Q8Cg0cZy.cjs';
|
|
4
|
+
export { m as AccessorColumn, n as Apply, B as BuiltinOperations, C as CELL_FALLBACK, o as CellContext, p as CellFailures, q as CellSlot, r as ColumnDef, s as ColumnValues, t as ContextBase, u as ContextPool, v as ControlledState, w as Core, E as ExplainStep, x as FetchOutcome, y as FetchParams, z as FetchResult, D as Fetcher, G as FilterFor, H as HotOpWarning, J as InterceptorRuntimeOptions, K as InvalidCellContext, L as IsAny, M as KeyColumn, N as Listener, P as LooseRow, Q as OpInput, U as OpKind, V as OpOutput, W as OpSpec, X as OperationInfo, Y as Operations, Z as OptionRule, _ as OptionType, $ as OptionsDefinition, a0 as OptionsSchema, a1 as Path, a2 as PathValue, a3 as Primitive, a4 as RenderReq, a5 as RowExplanation, a6 as RowModel, a7 as RowModelStatus, a8 as RowOf, a9 as RowStep, aa as RowStepOutcome, ab as SERVER_SOURCE, ac as SliceUpdater, ad as SortAlgorithm, ae as StateOf, af as TableDiagnosticsOptions, ag as TableExplanation, ah as UPDATE_DEPTH_LIMIT, ai as Updater, aj as createContext, ak as createContextPool, al as createInterceptorRuntime, am as defineInterceptor, an as defineOptions, ao as emitTableDiagnostic, ap as getOperation, aq as isOptionsDefinition, ar as listOperations, as as registerOperation, at as runCells } from './explain-Q8Cg0cZy.cjs';
|
|
5
5
|
import { ColumnFiltersTypes } from './column-filters.cjs';
|
|
6
6
|
export { ColumnFilter, ColumnFiltersApi, ColumnFiltersOptions, ColumnFiltersState, ContainsFilter, CustomFilter, EqualsFilter, InFilter, RangeFilter, RangeValue, columnFilters } from './column-filters.cjs';
|
|
7
7
|
import { PaginationState, PaginationApi } from './pagination.cjs';
|
|
8
8
|
export { PaginationOptions, pagination } from './pagination.cjs';
|
|
9
9
|
import { SearchApi } from './search.cjs';
|
|
10
10
|
export { SearchOptions, SearchTyping, SearchWait, search } from './search.cjs';
|
|
11
|
-
import { S as SearchState } from './search-async-entry-
|
|
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-CuLYPfY_.cjs';
|
|
12
|
+
export { A as AsyncSearchFn, a as SEARCH_ENGINE, b as SearchFn, c as SearchFnContext, d as SearchOutcome, e as SearchResult, f as SearchStatus, w as withSearchFn } from './search-async-entry-CuLYPfY_.cjs';
|
|
13
13
|
import { SortingTypes } from './sorting.cjs';
|
|
14
14
|
export { SortAlgorithmOption, SortDirection, SortEntry, SortingApi, SortingOptions, SortingState, compareValues, sorting, typeRank } from './sorting.cjs';
|
|
15
15
|
import { ValidityState, ValidityApi } from './validity.cjs';
|
|
@@ -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.
|
|
@@ -142,12 +143,12 @@ declare function createDiagnostics(options?: DiagnosticsOptions): Diagnostics;
|
|
|
142
143
|
*/
|
|
143
144
|
declare function toSafeJson(value: unknown): unknown;
|
|
144
145
|
|
|
145
|
-
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch';
|
|
146
|
+
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch' | 'column:batch';
|
|
146
147
|
/**
|
|
147
148
|
* The `tracer` interceptor (§3.4): timings and per-step counts of the last pipeline run, read by
|
|
148
149
|
* `table.explain()`. `createTable` installs it in development; in production it is off (§8.2) and
|
|
149
150
|
* opt-in with `createTable({ interceptors: [tracer()] })`. Keeps the last run only: counts, never
|
|
150
|
-
* rows.
|
|
151
|
+
* rows. Time-sliced work (§9.1) is added to the step that waited on it (#61).
|
|
151
152
|
*/
|
|
152
153
|
declare function tracer(): Interceptor<Traced>;
|
|
153
154
|
|
|
@@ -313,7 +314,9 @@ declare function createTable<T, S extends TableState = never, const Fs extends r
|
|
|
313
314
|
* starts no async work. The fetcher's first request and a `searchFn` for an initial query wait for
|
|
314
315
|
* `startTable(table)`; meanwhile the row model has `status: 'pending'`. Nothing is started, so a
|
|
315
316
|
* server render leaves no request, timer or microtask chain behind. On the server (no
|
|
316
|
-
* `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).
|
|
317
320
|
*
|
|
318
321
|
* `@datakable/react`'s `useDataTable` creates its table this way and starts it after the commit;
|
|
319
322
|
* effects never run on the server.
|
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.
|
|
@@ -142,12 +143,12 @@ declare function createDiagnostics(options?: DiagnosticsOptions): Diagnostics;
|
|
|
142
143
|
*/
|
|
143
144
|
declare function toSafeJson(value: unknown): unknown;
|
|
144
145
|
|
|
145
|
-
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch';
|
|
146
|
+
type Traced = 'pipeline:run' | 'pipeline:step' | 'state:dispatch' | 'column:batch';
|
|
146
147
|
/**
|
|
147
148
|
* The `tracer` interceptor (§3.4): timings and per-step counts of the last pipeline run, read by
|
|
148
149
|
* `table.explain()`. `createTable` installs it in development; in production it is off (§8.2) and
|
|
149
150
|
* opt-in with `createTable({ interceptors: [tracer()] })`. Keeps the last run only: counts, never
|
|
150
|
-
* rows.
|
|
151
|
+
* rows. Time-sliced work (§9.1) is added to the step that waited on it (#61).
|
|
151
152
|
*/
|
|
152
153
|
declare function tracer(): Interceptor<Traced>;
|
|
153
154
|
|
|
@@ -313,7 +314,9 @@ declare function createTable<T, S extends TableState = never, const Fs extends r
|
|
|
313
314
|
* starts no async work. The fetcher's first request and a `searchFn` for an initial query wait for
|
|
314
315
|
* `startTable(table)`; meanwhile the row model has `status: 'pending'`. Nothing is started, so a
|
|
315
316
|
* server render leaves no request, timer or microtask chain behind. On the server (no
|
|
316
|
-
* `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).
|
|
317
320
|
*
|
|
318
321
|
* `@datakable/react`'s `useDataTable` creates its table this way and starts it after the commit;
|
|
319
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":";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;AAElB,QAAI,EAAE,OAAO,QAAS,OAAO,OAAO,aAAa,CAAC,OAAO,OAAO,KAAK,GAAG,EAAI,QAAO;AACnF,QAAI,IAAI,KAAK,SAAS,EAAG,OAAO,IAAgC,GAAG;AAAA,EACrE;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,GAAW,GAAoB;AACtD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI,IAAI;AACR,SAAO,IAAI,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG;AACtC,SACE,IAAI,EAAE,SAAS,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;AAElG;AAOA,SAAS,YAAY,MAAc,YAAmC;AACpE,QAAM,IAAI,KAAK,YAAY;AAC3B,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,WAAY,KAAI,gBAAgB,GAAG,EAAE,YAAY,CAAC,EAAG,KAAI,KAAK,CAAC;AAC/E,aAAW,KAAK,QAAQ,MAAM,UAAU,EAAG,KAAI,CAAC,IAAI,SAAS,CAAC,EAAG,KAAI,KAAK,CAAC;AAC3E,SAAO;AACT;AAGA,SAAS,eAAe,MAA0B,QAA6B;AAC7E,QAAM,MAAM,oBAAI,IAAY;AAC5B,QAAM,OAAO,CAAC,KAA8B,QAAgB,UAAkB;AAC5E,eAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,UAAI,IAAI,QAAQ,eAAgB;AAChC,YAAM,IAAI,SAAS;AACnB,UAAI,IAAI,CAAC;AACT,UAAI,QAAQ,WAAW;AACrB,cAAM,IAAI,IAAI,CAAC;AACf,YAAI,SAAS,CAAC,EAAG,MAAK,GAAG,GAAG,CAAC,KAAK,QAAQ,CAAC;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,iBAAe,MAAM,QAAQ,CAAC,QAAQ,KAAK,KAAK,IAAI,CAAC,CAAC;AACtD,SAAO;AACT;AAeO,SAAS,iBAAiB,MAAY,SAAiB,eAA2B;AACvF,MAAI;AACJ,MAAI;AAEJ,WAAS,WAAW,MAA0B,SAA6B;AACzE,QAAI;AACJ,eAAW,OAAO,eAAe,OAAO,EAAE,MAAM;AAC9C,UAAI,CAAC,IAAI,KAAM;AACf,gBAAU,MAAM,KAAK,MAAM;AACzB,uBAAe,eAAe,MAAM,MAAM;AAC1C,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AAEA,WAAS,UAAU,MAA0B,KAAqB,OAA0B;AAC1F,UAAM,OAAO,IAAI;AACjB,QAAI,UAAU;AACd,QAAI,QAAQ;AACZ,UAAM,OAAiB,CAAC;AAExB,mBAAe,MAAM,QAAQ,CAAC,KAAK,UAAU;AAC3C,YAAM,IAAI,MAAM,KAAK,IAAI;AACzB;AACA,UAAI,MAAM,QAAQ;AAChB;AACA,YAAI,KAAK,SAAS,SAAU,MAAK,KAAK,KAAK;AAAA,MAC7C;AAAA,IACF,CAAC;AACD,QAAI,UAAU,EAAG;AACjB,QAAI,UAAU,SAAS;AACrB,YAAM,cAAc,YAAY,IAAI,IAAI,MAAM,CAAC;AAC/C,WAAK,4BAA4B,EAAE,QAAQ,IAAI,IAAI,OAAO,IAAI,OAAO,SAAS,YAAY,CAAC;AAAA,IAC7F,OAAO;AACL,WAAK,0BAA0B,EAAE,QAAQ,IAAI,IAAI,OAAO,IAAI,OAAO,SAAS,OAAO,KAAK,CAAC;AAAA,IAC3F;AAAA,EACF;AAEA,WAAS,WAAW,MAA0B;AAC5C,UAAM,UAA+C,CAAC;AACtD,UAAM,UAAU,oBAAI,IAAoB;AACxC,mBAAe,MAAM,QAAQ,CAAC,KAAK,UAAU;AAC3C,YAAM,OAAO,OAAO,KAAK,GAAG;AAC5B,cAAQ,KAAK,EAAE,OAAO,KAAK,CAAC;AAC5B,iBAAW,KAAK,KAAM,SAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,IAChE,CAAC;AACD,UAAM,UAAU,QAAQ;AACxB,UAAM,YAAgD,CAAC;AACvD,eAAW,CAAC,KAAK,CAAC,KAAK,QAAS,KAAI,IAAI,QAAS,WAAU,KAAK,EAAE,KAAK,SAAS,UAAU,EAAE,CAAC;AAC7F,QAAI,UAAU,WAAW,EAAG;AAC5B,UAAM,OAAiB,CAAC;AACxB,QAAI,WAAW;AACf,eAAW,KAAK,SAAS;AACvB,UAAI,EAAE,KAAK,WAAW,QAAQ,KAAM;AACpC;AACA,UAAI,KAAK,SAAS,SAAU,MAAK,KAAK,EAAE,KAAK;AAAA,IAC/C;AACA,SAAK,6BAA6B;AAAA,MAChC;AAAA,MACA,MAAM,UAAU,MAAM,GAAG,QAAQ;AAAA,MACjC,UAAU,UAAU;AAAA,MACpB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,MAAM,SAAS;AACnB,UAAI,SAAS,YAAY,YAAY,YAAa;AAClD,iBAAW;AACX,oBAAc;AACd,UAAI,KAAK,WAAW,EAAG;AACvB,UAAI,QAAS,YAAW,MAAM,OAAO;AAAA,UAChC,YAAW,IAAI;AAAA,IACtB;AAAA,EACF;AACF;","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)`);
|