@datakable/core 2.0.2 → 2.0.3

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 (113) hide show
  1. package/dist/{codes-AX8XkMNY.d.cts → codes-4sYQv_7J.d.cts} +36 -10
  2. package/dist/{codes-AX8XkMNY.d.ts → codes-4sYQv_7J.d.ts} +36 -10
  3. package/dist/column-filters.d.cts +2 -2
  4. package/dist/column-filters.d.ts +2 -2
  5. package/dist/diagnostics/build.cjs +1 -1
  6. package/dist/diagnostics/build.cjs.map +1 -1
  7. package/dist/diagnostics/build.js +2 -2
  8. package/dist/diagnostics/build.js.map +1 -1
  9. package/dist/diagnostics/console.cjs +15 -4
  10. package/dist/diagnostics/console.cjs.map +1 -1
  11. package/dist/diagnostics/console.js +15 -4
  12. package/dist/diagnostics/console.js.map +1 -1
  13. package/dist/diagnostics/prose.cjs +10 -1
  14. package/dist/diagnostics/prose.cjs.map +1 -1
  15. package/dist/diagnostics/prose.js +9 -1
  16. package/dist/diagnostics/prose.js.map +1 -1
  17. package/dist/{explain-pKAnnxEW.d.cts → explain-DmwN_RG9.d.cts} +11 -4
  18. package/dist/{explain-BVE3ln3G.d.ts → explain-Dr3SmMNY.d.ts} +11 -4
  19. package/dist/feature/install.cjs +2 -1
  20. package/dist/feature/install.cjs.map +1 -1
  21. package/dist/feature/install.js +2 -1
  22. package/dist/feature/install.js.map +1 -1
  23. package/dist/feature/slice-guard.cjs +76 -0
  24. package/dist/feature/slice-guard.cjs.map +1 -0
  25. package/dist/feature/slice-guard.js +50 -0
  26. package/dist/feature/slice-guard.js.map +1 -0
  27. package/dist/features/column-filters/codes.cjs +2 -19
  28. package/dist/features/column-filters/codes.cjs.map +1 -1
  29. package/dist/features/column-filters/codes.js +2 -19
  30. package/dist/features/column-filters/codes.js.map +1 -1
  31. package/dist/features/column-filters/column-filters.cjs +37 -22
  32. package/dist/features/column-filters/column-filters.cjs.map +1 -1
  33. package/dist/features/column-filters/column-filters.js +37 -22
  34. package/dist/features/column-filters/column-filters.js.map +1 -1
  35. package/dist/features/column-filters/match.cjs +6 -2
  36. package/dist/features/column-filters/match.cjs.map +1 -1
  37. package/dist/features/column-filters/match.js +6 -2
  38. package/dist/features/column-filters/match.js.map +1 -1
  39. package/dist/features/pagination/pagination.cjs +2 -2
  40. package/dist/features/pagination/pagination.cjs.map +1 -1
  41. package/dist/features/pagination/pagination.js +2 -2
  42. package/dist/features/pagination/pagination.js.map +1 -1
  43. package/dist/features/search/codes.cjs +18 -2
  44. package/dist/features/search/codes.cjs.map +1 -1
  45. package/dist/features/search/codes.js +16 -1
  46. package/dist/features/search/codes.js.map +1 -1
  47. package/dist/features/search/search-engine.cjs +24 -5
  48. package/dist/features/search/search-engine.cjs.map +1 -1
  49. package/dist/features/search/search-engine.js +25 -6
  50. package/dist/features/search/search-engine.js.map +1 -1
  51. package/dist/features/sorting/sorting.cjs +55 -24
  52. package/dist/features/sorting/sorting.cjs.map +1 -1
  53. package/dist/features/sorting/sorting.js +55 -24
  54. package/dist/features/sorting/sorting.js.map +1 -1
  55. package/dist/index.d.cts +26 -17
  56. package/dist/index.d.ts +26 -17
  57. package/dist/intercept/runtime.cjs +3 -2
  58. package/dist/intercept/runtime.cjs.map +1 -1
  59. package/dist/intercept/runtime.js +3 -2
  60. package/dist/intercept/runtime.js.map +1 -1
  61. package/dist/messages/index.cjs +2 -0
  62. package/dist/messages/index.cjs.map +1 -1
  63. package/dist/messages/index.js +2 -0
  64. package/dist/messages/index.js.map +1 -1
  65. package/dist/pagination.d.cts +2 -2
  66. package/dist/pagination.d.ts +2 -2
  67. package/dist/schema.d.cts +2 -2
  68. package/dist/schema.d.ts +2 -2
  69. package/dist/{search-async-entry-ByMJbHjc.d.cts → search-async-entry-BiemO3JT.d.cts} +2 -2
  70. package/dist/{search-async-entry-CFQODE08.d.ts → search-async-entry-GRj0E06n.d.ts} +2 -2
  71. package/dist/search-async.d.cts +3 -3
  72. package/dist/search-async.d.ts +3 -3
  73. package/dist/search.d.cts +4 -4
  74. package/dist/search.d.ts +4 -4
  75. package/dist/server.d.cts +3 -3
  76. package/dist/server.d.ts +3 -3
  77. package/dist/sorting.d.cts +2 -2
  78. package/dist/sorting.d.ts +2 -2
  79. package/dist/source/async-control.cjs +2 -1
  80. package/dist/source/async-control.cjs.map +1 -1
  81. package/dist/source/async-control.js +2 -1
  82. package/dist/source/async-control.js.map +1 -1
  83. package/dist/source/fetcher.cjs.map +1 -1
  84. package/dist/source/fetcher.js.map +1 -1
  85. package/dist/source/server.cjs +58 -5
  86. package/dist/source/server.cjs.map +1 -1
  87. package/dist/source/server.js +58 -5
  88. package/dist/source/server.js.map +1 -1
  89. package/dist/table/create-table.cjs +30 -20
  90. package/dist/table/create-table.cjs.map +1 -1
  91. package/dist/table/create-table.js +30 -20
  92. package/dist/table/create-table.js.map +1 -1
  93. package/dist/table/index.cjs +3 -0
  94. package/dist/table/index.cjs.map +1 -1
  95. package/dist/table/index.js +5 -1
  96. package/dist/table/index.js.map +1 -1
  97. package/dist/table/state-codes.cjs +72 -0
  98. package/dist/table/state-codes.cjs.map +1 -0
  99. package/dist/table/state-codes.js +45 -0
  100. package/dist/table/state-codes.js.map +1 -0
  101. package/dist/table/types.cjs.map +1 -1
  102. package/dist/table/types.js.map +1 -1
  103. package/dist/validity.d.cts +2 -2
  104. package/dist/validity.d.ts +2 -2
  105. package/dist/version.cjs +1 -1
  106. package/dist/version.cjs.map +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/version.js.map +1 -1
  109. package/package.json +1 -1
  110. package/dist/ingest/test-schemas.cjs +0 -73
  111. package/dist/ingest/test-schemas.cjs.map +0 -1
  112. package/dist/ingest/test-schemas.js +0 -46
  113. package/dist/ingest/test-schemas.js.map +0 -1
package/dist/sorting.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-AX8XkMNY.js';
2
- import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-BVE3ln3G.js';
1
+ export { W as comparatorDiagnostics, ar as sortingDiagnostics } from './codes-4sYQv_7J.js';
2
+ import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-Dr3SmMNY.js';
3
3
  import { a as DataMode } from './mode-DFV8ryXo.js';
4
4
 
5
5
  /** The rank of `v` in {@link RANK}. */
@@ -35,7 +35,8 @@ var ASYNC_STATUS = "datakable:asyncStatus";
35
35
  var abortReason = (status) => status === "timeout" ? new DOMException("The request timed out.", "TimeoutError") : new DOMException(`The request was ${status}.`, "AbortError");
36
36
  var isAbortError = (e) => e != null && typeof e === "object" && e.name === "AbortError";
