@datakable/core 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{codes-BKdS9EiO.d.cts → codes-OsjAR3Wz.d.cts} +16 -4
  2. package/dist/{codes-BKdS9EiO.d.ts → codes-OsjAR3Wz.d.ts} +16 -4
  3. package/dist/column-filters.d.cts +2 -2
  4. package/dist/column-filters.d.ts +2 -2
  5. package/dist/diagnostics/build.cjs +6 -3
  6. package/dist/diagnostics/build.cjs.map +1 -1
  7. package/dist/diagnostics/build.js +4 -2
  8. package/dist/diagnostics/build.js.map +1 -1
  9. package/dist/diagnostics/hub.cjs +1 -1
  10. package/dist/diagnostics/hub.cjs.map +1 -1
  11. package/dist/diagnostics/hub.js +2 -2
  12. package/dist/diagnostics/hub.js.map +1 -1
  13. package/dist/{explain-DcUZfFh5.d.ts → explain-Dn99IQt0.d.ts} +1 -1
  14. package/dist/{explain-aagaYHcy.d.cts → explain-vkD2cewE.d.cts} +1 -1
  15. package/dist/feature/define.cjs +3 -1
  16. package/dist/feature/define.cjs.map +1 -1
  17. package/dist/feature/define.js +3 -1
  18. package/dist/feature/define.js.map +1 -1
  19. package/dist/feature/runtime.cjs.map +1 -1
  20. package/dist/feature/runtime.js.map +1 -1
  21. package/dist/features/pagination/codes.cjs +5 -3
  22. package/dist/features/pagination/codes.cjs.map +1 -1
  23. package/dist/features/pagination/codes.js +5 -3
  24. package/dist/features/pagination/codes.js.map +1 -1
  25. package/dist/features/pagination/pagination.cjs +3 -1
  26. package/dist/features/pagination/pagination.cjs.map +1 -1
  27. package/dist/features/pagination/pagination.js +3 -1
  28. package/dist/features/pagination/pagination.js.map +1 -1
  29. package/dist/features/search/search-index.cjs +38 -34
  30. package/dist/features/search/search-index.cjs.map +1 -1
  31. package/dist/features/search/search-index.js +39 -35
  32. package/dist/features/search/search-index.js.map +1 -1
  33. package/dist/features/search/search.cjs +42 -16
  34. package/dist/features/search/search.cjs.map +1 -1
  35. package/dist/features/search/search.js +42 -16
  36. package/dist/features/search/search.js.map +1 -1
  37. package/dist/features/sorting/sorting.cjs +3 -3
  38. package/dist/features/sorting/sorting.cjs.map +1 -1
  39. package/dist/features/sorting/sorting.js +3 -3
  40. package/dist/features/sorting/sorting.js.map +1 -1
  41. package/dist/index.d.cts +9 -7
  42. package/dist/index.d.ts +9 -7
  43. package/dist/intercept/cell-batch.cjs +3 -0
  44. package/dist/intercept/cell-batch.cjs.map +1 -1
  45. package/dist/intercept/cell-batch.js +3 -0
  46. package/dist/intercept/cell-batch.js.map +1 -1
  47. package/dist/pagination.d.cts +2 -2
  48. package/dist/pagination.d.ts +2 -2
  49. package/dist/pipeline/row.cjs +8 -7
  50. package/dist/pipeline/row.cjs.map +1 -1
  51. package/dist/pipeline/row.js +8 -7
  52. package/dist/pipeline/row.js.map +1 -1
  53. package/dist/schema.d.cts +2 -2
  54. package/dist/schema.d.ts +2 -2
  55. package/dist/{search-async-entry-BmQYT120.d.ts → search-async-entry-DGSw2pDL.d.ts} +2 -2
  56. package/dist/{search-async-entry-C3mXxLP8.d.cts → search-async-entry-v5JdEk5Y.d.cts} +2 -2
  57. package/dist/search-async.d.cts +3 -3
  58. package/dist/search-async.d.ts +3 -3
  59. package/dist/search.d.cts +4 -4
  60. package/dist/search.d.ts +4 -4
  61. package/dist/server.d.cts +3 -3
  62. package/dist/server.d.ts +3 -3
  63. package/dist/sorting.d.cts +6 -3
  64. package/dist/sorting.d.ts +6 -3
  65. package/dist/validity.d.cts +2 -2
  66. package/dist/validity.d.ts +2 -2
  67. package/dist/version.cjs +1 -1
  68. package/dist/version.cjs.map +1 -1
  69. package/dist/version.js +1 -1
  70. package/dist/version.js.map +1 -1
  71. package/package.json +1 -1
@@ -353,19 +353,27 @@ interface RowSource {
353
353
  /** The issues of a flagged row that touch column `id` (`matchCellIssues`). */
354
354
  cellIssues(issues: readonly RowIssue[], id: string): readonly RowIssue[];
355
355
  }
356
+ /**
357
+ * The key a {@link Row} keeps its {@link RowSource} under. A symbol, not a `#private` field: a
358
+ * proxy-based store (Vue `ref` / `reactive`, Pinia, MobX) calls the getters and methods with
359
+ * `this` = its proxy, which has no private slot, so `#source` threw there (#11). A symbol key
360
+ * reads through any proxy, stays out of `Object.keys` and JSON, and is not exported.
361
+ */
362
+ declare const SOURCE: unique symbol;
356
363
  /**
357
364
  * One visible row (§6.6). Created only for the rows `getRowModel()` returns, never for the whole
358
365
  * data set (§9.1), and only when read ({@link lazyRows}); the methods live on the prototype, so a
359
366
  * row costs its four fields.
360
367
  */
