@datakable/core 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{codes-AX8XkMNY.d.cts → codes-4sYQv_7J.d.cts} +36 -10
- package/dist/{codes-AX8XkMNY.d.ts → codes-4sYQv_7J.d.ts} +36 -10
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +1 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +2 -2
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/console.cjs +15 -4
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +15 -4
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/prose.cjs +10 -1
- package/dist/diagnostics/prose.cjs.map +1 -1
- package/dist/diagnostics/prose.js +9 -1
- package/dist/diagnostics/prose.js.map +1 -1
- package/dist/{explain-pKAnnxEW.d.cts → explain-DmwN_RG9.d.cts} +11 -4
- package/dist/{explain-BVE3ln3G.d.ts → explain-Dr3SmMNY.d.ts} +11 -4
- package/dist/feature/install.cjs +2 -1
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +2 -1
- package/dist/feature/install.js.map +1 -1
- package/dist/feature/slice-guard.cjs +76 -0
- package/dist/feature/slice-guard.cjs.map +1 -0
- package/dist/feature/slice-guard.js +50 -0
- package/dist/feature/slice-guard.js.map +1 -0
- package/dist/features/column-filters/codes.cjs +2 -19
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +2 -19
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +37 -22
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +37 -22
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +6 -2
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +6 -2
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +2 -2
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +2 -2
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +18 -2
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +16 -1
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-engine.cjs +24 -5
- package/dist/features/search/search-engine.cjs.map +1 -1
- package/dist/features/search/search-engine.js +25 -6
- package/dist/features/search/search-engine.js.map +1 -1
- package/dist/features/search/search-index.cjs +23 -44
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +24 -45
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/sorting/ranks.cjs +117 -50
- package/dist/features/sorting/ranks.cjs.map +1 -1
- package/dist/features/sorting/ranks.js +117 -50
- package/dist/features/sorting/ranks.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +87 -32
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +87 -32
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/index.d.cts +26 -17
- package/dist/index.d.ts +26 -17
- package/dist/intercept/runtime.cjs +3 -2
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +3 -2
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +2 -0
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +2 -0
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/column-cache.cjs +6 -2
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +6 -2
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/release.cjs +34 -0
- package/dist/pipeline/release.cjs.map +1 -0
- package/dist/pipeline/release.js +8 -0
- package/dist/pipeline/release.js.map +1 -0
- package/dist/pipeline/runner.cjs +15 -1
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +15 -1
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-ByMJbHjc.d.cts → search-async-entry-BiemO3JT.d.cts} +2 -2
- package/dist/{search-async-entry-CFQODE08.d.ts → search-async-entry-GRj0E06n.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +4 -4
- package/dist/search.d.ts +4 -4
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/async-control.cjs +2 -1
- package/dist/source/async-control.cjs.map +1 -1
- package/dist/source/async-control.js +2 -1
- package/dist/source/async-control.js.map +1 -1
- package/dist/source/fetcher.cjs.map +1 -1
- package/dist/source/fetcher.js.map +1 -1
- package/dist/source/server.cjs +58 -5
- package/dist/source/server.cjs.map +1 -1
- package/dist/source/server.js +58 -5
- package/dist/source/server.js.map +1 -1
- package/dist/table/create-table.cjs +33 -20
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +33 -20
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/index.cjs +3 -0
- package/dist/table/index.cjs.map +1 -1
- package/dist/table/index.js +5 -1
- package/dist/table/index.js.map +1 -1
- package/dist/table/state-codes.cjs +72 -0
- package/dist/table/state-codes.cjs.map +1 -0
- package/dist/table/state-codes.js +45 -0
- package/dist/table/state-codes.js.map +1 -0
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/dist/ingest/test-schemas.cjs +0 -73
- package/dist/ingest/test-schemas.cjs.map +0 -1
- package/dist/ingest/test-schemas.js +0 -46
- 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-
|
|
2
|
-
import { ad as SortAlgorithm, a1 as Path, e as FeatureTypes, F as Feature } from './explain-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAiC;AACjC,qBAAgC;AAChC,oBAA8B;AAC9B,mBAAuB;AAGvB,yBAAmD;AAAA,IAEnD,gCAAgB,mCAAgB;
|
|
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":";AAAA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AAGvB,SAA2B,wBAAwB;AAEnD,gBAAgB,gBAAgB;
|
|
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":";AAAA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AAGvB,SAA2B,wBAAwB;AAEnD,gBAAgB,gBAAgB;AA2CzB,IAAM,WAAW,CAAC,MAAkB,EAAE,UAAU,UAAU,EAAE,SAAS;AAE5E,IAAM,OAAO,CAAC,MAAkB,OAAO,QAAQ,EAAE,KAAK;AAO/C,SAAS,eAAe,UAAiC,OAAqB;AACnF,MAAI;AACJ,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,UAAU,OAAQ;AACxB,QAAI,EAAE,SAAS,UAAU;AACvB,iBAAW;AACX;AAAA,IACF;AACA,QAAI,UAAU,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG;AACpC;AAAA,QACE;AAAA,QACA,EAAE,QAAQ,OAAO,MAAM,aAAa,OAAO,OAAO,QAAQ,EAAE,MAAM,aAAa,EAAE,MAAM;AAAA,QACvF,EAAE,MAAM;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,aACd,UACA,SACA,OACA,QAA+B,QACzB;AACN,MAAI,YAAY,UAAa,SAAS,KAAK,QAAQ,EAAG;AACtD;AAAA,IACE;AAAA,IACA,EAAE,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM;AAAA,IACjF,EAAE,OAAO,SAAS,MAAM;AAAA,EAC1B;AACF;AASO,SAAS,YAAY,QAA+B,OAA6B;AACtF,QAAM,IAAc,CAAC;AACrB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,QAAI,EAAE,SAAS,SAAU,GAAE,QAAQ,OAAO,GAAG,UAAU,WAAW,EAAE,QAAQ;AAAA,aACnE,EAAE,SAAS,gBAAiB,GAAE,UAAU,KAAK,CAAC;AAAA,aAC9C,EAAE,SAAS,UAAW,GAAE,OAAQ,GAAG,MAA8B,CAAC;AAAA,aAClE,EAAE,SAAS,cAAc;AAChC,QAAE,YAAY,GAAG;AACjB,QAAE,WAAW,GAAG;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,WAAW,GAAa,GAAsB;AAC5D,QAAM,IAAI;AACV,QAAM,IAAI;AACV,aAAW,KAAK,EAAG,KAAI,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAG,QAAO;AACtD,aAAW,KAAK,EAAG,KAAI,EAAE,KAAK,GAAI,QAAO;AACzC,SAAO;AACT;AAGO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAqB,OAAgB;AAAhB;AAAA,EAAiB;AACxC;AAEA,IAAM,eAAe,CAAC,MACpB,KAAK,QAAQ,OAAO,MAAM,YAAa,EAAyB,SAAS;AAO3E,eAAsB,YACpB,SACA,QACA,QACkB;AAClB,MAAI;AACF,WAAO,MAAM,QAAQ,EAAE,GAAG,QAAQ,OAAO,CAAC;AAAA,EAC5C,SAAS,GAAG;AACV,QAAI,OAAO,WAAW,aAAa,CAAC,EAAG,OAAM;AAC7C,WAAO,IAAI,aAAa,CAAC;AAAA,EAC3B;AACF;AAUO,SAAS,cAAc,GAAY,WAAoD;AAC5F,MAAI,MAAM,QAAQ,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,GAAG;AAC3D,WAAO,EAAE,SAAS,iBAAiB,UAAU,cAAc,CAAC,EAAE;AAAA,EAChE;AACA,QAAM,EAAE,MAAM,SAAS,IAAI;AAC3B,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,EAAE,SAAS,qBAAqB,UAAU,cAAc,IAAI,EAAE;AAC/F,MAAI,aAAa,OAAW,QAAO,YAAY,EAAE,SAAS,mBAAmB,IAAI;AACjF,MAAI,CAAC,OAAO,UAAU,QAAQ,KAAM,WAAsB,GAAG;AAC3D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI,cAAc,QAAQ;AAAA,IACpF;AAAA,EACF;AACA,MAAK,WAAsB,KAAK,QAAQ;AACtC,WAAO,EAAE,SAAS,sBAAsB,MAAM,KAAK,QAAQ,SAA6B;AAAA,EAC1F;AACA,SAAO;AACT;","names":[]}
|
package/dist/source/server.cjs
CHANGED
|
@@ -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 = (
|
|
82
|
-
if (!force && fetched && (0, import_fetcher.sameParams)(fetched, params)) return;
|
|
83
|
-
if (requests.defer("fetcher"))
|
|
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
|
-
|
|
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 = (
|
|
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAgC;AAChC,2BAAO;AACP,sBAA0B;AAG1B,qBAA+B;AAE/B,yBAAiC;AAEjC,qBAYO;AACP,sBAA8B;AAAA,IAwB9B,gCAAgB,mCAAgB;AAIzB,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;AAGA,MAAI;AACJ,MAAI;AACJ,MAAI,UAAgB,CAAC;AAOrB,WAAS,OAAO,IAAmB;AACjC,UAAM,UAAU,KAAK,QAAQ,EAAE;AAC/B,QAAI,OAAO,UAAU,YAAY,UAAU;AACzC,eAAS;AACT,iBAAW;AACX,YAAM,QAAQ,SAAS,aACnB,+BAAe,OAAO,EACnB,KAAK,OAAO,CAAC,MAAO,EAAE,IAA+B,aAAa,KAAK,EACvE,IAAI,CAAC,MAAM,EAAE,EAAE,IAClB;AACJ,YAAM,OAAO,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,MAAM;AACvD,cAAM,KAAK,GAAG;AACd,YAAI,OAAO,OAAO,SAAU,QAAO,CAAC;AACpC,YAAI,SAAS,CAAC,MAAM,SAAS,EAAE,GAAG;AAChC,cAAK,QAAQ,IAAI,aAAa,aAAe,MAAK,KAAK,wBAAwB,EAAE,OAAO,WAAW,KAAK,IAAI,MAAM,CAAC;AACnH,iBAAO,CAAC;AAAA,QACV;AACA,eAAO,CAAC,EAAE,IAAI,MAAM,EAAE,SAAS,KAAK,CAAC;AAAA,MACvC,CAAC;AAGD,YAAM,OACJ,MAAM,QAAQ,EAAE,KAChB,GAAG,WAAW,IAAI,UAClB,IAAI,MAAM,CAAC,GAAG,MAAM;AAClB,cAAM,IAAI,GAAG,CAAC;AACd,eAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,OAAO,KAAK,CAAC,EAAE,WAAW;AAAA,MACzE,CAAC;AACH,gBAAU,OAAQ,KAAc;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM;AACrB,UAAM,QAAI,4BAAY,gBAAgB,KAAK,MAAM,CAAC;AAClD,QAAI,EAAE,KAAM,GAAE,OAAO,OAAO,EAAE,IAAI;AAClC,WAAO;AAAA,EACT;AAGA,MAAI;AACJ,MAAI;AAEJ,OAAK,UAAU;AAAA,IACb,MAAM;AAAA,IACN,IAAI,CAAC,eAAe;AAAA,IACpB,UAAU,MAAM,MAAM;AACpB,oBAAc,EAAE,QAAQ,aAAa,CAAC;AACtC,aAAO,KAAK;AAAA,IACd;AAAA,EACF,CAAwB;AAExB,WAAS,KAAK,QAAQ,OAA0C;AAC9D,UAAM,UAAU,KAAK,QAAQ,EAAE;AAC/B,QAAI,OAAO,YAAY,cAAc,KAAK,UAAU,EAAG;AACvD,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,SAAS,CAAC,eAAe,eAAW,2BAAW,SAAS,MAAM,EAAG;AAEtE,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,UAAI,MAAO,WAAU,IAAI,QAAQ,CAAC,YAAa,cAAc,OAAQ;AACrE,aAAO;AAAA,IACT;AACA,cAAU;AACV,UAAM,UAAU;AAChB,kBAAc,QAAQ;AACtB,QAAI;AACJ,UAAM,OAAO,OAAqB;AAAA,MAChC,QAAQ,WAAW,KAAK,UAAU,IAAI,eAAe;AAAA,IACvD;AACA,UAAM,MAAM,SACT,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;AAClB,cAAU,GAAG;AACb,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,KAAK,QAAQ;AAC7B,mCAAa,KAAK,UAAU,QAAQ,SAAS,KAAK,IAAI;AAEtD,MAAI,QAAQ,gBAAgB,UAAa,QAAQ,SAAS,QAAW;AACnE,cAAU,SAAS;AACnB,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":[]}
|
package/dist/source/server.js
CHANGED
|
@@ -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 =
|
|
66
|
-
if (!force && fetched && sameParams(fetched, params)) return;
|
|
67
|
-
if (requests.defer("fetcher"))
|
|
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
|
-
|
|
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 =
|
|
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":[]}
|