37
37
  function asyncControl(options = {}) {
38
- const timeout = options.timeout ?? DEFAULT_ASYNC_TIMEOUT;
38
+ const given = options.timeout;
39
+ const timeout = given !== void 0 && given > 0 ? given : DEFAULT_ASYNC_TIMEOUT;
39
40
  const inflight = /* @__PURE__ */ new Map();
40
41
  function request(ctx, next) {
41
42
  const source = ctx.input.source;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/async-control.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { InterceptorContext } from '../intercept/context'\nimport type { Interceptor } from '../intercept/define'\nimport { FALLBACK } from '../intercept/fallbacks'\nimport { asyncDiagnostics } from './codes'\n\nkeepDiagnostics(asyncDiagnostics)\n\n/** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */\nexport const DEFAULT_ASYNC_TIMEOUT = 30_000\n\n/** Longest delay `setTimeout` honours; anything above it fires at once, so it means \"no timer\". */\nconst MAX_TIMER = 2_147_483_647\n\n/**\n * `ctx.meta` key holding how an `async:request` ended. Callers apply the result only when it is\n * `'applied'`; every other status resolves with the caller's `FALLBACK` (the previous results).\n */\nexport const ASYNC_STATUS = 'datakable:asyncStatus'\n\n/**\n * - `applied`: the callback's own result, from the latest request of its source.\n * - `superseded`: a newer request of the same source started (latest wins).\n * - `timeout`: `timeout` elapsed (`DK_ASYNC_TIMEOUT`).\n * - `aborted`: the caller's signal aborted, the callback rejected with an `AbortError`, or the table\n * was destroyed. Not an error (§6.3).\n * - `error`: the callback failed; the rejection goes on to the errorBoundary.\n */\nexport type AsyncStatus = 'applied' | 'superseded' | 'timeout' | 'aborted' | 'error'\n\nexport interface AsyncControlOptions {\n /** ms before a request is aborted. Default 30 000; `Infinity` disables the timer. */\n timeout?: number\n}\n\nexport type AsyncControl = Interceptor<'async:request' | 'table:destroy'>\n\ntype RequestCtx = InterceptorContext<'async:request'>\n\nconst abortReason = (status: AsyncStatus): DOMException =>\n status === 'timeout'\n ? new DOMException('The request timed out.', 'TimeoutError')\n : new DOMException(`The request was ${status}.`, 'AbortError')\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * The `asyncControl` interceptor (§3.4, §6.3, §7.1). Create one per table (it holds that table's\n * in-flight requests) and install it first in the interceptor list, right inside the errorBoundary,\n * so inner interceptors and the core see the controlled `ctx.signal`.\n *\n * Per `async:request`: one AbortController (linked to the caller's `ctx.signal`), latest-wins per\n * `input.source`, a timeout. The `run` promise settles as soon as the outcome is known — a\n * superseded or timed-out request resolves with `FALLBACK` at once, and whatever its callback does\n * later (resolve or reject) is dropped, so a callback that ignores the signal can never apply a\n * stale result or cause an unhandled rejection. The outcome is `ctx.meta.get(ASYNC_STATUS)`.\n * `table:destroy` aborts every in-flight request.\n */\nexport function asyncControl(options: AsyncControlOptions = {}): AsyncControl {\n const timeout = options.timeout ?? DEFAULT_ASYNC_TIMEOUT\n /** source → how to end its in-flight request. */\n const inflight = new Map<string, (status: AsyncStatus) => void>()\n\n function request(ctx: RequestCtx, next: () => Promise<unknown>): Promise<unknown> {\n const source: string = ctx.input.source\n inflight.get(source)?.('superseded')\n const controller = new AbortController()\n const outer = ctx.signal\n ctx.signal = controller.signal\n\n return new Promise((resolve, reject) => {\n let done = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle = (status: AsyncStatus) => {\n done = true\n if (timer !== undefined) clearTimeout(timer)\n outer?.removeEventListener('abort', onOuterAbort)\n if (inflight.get(source) === end) inflight.delete(source)\n ctx.meta.set(ASYNC_STATUS, status)\n }\n /** Ends the request without its result: abort it and keep the previous results. */\n const end = (status: AsyncStatus) => {\n if (done) return\n settle(status)\n controller.abort(abortReason(status))\n if (status === 'timeout') {\n ctx.emit(\n 'DK_ASYNC_TIMEOUT',\n { source, timeout },\n { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase },\n )\n }\n resolve(ctx.meta.get(FALLBACK))\n }\n function onOuterAbort() {\n end('aborted')\n }\n\n inflight.set(source, end)\n if (outer?.aborted) return end('aborted')\n outer?.addEventListener('abort', onOuterAbort, { once: true })\n if (timeout > 0 && timeout <= MAX_TIMER) timer = setTimeout(() => end('timeout'), timeout)\n\n let out: Promise<unknown>\n try {\n out = next()\n } catch (e) {\n if (done) return\n settle('error')\n return reject(e)\n }\n Promise.resolve(out).then(\n (value) => {\n if (done) return\n settle('applied')\n resolve(value)\n },\n (e: unknown) => {\n if (done) return\n if (isAbortError(e)) return end('aborted')\n settle('error')\n reject(e)\n },\n )\n })\n }\n\n return {\n name: 'asyncControl',\n on: ['async:request', 'table:destroy'],\n intercept(ctx, next) {\n if (ctx.op === 'table:destroy') {\n for (const end of [...inflight.values()]) end('aborted')\n return next()\n }\n return request(ctx, next as () => Promise<unknown>) as never\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAgC;AAGhC,uBAAyB;AACzB,mBAAiC;AAAA,IAEjC,gCAAgB,6BAAgB;AAGzB,IAAM,wBAAwB;AAGrC,IAAM,YAAY;AAMX,IAAM,eAAe;AAqB5B,IAAM,cAAc,CAAC,WACnB,WAAW,YACP,IAAI,aAAa,0BAA0B,cAAc,IACzD,IAAI,aAAa,mBAAmB,MAAM,KAAK,YAAY;AAEjE,IAAM,eAAe,CAAC,MACpB,KAAK,QAAQ,OAAO,MAAM,YAAa,EAAyB,SAAS;AAcpE,SAAS,aAAa,UAA+B,CAAC,GAAiB;AAC5E,QAAM,UAAU,QAAQ,WAAW;AAEnC,QAAM,WAAW,oBAAI,IAA2C;AAEhE,WAAS,QAAQ,KAAiB,MAAgD;AAChF,UAAM,SAAiB,IAAI,MAAM;AACjC,aAAS,IAAI,MAAM,IAAI,YAAY;AACnC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,IAAI;AAClB,QAAI,SAAS,WAAW;AAExB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,OAAO;AACX,UAAI;AAEJ,YAAM,SAAS,CAAC,WAAwB;AACtC,eAAO;AACP,YAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,eAAO,oBAAoB,SAAS,YAAY;AAChD,YAAI,SAAS,IAAI,MAAM,MAAM,IAAK,UAAS,OAAO,MAAM;AACxD,YAAI,KAAK,IAAI,cAAc,MAAM;AAAA,MACnC;AAEA,YAAM,MAAM,CAAC,WAAwB;AACnC,YAAI,KAAM;AACV,eAAO,MAAM;AACb,mBAAW,MAAM,YAAY,MAAM,CAAC;AACpC,YAAI,WAAW,WAAW;AACxB,cAAI;AAAA,YACF;AAAA,YACA,EAAE,QAAQ,QAAQ;AAAA,YAClB,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM;AAAA,UACzD;AAAA,QACF;AACA,gBAAQ,IAAI,KAAK,IAAI,yBAAQ,CAAC;AAAA,MAChC;AACA,eAAS,eAAe;AACtB,YAAI,SAAS;AAAA,MACf;AAEA,eAAS,IAAI,QAAQ,GAAG;AACxB,UAAI,OAAO,QAAS,QAAO,IAAI,SAAS;AACxC,aAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAC7D,UAAI,UAAU,KAAK,WAAW,UAAW,SAAQ,WAAW,MAAM,IAAI,SAAS,GAAG,OAAO;AAEzF,UAAI;AACJ,UAAI;AACF,cAAM,KAAK;AAAA,MACb,SAAS,GAAG;AACV,YAAI,KAAM;AACV,eAAO,OAAO;AACd,eAAO,OAAO,CAAC;AAAA,MACjB;AACA,cAAQ,QAAQ,GAAG,EAAE;AAAA,QACnB,CAAC,UAAU;AACT,cAAI,KAAM;AACV,iBAAO,SAAS;AAChB,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,MAAe;AACd,cAAI,KAAM;AACV,cAAI,aAAa,CAAC,EAAG,QAAO,IAAI,SAAS;AACzC,iBAAO,OAAO;AACd,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,CAAC,iBAAiB,eAAe;AAAA,IACrC,UAAU,KAAK,MAAM;AACnB,UAAI,IAAI,OAAO,iBAAiB;AAC9B,mBAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAG,KAAI,SAAS;AACvD,eAAO,KAAK;AAAA,MACd;AACA,aAAO,QAAQ,KAAK,IAA8B;AAAA,IACpD;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/source/async-control.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { InterceptorContext } from '../intercept/context'\nimport type { Interceptor } from '../intercept/define'\nimport { FALLBACK } from '../intercept/fallbacks'\nimport { asyncDiagnostics } from './codes'\n\nkeepDiagnostics(asyncDiagnostics)\n\n/** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */\nexport const DEFAULT_ASYNC_TIMEOUT = 30_000\n\n/** Longest delay `setTimeout` honours; anything above it fires at once, so it means \"no timer\". */\nconst MAX_TIMER = 2_147_483_647\n\n/**\n * `ctx.meta` key holding how an `async:request` ended. Callers apply the result only when it is\n * `'applied'`; every other status resolves with the caller's `FALLBACK` (the previous results).\n */\nexport const ASYNC_STATUS = 'datakable:asyncStatus'\n\n/**\n * - `applied`: the callback's own result, from the latest request of its source.\n * - `superseded`: a newer request of the same source started (latest wins).\n * - `timeout`: `timeout` elapsed (`DK_ASYNC_TIMEOUT`).\n * - `aborted`: the caller's signal aborted, the callback rejected with an `AbortError`, or the table\n * was destroyed. Not an error (§6.3).\n * - `error`: the callback failed; the rejection goes on to the errorBoundary.\n */\nexport type AsyncStatus = 'applied' | 'superseded' | 'timeout' | 'aborted' | 'error'\n\nexport interface AsyncControlOptions {\n /** ms before a request is aborted. Default 30 000; `Infinity` disables the timer. */\n timeout?: number\n}\n\nexport type AsyncControl = Interceptor<'async:request' | 'table:destroy'>\n\ntype RequestCtx = InterceptorContext<'async:request'>\n\nconst abortReason = (status: AsyncStatus): DOMException =>\n status === 'timeout'\n ? new DOMException('The request timed out.', 'TimeoutError')\n : new DOMException(`The request was ${status}.`, 'AbortError')\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * The `asyncControl` interceptor (§3.4, §6.3, §7.1). Create one per table (it holds that table's\n * in-flight requests) and install it first in the interceptor list, right inside the errorBoundary,\n * so inner interceptors and the core see the controlled `ctx.signal`.\n *\n * Per `async:request`: one AbortController (linked to the caller's `ctx.signal`), latest-wins per\n * `input.source`, a timeout. The `run` promise settles as soon as the outcome is known — a\n * superseded or timed-out request resolves with `FALLBACK` at once, and whatever its callback does\n * later (resolve or reject) is dropped, so a callback that ignores the signal can never apply a\n * stale result or cause an unhandled rejection. The outcome is `ctx.meta.get(ASYNC_STATUS)`.\n * `table:destroy` aborts every in-flight request.\n */\nexport function asyncControl(options: AsyncControlOptions = {}): AsyncControl {\n // Production has no option validator (#94): NaN, 0 or a negative value would disable the timer,\n // so anything but a positive number (or Infinity) falls back to the default.\n const given = options.timeout\n const timeout = given !== undefined && given > 0 ? given : DEFAULT_ASYNC_TIMEOUT\n /** source → how to end its in-flight request. */\n const inflight = new Map<string, (status: AsyncStatus) => void>()\n\n function request(ctx: RequestCtx, next: () => Promise<unknown>): Promise<unknown> {\n const source: string = ctx.input.source\n inflight.get(source)?.('superseded')\n const controller = new AbortController()\n const outer = ctx.signal\n ctx.signal = controller.signal\n\n return new Promise((resolve, reject) => {\n let done = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle = (status: AsyncStatus) => {\n done = true\n if (timer !== undefined) clearTimeout(timer)\n outer?.removeEventListener('abort', onOuterAbort)\n if (inflight.get(source) === end) inflight.delete(source)\n ctx.meta.set(ASYNC_STATUS, status)\n }\n /** Ends the request without its result: abort it and keep the previous results. */\n const end = (status: AsyncStatus) => {\n if (done) return\n settle(status)\n controller.abort(abortReason(status))\n if (status === 'timeout') {\n ctx.emit(\n 'DK_ASYNC_TIMEOUT',\n { source, timeout },\n { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase },\n )\n }\n resolve(ctx.meta.get(FALLBACK))\n }\n function onOuterAbort() {\n end('aborted')\n }\n\n inflight.set(source, end)\n if (outer?.aborted) return end('aborted')\n outer?.addEventListener('abort', onOuterAbort, { once: true })\n if (timeout > 0 && timeout <= MAX_TIMER) timer = setTimeout(() => end('timeout'), timeout)\n\n let out: Promise<unknown>\n try {\n out = next()\n } catch (e) {\n if (done) return\n settle('error')\n return reject(e)\n }\n Promise.resolve(out).then(\n (value) => {\n if (done) return\n settle('applied')\n resolve(value)\n },\n (e: unknown) => {\n if (done) return\n if (isAbortError(e)) return end('aborted')\n settle('error')\n reject(e)\n },\n )\n })\n }\n\n return {\n name: 'asyncControl',\n on: ['async:request', 'table:destroy'],\n intercept(ctx, next) {\n if (ctx.op === 'table:destroy') {\n for (const end of [...inflight.values()]) end('aborted')\n return next()\n }\n return request(ctx, next as () => Promise<unknown>) as never\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAgC;AAGhC,uBAAyB;AACzB,mBAAiC;AAAA,IAEjC,gCAAgB,6BAAgB;AAGzB,IAAM,wBAAwB;AAGrC,IAAM,YAAY;AAMX,IAAM,eAAe;AAqB5B,IAAM,cAAc,CAAC,WACnB,WAAW,YACP,IAAI,aAAa,0BAA0B,cAAc,IACzD,IAAI,aAAa,mBAAmB,MAAM,KAAK,YAAY;AAEjE,IAAM,eAAe,CAAC,MACpB,KAAK,QAAQ,OAAO,MAAM,YAAa,EAAyB,SAAS;AAcpE,SAAS,aAAa,UAA+B,CAAC,GAAiB;AAG5E,QAAM,QAAQ,QAAQ;AACtB,QAAM,UAAU,UAAU,UAAa,QAAQ,IAAI,QAAQ;AAE3D,QAAM,WAAW,oBAAI,IAA2C;AAEhE,WAAS,QAAQ,KAAiB,MAAgD;AAChF,UAAM,SAAiB,IAAI,MAAM;AACjC,aAAS,IAAI,MAAM,IAAI,YAAY;AACnC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,IAAI;AAClB,QAAI,SAAS,WAAW;AAExB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,OAAO;AACX,UAAI;AAEJ,YAAM,SAAS,CAAC,WAAwB;AACtC,eAAO;AACP,YAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,eAAO,oBAAoB,SAAS,YAAY;AAChD,YAAI,SAAS,IAAI,MAAM,MAAM,IAAK,UAAS,OAAO,MAAM;AACxD,YAAI,KAAK,IAAI,cAAc,MAAM;AAAA,MACnC;AAEA,YAAM,MAAM,CAAC,WAAwB;AACnC,YAAI,KAAM;AACV,eAAO,MAAM;AACb,mBAAW,MAAM,YAAY,MAAM,CAAC;AACpC,YAAI,WAAW,WAAW;AACxB,cAAI;AAAA,YACF;AAAA,YACA,EAAE,QAAQ,QAAQ;AAAA,YAClB,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM;AAAA,UACzD;AAAA,QACF;AACA,gBAAQ,IAAI,KAAK,IAAI,yBAAQ,CAAC;AAAA,MAChC;AACA,eAAS,eAAe;AACtB,YAAI,SAAS;AAAA,MACf;AAEA,eAAS,IAAI,QAAQ,GAAG;AACxB,UAAI,OAAO,QAAS,QAAO,IAAI,SAAS;AACxC,aAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAC7D,UAAI,UAAU,KAAK,WAAW,UAAW,SAAQ,WAAW,MAAM,IAAI,SAAS,GAAG,OAAO;AAEzF,UAAI;AACJ,UAAI;AACF,cAAM,KAAK;AAAA,MACb,SAAS,GAAG;AACV,YAAI,KAAM;AACV,eAAO,OAAO;AACd,eAAO,OAAO,CAAC;AAAA,MACjB;AACA,cAAQ,QAAQ,GAAG,EAAE;AAAA,QACnB,CAAC,UAAU;AACT,cAAI,KAAM;AACV,iBAAO,SAAS;AAChB,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,MAAe;AACd,cAAI,KAAM;AACV,cAAI,aAAa,CAAC,EAAG,QAAO,IAAI,SAAS;AACzC,iBAAO,OAAO;AACd,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,CAAC,iBAAiB,eAAe;AAAA,IACrC,UAAU,KAAK,MAAM;AACnB,UAAI,IAAI,OAAO,iBAAiB;AAC9B,mBAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAG,KAAI,SAAS;AACvD,eAAO,KAAK;AAAA,MACd;AACA,aAAO,QAAQ,KAAK,IAA8B;AAAA,IACpD;AAAA,EACF;AACF;","names":[]}
@@ -9,7 +9,8 @@ var ASYNC_STATUS = "datakable:asyncStatus";
9
9
  var abortReason = (status) => status === "timeout" ? new DOMException("The request timed out.", "TimeoutError") : new DOMException(`The request was ${status}.`, "AbortError");
10
10
  var isAbortError = (e) => e != null && typeof e === "object" && e.name === "AbortError";
11
11
  function asyncControl(options = {}) {
12
- const timeout = options.timeout ?? DEFAULT_ASYNC_TIMEOUT;
12
+ const given = options.timeout;
13
+ const timeout = given !== void 0 && given > 0 ? given : DEFAULT_ASYNC_TIMEOUT;
13
14
  const inflight = /* @__PURE__ */ new Map();
14
15
  function request(ctx, next) {
15
16
  const source = ctx.input.source;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/async-control.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { InterceptorContext } from '../intercept/context'\nimport type { Interceptor } from '../intercept/define'\nimport { FALLBACK } from '../intercept/fallbacks'\nimport { asyncDiagnostics } from './codes'\n\nkeepDiagnostics(asyncDiagnostics)\n\n/** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */\nexport const DEFAULT_ASYNC_TIMEOUT = 30_000\n\n/** Longest delay `setTimeout` honours; anything above it fires at once, so it means \"no timer\". */\nconst MAX_TIMER = 2_147_483_647\n\n/**\n * `ctx.meta` key holding how an `async:request` ended. Callers apply the result only when it is\n * `'applied'`; every other status resolves with the caller's `FALLBACK` (the previous results).\n */\nexport const ASYNC_STATUS = 'datakable:asyncStatus'\n\n/**\n * - `applied`: the callback's own result, from the latest request of its source.\n * - `superseded`: a newer request of the same source started (latest wins).\n * - `timeout`: `timeout` elapsed (`DK_ASYNC_TIMEOUT`).\n * - `aborted`: the caller's signal aborted, the callback rejected with an `AbortError`, or the table\n * was destroyed. Not an error (§6.3).\n * - `error`: the callback failed; the rejection goes on to the errorBoundary.\n */\nexport type AsyncStatus = 'applied' | 'superseded' | 'timeout' | 'aborted' | 'error'\n\nexport interface AsyncControlOptions {\n /** ms before a request is aborted. Default 30 000; `Infinity` disables the timer. */\n timeout?: number\n}\n\nexport type AsyncControl = Interceptor<'async:request' | 'table:destroy'>\n\ntype RequestCtx = InterceptorContext<'async:request'>\n\nconst abortReason = (status: AsyncStatus): DOMException =>\n status === 'timeout'\n ? new DOMException('The request timed out.', 'TimeoutError')\n : new DOMException(`The request was ${status}.`, 'AbortError')\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * The `asyncControl` interceptor (§3.4, §6.3, §7.1). Create one per table (it holds that table's\n * in-flight requests) and install it first in the interceptor list, right inside the errorBoundary,\n * so inner interceptors and the core see the controlled `ctx.signal`.\n *\n * Per `async:request`: one AbortController (linked to the caller's `ctx.signal`), latest-wins per\n * `input.source`, a timeout. The `run` promise settles as soon as the outcome is known — a\n * superseded or timed-out request resolves with `FALLBACK` at once, and whatever its callback does\n * later (resolve or reject) is dropped, so a callback that ignores the signal can never apply a\n * stale result or cause an unhandled rejection. The outcome is `ctx.meta.get(ASYNC_STATUS)`.\n * `table:destroy` aborts every in-flight request.\n */\nexport function asyncControl(options: AsyncControlOptions = {}): AsyncControl {\n const timeout = options.timeout ?? DEFAULT_ASYNC_TIMEOUT\n /** source → how to end its in-flight request. */\n const inflight = new Map<string, (status: AsyncStatus) => void>()\n\n function request(ctx: RequestCtx, next: () => Promise<unknown>): Promise<unknown> {\n const source: string = ctx.input.source\n inflight.get(source)?.('superseded')\n const controller = new AbortController()\n const outer = ctx.signal\n ctx.signal = controller.signal\n\n return new Promise((resolve, reject) => {\n let done = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle = (status: AsyncStatus) => {\n done = true\n if (timer !== undefined) clearTimeout(timer)\n outer?.removeEventListener('abort', onOuterAbort)\n if (inflight.get(source) === end) inflight.delete(source)\n ctx.meta.set(ASYNC_STATUS, status)\n }\n /** Ends the request without its result: abort it and keep the previous results. */\n const end = (status: AsyncStatus) => {\n if (done) return\n settle(status)\n controller.abort(abortReason(status))\n if (status === 'timeout') {\n ctx.emit(\n 'DK_ASYNC_TIMEOUT',\n { source, timeout },\n { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase },\n )\n }\n resolve(ctx.meta.get(FALLBACK))\n }\n function onOuterAbort() {\n end('aborted')\n }\n\n inflight.set(source, end)\n if (outer?.aborted) return end('aborted')\n outer?.addEventListener('abort', onOuterAbort, { once: true })\n if (timeout > 0 && timeout <= MAX_TIMER) timer = setTimeout(() => end('timeout'), timeout)\n\n let out: Promise<unknown>\n try {\n out = next()\n } catch (e) {\n if (done) return\n settle('error')\n return reject(e)\n }\n Promise.resolve(out).then(\n (value) => {\n if (done) return\n settle('applied')\n resolve(value)\n },\n (e: unknown) => {\n if (done) return\n if (isAbortError(e)) return end('aborted')\n settle('error')\n reject(e)\n },\n )\n })\n }\n\n return {\n name: 'asyncControl',\n on: ['async:request', 'table:destroy'],\n intercept(ctx, next) {\n if (ctx.op === 'table:destroy') {\n for (const end of [...inflight.values()]) end('aborted')\n return next()\n }\n return request(ctx, next as () => Promise<unknown>) as never\n },\n }\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAGhC,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAEjC,gBAAgB,gBAAgB;AAGzB,IAAM,wBAAwB;AAGrC,IAAM,YAAY;AAMX,IAAM,eAAe;AAqB5B,IAAM,cAAc,CAAC,WACnB,WAAW,YACP,IAAI,aAAa,0BAA0B,cAAc,IACzD,IAAI,aAAa,mBAAmB,MAAM,KAAK,YAAY;AAEjE,IAAM,eAAe,CAAC,MACpB,KAAK,QAAQ,OAAO,MAAM,YAAa,EAAyB,SAAS;AAcpE,SAAS,aAAa,UAA+B,CAAC,GAAiB;AAC5E,QAAM,UAAU,QAAQ,WAAW;AAEnC,QAAM,WAAW,oBAAI,IAA2C;AAEhE,WAAS,QAAQ,KAAiB,MAAgD;AAChF,UAAM,SAAiB,IAAI,MAAM;AACjC,aAAS,IAAI,MAAM,IAAI,YAAY;AACnC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,IAAI;AAClB,QAAI,SAAS,WAAW;AAExB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,OAAO;AACX,UAAI;AAEJ,YAAM,SAAS,CAAC,WAAwB;AACtC,eAAO;AACP,YAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,eAAO,oBAAoB,SAAS,YAAY;AAChD,YAAI,SAAS,IAAI,MAAM,MAAM,IAAK,UAAS,OAAO,MAAM;AACxD,YAAI,KAAK,IAAI,cAAc,MAAM;AAAA,MACnC;AAEA,YAAM,MAAM,CAAC,WAAwB;AACnC,YAAI,KAAM;AACV,eAAO,MAAM;AACb,mBAAW,MAAM,YAAY,MAAM,CAAC;AACpC,YAAI,WAAW,WAAW;AACxB,cAAI;AAAA,YACF;AAAA,YACA,EAAE,QAAQ,QAAQ;AAAA,YAClB,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM;AAAA,UACzD;AAAA,QACF;AACA,gBAAQ,IAAI,KAAK,IAAI,QAAQ,CAAC;AAAA,MAChC;AACA,eAAS,eAAe;AACtB,YAAI,SAAS;AAAA,MACf;AAEA,eAAS,IAAI,QAAQ,GAAG;AACxB,UAAI,OAAO,QAAS,QAAO,IAAI,SAAS;AACxC,aAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAC7D,UAAI,UAAU,KAAK,WAAW,UAAW,SAAQ,WAAW,MAAM,IAAI,SAAS,GAAG,OAAO;AAEzF,UAAI;AACJ,UAAI;AACF,cAAM,KAAK;AAAA,MACb,SAAS,GAAG;AACV,YAAI,KAAM;AACV,eAAO,OAAO;AACd,eAAO,OAAO,CAAC;AAAA,MACjB;AACA,cAAQ,QAAQ,GAAG,EAAE;AAAA,QACnB,CAAC,UAAU;AACT,cAAI,KAAM;AACV,iBAAO,SAAS;AAChB,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,MAAe;AACd,cAAI,KAAM;AACV,cAAI,aAAa,CAAC,EAAG,QAAO,IAAI,SAAS;AACzC,iBAAO,OAAO;AACd,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,CAAC,iBAAiB,eAAe;AAAA,IACrC,UAAU,KAAK,MAAM;AACnB,UAAI,IAAI,OAAO,iBAAiB;AAC9B,mBAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAG,KAAI,SAAS;AACvD,eAAO,KAAK;AAAA,MACd;AACA,aAAO,QAAQ,KAAK,IAA8B;AAAA,IACpD;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/source/async-control.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport type { InterceptorContext } from '../intercept/context'\nimport type { Interceptor } from '../intercept/define'\nimport { FALLBACK } from '../intercept/fallbacks'\nimport { asyncDiagnostics } from './codes'\n\nkeepDiagnostics(asyncDiagnostics)\n\n/** Spec §7.1: a request that has not settled after 30 s is aborted (`DK_ASYNC_TIMEOUT`). */\nexport const DEFAULT_ASYNC_TIMEOUT = 30_000\n\n/** Longest delay `setTimeout` honours; anything above it fires at once, so it means \"no timer\". */\nconst MAX_TIMER = 2_147_483_647\n\n/**\n * `ctx.meta` key holding how an `async:request` ended. Callers apply the result only when it is\n * `'applied'`; every other status resolves with the caller's `FALLBACK` (the previous results).\n */\nexport const ASYNC_STATUS = 'datakable:asyncStatus'\n\n/**\n * - `applied`: the callback's own result, from the latest request of its source.\n * - `superseded`: a newer request of the same source started (latest wins).\n * - `timeout`: `timeout` elapsed (`DK_ASYNC_TIMEOUT`).\n * - `aborted`: the caller's signal aborted, the callback rejected with an `AbortError`, or the table\n * was destroyed. Not an error (§6.3).\n * - `error`: the callback failed; the rejection goes on to the errorBoundary.\n */\nexport type AsyncStatus = 'applied' | 'superseded' | 'timeout' | 'aborted' | 'error'\n\nexport interface AsyncControlOptions {\n /** ms before a request is aborted. Default 30 000; `Infinity` disables the timer. */\n timeout?: number\n}\n\nexport type AsyncControl = Interceptor<'async:request' | 'table:destroy'>\n\ntype RequestCtx = InterceptorContext<'async:request'>\n\nconst abortReason = (status: AsyncStatus): DOMException =>\n status === 'timeout'\n ? new DOMException('The request timed out.', 'TimeoutError')\n : new DOMException(`The request was ${status}.`, 'AbortError')\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * The `asyncControl` interceptor (§3.4, §6.3, §7.1). Create one per table (it holds that table's\n * in-flight requests) and install it first in the interceptor list, right inside the errorBoundary,\n * so inner interceptors and the core see the controlled `ctx.signal`.\n *\n * Per `async:request`: one AbortController (linked to the caller's `ctx.signal`), latest-wins per\n * `input.source`, a timeout. The `run` promise settles as soon as the outcome is known — a\n * superseded or timed-out request resolves with `FALLBACK` at once, and whatever its callback does\n * later (resolve or reject) is dropped, so a callback that ignores the signal can never apply a\n * stale result or cause an unhandled rejection. The outcome is `ctx.meta.get(ASYNC_STATUS)`.\n * `table:destroy` aborts every in-flight request.\n */\nexport function asyncControl(options: AsyncControlOptions = {}): AsyncControl {\n // Production has no option validator (#94): NaN, 0 or a negative value would disable the timer,\n // so anything but a positive number (or Infinity) falls back to the default.\n const given = options.timeout\n const timeout = given !== undefined && given > 0 ? given : DEFAULT_ASYNC_TIMEOUT\n /** source → how to end its in-flight request. */\n const inflight = new Map<string, (status: AsyncStatus) => void>()\n\n function request(ctx: RequestCtx, next: () => Promise<unknown>): Promise<unknown> {\n const source: string = ctx.input.source\n inflight.get(source)?.('superseded')\n const controller = new AbortController()\n const outer = ctx.signal\n ctx.signal = controller.signal\n\n return new Promise((resolve, reject) => {\n let done = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle = (status: AsyncStatus) => {\n done = true\n if (timer !== undefined) clearTimeout(timer)\n outer?.removeEventListener('abort', onOuterAbort)\n if (inflight.get(source) === end) inflight.delete(source)\n ctx.meta.set(ASYNC_STATUS, status)\n }\n /** Ends the request without its result: abort it and keep the previous results. */\n const end = (status: AsyncStatus) => {\n if (done) return\n settle(status)\n controller.abort(abortReason(status))\n if (status === 'timeout') {\n ctx.emit(\n 'DK_ASYNC_TIMEOUT',\n { source, timeout },\n { run: ctx.run, trigger: ctx.trigger, phase: ctx.phase },\n )\n }\n resolve(ctx.meta.get(FALLBACK))\n }\n function onOuterAbort() {\n end('aborted')\n }\n\n inflight.set(source, end)\n if (outer?.aborted) return end('aborted')\n outer?.addEventListener('abort', onOuterAbort, { once: true })\n if (timeout > 0 && timeout <= MAX_TIMER) timer = setTimeout(() => end('timeout'), timeout)\n\n let out: Promise<unknown>\n try {\n out = next()\n } catch (e) {\n if (done) return\n settle('error')\n return reject(e)\n }\n Promise.resolve(out).then(\n (value) => {\n if (done) return\n settle('applied')\n resolve(value)\n },\n (e: unknown) => {\n if (done) return\n if (isAbortError(e)) return end('aborted')\n settle('error')\n reject(e)\n },\n )\n })\n }\n\n return {\n name: 'asyncControl',\n on: ['async:request', 'table:destroy'],\n intercept(ctx, next) {\n if (ctx.op === 'table:destroy') {\n for (const end of [...inflight.values()]) end('aborted')\n return next()\n }\n return request(ctx, next as () => Promise<unknown>) as never\n },\n }\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAGhC,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAEjC,gBAAgB,gBAAgB;AAGzB,IAAM,wBAAwB;AAGrC,IAAM,YAAY;AAMX,IAAM,eAAe;AAqB5B,IAAM,cAAc,CAAC,WACnB,WAAW,YACP,IAAI,aAAa,0BAA0B,cAAc,IACzD,IAAI,aAAa,mBAAmB,MAAM,KAAK,YAAY;AAEjE,IAAM,eAAe,CAAC,MACpB,KAAK,QAAQ,OAAO,MAAM,YAAa,EAAyB,SAAS;AAcpE,SAAS,aAAa,UAA+B,CAAC,GAAiB;AAG5E,QAAM,QAAQ,QAAQ;AACtB,QAAM,UAAU,UAAU,UAAa,QAAQ,IAAI,QAAQ;AAE3D,QAAM,WAAW,oBAAI,IAA2C;AAEhE,WAAS,QAAQ,KAAiB,MAAgD;AAChF,UAAM,SAAiB,IAAI,MAAM;AACjC,aAAS,IAAI,MAAM,IAAI,YAAY;AACnC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,IAAI;AAClB,QAAI,SAAS,WAAW;AAExB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,OAAO;AACX,UAAI;AAEJ,YAAM,SAAS,CAAC,WAAwB;AACtC,eAAO;AACP,YAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,eAAO,oBAAoB,SAAS,YAAY;AAChD,YAAI,SAAS,IAAI,MAAM,MAAM,IAAK,UAAS,OAAO,MAAM;AACxD,YAAI,KAAK,IAAI,cAAc,MAAM;AAAA,MACnC;AAEA,YAAM,MAAM,CAAC,WAAwB;AACnC,YAAI,KAAM;AACV,eAAO,MAAM;AACb,mBAAW,MAAM,YAAY,MAAM,CAAC;AACpC,YAAI,WAAW,WAAW;AACxB,cAAI;AAAA,YACF;AAAA,YACA,EAAE,QAAQ,QAAQ;AAAA,YAClB,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM;AAAA,UACzD;AAAA,QACF;AACA,gBAAQ,IAAI,KAAK,IAAI,QAAQ,CAAC;AAAA,MAChC;AACA,eAAS,eAAe;AACtB,YAAI,SAAS;AAAA,MACf;AAEA,eAAS,IAAI,QAAQ,GAAG;AACxB,UAAI,OAAO,QAAS,QAAO,IAAI,SAAS;AACxC,aAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAC7D,UAAI,UAAU,KAAK,WAAW,UAAW,SAAQ,WAAW,MAAM,IAAI,SAAS,GAAG,OAAO;AAEzF,UAAI;AACJ,UAAI;AACF,cAAM,KAAK;AAAA,MACb,SAAS,GAAG;AACV,YAAI,KAAM;AACV,eAAO,OAAO;AACd,eAAO,OAAO,CAAC;AAAA,MACjB;AACA,cAAQ,QAAQ,GAAG,EAAE;AAAA,QACnB,CAAC,UAAU;AACT,cAAI,KAAM;AACV,iBAAO,SAAS;AAChB,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,MAAe;AACd,cAAI,KAAM;AACV,cAAI,aAAa,CAAC,EAAG,QAAO,IAAI,SAAS;AACzC,iBAAO,OAAO;AACd,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,CAAC,iBAAiB,eAAe;AAAA,IACrC,UAAU,KAAK,MAAM;AACnB,UAAI,IAAI,OAAO,iBAAiB;AAC9B,mBAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAG,KAAI,SAAS;AACvD,eAAO,KAAK;AAAA,MACd;AACA,aAAO,QAAQ,KAAK,IAA8B;AAAA,IACpD;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/fetcher.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { describeValue } from '../diagnostics/redact'\nimport { STAGES } from '../feature/order'\nimport type { AnyFeature } from '../feature/types'\nimport type { TableState } from '../table/types'\nimport { type BadResponse, fetchDiagnostics } from './fetch-codes'\n\nkeepDiagnostics(fetchDiagnostics)\n\n/**\n * What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the\n * request's `signal` (aborted when superseded, timed out or the table is destroyed).\n */\nexport interface FetchParams {\n /** The applied search query (`search({ mode: 'server' })`). */\n query?: string\n /** The `columnFilters` slice (`columnFilters({ mode: 'server' })`). */\n filters?: Readonly<Record<string, unknown>>\n /** The sort entries, first key first (`sorting({ mode: 'server' })`). */\n sort?: readonly { readonly id: string; readonly desc: boolean }[]\n /** `pagination({ mode: 'server' })`. */\n pageIndex?: number\n pageSize?: number\n signal: AbortSignal\n}\n\n/**\n * What the fetcher resolves with: the rows to show and the total number of matching rows on the\n * server (required with server-mode pagination; else `rows.length`).\n */\nexport interface FetchResult<T> {\n rows: readonly T[]\n rowCount?: number\n}\n\n/**\n * What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;\n * `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed\n * (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.\n */\nexport interface FetchOutcome {\n status: 'applied' | 'superseded' | 'error'\n}\n\n/** The server source (§7.1). Runs through `async:request`: latest wins, abort, `timeout`. */\nexport type Fetcher<T> = (params: FetchParams) => FetchResult<T> | PromiseLike<FetchResult<T>>\n\nexport const isServer = (f: AnyFeature) => f.stage !== 'none' && f.mode === 'server'\n\nconst rank = (f: AnyFeature) => STAGES.indexOf(f.stage)\n\n/**\n * `DK_MODE_ORDER` (§7.1, a config error; checked in development, changed 2026-10-13 for §9.3):\n * server steps must form a prefix of the pipeline. Steps of the same stage commute (filters AND\n * together), so only a client step of an earlier stage is a violation.\n */\nexport function checkModeOrder(features: readonly AnyFeature[], table: string): void {\n let client: AnyFeature | undefined\n for (const f of features) {\n if (f.stage === 'none') continue\n if (f.mode !== 'server') {\n client ??= f\n continue\n }\n if (client && rank(client) < rank(f)) {\n throwConfigError(\n 'DK_MODE_ORDER',\n { client: client.name, clientStage: client.stage, server: f.name, serverStage: f.stage },\n { table },\n )\n }\n }\n}\n\n/**\n * `DK_FETCHER_WITHOUT_SERVER_MODE` (§7.1, a config error), at creation and on\n * `setOptions({ fetcher })`: a `fetcher` needs at least one server-mode feature.\n */\nexport function checkFetcher(\n features: readonly AnyFeature[],\n fetcher: unknown,\n table: string,\n phase: 'init' | 'setOptions' = 'init',\n): void {\n if (fetcher === undefined || features.some(isServer)) return\n throwConfigError(\n 'DK_FETCHER_WITHOUT_SERVER_MODE',\n { features: features.filter((f) => f.stage !== 'none').map((f) => f.name), phase },\n { table, trigger: phase },\n )\n}\n\n/** The fetcher params without `signal`. */\nexport type FetchKey = Omit<FetchParams, 'signal'>\n\n/**\n * The state of the server-mode built-ins as fetcher params. Only `search.query` counts for the\n * search slice, so typing (`input`, `status`) never refetches.\n */\nexport function fetchParams(server: readonly AnyFeature[], state: TableState): FetchKey {\n const p: FetchKey = {}\n for (const f of server) {\n const s = state[f.name] as Record<string, unknown> | undefined\n if (f.name === 'search') p.query = typeof s?.query === 'string' ? s.query : ''\n else if (f.name === 'columnFilters') p.filters = s ?? {}\n else if (f.name === 'sorting') p.sort = (s?.by as FetchParams['sort']) ?? []\n else if (f.name === 'pagination') {\n p.pageIndex = s?.pageIndex as number\n p.pageSize = s?.pageSize as number\n }\n }\n return p\n}\n\nexport function sameParams(a: FetchKey, b: FetchKey): boolean {\n const x = a as Record<string, unknown>\n const y = b as Record<string, unknown>\n for (const k in x) if (!Object.is(x[k], y[k])) return false\n for (const k in y) if (!(k in x)) return false\n return true\n}\n\n/** A fetcher that rejected or threw: resolved as a value so asyncControl reports 'applied'. */\nexport class FetchFailure {\n constructor(readonly error: unknown) {}\n}\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * Calls the fetcher. A failure resolves as a {@link FetchFailure} (reported as `DK_FETCH_FAILED`\n * by the caller once it is known to be the latest request); an abort rejects, so asyncControl\n * classifies it as `'aborted'`.\n */\nexport async function callFetcher<T>(\n fetcher: Fetcher<T>,\n params: FetchKey,\n signal: AbortSignal,\n): Promise<unknown> {\n try {\n return await fetcher({ ...params, signal })\n } catch (e) {\n if (signal.aborted || isAbortError(e)) throw e\n return new FetchFailure(e)\n }\n}\n\nexport interface BadResponseDetails {\n problem: BadResponse\n received?: string\n rows?: number\n rowCount?: number\n}\n\n/** Validates a fetcher result (§7.1); `needCount`: pagination is in server mode. */\nexport function checkResponse(v: unknown, needCount: boolean): BadResponseDetails | undefined {\n if (v === null || typeof v !== 'object' || Array.isArray(v)) {\n return { problem: 'not-an-object', received: describeValue(v) }\n }\n const { rows, rowCount } = v as { rows?: unknown; rowCount?: unknown }\n if (!Array.isArray(rows)) return { problem: 'rows-not-an-array', received: describeValue(rows) }\n if (rowCount === undefined) return needCount ? { problem: 'rowCount-missing' } : undefined\n if (!Number.isInteger(rowCount) || (rowCount as number) < 0) {\n return {\n problem: 'rowCount-invalid',\n received: typeof rowCount === 'number' ? String(rowCount) : describeValue(rowCount),\n }\n }\n if ((rowCount as number) < rows.length) {\n return { problem: 'rowCount-too-small', rows: rows.length, rowCount: rowCount as number }\n }\n return undefined\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/source/fetcher.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { describeValue } from '../diagnostics/redact'\nimport { STAGES } from '../feature/order'\nimport type { AnyFeature } from '../feature/types'\nimport type { TableState } from '../table/types'\nimport { type BadResponse, fetchDiagnostics } from './fetch-codes'\n\nkeepDiagnostics(fetchDiagnostics)\n\n/**\n * What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the\n * request's `signal` (aborted when superseded, timed out or the table is destroyed). The state may\n * be restored (URL, storage): treat these params as untrusted input on the backend (#100).\n */\nexport interface FetchParams {\n /** The applied search query (`search({ mode: 'server' })`). */\n query?: string\n /** The `columnFilters` slice (`columnFilters({ mode: 'server' })`). */\n filters?: Readonly<Record<string, unknown>>\n /** The sort entries, first key first (`sorting({ mode: 'server' })`). */\n sort?: readonly { readonly id: string; readonly desc: boolean }[]\n /** `pagination({ mode: 'server' })`. */\n pageIndex?: number\n pageSize?: number\n signal: AbortSignal\n}\n\n/**\n * What the fetcher resolves with: the rows to show and the total number of matching rows on the\n * server (required with server-mode pagination; else `rows.length`).\n */\nexport interface FetchResult<T> {\n rows: readonly T[]\n rowCount?: number\n}\n\n/**\n * What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;\n * `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed\n * (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.\n * On a `createDeferred` table it is the outcome of the request that runs after `startTable`\n * (`'superseded'` when the table is destroyed before it starts, #90).\n */\nexport interface FetchOutcome {\n status: 'applied' | 'superseded' | 'error'\n}\n\n/** The server source (§7.1). Runs through `async:request`: latest wins, abort, `timeout`. */\nexport type Fetcher<T> = (params: FetchParams) => FetchResult<T> | PromiseLike<FetchResult<T>>\n\nexport const isServer = (f: AnyFeature) => f.stage !== 'none' && f.mode === 'server'\n\nconst rank = (f: AnyFeature) => STAGES.indexOf(f.stage)\n\n/**\n * `DK_MODE_ORDER` (§7.1, a config error; checked in development, changed 2026-10-13 for §9.3):\n * server steps must form a prefix of the pipeline. Steps of the same stage commute (filters AND\n * together), so only a client step of an earlier stage is a violation.\n */\nexport function checkModeOrder(features: readonly AnyFeature[], table: string): void {\n let client: AnyFeature | undefined\n for (const f of features) {\n if (f.stage === 'none') continue\n if (f.mode !== 'server') {\n client ??= f\n continue\n }\n if (client && rank(client) < rank(f)) {\n throwConfigError(\n 'DK_MODE_ORDER',\n { client: client.name, clientStage: client.stage, server: f.name, serverStage: f.stage },\n { table },\n )\n }\n }\n}\n\n/**\n * `DK_FETCHER_WITHOUT_SERVER_MODE` (§7.1, a config error), at creation and on\n * `setOptions({ fetcher })`: a `fetcher` needs at least one server-mode feature.\n */\nexport function checkFetcher(\n features: readonly AnyFeature[],\n fetcher: unknown,\n table: string,\n phase: 'init' | 'setOptions' = 'init',\n): void {\n if (fetcher === undefined || features.some(isServer)) return\n throwConfigError(\n 'DK_FETCHER_WITHOUT_SERVER_MODE',\n { features: features.filter((f) => f.stage !== 'none').map((f) => f.name), phase },\n { table, trigger: phase },\n )\n}\n\n/** The fetcher params without `signal`. */\nexport type FetchKey = Omit<FetchParams, 'signal'>\n\n/**\n * The state of the server-mode built-ins as fetcher params. Only `search.query` counts for the\n * search slice, so typing (`input`, `status`) never refetches.\n */\nexport function fetchParams(server: readonly AnyFeature[], state: TableState): FetchKey {\n const p: FetchKey = {}\n for (const f of server) {\n const s = state[f.name] as Record<string, unknown> | undefined\n if (f.name === 'search') p.query = typeof s?.query === 'string' ? s.query : ''\n else if (f.name === 'columnFilters') p.filters = s ?? {}\n else if (f.name === 'sorting') p.sort = (s?.by as FetchParams['sort']) ?? []\n else if (f.name === 'pagination') {\n p.pageIndex = s?.pageIndex as number\n p.pageSize = s?.pageSize as number\n }\n }\n return p\n}\n\nexport function sameParams(a: FetchKey, b: FetchKey): boolean {\n const x = a as Record<string, unknown>\n const y = b as Record<string, unknown>\n for (const k in x) if (!Object.is(x[k], y[k])) return false\n for (const k in y) if (!(k in x)) return false\n return true\n}\n\n/** A fetcher that rejected or threw: resolved as a value so asyncControl reports 'applied'. */\nexport class FetchFailure {\n constructor(readonly error: unknown) {}\n}\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * Calls the fetcher. A failure resolves as a {@link FetchFailure} (reported as `DK_FETCH_FAILED`\n * by the caller once it is known to be the latest request); an abort rejects, so asyncControl\n * classifies it as `'aborted'`.\n */\nexport async function callFetcher<T>(\n fetcher: Fetcher<T>,\n params: FetchKey,\n signal: AbortSignal,\n): Promise<unknown> {\n try {\n return await fetcher({ ...params, signal })\n } catch (e) {\n if (signal.aborted || isAbortError(e)) throw e\n return new FetchFailure(e)\n }\n}\n\nexport interface BadResponseDetails {\n problem: BadResponse\n received?: string\n rows?: number\n rowCount?: number\n}\n\n/** Validates a fetcher result (§7.1); `needCount`: pagination is in server mode. */\nexport function checkResponse(v: unknown, needCount: boolean): BadResponseDetails | undefined {\n if (v === null || typeof v !== 'object' || Array.isArray(v)) {\n return { problem: 'not-an-object', received: describeValue(v) }\n }\n const { rows, rowCount } = v as { rows?: unknown; rowCount?: unknown }\n if (!Array.isArray(rows)) return { problem: 'rows-not-an-array', received: describeValue(rows) }\n if (rowCount === undefined) return needCount ? { problem: 'rowCount-missing' } : undefined\n if (!Number.isInteger(rowCount) || (rowCount as number) < 0) {\n return {\n problem: 'rowCount-invalid',\n received: typeof rowCount === 'number' ? String(rowCount) : describeValue(rowCount),\n }\n }\n if ((rowCount as number) < rows.length) {\n return { problem: 'rowCount-too-small', rows: rows.length, rowCount: rowCount as number }\n }\n return undefined\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/fetcher.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { describeValue } from '../diagnostics/redact'\nimport { STAGES } from '../feature/order'\nimport type { AnyFeature } from '../feature/types'\nimport type { TableState } from '../table/types'\nimport { type BadResponse, fetchDiagnostics } from './fetch-codes'\n\nkeepDiagnostics(fetchDiagnostics)\n\n/**\n * What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the\n * request's `signal` (aborted when superseded, timed out or the table is destroyed).\n */\nexport interface FetchParams {\n /** The applied search query (`search({ mode: 'server' })`). */\n query?: string\n /** The `columnFilters` slice (`columnFilters({ mode: 'server' })`). */\n filters?: Readonly<Record<string, unknown>>\n /** The sort entries, first key first (`sorting({ mode: 'server' })`). */\n sort?: readonly { readonly id: string; readonly desc: boolean }[]\n /** `pagination({ mode: 'server' })`. */\n pageIndex?: number\n pageSize?: number\n signal: AbortSignal\n}\n\n/**\n * What the fetcher resolves with: the rows to show and the total number of matching rows on the\n * server (required with server-mode pagination; else `rows.length`).\n */\nexport interface FetchResult<T> {\n rows: readonly T[]\n rowCount?: number\n}\n\n/**\n * What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;\n * `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed\n * (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.\n */\nexport interface FetchOutcome {\n status: 'applied' | 'superseded' | 'error'\n}\n\n/** The server source (§7.1). Runs through `async:request`: latest wins, abort, `timeout`. */\nexport type Fetcher<T> = (params: FetchParams) => FetchResult<T> | PromiseLike<FetchResult<T>>\n\nexport const isServer = (f: AnyFeature) => f.stage !== 'none' && f.mode === 'server'\n\nconst rank = (f: AnyFeature) => STAGES.indexOf(f.stage)\n\n/**\n * `DK_MODE_ORDER` (§7.1, a config error; checked in development, changed 2026-10-13 for §9.3):\n * server steps must form a prefix of the pipeline. Steps of the same stage commute (filters AND\n * together), so only a client step of an earlier stage is a violation.\n */\nexport function checkModeOrder(features: readonly AnyFeature[], table: string): void {\n let client: AnyFeature | undefined\n for (const f of features) {\n if (f.stage === 'none') continue\n if (f.mode !== 'server') {\n client ??= f\n continue\n }\n if (client && rank(client) < rank(f)) {\n throwConfigError(\n 'DK_MODE_ORDER',\n { client: client.name, clientStage: client.stage, server: f.name, serverStage: f.stage },\n { table },\n )\n }\n }\n}\n\n/**\n * `DK_FETCHER_WITHOUT_SERVER_MODE` (§7.1, a config error), at creation and on\n * `setOptions({ fetcher })`: a `fetcher` needs at least one server-mode feature.\n */\nexport function checkFetcher(\n features: readonly AnyFeature[],\n fetcher: unknown,\n table: string,\n phase: 'init' | 'setOptions' = 'init',\n): void {\n if (fetcher === undefined || features.some(isServer)) return\n throwConfigError(\n 'DK_FETCHER_WITHOUT_SERVER_MODE',\n { features: features.filter((f) => f.stage !== 'none').map((f) => f.name), phase },\n { table, trigger: phase },\n )\n}\n\n/** The fetcher params without `signal`. */\nexport type FetchKey = Omit<FetchParams, 'signal'>\n\n/**\n * The state of the server-mode built-ins as fetcher params. Only `search.query` counts for the\n * search slice, so typing (`input`, `status`) never refetches.\n */\nexport function fetchParams(server: readonly AnyFeature[], state: TableState): FetchKey {\n const p: FetchKey = {}\n for (const f of server) {\n const s = state[f.name] as Record<string, unknown> | undefined\n if (f.name === 'search') p.query = typeof s?.query === 'string' ? s.query : ''\n else if (f.name === 'columnFilters') p.filters = s ?? {}\n else if (f.name === 'sorting') p.sort = (s?.by as FetchParams['sort']) ?? []\n else if (f.name === 'pagination') {\n p.pageIndex = s?.pageIndex as number\n p.pageSize = s?.pageSize as number\n }\n }\n return p\n}\n\nexport function sameParams(a: FetchKey, b: FetchKey): boolean {\n const x = a as Record<string, unknown>\n const y = b as Record<string, unknown>\n for (const k in x) if (!Object.is(x[k], y[k])) return false\n for (const k in y) if (!(k in x)) return false\n return true\n}\n\n/** A fetcher that rejected or threw: resolved as a value so asyncControl reports 'applied'. */\nexport class FetchFailure {\n constructor(readonly error: unknown) {}\n}\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * Calls the fetcher. A failure resolves as a {@link FetchFailure} (reported as `DK_FETCH_FAILED`\n * by the caller once it is known to be the latest request); an abort rejects, so asyncControl\n * classifies it as `'aborted'`.\n */\nexport async function callFetcher<T>(\n fetcher: Fetcher<T>,\n params: FetchKey,\n signal: AbortSignal,\n): Promise<unknown> {\n try {\n return await fetcher({ ...params, signal })\n } catch (e) {\n if (signal.aborted || isAbortError(e)) throw e\n return new FetchFailure(e)\n }\n}\n\nexport interface BadResponseDetails {\n problem: BadResponse\n received?: string\n rows?: number\n rowCount?: number\n}\n\n/** Validates a fetcher result (§7.1); `needCount`: pagination is in server mode. */\nexport function checkResponse(v: unknown, needCount: boolean): BadResponseDetails | undefined {\n if (v === null || typeof v !== 'object' || Array.isArray(v)) {\n return { problem: 'not-an-object', received: describeValue(v) }\n }\n const { rows, rowCount } = v as { rows?: unknown; rowCount?: unknown }\n if (!Array.isArray(rows)) return { problem: 'rows-not-an-array', received: describeValue(rows) }\n if (rowCount === undefined) return needCount ? { problem: 'rowCount-missing' } : undefined\n if (!Number.isInteger(rowCount) || (rowCount as number) < 0) {\n return {\n problem: 'rowCount-invalid',\n received: typeof rowCount === 'number' ? String(rowCount) : describeValue(rowCount),\n }\n }\n if ((rowCount as number) < rows.length) {\n return { problem: 'rowCount-too-small', rows: rows.length, rowCount: rowCount as number }\n }\n return undefined\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/source/fetcher.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport { describeValue } from '../diagnostics/redact'\nimport { STAGES } from '../feature/order'\nimport type { AnyFeature } from '../feature/types'\nimport type { TableState } from '../table/types'\nimport { type BadResponse, fetchDiagnostics } from './fetch-codes'\n\nkeepDiagnostics(fetchDiagnostics)\n\n/**\n * What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the\n * request's `signal` (aborted when superseded, timed out or the table is destroyed). The state may\n * be restored (URL, storage): treat these params as untrusted input on the backend (#100).\n */\nexport interface FetchParams {\n /** The applied search query (`search({ mode: 'server' })`). */\n query?: string\n /** The `columnFilters` slice (`columnFilters({ mode: 'server' })`). */\n filters?: Readonly<Record<string, unknown>>\n /** The sort entries, first key first (`sorting({ mode: 'server' })`). */\n sort?: readonly { readonly id: string; readonly desc: boolean }[]\n /** `pagination({ mode: 'server' })`. */\n pageIndex?: number\n pageSize?: number\n signal: AbortSignal\n}\n\n/**\n * What the fetcher resolves with: the rows to show and the total number of matching rows on the\n * server (required with server-mode pagination; else `rows.length`).\n */\nexport interface FetchResult<T> {\n rows: readonly T[]\n rowCount?: number\n}\n\n/**\n * What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;\n * `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed\n * (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.\n * On a `createDeferred` table it is the outcome of the request that runs after `startTable`\n * (`'superseded'` when the table is destroyed before it starts, #90).\n */\nexport interface FetchOutcome {\n status: 'applied' | 'superseded' | 'error'\n}\n\n/** The server source (§7.1). Runs through `async:request`: latest wins, abort, `timeout`. */\nexport type Fetcher<T> = (params: FetchParams) => FetchResult<T> | PromiseLike<FetchResult<T>>\n\nexport const isServer = (f: AnyFeature) => f.stage !== 'none' && f.mode === 'server'\n\nconst rank = (f: AnyFeature) => STAGES.indexOf(f.stage)\n\n/**\n * `DK_MODE_ORDER` (§7.1, a config error; checked in development, changed 2026-10-13 for §9.3):\n * server steps must form a prefix of the pipeline. Steps of the same stage commute (filters AND\n * together), so only a client step of an earlier stage is a violation.\n */\nexport function checkModeOrder(features: readonly AnyFeature[], table: string): void {\n let client: AnyFeature | undefined\n for (const f of features) {\n if (f.stage === 'none') continue\n if (f.mode !== 'server') {\n client ??= f\n continue\n }\n if (client && rank(client) < rank(f)) {\n throwConfigError(\n 'DK_MODE_ORDER',\n { client: client.name, clientStage: client.stage, server: f.name, serverStage: f.stage },\n { table },\n )\n }\n }\n}\n\n/**\n * `DK_FETCHER_WITHOUT_SERVER_MODE` (§7.1, a config error), at creation and on\n * `setOptions({ fetcher })`: a `fetcher` needs at least one server-mode feature.\n */\nexport function checkFetcher(\n features: readonly AnyFeature[],\n fetcher: unknown,\n table: string,\n phase: 'init' | 'setOptions' = 'init',\n): void {\n if (fetcher === undefined || features.some(isServer)) return\n throwConfigError(\n 'DK_FETCHER_WITHOUT_SERVER_MODE',\n { features: features.filter((f) => f.stage !== 'none').map((f) => f.name), phase },\n { table, trigger: phase },\n )\n}\n\n/** The fetcher params without `signal`. */\nexport type FetchKey = Omit<FetchParams, 'signal'>\n\n/**\n * The state of the server-mode built-ins as fetcher params. Only `search.query` counts for the\n * search slice, so typing (`input`, `status`) never refetches.\n */\nexport function fetchParams(server: readonly AnyFeature[], state: TableState): FetchKey {\n const p: FetchKey = {}\n for (const f of server) {\n const s = state[f.name] as Record<string, unknown> | undefined\n if (f.name === 'search') p.query = typeof s?.query === 'string' ? s.query : ''\n else if (f.name === 'columnFilters') p.filters = s ?? {}\n else if (f.name === 'sorting') p.sort = (s?.by as FetchParams['sort']) ?? []\n else if (f.name === 'pagination') {\n p.pageIndex = s?.pageIndex as number\n p.pageSize = s?.pageSize as number\n }\n }\n return p\n}\n\nexport function sameParams(a: FetchKey, b: FetchKey): boolean {\n const x = a as Record<string, unknown>\n const y = b as Record<string, unknown>\n for (const k in x) if (!Object.is(x[k], y[k])) return false\n for (const k in y) if (!(k in x)) return false\n return true\n}\n\n/** A fetcher that rejected or threw: resolved as a value so asyncControl reports 'applied'. */\nexport class FetchFailure {\n constructor(readonly error: unknown) {}\n}\n\nconst isAbortError = (e: unknown) =>\n e != null && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError'\n\n/**\n * Calls the fetcher. A failure resolves as a {@link FetchFailure} (reported as `DK_FETCH_FAILED`\n * by the caller once it is known to be the latest request); an abort rejects, so asyncControl\n * classifies it as `'aborted'`.\n */\nexport async function callFetcher<T>(\n fetcher: Fetcher<T>,\n params: FetchKey,\n signal: AbortSignal,\n): Promise<unknown> {\n try {\n return await fetcher({ ...params, signal })\n } catch (e) {\n if (signal.aborted || isAbortError(e)) throw e\n return new FetchFailure(e)\n }\n}\n\nexport interface BadResponseDetails {\n problem: BadResponse\n received?: string\n rows?: number\n rowCount?: number\n}\n\n/** Validates a fetcher result (§7.1); `needCount`: pagination is in server mode. */\nexport function checkResponse(v: unknown, needCount: boolean): BadResponseDetails | undefined {\n if (v === null || typeof v !== 'object' || Array.isArray(v)) {\n return { problem: 'not-an-object', received: describeValue(v) }\n }\n const { rows, rowCount } = v as { rows?: unknown; rowCount?: unknown }\n if (!Array.isArray(rows)) return { problem: 'rows-not-an-array', received: describeValue(rows) }\n if (rowCount === undefined) return needCount ? { problem: 'rowCount-missing' } : undefined\n if (!Number.isInteger(rowCount) || (rowCount as number) < 0) {\n return {\n problem: 'rowCount-invalid',\n received: typeof rowCount === 'number' ? String(rowCount) : describeValue(rowCount),\n }\n }\n if ((rowCount as number) < rows.length) {\n return { problem: 'rowCount-too-small', rows: rows.length, rowCount: rowCount as number }\n }\n return undefined\n}\n"],"mappings":"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","names":[]}
@@ -23,9 +23,14 @@ __export(server_exports, {
23
23
  serverSource: () => serverSource
24
24
  });
25
25
  module.exports = __toCommonJS(server_exports);
26
+ var import_catalog = require("../diagnostics/catalog.cjs");
27
+ var import_process_guard = require("../diagnostics/process-guard.cjs");
26
28
  var import_classify = require("../intercept/classify.cjs");
29
+ var import_columns = require("../pipeline/columns.cjs");
30
+ var import_state_codes = require("../table/state-codes.cjs");
27
31
  var import_fetcher = require("./fetcher.cjs");
28
32
  var import_requests = require("./requests.cjs");
33
+ (0, import_catalog.keepDiagnostics)(import_state_codes.stateDiagnostics);
29
34
  var serverSource = (host) => {
30
35
  const requests = host.use(import_requests.asyncRequests);
31
36
  const serverFeatures = host.features.filter(import_fetcher.isServer);
@@ -75,18 +80,64 @@ var serverSource = (host) => {
75
80
  host.ingest(result.rows);
76
81
  return true;
77
82
  }
83
+ let sortIn;
84
+ let sortCols;
85
+ let sortOut = [];
86
+ function sortOf(by) {
87
+ const columns = host.options().columns;
88
+ if (by !== sortIn || columns !== sortCols) {
89
+ sortIn = by;
90
+ sortCols = columns;
91
+ const known = columns?.length ? (0, import_columns.resolveColumns)(columns).list.filter((c) => c.def.sortable !== false).map((c) => c.id) : void 0;
92
+ const out = (Array.isArray(by) ? by : []).flatMap((e) => {
93
+ const id = e?.id;
94
+ if (typeof id !== "string") return [];
95
+ if (known && !known.includes(id)) {
96
+ if (process.env.NODE_ENV !== "production") host.emit("DK_STATE_UNKNOWN_KEY", { slice: "sorting", key: id, known });
97
+ return [];
98
+ }
99
+ return [{ id, desc: e.desc === true }];
100
+ });
101
+ const same = Array.isArray(by) && by.length === out.length && out.every((e, i) => {
102
+ const b = by[i];
103
+ return b.id === e.id && b.desc === e.desc && Object.keys(b).length === 2;
104
+ });
105
+ sortOut = same ? by : out;
106
+ }
107
+ return sortOut;
108
+ }
109
+ const paramsOf = () => {
110
+ const p = (0, import_fetcher.fetchParams)(serverFeatures, host.state());
111
+ if (p.sort) p.sort = sortOf(p.sort);
112
+ return p;
113
+ };
114
+ let later;
115
+ let settleLater;
116
+ host.intercept({
117
+ name: "serverSource",
118
+ on: ["table:destroy"],
119
+ intercept(_ctx, next) {
120
+ settleLater?.({ status: "superseded" });
121
+ return next();
122
+ }
123
+ });
78
124
  function sync(force = false) {
79
125
  const fetcher = host.options().fetcher;
80
126
  if (typeof fetcher !== "function" || host.destroyed()) return;
81
- const params = (0, import_fetcher.fetchParams)(serverFeatures, host.state());
82
- if (!force && fetched && (0, import_fetcher.sameParams)(fetched, params)) return;
83
- if (requests.defer("fetcher")) return;
127
+ const params = paramsOf();
128
+ if (!force && !settleLater && fetched && (0, import_fetcher.sameParams)(fetched, params)) return;
129
+ if (requests.defer("fetcher")) {
130
+ if (force) later ??= new Promise((resolve) => settleLater = resolve);
131
+ return later;
132
+ }
84
133
  fetched = params;
134
+ const waiting = settleLater;
135
+ settleLater = later = void 0;
85
136
  let result;
86
137
  const done = () => ({
87
138
  status: result ?? (host.destroyed() ? "superseded" : "error")
88
139
  });
89
- return requests.request("fetcher", (signal) => (0, import_fetcher.callFetcher)(fetcher, params, signal), {
140
+ const out = requests.request("fetcher", (signal) => (0, import_fetcher.callFetcher)(fetcher, params, signal), {
90
141
  params,
91
142
  onSettle: (value, outcome) => {
92
143
  result = outcome === "superseded" || outcome === "aborted" ? "superseded" : "error";
@@ -94,11 +145,13 @@ var serverSource = (host) => {
94
145
  }
95
146
  // strict: the DatakableError was reported by the hub; the status is 'error' (apply).
96
147
  }).then(done, done);
148
+ waiting?.(out);
149
+ return out;
97
150
  }
98
151
  const options = host.options();
99
152
  (0, import_fetcher.checkFetcher)(host.features, options.fetcher, host.name);
100
153
  if (options.initialData !== void 0 && options.data === void 0) {
101
- fetched = (0, import_fetcher.fetchParams)(serverFeatures, host.state());
154
+ fetched = paramsOf();
102
155
  rowCount = options.rowCount ?? options.initialData.length;
103
156
  }
104
157
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/server.ts"],"sourcesContent":["import { errorName } from '../intercept/classify'\nimport type { ExtensionFactory, TableExtension, TableHost } from '../table/internals'\nimport type { AsyncStatus } from './async-control'\nimport {\n callFetcher,\n checkFetcher,\n checkResponse,\n type Fetcher,\n FetchFailure,\n type FetchKey,\n type FetchOutcome,\n fetchParams,\n isServer,\n sameParams,\n} from './fetcher'\nimport { asyncRequests } from './requests'\n\n/** The server source of one table (§7.1): the fetcher's requests, its status and row count. */\nexport interface ServerSource extends TableExtension {\n /**\n * Starts a fetcher request when the server-mode state differs from the last request's (or\n * `force`: `refetch()`). Resolves with the outcome; never rejects.\n */\n sync(force?: boolean): Promise<FetchOutcome> | undefined\n /** `rowCount` of the last applied fetcher result (§6.5). */\n rowCount(): number | undefined\n /** `setOptions({ fetcher })`, before anything changes: `DK_FETCHER_WITHOUT_SERVER_MODE`. */\n check(fetcher: unknown): void\n /** A new fetcher reference: it runs at once, even with the same params (flush → sync). */\n reset(): void\n}\n\n/**\n * The server source as a table extension (changed 2026-10-13 for §9.3): it comes with\n * `withFetcher(fetcher)`, so a table without a fetcher never bundles it.\n */\nexport const serverSource: ExtensionFactory<ServerSource> = (host: TableHost) => {\n const requests = host.use(asyncRequests)\n const serverFeatures = host.features.filter(isServer)\n /** The latest fetcher request failed or returned a bad response: 'error' until one applies. */\n let failed = false\n /** `rowCount` of the last applied fetcher result (§6.5). */\n let rowCount: number | undefined\n /** Params of the last fetcher request; a flush with other params starts a new one (§7.1). */\n let fetched: FetchKey | undefined\n\n function fail() {\n if (failed) return\n failed = true\n host.touch()\n }\n\n /**\n * Applies a fetcher outcome; only the latest request reaches `'applied'` (asyncControl). `true`\n * when its rows were applied.\n */\n function apply(value: unknown, outcome: AsyncStatus): boolean {\n if (outcome === 'superseded') {\n host.emit('DK_FETCH_ABORTED', {})\n return false\n }\n if (outcome === 'aborted') return false\n // 'error' / 'timeout': already reported by the errorBoundary / asyncControl.\n if (outcome !== 'applied') {\n fail()\n return false\n }\n if (value instanceof FetchFailure) {\n fail()\n const name = errorName(value.error)\n host.emit(\n 'DK_FETCH_FAILED',\n { ...(name === undefined ? {} : { errorName: name }), hadRows: host.loaded() },\n { cause: value.error },\n )\n return false\n }\n const bad = checkResponse(\n value,\n serverFeatures.some((f) => f.stage === 'paginate'),\n )\n if (bad) {\n fail()\n host.emit('DK_FETCH_BAD_RESPONSE', bad)\n return false\n }\n if (failed) {\n failed = false\n host.touch()\n }\n const result = value as { rows: readonly unknown[]; rowCount?: number }\n rowCount = result.rowCount ?? result.rows.length\n host.ingest(result.rows)\n return true\n }\n\n function sync(force = false): Promise<FetchOutcome> | undefined {\n const fetcher = host.options().fetcher as Fetcher<unknown> | undefined\n if (typeof fetcher !== 'function' || host.destroyed()) return\n const params = fetchParams(serverFeatures, host.state())\n if (!force && fetched && sameParams(fetched, params)) return\n // Deferred (SSR, §7.2): no request until startTable; then the latest params are fetched.\n if (requests.defer('fetcher')) return\n fetched = params\n let result: FetchOutcome['status'] | undefined\n const done = (): FetchOutcome => ({\n status: result ?? (host.destroyed() ? 'superseded' : 'error'),\n })\n return requests\n .request('fetcher', (signal) => callFetcher(fetcher, params, signal), {\n params,\n onSettle: (value, outcome) => {\n result = outcome === 'superseded' || outcome === 'aborted' ? 'superseded' : 'error'\n if (apply(value, outcome)) result = 'applied'\n },\n // strict: the DatakableError was reported by the hub; the status is 'error' (apply).\n })\n .then(done, done)\n }\n\n const options = host.options()\n checkFetcher(host.features, options.fetcher, host.name)\n // initialData answers the initial server state (§7.2): no first request, its total as rowCount.\n if (options.initialData !== undefined && options.data === undefined) {\n fetched = fetchParams(serverFeatures, host.state())\n rowCount = options.rowCount ?? options.initialData.length\n }\n\n return {\n sync,\n rowCount: () => rowCount,\n status: () => (failed ? 'error' : undefined),\n check: (fetcher) => checkFetcher(host.features, fetcher, host.name, 'setOptions'),\n reset() {\n fetched = undefined\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAA0B;AAG1B,qBAWO;AACP,sBAA8B;AAqBvB,IAAM,eAA+C,CAAC,SAAoB;AAC/E,QAAM,WAAW,KAAK,IAAI,6BAAa;AACvC,QAAM,iBAAiB,KAAK,SAAS,OAAO,uBAAQ;AAEpD,MAAI,SAAS;AAEb,MAAI;AAEJ,MAAI;AAEJ,WAAS,OAAO;AACd,QAAI,OAAQ;AACZ,aAAS;AACT,SAAK,MAAM;AAAA,EACb;AAMA,WAAS,MAAM,OAAgB,SAA+B;AAC5D,QAAI,YAAY,cAAc;AAC5B,WAAK,KAAK,oBAAoB,CAAC,CAAC;AAChC,aAAO;AAAA,IACT;AACA,QAAI,YAAY,UAAW,QAAO;AAElC,QAAI,YAAY,WAAW;AACzB,WAAK;AACL,aAAO;AAAA,IACT;AACA,QAAI,iBAAiB,6BAAc;AACjC,WAAK;AACL,YAAM,WAAO,2BAAU,MAAM,KAAK;AAClC,WAAK;AAAA,QACH;AAAA,QACA,EAAE,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK,GAAI,SAAS,KAAK,OAAO,EAAE;AAAA,QAC7E,EAAE,OAAO,MAAM,MAAM;AAAA,MACvB;AACA,aAAO;AAAA,IACT;AACA,UAAM,UAAM;AAAA,MACV;AAAA,MACA,eAAe,KAAK,CAAC,MAAM,EAAE,UAAU,UAAU;AAAA,IACnD;AACA,QAAI,KAAK;AACP,WAAK;AACL,WAAK,KAAK,yBAAyB,GAAG;AACtC,aAAO;AAAA,IACT;AACA,QAAI,QAAQ;AACV,eAAS;AACT,WAAK,MAAM;AAAA,IACb;AACA,UAAM,SAAS;AACf,eAAW,OAAO,YAAY,OAAO,KAAK;AAC1C,SAAK,OAAO,OAAO,IAAI;AACvB,WAAO;AAAA,EACT;AAEA,WAAS,KAAK,QAAQ,OAA0C;AAC9D,UAAM,UAAU,KAAK,QAAQ,EAAE;AAC/B,QAAI,OAAO,YAAY,cAAc,KAAK,UAAU,EAAG;AACvD,UAAM,aAAS,4BAAY,gBAAgB,KAAK,MAAM,CAAC;AACvD,QAAI,CAAC,SAAS,eAAW,2BAAW,SAAS,MAAM,EAAG;AAEtD,QAAI,SAAS,MAAM,SAAS,EAAG;AAC/B,cAAU;AACV,QAAI;AACJ,UAAM,OAAO,OAAqB;AAAA,MAChC,QAAQ,WAAW,KAAK,UAAU,IAAI,eAAe;AAAA,IACvD;AACA,WAAO,SACJ,QAAQ,WAAW,CAAC,eAAW,4BAAY,SAAS,QAAQ,MAAM,GAAG;AAAA,MACpE;AAAA,MACA,UAAU,CAAC,OAAO,YAAY;AAC5B,iBAAS,YAAY,gBAAgB,YAAY,YAAY,eAAe;AAC5E,YAAI,MAAM,OAAO,OAAO,EAAG,UAAS;AAAA,MACtC;AAAA;AAAA,IAEF,CAAC,EACA,KAAK,MAAM,IAAI;AAAA,EACpB;AAEA,QAAM,UAAU,KAAK,QAAQ;AAC7B,mCAAa,KAAK,UAAU,QAAQ,SAAS,KAAK,IAAI;AAEtD,MAAI,QAAQ,gBAAgB,UAAa,QAAQ,SAAS,QAAW;AACnE,kBAAU,4BAAY,gBAAgB,KAAK,MAAM,CAAC;AAClD,eAAW,QAAQ,YAAY,QAAQ,YAAY;AAAA,EACrD;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,QAAQ,MAAO,SAAS,UAAU;AAAA,IAClC,OAAO,CAAC,gBAAY,6BAAa,KAAK,UAAU,SAAS,KAAK,MAAM,YAAY;AAAA,IAChF,QAAQ;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/source/server.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { errorName } from '../intercept/classify'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport { resolveColumns } from '../pipeline/columns'\nimport type { ExtensionFactory, TableExtension, TableHost } from '../table/internals'\nimport { stateDiagnostics } from '../table/state-codes'\nimport type { AsyncStatus } from './async-control'\nimport {\n callFetcher,\n checkFetcher,\n checkResponse,\n type Fetcher,\n FetchFailure,\n type FetchKey,\n type FetchOutcome,\n type FetchParams,\n fetchParams,\n isServer,\n sameParams,\n} from './fetcher'\nimport { asyncRequests } from './requests'\n\n/** The server source of one table (§7.1): the fetcher's requests, its status and row count. */\nexport interface ServerSource extends TableExtension {\n /**\n * Starts a fetcher request when the server-mode state differs from the last request's (or\n * `force`: `refetch()`). Resolves with the outcome; never rejects. Deferred (`createDeferred`),\n * `force` resolves with the request that runs after `startTable` (`'superseded'` when the table\n * is destroyed first, #90).\n */\n sync(force?: boolean): Promise<FetchOutcome> | undefined\n /** `rowCount` of the last applied fetcher result (§6.5). */\n rowCount(): number | undefined\n /** `setOptions({ fetcher })`, before anything changes: `DK_FETCHER_WITHOUT_SERVER_MODE`. */\n check(fetcher: unknown): void\n /** A new fetcher reference: it runs at once, even with the same params (flush → sync). */\n reset(): void\n}\n\n/**\n * The server source as a table extension (changed 2026-10-13 for §9.3): it comes with\n * `withFetcher(fetcher)`, so a table without a fetcher never bundles it.\n */\n// DK_STATE_UNKNOWN_KEY for a restored sort id that names no column (#100).\nkeepDiagnostics(stateDiagnostics)\n\ntype Sort = NonNullable<FetchParams['sort']>\n\nexport const serverSource: ExtensionFactory<ServerSource> = (host: TableHost) => {\n const requests = host.use(asyncRequests)\n const serverFeatures = host.features.filter(isServer)\n /** The latest fetcher request failed or returned a bad response: 'error' until one applies. */\n let failed = false\n /** `rowCount` of the last applied fetcher result (§6.5). */\n let rowCount: number | undefined\n /** Params of the last fetcher request; a flush with other params starts a new one (§7.1). */\n let fetched: FetchKey | undefined\n\n function fail() {\n if (failed) return\n failed = true\n host.touch()\n }\n\n /**\n * Applies a fetcher outcome; only the latest request reaches `'applied'` (asyncControl). `true`\n * when its rows were applied.\n */\n function apply(value: unknown, outcome: AsyncStatus): boolean {\n if (outcome === 'superseded') {\n host.emit('DK_FETCH_ABORTED', {})\n return false\n }\n if (outcome === 'aborted') return false\n // 'error' / 'timeout': already reported by the errorBoundary / asyncControl.\n if (outcome !== 'applied') {\n fail()\n return false\n }\n if (value instanceof FetchFailure) {\n fail()\n const name = errorName(value.error)\n host.emit(\n 'DK_FETCH_FAILED',\n { ...(name === undefined ? {} : { errorName: name }), hadRows: host.loaded() },\n { cause: value.error },\n )\n return false\n }\n const bad = checkResponse(\n value,\n serverFeatures.some((f) => f.stage === 'paginate'),\n )\n if (bad) {\n fail()\n host.emit('DK_FETCH_BAD_RESPONSE', bad)\n return false\n }\n if (failed) {\n failed = false\n host.touch()\n }\n const result = value as { rows: readonly unknown[]; rowCount?: number }\n rowCount = result.rowCount ?? result.rows.length\n host.ingest(result.rows)\n return true\n }\n\n /** The last sort slice, its columns and the entries sent for them: same input, same array. */\n let sortIn: unknown\n let sortCols: unknown\n let sortOut: Sort = []\n\n /**\n * The sort entries sent to the fetcher (#100): restored state is untrusted. Only\n * `{ id: string, desc: boolean }`; with `columns`, an id that names no sortable column is\n * dropped (in development reported as `DK_STATE_UNKNOWN_KEY`, as columnFilters does).\n */\n function sortOf(by: unknown): Sort {\n const columns = host.options().columns\n if (by !== sortIn || columns !== sortCols) {\n sortIn = by\n sortCols = columns\n const known = columns?.length\n ? resolveColumns(columns)\n .list.filter((c) => (c.def as { sortable?: unknown }).sortable !== false)\n .map((c) => c.id)\n : undefined\n const out = (Array.isArray(by) ? by : []).flatMap((e) => {\n const id = e?.id\n if (typeof id !== 'string') return []\n if (known && !known.includes(id)) {\n if ((process.env.NODE_ENV !== \"production\")) host.emit('DK_STATE_UNKNOWN_KEY', { slice: 'sorting', key: id, known })\n return []\n }\n return [{ id, desc: e.desc === true }]\n })\n // Unchanged: the slice itself, so the same state never refetches.\n // Field by field, never JSON.stringify (a deep restored entry would overflow it, #103).\n const same =\n Array.isArray(by) &&\n by.length === out.length &&\n out.every((e, i) => {\n const b = by[i]\n return b.id === e.id && b.desc === e.desc && Object.keys(b).length === 2\n })\n sortOut = same ? (by as Sort) : out\n }\n return sortOut\n }\n\n const paramsOf = () => {\n const p = fetchParams(serverFeatures, host.state())\n if (p.sort) p.sort = sortOf(p.sort)\n return p\n }\n\n /** `refetch()` while deferred (#90): settled with the first request after `startTable`. */\n let later: Promise<FetchOutcome> | undefined\n let settleLater: ((outcome: FetchOutcome | Promise<FetchOutcome>) => void) | undefined\n\n host.intercept({\n name: 'serverSource',\n on: ['table:destroy'],\n intercept(_ctx, next) {\n settleLater?.({ status: 'superseded' })\n return next()\n },\n } as Interceptor<OpName>)\n\n function sync(force = false): Promise<FetchOutcome> | undefined {\n const fetcher = host.options().fetcher as Fetcher<unknown> | undefined\n if (typeof fetcher !== 'function' || host.destroyed()) return\n const params = paramsOf()\n if (!force && !settleLater && fetched && sameParams(fetched, params)) return\n // Deferred (SSR, §7.2): no request until startTable; then the latest params are fetched.\n if (requests.defer('fetcher')) {\n if (force) later ??= new Promise((resolve) => (settleLater = resolve))\n return later\n }\n fetched = params\n const waiting = settleLater\n settleLater = later = undefined\n let result: FetchOutcome['status'] | undefined\n const done = (): FetchOutcome => ({\n status: result ?? (host.destroyed() ? 'superseded' : 'error'),\n })\n const out = requests\n .request('fetcher', (signal) => callFetcher(fetcher, params, signal), {\n params,\n onSettle: (value, outcome) => {\n result = outcome === 'superseded' || outcome === 'aborted' ? 'superseded' : 'error'\n if (apply(value, outcome)) result = 'applied'\n },\n // strict: the DatakableError was reported by the hub; the status is 'error' (apply).\n })\n .then(done, done)\n waiting?.(out)\n return out\n }\n\n const options = host.options()\n checkFetcher(host.features, options.fetcher, host.name)\n // initialData answers the initial server state (§7.2): no first request, its total as rowCount.\n if (options.initialData !== undefined && options.data === undefined) {\n fetched = paramsOf()\n rowCount = options.rowCount ?? options.initialData.length\n }\n\n return {\n sync,\n rowCount: () => rowCount,\n status: () => (failed ? 'error' : undefined),\n check: (fetcher) => checkFetcher(host.features, fetcher, host.name, 'setOptions'),\n reset() {\n fetched = undefined\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,9 @@
1
1
  // src/source/server.ts
2
+ import { keepDiagnostics } from "../diagnostics/catalog.js";
3
+ import "../diagnostics/process-guard.js";
2
4
  import { errorName } from "../intercept/classify.js";
5
+ import { resolveColumns } from "../pipeline/columns.js";
6
+ import { stateDiagnostics } from "../table/state-codes.js";
3
7
  import {
4
8
  callFetcher,
5
9
  checkFetcher,
@@ -10,6 +14,7 @@ import {
10
14
  sameParams
11
15
  } from "./fetcher.js";
12
16
  import { asyncRequests } from "./requests.js";
17
+ keepDiagnostics(stateDiagnostics);
13
18
  var serverSource = (host) => {
14
19
  const requests = host.use(asyncRequests);
15
20
  const serverFeatures = host.features.filter(isServer);
@@ -59,18 +64,64 @@ var serverSource = (host) => {
59
64
  host.ingest(result.rows);
60
65
  return true;
61
66
  }
67
+ let sortIn;
68
+ let sortCols;
69
+ let sortOut = [];
70
+ function sortOf(by) {
71
+ const columns = host.options().columns;
72
+ if (by !== sortIn || columns !== sortCols) {
73
+ sortIn = by;
74
+ sortCols = columns;
75
+ const known = columns?.length ? resolveColumns(columns).list.filter((c) => c.def.sortable !== false).map((c) => c.id) : void 0;
76
+ const out = (Array.isArray(by) ? by : []).flatMap((e) => {
77
+ const id = e?.id;
78
+ if (typeof id !== "string") return [];
79
+ if (known && !known.includes(id)) {
80
+ if (process.env.NODE_ENV !== "production") host.emit("DK_STATE_UNKNOWN_KEY", { slice: "sorting", key: id, known });
81
+ return [];
82
+ }
83
+ return [{ id, desc: e.desc === true }];
84
+ });
85
+ const same = Array.isArray(by) && by.length === out.length && out.every((e, i) => {
86
+ const b = by[i];
87
+ return b.id === e.id && b.desc === e.desc && Object.keys(b).length === 2;
88
+ });
89
+ sortOut = same ? by : out;
90
+ }
91
+ return sortOut;
92
+ }
93
+ const paramsOf = () => {
94
+ const p = fetchParams(serverFeatures, host.state());
95
+ if (p.sort) p.sort = sortOf(p.sort);
96
+ return p;
97
+ };
98
+ let later;
99
+ let settleLater;
100
+ host.intercept({
101
+ name: "serverSource",
102
+ on: ["table:destroy"],
103
+ intercept(_ctx, next) {
104
+ settleLater?.({ status: "superseded" });
105
+ return next();
106
+ }
107
+ });
62
108
  function sync(force = false) {
63
109
  const fetcher = host.options().fetcher;
64
110
  if (typeof fetcher !== "function" || host.destroyed()) return;
65
- const params = fetchParams(serverFeatures, host.state());
66
- if (!force && fetched && sameParams(fetched, params)) return;
67
- if (requests.defer("fetcher")) return;
111
+ const params = paramsOf();
112
+ if (!force && !settleLater && fetched && sameParams(fetched, params)) return;
113
+ if (requests.defer("fetcher")) {
114
+ if (force) later ??= new Promise((resolve) => settleLater = resolve);
115
+ return later;
116
+ }
68
117
  fetched = params;
118
+ const waiting = settleLater;
119
+ settleLater = later = void 0;
69
120
  let result;
70
121
  const done = () => ({
71
122
  status: result ?? (host.destroyed() ? "superseded" : "error")
72
123
  });
73
- return requests.request("fetcher", (signal) => callFetcher(fetcher, params, signal), {
124
+ const out = requests.request("fetcher", (signal) => callFetcher(fetcher, params, signal), {
74
125
  params,
75
126
  onSettle: (value, outcome) => {
76
127
  result = outcome === "superseded" || outcome === "aborted" ? "superseded" : "error";
@@ -78,11 +129,13 @@ var serverSource = (host) => {
78
129
  }
79
130
  // strict: the DatakableError was reported by the hub; the status is 'error' (apply).
80
131
  }).then(done, done);
132
+ waiting?.(out);
133
+ return out;
81
134
  }
82
135
  const options = host.options();
83
136
  checkFetcher(host.features, options.fetcher, host.name);
84
137
  if (options.initialData !== void 0 && options.data === void 0) {
85
- fetched = fetchParams(serverFeatures, host.state());
138
+ fetched = paramsOf();
86
139
  rowCount = options.rowCount ?? options.initialData.length;
87
140
  }
88
141
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/source/server.ts"],"sourcesContent":["import { errorName } from '../intercept/classify'\nimport type { ExtensionFactory, TableExtension, TableHost } from '../table/internals'\nimport type { AsyncStatus } from './async-control'\nimport {\n callFetcher,\n checkFetcher,\n checkResponse,\n type Fetcher,\n FetchFailure,\n type FetchKey,\n type FetchOutcome,\n fetchParams,\n isServer,\n sameParams,\n} from './fetcher'\nimport { asyncRequests } from './requests'\n\n/** The server source of one table (§7.1): the fetcher's requests, its status and row count. */\nexport interface ServerSource extends TableExtension {\n /**\n * Starts a fetcher request when the server-mode state differs from the last request's (or\n * `force`: `refetch()`). Resolves with the outcome; never rejects.\n */\n sync(force?: boolean): Promise<FetchOutcome> | undefined\n /** `rowCount` of the last applied fetcher result (§6.5). */\n rowCount(): number | undefined\n /** `setOptions({ fetcher })`, before anything changes: `DK_FETCHER_WITHOUT_SERVER_MODE`. */\n check(fetcher: unknown): void\n /** A new fetcher reference: it runs at once, even with the same params (flush → sync). */\n reset(): void\n}\n\n/**\n * The server source as a table extension (changed 2026-10-13 for §9.3): it comes with\n * `withFetcher(fetcher)`, so a table without a fetcher never bundles it.\n */\nexport const serverSource: ExtensionFactory<ServerSource> = (host: TableHost) => {\n const requests = host.use(asyncRequests)\n const serverFeatures = host.features.filter(isServer)\n /** The latest fetcher request failed or returned a bad response: 'error' until one applies. */\n let failed = false\n /** `rowCount` of the last applied fetcher result (§6.5). */\n let rowCount: number | undefined\n /** Params of the last fetcher request; a flush with other params starts a new one (§7.1). */\n let fetched: FetchKey | undefined\n\n function fail() {\n if (failed) return\n failed = true\n host.touch()\n }\n\n /**\n * Applies a fetcher outcome; only the latest request reaches `'applied'` (asyncControl). `true`\n * when its rows were applied.\n */\n function apply(value: unknown, outcome: AsyncStatus): boolean {\n if (outcome === 'superseded') {\n host.emit('DK_FETCH_ABORTED', {})\n return false\n }\n if (outcome === 'aborted') return false\n // 'error' / 'timeout': already reported by the errorBoundary / asyncControl.\n if (outcome !== 'applied') {\n fail()\n return false\n }\n if (value instanceof FetchFailure) {\n fail()\n const name = errorName(value.error)\n host.emit(\n 'DK_FETCH_FAILED',\n { ...(name === undefined ? {} : { errorName: name }), hadRows: host.loaded() },\n { cause: value.error },\n )\n return false\n }\n const bad = checkResponse(\n value,\n serverFeatures.some((f) => f.stage === 'paginate'),\n )\n if (bad) {\n fail()\n host.emit('DK_FETCH_BAD_RESPONSE', bad)\n return false\n }\n if (failed) {\n failed = false\n host.touch()\n }\n const result = value as { rows: readonly unknown[]; rowCount?: number }\n rowCount = result.rowCount ?? result.rows.length\n host.ingest(result.rows)\n return true\n }\n\n function sync(force = false): Promise<FetchOutcome> | undefined {\n const fetcher = host.options().fetcher as Fetcher<unknown> | undefined\n if (typeof fetcher !== 'function' || host.destroyed()) return\n const params = fetchParams(serverFeatures, host.state())\n if (!force && fetched && sameParams(fetched, params)) return\n // Deferred (SSR, §7.2): no request until startTable; then the latest params are fetched.\n if (requests.defer('fetcher')) return\n fetched = params\n let result: FetchOutcome['status'] | undefined\n const done = (): FetchOutcome => ({\n status: result ?? (host.destroyed() ? 'superseded' : 'error'),\n })\n return requests\n .request('fetcher', (signal) => callFetcher(fetcher, params, signal), {\n params,\n onSettle: (value, outcome) => {\n result = outcome === 'superseded' || outcome === 'aborted' ? 'superseded' : 'error'\n if (apply(value, outcome)) result = 'applied'\n },\n // strict: the DatakableError was reported by the hub; the status is 'error' (apply).\n })\n .then(done, done)\n }\n\n const options = host.options()\n checkFetcher(host.features, options.fetcher, host.name)\n // initialData answers the initial server state (§7.2): no first request, its total as rowCount.\n if (options.initialData !== undefined && options.data === undefined) {\n fetched = fetchParams(serverFeatures, host.state())\n rowCount = options.rowCount ?? options.initialData.length\n }\n\n return {\n sync,\n rowCount: () => rowCount,\n status: () => (failed ? 'error' : undefined),\n check: (fetcher) => checkFetcher(host.features, fetcher, host.name, 'setOptions'),\n reset() {\n fetched = undefined\n },\n }\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAG1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAqBvB,IAAM,eAA+C,CAAC,SAAoB;AAC/E,QAAM,WAAW,KAAK,IAAI,aAAa;AACvC,QAAM,iBAAiB,KAAK,SAAS,OAAO,QAAQ;AAEpD,MAAI,SAAS;AAEb,MAAI;AAEJ,MAAI;AAEJ,WAAS,OAAO;AACd,QAAI,OAAQ;AACZ,aAAS;AACT,SAAK,MAAM;AAAA,EACb;AAMA,WAAS,MAAM,OAAgB,SAA+B;AAC5D,QAAI,YAAY,cAAc;AAC5B,WAAK,KAAK,oBAAoB,CAAC,CAAC;AAChC,aAAO;AAAA,IACT;AACA,QAAI,YAAY,UAAW,QAAO;AAElC,QAAI,YAAY,WAAW;AACzB,WAAK;AACL,aAAO;AAAA,IACT;AACA,QAAI,iBAAiB,cAAc;AACjC,WAAK;AACL,YAAM,OAAO,UAAU,MAAM,KAAK;AAClC,WAAK;AAAA,QACH;AAAA,QACA,EAAE,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK,GAAI,SAAS,KAAK,OAAO,EAAE;AAAA,QAC7E,EAAE,OAAO,MAAM,MAAM;AAAA,MACvB;AACA,aAAO;AAAA,IACT;AACA,UAAM,MAAM;AAAA,MACV;AAAA,MACA,eAAe,KAAK,CAAC,MAAM,EAAE,UAAU,UAAU;AAAA,IACnD;AACA,QAAI,KAAK;AACP,WAAK;AACL,WAAK,KAAK,yBAAyB,GAAG;AACtC,aAAO;AAAA,IACT;AACA,QAAI,QAAQ;AACV,eAAS;AACT,WAAK,MAAM;AAAA,IACb;AACA,UAAM,SAAS;AACf,eAAW,OAAO,YAAY,OAAO,KAAK;AAC1C,SAAK,OAAO,OAAO,IAAI;AACvB,WAAO;AAAA,EACT;AAEA,WAAS,KAAK,QAAQ,OAA0C;AAC9D,UAAM,UAAU,KAAK,QAAQ,EAAE;AAC/B,QAAI,OAAO,YAAY,cAAc,KAAK,UAAU,EAAG;AACvD,UAAM,SAAS,YAAY,gBAAgB,KAAK,MAAM,CAAC;AACvD,QAAI,CAAC,SAAS,WAAW,WAAW,SAAS,MAAM,EAAG;AAEtD,QAAI,SAAS,MAAM,SAAS,EAAG;AAC/B,cAAU;AACV,QAAI;AACJ,UAAM,OAAO,OAAqB;AAAA,MAChC,QAAQ,WAAW,KAAK,UAAU,IAAI,eAAe;AAAA,IACvD;AACA,WAAO,SACJ,QAAQ,WAAW,CAAC,WAAW,YAAY,SAAS,QAAQ,MAAM,GAAG;AAAA,MACpE;AAAA,MACA,UAAU,CAAC,OAAO,YAAY;AAC5B,iBAAS,YAAY,gBAAgB,YAAY,YAAY,eAAe;AAC5E,YAAI,MAAM,OAAO,OAAO,EAAG,UAAS;AAAA,MACtC;AAAA;AAAA,IAEF,CAAC,EACA,KAAK,MAAM,IAAI;AAAA,EACpB;AAEA,QAAM,UAAU,KAAK,QAAQ;AAC7B,eAAa,KAAK,UAAU,QAAQ,SAAS,KAAK,IAAI;AAEtD,MAAI,QAAQ,gBAAgB,UAAa,QAAQ,SAAS,QAAW;AACnE,cAAU,YAAY,gBAAgB,KAAK,MAAM,CAAC;AAClD,eAAW,QAAQ,YAAY,QAAQ,YAAY;AAAA,EACrD;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,QAAQ,MAAO,SAAS,UAAU;AAAA,IAClC,OAAO,CAAC,YAAY,aAAa,KAAK,UAAU,SAAS,KAAK,MAAM,YAAY;AAAA,IAChF,QAAQ;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/source/server.ts"],"sourcesContent":["import { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { errorName } from '../intercept/classify'\nimport type { Interceptor } from '../intercept/define'\nimport type { OpName } from '../intercept/operations'\nimport { resolveColumns } from '../pipeline/columns'\nimport type { ExtensionFactory, TableExtension, TableHost } from '../table/internals'\nimport { stateDiagnostics } from '../table/state-codes'\nimport type { AsyncStatus } from './async-control'\nimport {\n callFetcher,\n checkFetcher,\n checkResponse,\n type Fetcher,\n FetchFailure,\n type FetchKey,\n type FetchOutcome,\n type FetchParams,\n fetchParams,\n isServer,\n sameParams,\n} from './fetcher'\nimport { asyncRequests } from './requests'\n\n/** The server source of one table (§7.1): the fetcher's requests, its status and row count. */\nexport interface ServerSource extends TableExtension {\n /**\n * Starts a fetcher request when the server-mode state differs from the last request's (or\n * `force`: `refetch()`). Resolves with the outcome; never rejects. Deferred (`createDeferred`),\n * `force` resolves with the request that runs after `startTable` (`'superseded'` when the table\n * is destroyed first, #90).\n */\n sync(force?: boolean): Promise<FetchOutcome> | undefined\n /** `rowCount` of the last applied fetcher result (§6.5). */\n rowCount(): number | undefined\n /** `setOptions({ fetcher })`, before anything changes: `DK_FETCHER_WITHOUT_SERVER_MODE`. */\n check(fetcher: unknown): void\n /** A new fetcher reference: it runs at once, even with the same params (flush → sync). */\n reset(): void\n}\n\n/**\n * The server source as a table extension (changed 2026-10-13 for §9.3): it comes with\n * `withFetcher(fetcher)`, so a table without a fetcher never bundles it.\n */\n// DK_STATE_UNKNOWN_KEY for a restored sort id that names no column (#100).\nkeepDiagnostics(stateDiagnostics)\n\ntype Sort = NonNullable<FetchParams['sort']>\n\nexport const serverSource: ExtensionFactory<ServerSource> = (host: TableHost) => {\n const requests = host.use(asyncRequests)\n const serverFeatures = host.features.filter(isServer)\n /** The latest fetcher request failed or returned a bad response: 'error' until one applies. */\n let failed = false\n /** `rowCount` of the last applied fetcher result (§6.5). */\n let rowCount: number | undefined\n /** Params of the last fetcher request; a flush with other params starts a new one (§7.1). */\n let fetched: FetchKey | undefined\n\n function fail() {\n if (failed) return\n failed = true\n host.touch()\n }\n\n /**\n * Applies a fetcher outcome; only the latest request reaches `'applied'` (asyncControl). `true`\n * when its rows were applied.\n */\n function apply(value: unknown, outcome: AsyncStatus): boolean {\n if (outcome === 'superseded') {\n host.emit('DK_FETCH_ABORTED', {})\n return false\n }\n if (outcome === 'aborted') return false\n // 'error' / 'timeout': already reported by the errorBoundary / asyncControl.\n if (outcome !== 'applied') {\n fail()\n return false\n }\n if (value instanceof FetchFailure) {\n fail()\n const name = errorName(value.error)\n host.emit(\n 'DK_FETCH_FAILED',\n { ...(name === undefined ? {} : { errorName: name }), hadRows: host.loaded() },\n { cause: value.error },\n )\n return false\n }\n const bad = checkResponse(\n value,\n serverFeatures.some((f) => f.stage === 'paginate'),\n )\n if (bad) {\n fail()\n host.emit('DK_FETCH_BAD_RESPONSE', bad)\n return false\n }\n if (failed) {\n failed = false\n host.touch()\n }\n const result = value as { rows: readonly unknown[]; rowCount?: number }\n rowCount = result.rowCount ?? result.rows.length\n host.ingest(result.rows)\n return true\n }\n\n /** The last sort slice, its columns and the entries sent for them: same input, same array. */\n let sortIn: unknown\n let sortCols: unknown\n let sortOut: Sort = []\n\n /**\n * The sort entries sent to the fetcher (#100): restored state is untrusted. Only\n * `{ id: string, desc: boolean }`; with `columns`, an id that names no sortable column is\n * dropped (in development reported as `DK_STATE_UNKNOWN_KEY`, as columnFilters does).\n */\n function sortOf(by: unknown): Sort {\n const columns = host.options().columns\n if (by !== sortIn || columns !== sortCols) {\n sortIn = by\n sortCols = columns\n const known = columns?.length\n ? resolveColumns(columns)\n .list.filter((c) => (c.def as { sortable?: unknown }).sortable !== false)\n .map((c) => c.id)\n : undefined\n const out = (Array.isArray(by) ? by : []).flatMap((e) => {\n const id = e?.id\n if (typeof id !== 'string') return []\n if (known && !known.includes(id)) {\n if ((process.env.NODE_ENV !== \"production\")) host.emit('DK_STATE_UNKNOWN_KEY', { slice: 'sorting', key: id, known })\n return []\n }\n return [{ id, desc: e.desc === true }]\n })\n // Unchanged: the slice itself, so the same state never refetches.\n // Field by field, never JSON.stringify (a deep restored entry would overflow it, #103).\n const same =\n Array.isArray(by) &&\n by.length === out.length &&\n out.every((e, i) => {\n const b = by[i]\n return b.id === e.id && b.desc === e.desc && Object.keys(b).length === 2\n })\n sortOut = same ? (by as Sort) : out\n }\n return sortOut\n }\n\n const paramsOf = () => {\n const p = fetchParams(serverFeatures, host.state())\n if (p.sort) p.sort = sortOf(p.sort)\n return p\n }\n\n /** `refetch()` while deferred (#90): settled with the first request after `startTable`. */\n let later: Promise<FetchOutcome> | undefined\n let settleLater: ((outcome: FetchOutcome | Promise<FetchOutcome>) => void) | undefined\n\n host.intercept({\n name: 'serverSource',\n on: ['table:destroy'],\n intercept(_ctx, next) {\n settleLater?.({ status: 'superseded' })\n return next()\n },\n } as Interceptor<OpName>)\n\n function sync(force = false): Promise<FetchOutcome> | undefined {\n const fetcher = host.options().fetcher as Fetcher<unknown> | undefined\n if (typeof fetcher !== 'function' || host.destroyed()) return\n const params = paramsOf()\n if (!force && !settleLater && fetched && sameParams(fetched, params)) return\n // Deferred (SSR, §7.2): no request until startTable; then the latest params are fetched.\n if (requests.defer('fetcher')) {\n if (force) later ??= new Promise((resolve) => (settleLater = resolve))\n return later\n }\n fetched = params\n const waiting = settleLater\n settleLater = later = undefined\n let result: FetchOutcome['status'] | undefined\n const done = (): FetchOutcome => ({\n status: result ?? (host.destroyed() ? 'superseded' : 'error'),\n })\n const out = requests\n .request('fetcher', (signal) => callFetcher(fetcher, params, signal), {\n params,\n onSettle: (value, outcome) => {\n result = outcome === 'superseded' || outcome === 'aborted' ? 'superseded' : 'error'\n if (apply(value, outcome)) result = 'applied'\n },\n // strict: the DatakableError was reported by the hub; the status is 'error' (apply).\n })\n .then(done, done)\n waiting?.(out)\n return out\n }\n\n const options = host.options()\n checkFetcher(host.features, options.fetcher, host.name)\n // initialData answers the initial server state (§7.2): no first request, its total as rowCount.\n if (options.initialData !== undefined && options.data === undefined) {\n fetched = paramsOf()\n rowCount = options.rowCount ?? options.initialData.length\n }\n\n return {\n sync,\n rowCount: () => rowCount,\n status: () => (failed ? 'error' : undefined),\n check: (fetcher) => checkFetcher(host.features, fetcher, host.name, 'setOptions'),\n reset() {\n fetched = undefined\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -49,13 +49,15 @@ var import_codes2 = require("./codes.cjs");
49
49
  var import_data_mutation = require("./data-mutation.cjs");
50
50
  var import_internals = require("./internals.cjs");
51
51
  var import_read_once = require("./read-once.cjs");
52
+ var import_state_codes = require("./state-codes.cjs");
52
53
  var import_state_feedback = require("./state-feedback.cjs");
53
54
  var import_types = require("./types.cjs");
54
55
  (0, import_catalog.keepDiagnostics)(
55
56
  import_codes2.tableDiagnostics,
56
57
  import_data_mutation.dataMutationDiagnostics,
57
58
  import_state_feedback.stateFeedbackDiagnostics,
58
- import_codes.optionDiagnostics
59
+ import_codes.optionDiagnostics,
60
+ import_state_codes.stateDiagnostics
59
61
  );
60
62
  var EMPTY = [];
61
63
  var RANK = [
@@ -68,17 +70,21 @@ var RANK = [
68
70
  ];
69
71
  var isThenable = (v) => v != null && typeof v.then === "function";
70
72
  var deepFrozen = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakSet() : void 0;
71
- function deepFreeze(v) {
72
- if (v === null || typeof v !== "object" || deepFrozen?.has(v)) return;
73
- const isArray = Array.isArray(v);
74
- if (!isArray) {
75
- const proto = Object.getPrototypeOf(v);
76
- if (proto !== Object.prototype && proto !== null) return;
77
- }
78
- deepFrozen?.add(v);
79
- Object.freeze(v);
80
- if (isArray) for (let i = 0; i < v.length; i++) deepFreeze(v[i]);
81
- else for (const k in v) deepFreeze(v[k]);
73
+ function deepFreeze(root) {
74
+ const stack = [root];
75
+ while (stack.length > 0) {
76
+ const v = stack.pop();
77
+ if (v === null || typeof v !== "object" || deepFrozen?.has(v)) continue;
78
+ const isArray = Array.isArray(v);
79
+ if (!isArray) {
80
+ const proto = Object.getPrototypeOf(v);
81
+ if (proto !== Object.prototype && proto !== null) continue;
82
+ }
83
+ deepFrozen?.add(v);
84
+ Object.freeze(v);
85
+ if (isArray) for (let i = 0; i < v.length; i++) stack.push(v[i]);
86
+ else for (const k in v) stack.push(v[k]);
87
+ }
82
88
  }
83
89
  var freeze = (s) => {
84
90
  if (process.env.NODE_ENV !== "production") deepFreeze(s);
@@ -96,7 +102,7 @@ function createTable(initialOptions) {
96
102
  let ingestFailed = false;
97
103
  let validatingSeq = 0;
98
104
  let schemaOutcome;
99
- let initialInput = options.data === void 0 ? options.initialData : void 0;
105
+ let initialInput = options.data == null ? options.initialData : void 0;
100
106
  let initialRows;
101
107
  let deferring = import_internals.deferral.depth > 0;
102
108
  const extensions = /* @__PURE__ */ new Map();
@@ -207,10 +213,15 @@ function createTable(initialOptions) {
207
213
  trigger = prev;
208
214
  }
209
215
  }
216
+ function protoKey(k) {
217
+ if (k !== import_state_codes.PROTO_KEY) return false;
218
+ hub.emit("DK_STATE_UNKNOWN_KEY", { slice: "", key: k, known: EMPTY });
219
+ return true;
220
+ }
210
221
  function controlledKeys(state) {
211
222
  const keys = /* @__PURE__ */ new Set();
212
223
  if (state) {
213
- for (const k of Object.keys(state)) if (state[k] !== void 0) keys.add(k);
224
+ for (const k of Object.keys(state)) if (state[k] !== void 0 && !protoKey(k)) keys.add(k);
214
225
  }
215
226
  return keys;
216
227
  }
@@ -257,6 +268,7 @@ function createTable(initialOptions) {
257
268
  let changes;
258
269
  let dropped;
259
270
  for (const k of Object.keys(partial)) {
271
+ if (protoKey(k)) continue;
260
272
  const v = partial[k];
261
273
  if (Object.is(snapshot[k], v)) continue;
262
274
  if (controlled.has(k)) {
@@ -459,7 +471,7 @@ function createTable(initialOptions) {
459
471
  const initial = fetched === void 0 && initialInput !== void 0;
460
472
  const input = fetched ?? (initial ? initialInput : options.data);
461
473
  initialInput = void 0;
462
- const given = input !== void 0;
474
+ const given = input != null;
463
475
  const source = fetched ? "fetch" : "data-change";
464
476
  if (validatingSeq !== 0) {
465
477
  validatingSeq = 0;
@@ -694,11 +706,9 @@ function createTable(initialOptions) {
694
706
  });
695
707
  controlled = controlledKeys(options.state);
696
708
  if (process.env.NODE_ENV !== "production") feedback?.settle(options.state, controlled);
697
- snapshot = overlay(
698
- freeze(
699
- rt ? rt.initialSlices(features, options.initialState, hub.emit) : { ...options.initialState }
700
- )
701
- );
709
+ const restored = { ...options.initialState };
710
+ if (Object.hasOwn(restored, import_state_codes.PROTO_KEY) && protoKey(import_state_codes.PROTO_KEY)) delete restored[import_state_codes.PROTO_KEY];
711
+ snapshot = overlay(freeze(rt ? rt.initialSlices(features, restored, hub.emit) : restored));
702
712
  notified = snapshot;
703
713
  rt?.installApis(features, {
704
714
  table,