361
368
  declare class Row<T = unknown> {
362
- #private;
363
369
  /** The row's `rowKey` value (the row index without `rowKey`). */
364
370
  readonly key: RowKey;
365
371
  /** Position in the ingested data. */
366
372
  readonly index: number;
367
373
  /** The row object as passed in `data`. */
368
374
  readonly original: T;
375
+ /** @internal */
376
+ private readonly [SOURCE];
369
377
  /** @internal Rows are created by the pipeline. */
370
378
  constructor(source: RowSource, index: number, key: RowKey, original: T);
371
379
  /** `false` for a row flagged by schema validation (`invalidRows: 'flag'`). */
@@ -405,7 +413,7 @@ interface BuildInput {
405
413
  phase?: Phase;
406
414
  mode?: Record<string, 'client' | 'server'>;
407
415
  state?: DiagnosticStateSummary;
408
- /** Timestamp for `occurrences`. Default `Date.now()`. */
416
+ /** Timestamp for `occurrences` (epoch ms). Default `wallClock()`. */
409
417
  now?: number;
410
418
  cause?: unknown;
411
419
  causedBy?: string;
@@ -680,8 +688,12 @@ declare const featureDiagnostics: (DiagnosticDefinition<"DK_FEATURE_DUPLICATE">
680
688
  declare module '@datakable/core' {
681
689
  interface DiagnosticCatalog {
682
690
  DK_INVALID_PAGINATION: {
683
- /** The API method that received the value, or `'state'` for a slice written to the store. */
684
- source: 'setPageIndex' | 'setPageSize' | 'state';
691
+ /**
692
+ * The API method that received the value, `'state'` for a slice written to the store, or
693
+ * `'option'` for `pagination({ pageSize })` (checked here in production, where the option
694
+ * validator is off).
695
+ */
696
+ source: 'setPageIndex' | 'setPageSize' | 'state' | 'option';
685
697
  field: 'pageIndex' | 'pageSize';
686
698
  /** The value's type only (`describeValue`), plus the number itself when it is one. */
687
699
  received: string;
@@ -353,19 +353,27 @@ interface RowSource {
353
353
  /** The issues of a flagged row that touch column `id` (`matchCellIssues`). */
354
354
  cellIssues(issues: readonly RowIssue[], id: string): readonly RowIssue[];
355
355
  }
356
+ /**
357
+ * The key a {@link Row} keeps its {@link RowSource} under. A symbol, not a `#private` field: a
358
+ * proxy-based store (Vue `ref` / `reactive`, Pinia, MobX) calls the getters and methods with
359
+ * `this` = its proxy, which has no private slot, so `#source` threw there (#11). A symbol key
360
+ * reads through any proxy, stays out of `Object.keys` and JSON, and is not exported.
361
+ */
362
+ declare const SOURCE: unique symbol;
356
363
  /**
357
364
  * One visible row (§6.6). Created only for the rows `getRowModel()` returns, never for the whole
358
365
  * data set (§9.1), and only when read ({@link lazyRows}); the methods live on the prototype, so a
359
366
  * row costs its four fields.
360
367
  */
361
368
  declare class Row<T = unknown> {
362
- #private;
363
369
  /** The row's `rowKey` value (the row index without `rowKey`). */
364
370
  readonly key: RowKey;
365
371
  /** Position in the ingested data. */
366
372
  readonly index: number;
367
373
  /** The row object as passed in `data`. */
368
374
  readonly original: T;
375
+ /** @internal */
376
+ private readonly [SOURCE];
369
377
  /** @internal Rows are created by the pipeline. */
370
378
  constructor(source: RowSource, index: number, key: RowKey, original: T);
371
379
  /** `false` for a row flagged by schema validation (`invalidRows: 'flag'`). */
@@ -405,7 +413,7 @@ interface BuildInput {
405
413
  phase?: Phase;
406
414
  mode?: Record<string, 'client' | 'server'>;
407
415
  state?: DiagnosticStateSummary;
408
- /** Timestamp for `occurrences`. Default `Date.now()`. */
416
+ /** Timestamp for `occurrences` (epoch ms). Default `wallClock()`. */
409
417
  now?: number;
410
418
  cause?: unknown;
411
419
  causedBy?: string;
@@ -680,8 +688,12 @@ declare const featureDiagnostics: (DiagnosticDefinition<"DK_FEATURE_DUPLICATE">
680
688
  declare module '@datakable/core' {
681
689
  interface DiagnosticCatalog {
682
690
  DK_INVALID_PAGINATION: {
683
- /** The API method that received the value, or `'state'` for a slice written to the store. */
684
- source: 'setPageIndex' | 'setPageSize' | 'state';
691
+ /**
692
+ * The API method that received the value, `'state'` for a slice written to the store, or
693
+ * `'option'` for `pagination({ pageSize })` (checked here in production, where the option
694
+ * validator is off).
695
+ */
696
+ source: 'setPageIndex' | 'setPageSize' | 'state' | 'option';
685
697
  field: 'pageIndex' | 'pageSize';
686
698
  /** The value's type only (`describeValue`), plus the number itself when it is one. */
687
699
  received: string;
@@ -1,5 +1,5 @@
1
- export { U as columnFiltersDiagnostics } from './codes-BKdS9EiO.cjs';
2
- import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-aagaYHcy.cjs';
1
+ export { U as columnFiltersDiagnostics } from './codes-OsjAR3Wz.cjs';
2
+ import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-vkD2cewE.cjs';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.cjs';
4
4
 
5
5
  /** The value a filter compares against: the element type for array-valued cells (§6.1). */
@@ -1,5 +1,5 @@
1
- export { U as columnFiltersDiagnostics } from './codes-BKdS9EiO.js';
2
- import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-DcUZfFh5.js';
1
+ export { U as columnFiltersDiagnostics } from './codes-OsjAR3Wz.js';
2
+ import { G as FilterFor, a1 as Path, P as LooseRow, a2 as PathValue, e as FeatureTypes, F as Feature } from './explain-Dn99IQt0.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
4
 
5
5
  /** The value a filter compares against: the element type for array-valued cells (§6.1). */
@@ -27,7 +27,8 @@ __export(build_exports, {
27
27
  defaultPhase: () => defaultPhase,
28
28
  formatWhere: () => formatWhere,
29
29
  internalError: () => internalError,
30
- throwConfigError: () => throwConfigError
30
+ throwConfigError: () => throwConfigError,
31
+ wallClock: () => wallClock
31
32
  });
32
33
  module.exports = __toCommonJS(build_exports);
33
34
  var import_version = require("../version.cjs");
@@ -38,6 +39,7 @@ var import_safe_json = require("./safe-json.cjs");
38
39
  var PACKAGE = `@datakable/core@${import_version.VERSION}`;
39
40
  var DOCS = "https://github.com/Ink01101011/datakable/blob/main/docs/errors.md";
40
41
  var INTERNAL = import_codes.coreDiagnostics.find((d) => d.code === "DK_INTERNAL");
42
+ var wallClock = () => typeof performance === "object" && performance?.timeOrigin ? Math.floor(performance.timeOrigin + performance.now()) : Date.now();
41
43
  var defaultPhase = () => typeof document === "undefined" ? "ssr" : "client";
42
44
  function formatWhere(where) {
43
45
  switch (where.kind) {
@@ -107,7 +109,7 @@ function buildDiagnostic(code, details, input = {}) {
107
109
  const dd = d;
108
110
  const t = dev || (0, import_catalog.hasMessages)() ? (0, import_catalog.getDiagnosticText)(def.code) : void 0;
109
111
  const whereText = formatWhere(where);
110
- const now = input.now ?? Date.now();
112
+ const now = input.now ?? wallClock();
111
113
  const context = {
112
114
  table: input.table ?? "(global)",
113
115
  package: PACKAGE,
@@ -180,6 +182,7 @@ function internalError(op, problem) {
180
182
  defaultPhase,
181
183
  formatWhere,
182
184
  internalError,
183
- throwConfigError
185
+ throwConfigError,
186
+ wallClock
184
187
  });
185
188
  //# sourceMappingURL=build.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/build.ts"],"sourcesContent":["import { VERSION } from '../version'\nimport {\n type DiagnosticCode,\n type DiagnosticDefinition,\n type DiagnosticDetails,\n type DiagnosticText,\n getDiagnosticDefinition,\n getDiagnosticText,\n hasMessages,\n} from './catalog'\nimport { coreDiagnostics } from './codes'\nimport {\n type RedactField,\n type Redactor,\n redactDetails,\n redactLocation,\n resolveRedaction,\n} from './redact'\nimport { toSafeJson } from './safe-json'\nimport type {\n Diagnostic,\n DiagnosticContext,\n DiagnosticLevel,\n DiagnosticStateSummary,\n Location,\n Phase,\n Trigger,\n} from './types'\n\nexport const PACKAGE = `@datakable/core@${VERSION}`\nconst DOCS = 'https://github.com/Ink01101011/datakable/blob/main/docs/errors.md'\n\n// Referencing the core entries keeps them in the bundle (`sideEffects: false`).\nconst INTERNAL = coreDiagnostics.find((d) => d.code === 'DK_INTERNAL') as DiagnosticDefinition\n\n/** Everything the enrich step adds; all optional so a diagnostic can be built outside a table. */\nexport interface BuildInput {\n table?: string\n createdAt?: string\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n /** Timestamp for `occurrences`. Default `Date.now()`. */\n now?: number\n cause?: unknown\n causedBy?: string\n /** Build the problem / impact / fix text. Default: development builds only (§8.2); production\n * builds also build it once `@datakable/core/messages` is installed. */\n dev?: boolean\n /** Redaction policy (§8.2). Default: `'keys'` when `dev`, else `'safe'`. */\n redaction?: Redactor\n}\n\nexport const defaultPhase = (): Phase => (typeof document === 'undefined' ? 'ssr' : 'client')\n\nexport function formatWhere(where: Location): string {\n switch (where.kind) {\n case 'column':\n return `columns[${where.index}] (id \"${where.id}\")`\n case 'feature':\n return `feature \"${where.name}\"`\n case 'option':\n return `options.${where.path}`\n case 'row':\n return where.key === undefined\n ? `row #${where.index}`\n : `row #${where.index} (key ${where.key})`\n case 'interceptor':\n return `interceptor \"${where.name}\" on ${where.op}`\n case 'operation':\n return where.slot ? `operation ${where.op} at ${where.slot}` : `operation ${where.op}`\n }\n}\n\nclass DiagnosticRecord implements Diagnostic {\n constructor(\n public code: DiagnosticCode,\n public level: DiagnosticLevel,\n public problem: string,\n public where: Location,\n public impact: string,\n public fix: string[],\n public docs: string,\n public details: Record<string, unknown>,\n public context: DiagnosticContext,\n public fingerprint: string,\n ) {}\n\n toJSON(): object {\n const { cause: _cause, ...context } = this.context\n return {\n code: this.code,\n level: this.level,\n problem: this.problem,\n where: toSafeJson(this.where),\n impact: this.impact,\n fix: this.fix,\n docs: this.docs,\n details: toSafeJson(this.details),\n context: toSafeJson(context),\n fingerprint: this.fingerprint,\n }\n }\n}\n\n/**\n * Builds a diagnostic from a catalog code + details (the `enrich` step). Never throws for an unknown\n * code: that becomes `DK_INTERNAL`, so a typo in a plugin cannot take the logger down.\n */\nexport function buildDiagnostic<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input: BuildInput = {},\n): Diagnostic {\n let def = getDiagnosticDefinition(code)\n let d = details as Record<string, unknown>\n if (!def) {\n def = INTERNAL\n d = {\n op: 'diagnostic:emit',\n problem: `unknown diagnostic code \"${code}\": define it with defineDiagnostic() first`,\n }\n }\n const b = def as unknown as DiagnosticDefinition<'DK_INTERNAL'>\n const dev = input.dev ?? (process.env.NODE_ENV !== \"production\")\n // Redact first: `where`, the text builders, the fingerprint and the docs URL only ever see\n // redacted data, so no consumer downstream (interceptors, sinks, strict errors) can leak it.\n const r = input.redaction ?? resolveRedaction(undefined, dev)\n const where = redactLocation(b.where(d as DiagnosticDetails<'DK_INTERNAL'>), r)\n d = redactDetails(\n d,\n def.redact as Readonly<Record<string, RedactField | undefined>> | undefined,\n where,\n r,\n )\n const dd = d as DiagnosticDetails<'DK_INTERNAL'>\n // Text: development builds, or production with `@datakable/core/messages` installed (§8.2).\n const t =\n dev || hasMessages()\n ? (getDiagnosticText(def.code) as DiagnosticText<'DK_INTERNAL'> | undefined)\n : undefined\n const whereText = formatWhere(where)\n const now = input.now ?? Date.now()\n const context: DiagnosticContext = {\n table: input.table ?? '(global)',\n package: PACKAGE,\n trigger: input.trigger ?? 'init',\n run: input.run ?? 0,\n phase: input.phase ?? defaultPhase(),\n mode: input.mode ?? {},\n state: input.state ?? { searchLength: 0, activeFilters: [], sort: [], page: 0 },\n occurrences: { count: 1, first: now, last: now },\n }\n if (input.createdAt !== undefined) context.createdAt = input.createdAt\n if (input.causedBy !== undefined) context.causedBy = input.causedBy\n if (input.cause !== undefined) context.cause = input.cause\n let docs = def.docs ?? `${DOCS}#${def.code.toLowerCase()}`\n if (!dev) {\n const hash = docs.indexOf('#')\n const params = `?v=${VERSION}&where=${encodeURIComponent(whereText)}`\n docs = hash < 0 ? docs + params : docs.slice(0, hash) + params + docs.slice(hash)\n }\n return new DiagnosticRecord(\n def.code,\n def.level,\n t ? t.problem(dd) : '',\n where,\n t ? t.impact(dd) : '',\n t ? t.fix(dd) : [],\n docs,\n d,\n context,\n `${def.code}@${whereText}`,\n )\n}\n\n/** Thrown under `strict: true` for error-level diagnostics, and for core invariants (§8.2). */\nexport class DatakableError extends Error {\n override name = 'DatakableError'\n readonly code: DiagnosticCode\n readonly level: DiagnosticLevel\n readonly problem: string\n readonly where: Location\n readonly impact: string\n readonly fix: string[]\n readonly docs: string\n readonly diagnostic: Diagnostic\n\n constructor(d: Diagnostic) {\n const lines = [`${d.code}: ${d.problem || '(see docs)'}`, ` where ${formatWhere(d.where)}`]\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n super(lines.join('\\n'), d.context.cause === undefined ? undefined : { cause: d.context.cause })\n this.code = d.code\n this.level = d.level\n this.problem = d.problem\n this.where = d.where\n this.impact = d.impact\n this.fix = d.fix\n this.docs = d.docs\n this.diagnostic = d\n }\n}\n\n/** Deterministic programmer errors thrown at creation (`error (config)` codes, §8.6). */\nexport class DatakableConfigError extends DatakableError {\n override name = 'DatakableConfigError'\n}\n\nexport function throwConfigError<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input?: BuildInput,\n): never {\n throw new DatakableConfigError(buildDiagnostic(code, details, input))\n}\n\n/** A core invariant broke (unregistered op, boundary installed twice): `DK_INTERNAL`. */\nexport function internalError(op: string, problem: string): DatakableError {\n return new DatakableError(buildDiagnostic('DK_INTERNAL', { op, problem }))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,qBAQO;AACP,mBAAgC;AAChC,oBAMO;AACP,uBAA2B;AAWpB,IAAM,UAAU,mBAAmB,sBAAO;AACjD,IAAM,OAAO;AAGb,IAAM,WAAW,6BAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,aAAa;AAsB9D,IAAM,eAAe,MAAc,OAAO,aAAa,cAAc,QAAQ;AAE7E,SAAS,YAAY,OAAyB;AACnD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,WAAW,MAAM,KAAK,UAAU,MAAM,EAAE;AAAA,IACjD,KAAK;AACH,aAAO,YAAY,MAAM,IAAI;AAAA,IAC/B,KAAK;AACH,aAAO,WAAW,MAAM,IAAI;AAAA,IAC9B,KAAK;AACH,aAAO,MAAM,QAAQ,SACjB,QAAQ,MAAM,KAAK,KACnB,QAAQ,MAAM,KAAK,SAAS,MAAM,GAAG;AAAA,IAC3C,KAAK;AACH,aAAO,gBAAgB,MAAM,IAAI,QAAQ,MAAM,EAAE;AAAA,IACnD,KAAK;AACH,aAAO,MAAM,OAAO,aAAa,MAAM,EAAE,OAAO,MAAM,IAAI,KAAK,aAAa,MAAM,EAAE;AAAA,EACxF;AACF;AAEA,IAAM,mBAAN,MAA6C;AAAA,EAC3C,YACS,MACA,OACA,SACA,OACA,QACA,KACA,MACA,SACA,SACA,aACP;AAVO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA,EACN;AAAA,EAEH,SAAiB;AACf,UAAM,EAAE,OAAO,QAAQ,GAAG,QAAQ,IAAI,KAAK;AAC3C,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,WAAO,6BAAW,KAAK,KAAK;AAAA,MAC5B,QAAQ,KAAK;AAAA,MACb,KAAK,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,aAAS,6BAAW,KAAK,OAAO;AAAA,MAChC,aAAS,6BAAW,OAAO;AAAA,MAC3B,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AACF;AAMO,SAAS,gBACd,MACA,SACA,QAAoB,CAAC,GACT;AACZ,MAAI,UAAM,wCAAwB,IAAI;AACtC,MAAI,IAAI;AACR,MAAI,CAAC,KAAK;AACR,UAAM;AACN,QAAI;AAAA,MACF,IAAI;AAAA,MACJ,SAAS,4BAA4B,IAAI;AAAA,IAC3C;AAAA,EACF;AACA,QAAM,IAAI;AACV,QAAM,MAAM,MAAM,OAAQ,QAAQ,IAAI,aAAa;AAGnD,QAAM,IAAI,MAAM,iBAAa,gCAAiB,QAAW,GAAG;AAC5D,QAAM,YAAQ,8BAAe,EAAE,MAAM,CAAqC,GAAG,CAAC;AAC9E,UAAI;AAAA,IACF;AAAA,IACA,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AACA,QAAM,KAAK;AAEX,QAAM,IACJ,WAAO,4BAAY,QACd,kCAAkB,IAAI,IAAI,IAC3B;AACN,QAAM,YAAY,YAAY,KAAK;AACnC,QAAM,MAAM,MAAM,OAAO,KAAK,IAAI;AAClC,QAAM,UAA6B;AAAA,IACjC,OAAO,MAAM,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,SAAS,MAAM,WAAW;AAAA,IAC1B,KAAK,MAAM,OAAO;AAAA,IAClB,OAAO,MAAM,SAAS,aAAa;AAAA,IACnC,MAAM,MAAM,QAAQ,CAAC;AAAA,IACrB,OAAO,MAAM,SAAS,EAAE,cAAc,GAAG,eAAe,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,EAAE;AAAA,IAC9E,aAAa,EAAE,OAAO,GAAG,OAAO,KAAK,MAAM,IAAI;AAAA,EACjD;AACA,MAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,MAAI,MAAM,aAAa,OAAW,SAAQ,WAAW,MAAM;AAC3D,MAAI,MAAM,UAAU,OAAW,SAAQ,QAAQ,MAAM;AACrD,MAAI,OAAO,IAAI,QAAQ,GAAG,IAAI,IAAI,IAAI,KAAK,YAAY,CAAC;AACxD,MAAI,CAAC,KAAK;AACR,UAAM,OAAO,KAAK,QAAQ,GAAG;AAC7B,UAAM,SAAS,MAAM,sBAAO,UAAU,mBAAmB,SAAS,CAAC;AACnE,WAAO,OAAO,IAAI,OAAO,SAAS,KAAK,MAAM,GAAG,IAAI,IAAI,SAAS,KAAK,MAAM,IAAI;AAAA,EAClF;AACA,SAAO,IAAI;AAAA,IACT,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI,EAAE,QAAQ,EAAE,IAAI;AAAA,IACpB;AAAA,IACA,IAAI,EAAE,OAAO,EAAE,IAAI;AAAA,IACnB,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,IAAI,IAAI,IAAI,SAAS;AAAA,EAC1B;AACF;AAGO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EAWxC,YAAY,GAAe;AACzB,UAAM,QAAQ,CAAC,GAAG,EAAE,IAAI,KAAK,EAAE,WAAW,YAAY,IAAI,aAAa,YAAY,EAAE,KAAK,CAAC,EAAE;AAC7F,MAAE,IAAI,QAAQ,CAAC,GAAG,MAAM;AACtB,YAAM,KAAK,GAAG,MAAM,IAAI,eAAe,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE;AAAA,IACrE,CAAC;AACD,UAAM,KAAK,aAAa,EAAE,IAAI,EAAE;AAChC,UAAM,MAAM,KAAK,IAAI,GAAG,EAAE,QAAQ,UAAU,SAAY,SAAY,EAAE,OAAO,EAAE,QAAQ,MAAM,CAAC;AAhBhG,SAAS,OAAO;AAiBd,SAAK,OAAO,EAAE;AACd,SAAK,QAAQ,EAAE;AACf,SAAK,UAAU,EAAE;AACjB,SAAK,QAAQ,EAAE;AACf,SAAK,SAAS,EAAE;AAChB,SAAK,MAAM,EAAE;AACb,SAAK,OAAO,EAAE;AACd,SAAK,aAAa;AAAA,EACpB;AACF;AAGO,IAAM,uBAAN,cAAmC,eAAe;AAAA,EAAlD;AAAA;AACL,SAAS,OAAO;AAAA;AAClB;AAEO,SAAS,iBACd,MACA,SACA,OACO;AACP,QAAM,IAAI,qBAAqB,gBAAgB,MAAM,SAAS,KAAK,CAAC;AACtE;AAGO,SAAS,cAAc,IAAY,SAAiC;AACzE,SAAO,IAAI,eAAe,gBAAgB,eAAe,EAAE,IAAI,QAAQ,CAAC,CAAC;AAC3E;","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/build.ts"],"sourcesContent":["import { VERSION } from '../version'\nimport {\n type DiagnosticCode,\n type DiagnosticDefinition,\n type DiagnosticDetails,\n type DiagnosticText,\n getDiagnosticDefinition,\n getDiagnosticText,\n hasMessages,\n} from './catalog'\nimport { coreDiagnostics } from './codes'\nimport {\n type RedactField,\n type Redactor,\n redactDetails,\n redactLocation,\n resolveRedaction,\n} from './redact'\nimport { toSafeJson } from './safe-json'\nimport type {\n Diagnostic,\n DiagnosticContext,\n DiagnosticLevel,\n DiagnosticStateSummary,\n Location,\n Phase,\n Trigger,\n} from './types'\n\nexport const PACKAGE = `@datakable/core@${VERSION}`\nconst DOCS = 'https://github.com/Ink01101011/datakable/blob/main/docs/errors.md'\n\n// Referencing the core entries keeps them in the bundle (`sideEffects: false`).\nconst INTERNAL = coreDiagnostics.find((d) => d.code === 'DK_INTERNAL') as DiagnosticDefinition\n\n/** Everything the enrich step adds; all optional so a diagnostic can be built outside a table. */\nexport interface BuildInput {\n table?: string\n createdAt?: string\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n /** Timestamp for `occurrences` (epoch ms). Default `wallClock()`. */\n now?: number\n cause?: unknown\n causedBy?: string\n /** Build the problem / impact / fix text. Default: development builds only (§8.2); production\n * builds also build it once `@datakable/core/messages` is installed. */\n dev?: boolean\n /** Redaction policy (§8.2). Default: `'keys'` when `dev`, else `'safe'`. */\n redaction?: Redactor\n}\n\n/**\n * Epoch ms read through `performance`, never `Date` (#7): Next 16 `cacheComponents` aborts a\n * prerender that reads `Date.now()` and reserves `performance` for introspection like this.\n */\nexport const wallClock = (): number =>\n typeof performance === 'object' && performance?.timeOrigin\n ? Math.floor(performance.timeOrigin + performance.now())\n : Date.now()\n\nexport const defaultPhase = (): Phase => (typeof document === 'undefined' ? 'ssr' : 'client')\n\nexport function formatWhere(where: Location): string {\n switch (where.kind) {\n case 'column':\n return `columns[${where.index}] (id \"${where.id}\")`\n case 'feature':\n return `feature \"${where.name}\"`\n case 'option':\n return `options.${where.path}`\n case 'row':\n return where.key === undefined\n ? `row #${where.index}`\n : `row #${where.index} (key ${where.key})`\n case 'interceptor':\n return `interceptor \"${where.name}\" on ${where.op}`\n case 'operation':\n return where.slot ? `operation ${where.op} at ${where.slot}` : `operation ${where.op}`\n }\n}\n\nclass DiagnosticRecord implements Diagnostic {\n constructor(\n public code: DiagnosticCode,\n public level: DiagnosticLevel,\n public problem: string,\n public where: Location,\n public impact: string,\n public fix: string[],\n public docs: string,\n public details: Record<string, unknown>,\n public context: DiagnosticContext,\n public fingerprint: string,\n ) {}\n\n toJSON(): object {\n const { cause: _cause, ...context } = this.context\n return {\n code: this.code,\n level: this.level,\n problem: this.problem,\n where: toSafeJson(this.where),\n impact: this.impact,\n fix: this.fix,\n docs: this.docs,\n details: toSafeJson(this.details),\n context: toSafeJson(context),\n fingerprint: this.fingerprint,\n }\n }\n}\n\n/**\n * Builds a diagnostic from a catalog code + details (the `enrich` step). Never throws for an unknown\n * code: that becomes `DK_INTERNAL`, so a typo in a plugin cannot take the logger down.\n */\nexport function buildDiagnostic<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input: BuildInput = {},\n): Diagnostic {\n let def = getDiagnosticDefinition(code)\n let d = details as Record<string, unknown>\n if (!def) {\n def = INTERNAL\n d = {\n op: 'diagnostic:emit',\n problem: `unknown diagnostic code \"${code}\": define it with defineDiagnostic() first`,\n }\n }\n const b = def as unknown as DiagnosticDefinition<'DK_INTERNAL'>\n const dev = input.dev ?? (process.env.NODE_ENV !== \"production\")\n // Redact first: `where`, the text builders, the fingerprint and the docs URL only ever see\n // redacted data, so no consumer downstream (interceptors, sinks, strict errors) can leak it.\n const r = input.redaction ?? resolveRedaction(undefined, dev)\n const where = redactLocation(b.where(d as DiagnosticDetails<'DK_INTERNAL'>), r)\n d = redactDetails(\n d,\n def.redact as Readonly<Record<string, RedactField | undefined>> | undefined,\n where,\n r,\n )\n const dd = d as DiagnosticDetails<'DK_INTERNAL'>\n // Text: development builds, or production with `@datakable/core/messages` installed (§8.2).\n const t =\n dev || hasMessages()\n ? (getDiagnosticText(def.code) as DiagnosticText<'DK_INTERNAL'> | undefined)\n : undefined\n const whereText = formatWhere(where)\n const now = input.now ?? wallClock()\n const context: DiagnosticContext = {\n table: input.table ?? '(global)',\n package: PACKAGE,\n trigger: input.trigger ?? 'init',\n run: input.run ?? 0,\n phase: input.phase ?? defaultPhase(),\n mode: input.mode ?? {},\n state: input.state ?? { searchLength: 0, activeFilters: [], sort: [], page: 0 },\n occurrences: { count: 1, first: now, last: now },\n }\n if (input.createdAt !== undefined) context.createdAt = input.createdAt\n if (input.causedBy !== undefined) context.causedBy = input.causedBy\n if (input.cause !== undefined) context.cause = input.cause\n let docs = def.docs ?? `${DOCS}#${def.code.toLowerCase()}`\n if (!dev) {\n const hash = docs.indexOf('#')\n const params = `?v=${VERSION}&where=${encodeURIComponent(whereText)}`\n docs = hash < 0 ? docs + params : docs.slice(0, hash) + params + docs.slice(hash)\n }\n return new DiagnosticRecord(\n def.code,\n def.level,\n t ? t.problem(dd) : '',\n where,\n t ? t.impact(dd) : '',\n t ? t.fix(dd) : [],\n docs,\n d,\n context,\n `${def.code}@${whereText}`,\n )\n}\n\n/** Thrown under `strict: true` for error-level diagnostics, and for core invariants (§8.2). */\nexport class DatakableError extends Error {\n override name = 'DatakableError'\n readonly code: DiagnosticCode\n readonly level: DiagnosticLevel\n readonly problem: string\n readonly where: Location\n readonly impact: string\n readonly fix: string[]\n readonly docs: string\n readonly diagnostic: Diagnostic\n\n constructor(d: Diagnostic) {\n const lines = [`${d.code}: ${d.problem || '(see docs)'}`, ` where ${formatWhere(d.where)}`]\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n super(lines.join('\\n'), d.context.cause === undefined ? undefined : { cause: d.context.cause })\n this.code = d.code\n this.level = d.level\n this.problem = d.problem\n this.where = d.where\n this.impact = d.impact\n this.fix = d.fix\n this.docs = d.docs\n this.diagnostic = d\n }\n}\n\n/** Deterministic programmer errors thrown at creation (`error (config)` codes, §8.6). */\nexport class DatakableConfigError extends DatakableError {\n override name = 'DatakableConfigError'\n}\n\nexport function throwConfigError<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input?: BuildInput,\n): never {\n throw new DatakableConfigError(buildDiagnostic(code, details, input))\n}\n\n/** A core invariant broke (unregistered op, boundary installed twice): `DK_INTERNAL`. */\nexport function internalError(op: string, problem: string): DatakableError {\n return new DatakableError(buildDiagnostic('DK_INTERNAL', { op, problem }))\n}\n"],"mappings":"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","names":[]}
@@ -15,6 +15,7 @@ import { toSafeJson } from "./safe-json.js";
15
15
  var PACKAGE = `@datakable/core@${VERSION}`;
16
16
  var DOCS = "https://github.com/Ink01101011/datakable/blob/main/docs/errors.md";
17
17
  var INTERNAL = coreDiagnostics.find((d) => d.code === "DK_INTERNAL");
18
+ var wallClock = () => typeof performance === "object" && performance?.timeOrigin ? Math.floor(performance.timeOrigin + performance.now()) : Date.now();
18
19
  var defaultPhase = () => typeof document === "undefined" ? "ssr" : "client";
19
20
  function formatWhere(where) {
20
21
  switch (where.kind) {
@@ -84,7 +85,7 @@ function buildDiagnostic(code, details, input = {}) {
84
85
  const dd = d;
85
86
  const t = dev || hasMessages() ? getDiagnosticText(def.code) : void 0;
86
87
  const whereText = formatWhere(where);
87
- const now = input.now ?? Date.now();
88
+ const now = input.now ?? wallClock();
88
89
  const context = {
89
90
  table: input.table ?? "(global)",
90
91
  package: PACKAGE,
@@ -156,6 +157,7 @@ export {
156
157
  defaultPhase,
157
158
  formatWhere,
158
159
  internalError,
159
- throwConfigError
160
+ throwConfigError,
161
+ wallClock
160
162
  };
161
163
  //# sourceMappingURL=build.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/build.ts"],"sourcesContent":["import { VERSION } from '../version'\nimport {\n type DiagnosticCode,\n type DiagnosticDefinition,\n type DiagnosticDetails,\n type DiagnosticText,\n getDiagnosticDefinition,\n getDiagnosticText,\n hasMessages,\n} from './catalog'\nimport { coreDiagnostics } from './codes'\nimport {\n type RedactField,\n type Redactor,\n redactDetails,\n redactLocation,\n resolveRedaction,\n} from './redact'\nimport { toSafeJson } from './safe-json'\nimport type {\n Diagnostic,\n DiagnosticContext,\n DiagnosticLevel,\n DiagnosticStateSummary,\n Location,\n Phase,\n Trigger,\n} from './types'\n\nexport const PACKAGE = `@datakable/core@${VERSION}`\nconst DOCS = 'https://github.com/Ink01101011/datakable/blob/main/docs/errors.md'\n\n// Referencing the core entries keeps them in the bundle (`sideEffects: false`).\nconst INTERNAL = coreDiagnostics.find((d) => d.code === 'DK_INTERNAL') as DiagnosticDefinition\n\n/** Everything the enrich step adds; all optional so a diagnostic can be built outside a table. */\nexport interface BuildInput {\n table?: string\n createdAt?: string\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n /** Timestamp for `occurrences`. Default `Date.now()`. */\n now?: number\n cause?: unknown\n causedBy?: string\n /** Build the problem / impact / fix text. Default: development builds only (§8.2); production\n * builds also build it once `@datakable/core/messages` is installed. */\n dev?: boolean\n /** Redaction policy (§8.2). Default: `'keys'` when `dev`, else `'safe'`. */\n redaction?: Redactor\n}\n\nexport const defaultPhase = (): Phase => (typeof document === 'undefined' ? 'ssr' : 'client')\n\nexport function formatWhere(where: Location): string {\n switch (where.kind) {\n case 'column':\n return `columns[${where.index}] (id \"${where.id}\")`\n case 'feature':\n return `feature \"${where.name}\"`\n case 'option':\n return `options.${where.path}`\n case 'row':\n return where.key === undefined\n ? `row #${where.index}`\n : `row #${where.index} (key ${where.key})`\n case 'interceptor':\n return `interceptor \"${where.name}\" on ${where.op}`\n case 'operation':\n return where.slot ? `operation ${where.op} at ${where.slot}` : `operation ${where.op}`\n }\n}\n\nclass DiagnosticRecord implements Diagnostic {\n constructor(\n public code: DiagnosticCode,\n public level: DiagnosticLevel,\n public problem: string,\n public where: Location,\n public impact: string,\n public fix: string[],\n public docs: string,\n public details: Record<string, unknown>,\n public context: DiagnosticContext,\n public fingerprint: string,\n ) {}\n\n toJSON(): object {\n const { cause: _cause, ...context } = this.context\n return {\n code: this.code,\n level: this.level,\n problem: this.problem,\n where: toSafeJson(this.where),\n impact: this.impact,\n fix: this.fix,\n docs: this.docs,\n details: toSafeJson(this.details),\n context: toSafeJson(context),\n fingerprint: this.fingerprint,\n }\n }\n}\n\n/**\n * Builds a diagnostic from a catalog code + details (the `enrich` step). Never throws for an unknown\n * code: that becomes `DK_INTERNAL`, so a typo in a plugin cannot take the logger down.\n */\nexport function buildDiagnostic<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input: BuildInput = {},\n): Diagnostic {\n let def = getDiagnosticDefinition(code)\n let d = details as Record<string, unknown>\n if (!def) {\n def = INTERNAL\n d = {\n op: 'diagnostic:emit',\n problem: `unknown diagnostic code \"${code}\": define it with defineDiagnostic() first`,\n }\n }\n const b = def as unknown as DiagnosticDefinition<'DK_INTERNAL'>\n const dev = input.dev ?? (process.env.NODE_ENV !== \"production\")\n // Redact first: `where`, the text builders, the fingerprint and the docs URL only ever see\n // redacted data, so no consumer downstream (interceptors, sinks, strict errors) can leak it.\n const r = input.redaction ?? resolveRedaction(undefined, dev)\n const where = redactLocation(b.where(d as DiagnosticDetails<'DK_INTERNAL'>), r)\n d = redactDetails(\n d,\n def.redact as Readonly<Record<string, RedactField | undefined>> | undefined,\n where,\n r,\n )\n const dd = d as DiagnosticDetails<'DK_INTERNAL'>\n // Text: development builds, or production with `@datakable/core/messages` installed (§8.2).\n const t =\n dev || hasMessages()\n ? (getDiagnosticText(def.code) as DiagnosticText<'DK_INTERNAL'> | undefined)\n : undefined\n const whereText = formatWhere(where)\n const now = input.now ?? Date.now()\n const context: DiagnosticContext = {\n table: input.table ?? '(global)',\n package: PACKAGE,\n trigger: input.trigger ?? 'init',\n run: input.run ?? 0,\n phase: input.phase ?? defaultPhase(),\n mode: input.mode ?? {},\n state: input.state ?? { searchLength: 0, activeFilters: [], sort: [], page: 0 },\n occurrences: { count: 1, first: now, last: now },\n }\n if (input.createdAt !== undefined) context.createdAt = input.createdAt\n if (input.causedBy !== undefined) context.causedBy = input.causedBy\n if (input.cause !== undefined) context.cause = input.cause\n let docs = def.docs ?? `${DOCS}#${def.code.toLowerCase()}`\n if (!dev) {\n const hash = docs.indexOf('#')\n const params = `?v=${VERSION}&where=${encodeURIComponent(whereText)}`\n docs = hash < 0 ? docs + params : docs.slice(0, hash) + params + docs.slice(hash)\n }\n return new DiagnosticRecord(\n def.code,\n def.level,\n t ? t.problem(dd) : '',\n where,\n t ? t.impact(dd) : '',\n t ? t.fix(dd) : [],\n docs,\n d,\n context,\n `${def.code}@${whereText}`,\n )\n}\n\n/** Thrown under `strict: true` for error-level diagnostics, and for core invariants (§8.2). */\nexport class DatakableError extends Error {\n override name = 'DatakableError'\n readonly code: DiagnosticCode\n readonly level: DiagnosticLevel\n readonly problem: string\n readonly where: Location\n readonly impact: string\n readonly fix: string[]\n readonly docs: string\n readonly diagnostic: Diagnostic\n\n constructor(d: Diagnostic) {\n const lines = [`${d.code}: ${d.problem || '(see docs)'}`, ` where ${formatWhere(d.where)}`]\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n super(lines.join('\\n'), d.context.cause === undefined ? undefined : { cause: d.context.cause })\n this.code = d.code\n this.level = d.level\n this.problem = d.problem\n this.where = d.where\n this.impact = d.impact\n this.fix = d.fix\n this.docs = d.docs\n this.diagnostic = d\n }\n}\n\n/** Deterministic programmer errors thrown at creation (`error (config)` codes, §8.6). */\nexport class DatakableConfigError extends DatakableError {\n override name = 'DatakableConfigError'\n}\n\nexport function throwConfigError<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input?: BuildInput,\n): never {\n throw new DatakableConfigError(buildDiagnostic(code, details, input))\n}\n\n/** A core invariant broke (unregistered op, boundary installed twice): `DK_INTERNAL`. */\nexport function internalError(op: string, problem: string): DatakableError {\n return new DatakableError(buildDiagnostic('DK_INTERNAL', { op, problem }))\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/build.ts"],"sourcesContent":["import { VERSION } from '../version'\nimport {\n type DiagnosticCode,\n type DiagnosticDefinition,\n type DiagnosticDetails,\n type DiagnosticText,\n getDiagnosticDefinition,\n getDiagnosticText,\n hasMessages,\n} from './catalog'\nimport { coreDiagnostics } from './codes'\nimport {\n type RedactField,\n type Redactor,\n redactDetails,\n redactLocation,\n resolveRedaction,\n} from './redact'\nimport { toSafeJson } from './safe-json'\nimport type {\n Diagnostic,\n DiagnosticContext,\n DiagnosticLevel,\n DiagnosticStateSummary,\n Location,\n Phase,\n Trigger,\n} from './types'\n\nexport const PACKAGE = `@datakable/core@${VERSION}`\nconst DOCS = 'https://github.com/Ink01101011/datakable/blob/main/docs/errors.md'\n\n// Referencing the core entries keeps them in the bundle (`sideEffects: false`).\nconst INTERNAL = coreDiagnostics.find((d) => d.code === 'DK_INTERNAL') as DiagnosticDefinition\n\n/** Everything the enrich step adds; all optional so a diagnostic can be built outside a table. */\nexport interface BuildInput {\n table?: string\n createdAt?: string\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n /** Timestamp for `occurrences` (epoch ms). Default `wallClock()`. */\n now?: number\n cause?: unknown\n causedBy?: string\n /** Build the problem / impact / fix text. Default: development builds only (§8.2); production\n * builds also build it once `@datakable/core/messages` is installed. */\n dev?: boolean\n /** Redaction policy (§8.2). Default: `'keys'` when `dev`, else `'safe'`. */\n redaction?: Redactor\n}\n\n/**\n * Epoch ms read through `performance`, never `Date` (#7): Next 16 `cacheComponents` aborts a\n * prerender that reads `Date.now()` and reserves `performance` for introspection like this.\n */\nexport const wallClock = (): number =>\n typeof performance === 'object' && performance?.timeOrigin\n ? Math.floor(performance.timeOrigin + performance.now())\n : Date.now()\n\nexport const defaultPhase = (): Phase => (typeof document === 'undefined' ? 'ssr' : 'client')\n\nexport function formatWhere(where: Location): string {\n switch (where.kind) {\n case 'column':\n return `columns[${where.index}] (id \"${where.id}\")`\n case 'feature':\n return `feature \"${where.name}\"`\n case 'option':\n return `options.${where.path}`\n case 'row':\n return where.key === undefined\n ? `row #${where.index}`\n : `row #${where.index} (key ${where.key})`\n case 'interceptor':\n return `interceptor \"${where.name}\" on ${where.op}`\n case 'operation':\n return where.slot ? `operation ${where.op} at ${where.slot}` : `operation ${where.op}`\n }\n}\n\nclass DiagnosticRecord implements Diagnostic {\n constructor(\n public code: DiagnosticCode,\n public level: DiagnosticLevel,\n public problem: string,\n public where: Location,\n public impact: string,\n public fix: string[],\n public docs: string,\n public details: Record<string, unknown>,\n public context: DiagnosticContext,\n public fingerprint: string,\n ) {}\n\n toJSON(): object {\n const { cause: _cause, ...context } = this.context\n return {\n code: this.code,\n level: this.level,\n problem: this.problem,\n where: toSafeJson(this.where),\n impact: this.impact,\n fix: this.fix,\n docs: this.docs,\n details: toSafeJson(this.details),\n context: toSafeJson(context),\n fingerprint: this.fingerprint,\n }\n }\n}\n\n/**\n * Builds a diagnostic from a catalog code + details (the `enrich` step). Never throws for an unknown\n * code: that becomes `DK_INTERNAL`, so a typo in a plugin cannot take the logger down.\n */\nexport function buildDiagnostic<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input: BuildInput = {},\n): Diagnostic {\n let def = getDiagnosticDefinition(code)\n let d = details as Record<string, unknown>\n if (!def) {\n def = INTERNAL\n d = {\n op: 'diagnostic:emit',\n problem: `unknown diagnostic code \"${code}\": define it with defineDiagnostic() first`,\n }\n }\n const b = def as unknown as DiagnosticDefinition<'DK_INTERNAL'>\n const dev = input.dev ?? (process.env.NODE_ENV !== \"production\")\n // Redact first: `where`, the text builders, the fingerprint and the docs URL only ever see\n // redacted data, so no consumer downstream (interceptors, sinks, strict errors) can leak it.\n const r = input.redaction ?? resolveRedaction(undefined, dev)\n const where = redactLocation(b.where(d as DiagnosticDetails<'DK_INTERNAL'>), r)\n d = redactDetails(\n d,\n def.redact as Readonly<Record<string, RedactField | undefined>> | undefined,\n where,\n r,\n )\n const dd = d as DiagnosticDetails<'DK_INTERNAL'>\n // Text: development builds, or production with `@datakable/core/messages` installed (§8.2).\n const t =\n dev || hasMessages()\n ? (getDiagnosticText(def.code) as DiagnosticText<'DK_INTERNAL'> | undefined)\n : undefined\n const whereText = formatWhere(where)\n const now = input.now ?? wallClock()\n const context: DiagnosticContext = {\n table: input.table ?? '(global)',\n package: PACKAGE,\n trigger: input.trigger ?? 'init',\n run: input.run ?? 0,\n phase: input.phase ?? defaultPhase(),\n mode: input.mode ?? {},\n state: input.state ?? { searchLength: 0, activeFilters: [], sort: [], page: 0 },\n occurrences: { count: 1, first: now, last: now },\n }\n if (input.createdAt !== undefined) context.createdAt = input.createdAt\n if (input.causedBy !== undefined) context.causedBy = input.causedBy\n if (input.cause !== undefined) context.cause = input.cause\n let docs = def.docs ?? `${DOCS}#${def.code.toLowerCase()}`\n if (!dev) {\n const hash = docs.indexOf('#')\n const params = `?v=${VERSION}&where=${encodeURIComponent(whereText)}`\n docs = hash < 0 ? docs + params : docs.slice(0, hash) + params + docs.slice(hash)\n }\n return new DiagnosticRecord(\n def.code,\n def.level,\n t ? t.problem(dd) : '',\n where,\n t ? t.impact(dd) : '',\n t ? t.fix(dd) : [],\n docs,\n d,\n context,\n `${def.code}@${whereText}`,\n )\n}\n\n/** Thrown under `strict: true` for error-level diagnostics, and for core invariants (§8.2). */\nexport class DatakableError extends Error {\n override name = 'DatakableError'\n readonly code: DiagnosticCode\n readonly level: DiagnosticLevel\n readonly problem: string\n readonly where: Location\n readonly impact: string\n readonly fix: string[]\n readonly docs: string\n readonly diagnostic: Diagnostic\n\n constructor(d: Diagnostic) {\n const lines = [`${d.code}: ${d.problem || '(see docs)'}`, ` where ${formatWhere(d.where)}`]\n d.fix.forEach((f, i) => {\n lines.push(`${i === 0 ? ' fix ' : ' '}${i + 1}. ${f}`)\n })\n lines.push(` docs ${d.docs}`)\n super(lines.join('\\n'), d.context.cause === undefined ? undefined : { cause: d.context.cause })\n this.code = d.code\n this.level = d.level\n this.problem = d.problem\n this.where = d.where\n this.impact = d.impact\n this.fix = d.fix\n this.docs = d.docs\n this.diagnostic = d\n }\n}\n\n/** Deterministic programmer errors thrown at creation (`error (config)` codes, §8.6). */\nexport class DatakableConfigError extends DatakableError {\n override name = 'DatakableConfigError'\n}\n\nexport function throwConfigError<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n input?: BuildInput,\n): never {\n throw new DatakableConfigError(buildDiagnostic(code, details, input))\n}\n\n/** A core invariant broke (unregistered op, boundary installed twice): `DK_INTERNAL`. */\nexport function internalError(op: string, problem: string): DatakableError {\n return new DatakableError(buildDiagnostic('DK_INTERNAL', { op, problem }))\n}\n"],"mappings":"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","names":[]}
@@ -32,7 +32,7 @@ var tableSeq = 0;
32
32
  function createDiagnostics(options = {}) {
33
33
  const table = options.name ?? `table#${++tableSeq}`;
34
34
  const dev = options.dev ?? process.env.NODE_ENV !== "production";
35
- const now = options.now ?? Date.now;
35
+ const now = options.now ?? import_build.wallClock;
36
36
  const dedupeLimit = options.dedupeLimit ?? 500;
37
37
  const buffer = new import_ring_buffer.RingBuffer(options.bufferSize ?? 100);
38
38
  const seen = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? Date.now\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  // src/diagnostics/hub.ts
2
2
  import { createContext } from "../intercept/context.js";
3
- import { buildDiagnostic, DatakableError, defaultPhase, formatWhere } from "./build.js";
3
+ import { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from "./build.js";
4
4
  import { createConsoleSink } from "./console.js";
5
5
  import {
6
6
  DK_UNSAFE_REDACTION,
@@ -12,7 +12,7 @@ var tableSeq = 0;
12
12
  function createDiagnostics(options = {}) {
13
13
  const table = options.name ?? `table#${++tableSeq}`;
14
14
  const dev = options.dev ?? process.env.NODE_ENV !== "production";
15
- const now = options.now ?? Date.now;
15
+ const now = options.now ?? wallClock;
16
16
  const dedupeLimit = options.dedupeLimit ?? 500;
17
17
  const buffer = new RingBuffer(options.bufferSize ?? 100);
18
18
  const seen = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? Date.now\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diagnostics/hub.ts"],"sourcesContent":["import { createContext } from '../intercept/context'\nimport type { InterceptorRuntime } from '../intercept/runtime'\nimport { buildDiagnostic, DatakableError, defaultPhase, formatWhere, wallClock } from './build'\nimport type { DiagnosticCode, DiagnosticDetails } from './catalog'\nimport { createConsoleSink } from './console'\nimport {\n DK_UNSAFE_REDACTION,\n type RedactionOptions,\n type RedactLevel,\n resolveRedaction,\n scrubError,\n} from './redact'\nimport { RingBuffer } from './ring-buffer'\nimport type { Diagnostic, DiagnosticStateSummary, EmitOptions, Phase, Trigger } from './types'\n\n/** Live values the store supplies at emit time (T07+). Missing ones use defaults. */\nexport interface LiveDiagnosticContext {\n trigger?: Trigger\n run?: number\n phase?: Phase\n mode?: Record<string, 'client' | 'server'>\n state?: DiagnosticStateSummary\n}\n\nexport interface DiagnosticsOptions {\n /** `options.name` of the table; default `table#n`. */\n name?: string\n /** The table instance, put on the `diagnostic:emit` ctx. */\n table?: unknown\n /** Dev: the table's creation site. */\n createdAt?: string\n getContext?: () => LiveDiagnosticContext\n /** Dedupe is per data reference: a new reference reports known diagnostics again. */\n getDataRef?: () => unknown\n /** User sink (e.g. Sentry). Throwing is swallowed with one console.error. */\n onDiagnostic?: (d: Diagnostic) => void\n /** Rethrow error-level diagnostics as `DatakableError`. */\n strict?: boolean\n /** Default console sink. Default `true`. */\n console?: boolean\n /** Text builders + dev console format. Default: `process.env.NODE_ENV !== 'production'`. */\n dev?: boolean\n /**\n * Redaction policy (§8.2, `diagnostics.redact`): a level or `{ level, redact(path, value),\n * sensitiveColumns, rowKeySensitive }`. Default `'keys'` in development, `'safe'` in production.\n * Applied while the diagnostic is built, so `diagnostic:emit` interceptors, the `redact` seam and\n * every sink only see redacted data. `'full'` in production warns (`DK_UNSAFE_REDACTION`).\n */\n redaction?: RedactLevel | RedactionOptions\n /**\n * Extra whole-diagnostic rewrite after the `diagnostic:emit` interceptors, before dedupe and\n * sinks. It receives already-redacted data; `fingerprint` is not recomputed.\n */\n redact?: (d: Diagnostic) => Diagnostic\n /** Ring buffer size behind getDiagnostics(). Default 100. */\n bufferSize?: number\n /** Max fingerprints remembered for dedupe. Default 500. */\n dedupeLimit?: number\n /** Console rate limit. Default 20 per 1000 ms. */\n rateLimit?: { max: number; windowMs: number }\n /** Clock (epoch ms) for `occurrences` and the rate limit. Default `wallClock` (never `Date`, #7). */\n now?: () => number\n}\n\nexport interface Diagnostics {\n /** Resolved table name (`context.table`). */\n readonly table: string\n /**\n * Emits a catalog diagnostic through `diagnostic:emit`. Returns the stored diagnostic (the\n * aggregated one on a dedupe hit), or `undefined` if an interceptor dropped it. Never throws,\n * except `DatakableError` under `strict: true`.\n */\n emit<C extends DiagnosticCode>(\n code: C,\n details: DiagnosticDetails<C>,\n options?: EmitOptions,\n ): Diagnostic | undefined\n /** Oldest first; at most `bufferSize` entries. */\n getDiagnostics(): Diagnostic[]\n /** Runs emits through this runtime's `diagnostic:emit` chain (user interceptors can redact/drop). */\n attach(runtime: InterceptorRuntime): void\n clear(): void\n}\n\nlet tableSeq = 0\n\n/**\n * One per table. Pipeline (§8.2): enrich + redact (build: context, policy redaction, then text,\n * where, fingerprint and docs from the redacted data) → `diagnostic:emit` interceptors → `redact`\n * seam → dedupe (aggregates `occurrences`) → ring buffer → sinks (onDiagnostic, console) → strict.\n */\nexport function createDiagnostics(options: DiagnosticsOptions = {}): Diagnostics {\n const table = options.name ?? `table#${++tableSeq}`\n const dev = options.dev ?? (process.env.NODE_ENV !== \"production\")\n const now = options.now ?? wallClock\n const dedupeLimit = options.dedupeLimit ?? 500\n const buffer = new RingBuffer<Diagnostic>(options.bufferSize ?? 100)\n const seen = new Map<string, Diagnostic>()\n const { onDiagnostic, getContext, getDataRef, redact, strict } = options\n const toConsole =\n options.console === false\n ? undefined\n : createConsoleSink({\n table,\n dev,\n now,\n rateLimit: options.rateLimit ?? { max: 20, windowMs: 1000 },\n })\n\n let runtime: InterceptorRuntime | undefined\n let depth = 0\n let lastRef: unknown\n let failed = false\n\n const fail = (e: unknown) => {\n // The logger never kills the table: one console.error, then silence.\n if (failed) return\n failed = true\n // Production prints the error name only: its message can embed row values (§8.2).\n console.error(\n `[DataKable ${table}] diagnostics pipeline failed; later failures are silent`,\n dev ? e : scrubError(e),\n )\n }\n\n function deliver(input: Diagnostic): Diagnostic {\n const d = redact ? redact(input) : input\n if (getDataRef) {\n const ref = getDataRef()\n if (ref !== lastRef) {\n seen.clear()\n lastRef = ref\n }\n }\n const prev = seen.get(d.fingerprint)\n if (prev) {\n prev.context.occurrences.count++\n prev.context.occurrences.last = d.context.occurrences.last\n return prev\n }\n seen.set(d.fingerprint, d)\n if (seen.size > dedupeLimit) seen.delete(seen.keys().next().value as string)\n buffer.push(d)\n if (onDiagnostic) {\n try {\n onDiagnostic(d)\n } catch (e) {\n fail(e)\n }\n }\n toConsole?.(d)\n return d\n }\n\n // Resolved per emit: sensitive columns and the rowKey can change with setOptions.\n const redactor = () => {\n const r = resolveRedaction(options.redaction, dev)\n r.onError = fail\n return r\n }\n\n const emit: Diagnostics['emit'] = (code, details, opts = {}) => {\n let out: Diagnostic | undefined\n try {\n const live = getContext?.() ?? {}\n const d = buildDiagnostic(code, details, {\n table,\n ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),\n trigger: opts.trigger ?? live.trigger ?? 'init',\n run: opts.run ?? live.run ?? 0,\n phase: opts.phase ?? live.phase ?? defaultPhase(),\n ...(live.mode ? { mode: live.mode } : {}),\n ...(live.state ? { state: live.state } : {}),\n now: now(),\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n ...(opts.causedBy === undefined ? {} : { causedBy: opts.causedBy }),\n dev,\n redaction: redactor(),\n })\n // Emits made while the chain runs (e.g. DK_INTERCEPTOR_FAILED for an interceptor on\n // diagnostic:emit) skip the chain, so a broken logger cannot recurse.\n if (runtime && depth === 0) {\n const c = d.context\n // ctx.where reaches slots, fingerprints and issue links unredacted: never the row key.\n const at = d.where.kind === 'row' ? { kind: 'row' as const, index: d.where.index } : d.where\n const ctx = createContext('diagnostic:emit', { diagnostic: d }, formatWhere(at), {\n table: options.table,\n run: c.run,\n trigger: c.trigger,\n phase: c.phase,\n emit,\n })\n depth++\n try {\n runtime.run('diagnostic:emit', ctx, (x) => {\n out = deliver(x.input.diagnostic)\n })\n } finally {\n depth--\n }\n } else {\n out = deliver(d)\n }\n } catch (e) {\n // strict: an error-level emit nested in the chain (e.g. DK_INTERCEPTOR_FAILED) still throws.\n if (strict && e instanceof DatakableError) throw e\n fail(e)\n return undefined\n }\n if (strict && out?.level === 'error') throw new DatakableError(out)\n return out\n }\n\n if (!dev && resolveRedaction(options.redaction, dev).level === 'full') {\n emit(DK_UNSAFE_REDACTION.code, { level: 'full' })\n }\n\n return {\n table,\n emit,\n getDiagnostics: () => buffer.toArray(),\n attach(rt) {\n runtime = rt\n },\n clear() {\n buffer.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-BKdS9EiO.js';
1
+ import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-OsjAR3Wz.js';
2
2
 
3
3
  /** Runtime types an option can accept (`'array'` is checked before `'object'`). */
4
4
  type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
@@ -1,4 +1,4 @@
1
- import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-BKdS9EiO.cjs';
1
+ import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-OsjAR3Wz.cjs';
2
2
 
3
3
  /** Runtime types an option can accept (`'array'` is checked before `'object'`). */
4
4
  type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
@@ -29,7 +29,7 @@ var import_slice = require("../pipeline/slice.cjs");
29
29
  var import_install = require("./install.cjs");
30
30
  var import_order = require("./order.cjs");
31
31
  var import_runtime = require("./runtime.cjs");
32
- import_runtime.featureRuntime.current ??= {
32
+ var runtime = {
33
33
  orderFeatures: import_order.orderFeatures,
34
34
  initialSlices: import_install.initialSlices,
35
35
  installApis: import_install.installApis,
@@ -37,7 +37,9 @@ import_runtime.featureRuntime.current ??= {
37
37
  createSlicer: import_slice.createSlicer,
38
38
  explainDetail: import_explain_detail.explainDetail
39
39
  };
40
+ import_runtime.featureRuntime.current ??= runtime;
40
41
  function defineFeature(definition) {
42
+ import_runtime.featureRuntime.current ??= runtime;
41
43
  return definition;
42
44
  }
43
45
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feature/define.ts"],"sourcesContent":["import { explainDetail } from '../diagnostics/explain-detail'\nimport { createColumnCache } from '../pipeline/column-cache'\nimport { createSlicer } from '../pipeline/slice'\nimport { initialSlices, installApis } from './install'\nimport { orderFeatures } from './order'\nimport { featureRuntime } from './runtime'\nimport type {\n AnyRow,\n Feature,\n FeatureContext,\n FeatureTypes,\n FixedTypes,\n RowModelContext,\n} from './types'\n\n// Features bring their runtime (§3.3, changed 2026-10-13 for §9.3): loading defineFeature installs\n// it, so a table without features never bundles it.\nfeatureRuntime.current ??= {\n orderFeatures,\n initialSlices,\n installApis,\n createColumnCache,\n createSlicer,\n explainDetail,\n}\n\n/** A feature whose state `S` and API `A` do not depend on the row type (inferred). */\nexport interface FixedFeatureDefinition<Name extends string, S, A>\n extends Omit<\n Feature<Name, FixedTypes<S, A>>,\n '_types' | 'initialState' | 'createApi' | 'rowModel'\n > {\n initialState: () => S\n createApi: (ctx: FeatureContext<S>) => A\n rowModel?: (indices: Uint32Array, state: S, ctx: RowModelContext<AnyRow>) => Uint32Array\n}\n\n/**\n * Defines a feature (§3.3). Identity at runtime; it exists for typing.\n *\n * Row-typed feature (all built-ins): declare the types interface and pass both type arguments.\n *\n * ```ts\n * interface SortingTypes extends FeatureTypes { state: SortState<this['T']>; api: SortApi<this['T']> }\n * export const sorting = (opts: SortingOptions = {}) =>\n * defineFeature<'sorting', SortingTypes>({ name: 'sorting', stage: 'sort', ... })\n * ```\n *\n * Feature that does not depend on `T`: no type arguments, state and API are inferred.\n */\nexport function defineFeature<const Name extends string, S, A extends object>(\n definition: FixedFeatureDefinition<Name, S, A>,\n): Feature<Name, FixedTypes<S, A>>\nexport function defineFeature<const Name extends string, F extends FeatureTypes>(\n definition: Feature<Name, F>,\n): Feature<Name, F>\nexport function defineFeature(definition: Feature): Feature {\n return definition\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAA8B;AAC9B,0BAAkC;AAClC,mBAA6B;AAC7B,qBAA2C;AAC3C,mBAA8B;AAC9B,qBAA+B;AAY/B,8BAAe,YAAY;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAgCO,SAAS,cAAc,YAA8B;AAC1D,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/feature/define.ts"],"sourcesContent":["import { explainDetail } from '../diagnostics/explain-detail'\nimport { createColumnCache } from '../pipeline/column-cache'\nimport { createSlicer } from '../pipeline/slice'\nimport { initialSlices, installApis } from './install'\nimport { orderFeatures } from './order'\nimport { featureRuntime } from './runtime'\nimport type {\n AnyRow,\n Feature,\n FeatureContext,\n FeatureTypes,\n FixedTypes,\n RowModelContext,\n} from './types'\n\n// Features bring their runtime (§3.3, changed 2026-10-13 for §9.3): defineFeature installs it, so a\n// table without features never bundles it. It is installed by the CALL, not only by loading this\n// module: the package is `sideEffects`-free here, and a bundler that inlines an identity\n// defineFeature (esbuild --minify) then drops this module with its top-level statement (#5).\nconst runtime = {\n orderFeatures,\n initialSlices,\n installApis,\n createColumnCache,\n createSlicer,\n explainDetail,\n}\nfeatureRuntime.current ??= runtime\n\n/** A feature whose state `S` and API `A` do not depend on the row type (inferred). */\nexport interface FixedFeatureDefinition<Name extends string, S, A>\n extends Omit<\n Feature<Name, FixedTypes<S, A>>,\n '_types' | 'initialState' | 'createApi' | 'rowModel'\n > {\n initialState: () => S\n createApi: (ctx: FeatureContext<S>) => A\n rowModel?: (indices: Uint32Array, state: S, ctx: RowModelContext<AnyRow>) => Uint32Array\n}\n\n/**\n * Defines a feature (§3.3). Returns `definition` as is (it exists for typing) and installs the\n * feature runtime.\n *\n * Row-typed feature (all built-ins): declare the types interface and pass both type arguments.\n *\n * ```ts\n * interface SortingTypes extends FeatureTypes { state: SortState<this['T']>; api: SortApi<this['T']> }\n * export const sorting = (opts: SortingOptions = {}) =>\n * defineFeature<'sorting', SortingTypes>({ name: 'sorting', stage: 'sort', ... })\n * ```\n *\n * Feature that does not depend on `T`: no type arguments, state and API are inferred.\n */\nexport function defineFeature<const Name extends string, S, A extends object>(\n definition: FixedFeatureDefinition<Name, S, A>,\n): Feature<Name, FixedTypes<S, A>>\nexport function defineFeature<const Name extends string, F extends FeatureTypes>(\n definition: Feature<Name, F>,\n): Feature<Name, F>\nexport function defineFeature(definition: Feature): Feature {\n featureRuntime.current ??= runtime\n return definition\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAA8B;AAC9B,0BAAkC;AAClC,mBAA6B;AAC7B,qBAA2C;AAC3C,mBAA8B;AAC9B,qBAA+B;AAc/B,IAAM,UAAU;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,8BAAe,YAAY;AAiCpB,SAAS,cAAc,YAA8B;AAC1D,gCAAe,YAAY;AAC3B,SAAO;AACT;","names":[]}
@@ -5,7 +5,7 @@ import { createSlicer } from "../pipeline/slice.js";
5
5
  import { initialSlices, installApis } from "./install.js";
6
6
  import { orderFeatures } from "./order.js";
7
7
  import { featureRuntime } from "./runtime.js";
8
- featureRuntime.current ??= {
8
+ var runtime = {
9
9
  orderFeatures,
10
10
  initialSlices,
11
11
  installApis,
@@ -13,7 +13,9 @@ featureRuntime.current ??= {
13
13
  createSlicer,
14
14
  explainDetail
15
15
  };
16
+ featureRuntime.current ??= runtime;
16
17
  function defineFeature(definition) {
18
+ featureRuntime.current ??= runtime;
17
19
  return definition;
18
20
  }
19
21
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feature/define.ts"],"sourcesContent":["import { explainDetail } from '../diagnostics/explain-detail'\nimport { createColumnCache } from '../pipeline/column-cache'\nimport { createSlicer } from '../pipeline/slice'\nimport { initialSlices, installApis } from './install'\nimport { orderFeatures } from './order'\nimport { featureRuntime } from './runtime'\nimport type {\n AnyRow,\n Feature,\n FeatureContext,\n FeatureTypes,\n FixedTypes,\n RowModelContext,\n} from './types'\n\n// Features bring their runtime (§3.3, changed 2026-10-13 for §9.3): loading defineFeature installs\n// it, so a table without features never bundles it.\nfeatureRuntime.current ??= {\n orderFeatures,\n initialSlices,\n installApis,\n createColumnCache,\n createSlicer,\n explainDetail,\n}\n\n/** A feature whose state `S` and API `A` do not depend on the row type (inferred). */\nexport interface FixedFeatureDefinition<Name extends string, S, A>\n extends Omit<\n Feature<Name, FixedTypes<S, A>>,\n '_types' | 'initialState' | 'createApi' | 'rowModel'\n > {\n initialState: () => S\n createApi: (ctx: FeatureContext<S>) => A\n rowModel?: (indices: Uint32Array, state: S, ctx: RowModelContext<AnyRow>) => Uint32Array\n}\n\n/**\n * Defines a feature (§3.3). Identity at runtime; it exists for typing.\n *\n * Row-typed feature (all built-ins): declare the types interface and pass both type arguments.\n *\n * ```ts\n * interface SortingTypes extends FeatureTypes { state: SortState<this['T']>; api: SortApi<this['T']> }\n * export const sorting = (opts: SortingOptions = {}) =>\n * defineFeature<'sorting', SortingTypes>({ name: 'sorting', stage: 'sort', ... })\n * ```\n *\n * Feature that does not depend on `T`: no type arguments, state and API are inferred.\n */\nexport function defineFeature<const Name extends string, S, A extends object>(\n definition: FixedFeatureDefinition<Name, S, A>,\n): Feature<Name, FixedTypes<S, A>>\nexport function defineFeature<const Name extends string, F extends FeatureTypes>(\n definition: Feature<Name, F>,\n): Feature<Name, F>\nexport function defineFeature(definition: Feature): Feature {\n return definition\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,eAAe,mBAAmB;AAC3C,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAY/B,eAAe,YAAY;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAgCO,SAAS,cAAc,YAA8B;AAC1D,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/feature/define.ts"],"sourcesContent":["import { explainDetail } from '../diagnostics/explain-detail'\nimport { createColumnCache } from '../pipeline/column-cache'\nimport { createSlicer } from '../pipeline/slice'\nimport { initialSlices, installApis } from './install'\nimport { orderFeatures } from './order'\nimport { featureRuntime } from './runtime'\nimport type {\n AnyRow,\n Feature,\n FeatureContext,\n FeatureTypes,\n FixedTypes,\n RowModelContext,\n} from './types'\n\n// Features bring their runtime (§3.3, changed 2026-10-13 for §9.3): defineFeature installs it, so a\n// table without features never bundles it. It is installed by the CALL, not only by loading this\n// module: the package is `sideEffects`-free here, and a bundler that inlines an identity\n// defineFeature (esbuild --minify) then drops this module with its top-level statement (#5).\nconst runtime = {\n orderFeatures,\n initialSlices,\n installApis,\n createColumnCache,\n createSlicer,\n explainDetail,\n}\nfeatureRuntime.current ??= runtime\n\n/** A feature whose state `S` and API `A` do not depend on the row type (inferred). */\nexport interface FixedFeatureDefinition<Name extends string, S, A>\n extends Omit<\n Feature<Name, FixedTypes<S, A>>,\n '_types' | 'initialState' | 'createApi' | 'rowModel'\n > {\n initialState: () => S\n createApi: (ctx: FeatureContext<S>) => A\n rowModel?: (indices: Uint32Array, state: S, ctx: RowModelContext<AnyRow>) => Uint32Array\n}\n\n/**\n * Defines a feature (§3.3). Returns `definition` as is (it exists for typing) and installs the\n * feature runtime.\n *\n * Row-typed feature (all built-ins): declare the types interface and pass both type arguments.\n *\n * ```ts\n * interface SortingTypes extends FeatureTypes { state: SortState<this['T']>; api: SortApi<this['T']> }\n * export const sorting = (opts: SortingOptions = {}) =>\n * defineFeature<'sorting', SortingTypes>({ name: 'sorting', stage: 'sort', ... })\n * ```\n *\n * Feature that does not depend on `T`: no type arguments, state and API are inferred.\n */\nexport function defineFeature<const Name extends string, S, A extends object>(\n definition: FixedFeatureDefinition<Name, S, A>,\n): Feature<Name, FixedTypes<S, A>>\nexport function defineFeature<const Name extends string, F extends FeatureTypes>(\n definition: Feature<Name, F>,\n): Feature<Name, F>\nexport function defineFeature(definition: Feature): Feature {\n featureRuntime.current ??= runtime\n return definition\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,eAAe,mBAAmB;AAC3C,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAc/B,IAAM,UAAU;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,eAAe,YAAY;AAiCpB,SAAS,cAAc,YAA8B;AAC1D,iBAAe,YAAY;AAC3B,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feature/runtime.ts"],"sourcesContent":["import type { explainDetail } from '../diagnostics/explain-detail'\nimport type { createColumnCache } from '../pipeline/column-cache'\nimport type { createSlicer } from '../pipeline/slice'\nimport type { initialSlices, installApis } from './install'\nimport type { orderFeatures } from './order'\n\n/**\n * What features bring to the table they are installed in (§3.3, changed 2026-10-13 for §9.3): the\n * stage ordering and API install, the column cache and time-slicing of the pipeline steps, and the\n * step details of `explainRow`. `defineFeature` (every built-in feature uses it) loads it, so a\n * table without features does not bundle it.\n */\nexport interface FeatureRuntime {\n readonly orderFeatures: typeof orderFeatures\n readonly initialSlices: typeof initialSlices\n readonly installApis: typeof installApis\n readonly createColumnCache: typeof createColumnCache\n readonly createSlicer: typeof createSlicer\n readonly explainDetail: typeof explainDetail\n}\n\n/** Set when `feature/define.ts` is loaded (the same module-scoped registry as the catalogs). */\nexport const featureRuntime: { current?: FeatureRuntime | undefined } = {}\n\n/** The runtime for `features`: `undefined` without features; throws when none was loaded. */\nexport function runtimeFor(features: readonly unknown[]): FeatureRuntime | undefined {\n if (features.length === 0) return undefined\n const rt = featureRuntime.current\n if (!rt) {\n throw new TypeError(\n '[DataKable] options.features is set but no feature runtime is loaded: create features with ' +\n \"defineFeature() from '@datakable/core' (every built-in feature does)\",\n )\n }\n return rt\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBO,IAAM,iBAA2D,CAAC;AAGlE,SAAS,WAAW,UAA0D;AACnF,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,KAAK,eAAe;AAC1B,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/feature/runtime.ts"],"sourcesContent":["import type { explainDetail } from '../diagnostics/explain-detail'\nimport type { createColumnCache } from '../pipeline/column-cache'\nimport type { createSlicer } from '../pipeline/slice'\nimport type { initialSlices, installApis } from './install'\nimport type { orderFeatures } from './order'\n\n/**\n * What features bring to the table they are installed in (§3.3, changed 2026-10-13 for §9.3): the\n * stage ordering and API install, the column cache and time-slicing of the pipeline steps, and the\n * step details of `explainRow`. Calling `defineFeature` (every built-in feature does) installs it,\n * so a table without features does not bundle it.\n */\nexport interface FeatureRuntime {\n readonly orderFeatures: typeof orderFeatures\n readonly initialSlices: typeof initialSlices\n readonly installApis: typeof installApis\n readonly createColumnCache: typeof createColumnCache\n readonly createSlicer: typeof createSlicer\n readonly explainDetail: typeof explainDetail\n}\n\n/** Set by `defineFeature` and when `feature/define.ts` loads (module-scoped, as the catalogs). */\nexport const featureRuntime: { current?: FeatureRuntime | undefined } = {}\n\n/** The runtime for `features`: `undefined` without features; throws when none was loaded. */\nexport function runtimeFor(features: readonly unknown[]): FeatureRuntime | undefined {\n if (features.length === 0) return undefined\n const rt = featureRuntime.current\n if (!rt) {\n throw new TypeError(\n '[DataKable] options.features is set but no feature runtime is loaded: create features with ' +\n \"defineFeature() from '@datakable/core' (every built-in feature does)\",\n )\n }\n return rt\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBO,IAAM,iBAA2D,CAAC;AAGlE,SAAS,WAAW,UAA0D;AACnF,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,KAAK,eAAe;AAC1B,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feature/runtime.ts"],"sourcesContent":["import type { explainDetail } from '../diagnostics/explain-detail'\nimport type { createColumnCache } from '../pipeline/column-cache'\nimport type { createSlicer } from '../pipeline/slice'\nimport type { initialSlices, installApis } from './install'\nimport type { orderFeatures } from './order'\n\n/**\n * What features bring to the table they are installed in (§3.3, changed 2026-10-13 for §9.3): the\n * stage ordering and API install, the column cache and time-slicing of the pipeline steps, and the\n * step details of `explainRow`. `defineFeature` (every built-in feature uses it) loads it, so a\n * table without features does not bundle it.\n */\nexport interface FeatureRuntime {\n readonly orderFeatures: typeof orderFeatures\n readonly initialSlices: typeof initialSlices\n readonly installApis: typeof installApis\n readonly createColumnCache: typeof createColumnCache\n readonly createSlicer: typeof createSlicer\n readonly explainDetail: typeof explainDetail\n}\n\n/** Set when `feature/define.ts` is loaded (the same module-scoped registry as the catalogs). */\nexport const featureRuntime: { current?: FeatureRuntime | undefined } = {}\n\n/** The runtime for `features`: `undefined` without features; throws when none was loaded. */\nexport function runtimeFor(features: readonly unknown[]): FeatureRuntime | undefined {\n if (features.length === 0) return undefined\n const rt = featureRuntime.current\n if (!rt) {\n throw new TypeError(\n '[DataKable] options.features is set but no feature runtime is loaded: create features with ' +\n \"defineFeature() from '@datakable/core' (every built-in feature does)\",\n )\n }\n return rt\n}\n"],"mappings":";AAsBO,IAAM,iBAA2D,CAAC;AAGlE,SAAS,WAAW,UAA0D;AACnF,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,KAAK,eAAe;AAC1B,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/feature/runtime.ts"],"sourcesContent":["import type { explainDetail } from '../diagnostics/explain-detail'\nimport type { createColumnCache } from '../pipeline/column-cache'\nimport type { createSlicer } from '../pipeline/slice'\nimport type { initialSlices, installApis } from './install'\nimport type { orderFeatures } from './order'\n\n/**\n * What features bring to the table they are installed in (§3.3, changed 2026-10-13 for §9.3): the\n * stage ordering and API install, the column cache and time-slicing of the pipeline steps, and the\n * step details of `explainRow`. Calling `defineFeature` (every built-in feature does) installs it,\n * so a table without features does not bundle it.\n */\nexport interface FeatureRuntime {\n readonly orderFeatures: typeof orderFeatures\n readonly initialSlices: typeof initialSlices\n readonly installApis: typeof installApis\n readonly createColumnCache: typeof createColumnCache\n readonly createSlicer: typeof createSlicer\n readonly explainDetail: typeof explainDetail\n}\n\n/** Set by `defineFeature` and when `feature/define.ts` loads (module-scoped, as the catalogs). */\nexport const featureRuntime: { current?: FeatureRuntime | undefined } = {}\n\n/** The runtime for `features`: `undefined` without features; throws when none was loaded. */\nexport function runtimeFor(features: readonly unknown[]): FeatureRuntime | undefined {\n if (features.length === 0) return undefined\n const rt = featureRuntime.current\n if (!rt) {\n throw new TypeError(\n '[DataKable] options.features is set but no feature runtime is loaded: create features with ' +\n \"defineFeature() from '@datakable/core' (every built-in feature does)\",\n )\n }\n return rt\n}\n"],"mappings":";AAsBO,IAAM,iBAA2D,CAAC;AAGlE,SAAS,WAAW,UAA0D;AACnF,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,KAAK,eAAe;AAC1B,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -31,9 +31,11 @@ var ACCEPTED = {
31
31
  };
32
32
  var paginationMessages = {
33
33
  DK_INVALID_PAGINATION: {
34
- problem: (d) => d.source === "state" ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}` : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,
34
+ problem: (d) => d.source === "option" ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}` : d.source === "state" ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}` : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,
35
35
  impact: (d) => d.fallback,
36
- fix: (d) => d.source === "state" ? [
36
+ fix: (d) => d.source === "option" ? [
37
+ `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the default of 10`
38
+ ] : d.source === "state" ? [
37
39
  `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and the controlled options.state)`,
38
40
  d.field === "pageIndex" ? "use table.setPageIndex(i): it clamps to the existing pages" : "use table.setPageSize(n): it also keeps the first visible row on screen"
39
41
  ] : [
@@ -54,7 +56,7 @@ var paginationDiagnostics = [
54
56
  {
55
57
  code: "DK_INVALID_PAGINATION",
56
58
  level: "warn",
57
- where: (d) => d.source === "state" ? { kind: "option", path: `state.pagination.${d.field}` } : { kind: "operation", op: `table.${d.source}` }
59
+ where: (d) => d.source === "option" ? { kind: "feature", name: "pagination" } : d.source === "state" ? { kind: "option", path: `state.pagination.${d.field}` } : { kind: "operation", op: `table.${d.source}` }
58
60
  },
59
61
  process.env.NODE_ENV !== "production" && paginationMessages.DK_INVALID_PAGINATION
60
62
  ),