@datakable/core 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/catalog/codes.cjs +12 -8
- package/dist/catalog/codes.cjs.map +1 -1
- package/dist/catalog/codes.js +12 -8
- package/dist/catalog/codes.js.map +1 -1
- package/dist/catalog/slots.cjs +10 -6
- package/dist/catalog/slots.cjs.map +1 -1
- package/dist/catalog/slots.js +10 -6
- package/dist/catalog/slots.js.map +1 -1
- package/dist/{codes-OsjAR3Wz.d.cts → codes-B9bCK73-.d.cts} +30 -4
- package/dist/{codes-OsjAR3Wz.d.ts → codes-B9bCK73-.d.ts} +30 -4
- package/dist/column/column-def.cjs.map +1 -1
- package/dist/column/get-value.cjs +6 -1
- package/dist/column/get-value.cjs.map +1 -1
- package/dist/column/get-value.js +6 -1
- package/dist/column/get-value.js.map +1 -1
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +4 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +4 -1
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/catalog.cjs +11 -5
- package/dist/diagnostics/catalog.cjs.map +1 -1
- package/dist/diagnostics/catalog.js +10 -5
- package/dist/diagnostics/catalog.js.map +1 -1
- package/dist/diagnostics/codes.cjs +12 -8
- package/dist/diagnostics/codes.cjs.map +1 -1
- package/dist/diagnostics/codes.js +12 -8
- package/dist/diagnostics/codes.js.map +1 -1
- package/dist/diagnostics/console.cjs +13 -6
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +13 -6
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/env.cjs +1 -0
- package/dist/diagnostics/env.cjs.map +1 -1
- package/dist/diagnostics/env.js +1 -0
- package/dist/diagnostics/env.js.map +1 -1
- package/dist/diagnostics/explain-text.cjs +2 -1
- package/dist/diagnostics/explain-text.cjs.map +1 -1
- package/dist/diagnostics/explain-text.js +2 -1
- package/dist/diagnostics/explain-text.js.map +1 -1
- package/dist/diagnostics/explain.cjs +3 -2
- package/dist/diagnostics/explain.cjs.map +1 -1
- package/dist/diagnostics/explain.js +3 -2
- package/dist/diagnostics/explain.js.map +1 -1
- package/dist/diagnostics/hub.cjs +5 -4
- package/dist/diagnostics/hub.cjs.map +1 -1
- package/dist/diagnostics/hub.js +5 -4
- package/dist/diagnostics/hub.js.map +1 -1
- package/dist/diagnostics/process-guard.cjs +26 -0
- package/dist/diagnostics/process-guard.cjs.map +1 -0
- package/dist/diagnostics/process-guard.js +9 -0
- package/dist/diagnostics/process-guard.js.map +1 -0
- package/dist/diagnostics/prose.cjs +120 -0
- package/dist/diagnostics/prose.cjs.map +1 -0
- package/dist/diagnostics/prose.js +93 -0
- package/dist/diagnostics/prose.js.map +1 -0
- package/dist/diagnostics/redact.cjs +7 -4
- package/dist/diagnostics/redact.cjs.map +1 -1
- package/dist/diagnostics/redact.js +6 -4
- package/dist/diagnostics/redact.js.map +1 -1
- package/dist/diagnostics/trace-store.cjs +6 -0
- package/dist/diagnostics/trace-store.cjs.map +1 -1
- package/dist/diagnostics/trace-store.js +4 -0
- package/dist/diagnostics/trace-store.js.map +1 -1
- package/dist/diagnostics/tracer.cjs +74 -13
- package/dist/diagnostics/tracer.cjs.map +1 -1
- package/dist/diagnostics/tracer.js +75 -14
- package/dist/diagnostics/tracer.js.map +1 -1
- package/dist/diagnostics/types.cjs.map +1 -1
- package/dist/{explain-vkD2cewE.d.cts → explain-Q8Cg0cZy.d.cts} +13 -4
- package/dist/{explain-Dn99IQt0.d.ts → explain-i0OATi_W.d.ts} +13 -4
- package/dist/feature/codes.cjs +1 -0
- package/dist/feature/codes.cjs.map +1 -1
- package/dist/feature/codes.js +1 -0
- package/dist/feature/codes.js.map +1 -1
- package/dist/feature/install.cjs +4 -3
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +4 -3
- package/dist/feature/install.js.map +1 -1
- package/dist/features/column-filters/codes.cjs +1 -0
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +1 -0
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +4 -2
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +4 -2
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +5 -5
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +5 -5
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/codes.cjs +1 -0
- package/dist/features/pagination/codes.cjs.map +1 -1
- package/dist/features/pagination/codes.js +1 -0
- package/dist/features/pagination/codes.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +16 -13
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +16 -13
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +1 -0
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +1 -0
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-index.cjs +68 -15
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +68 -15
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/search/search.cjs +41 -9
- package/dist/features/search/search.cjs.map +1 -1
- package/dist/features/search/search.js +41 -9
- package/dist/features/search/search.js.map +1 -1
- package/dist/features/search/skipped-codes.cjs +45 -0
- package/dist/features/search/skipped-codes.cjs.map +1 -0
- package/dist/features/search/skipped-codes.js +20 -0
- package/dist/features/search/skipped-codes.js.map +1 -0
- package/dist/features/sorting/codes.cjs +1 -0
- package/dist/features/sorting/codes.cjs.map +1 -1
- package/dist/features/sorting/codes.js +1 -0
- package/dist/features/sorting/codes.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +16 -20
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +16 -20
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/features/sorting/strategy.cjs +4 -4
- package/dist/features/sorting/strategy.cjs.map +1 -1
- package/dist/features/sorting/strategy.js +4 -4
- package/dist/features/sorting/strategy.js.map +1 -1
- package/dist/features/validity/codes.cjs +1 -0
- package/dist/features/validity/codes.cjs.map +1 -1
- package/dist/features/validity/codes.js +1 -0
- package/dist/features/validity/codes.js.map +1 -1
- package/dist/features/validity/validity.cjs +3 -3
- package/dist/features/validity/validity.cjs.map +1 -1
- package/dist/features/validity/validity.js +3 -3
- package/dist/features/validity/validity.js.map +1 -1
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/ingest/data-check-codes.cjs +4 -2
- package/dist/ingest/data-check-codes.cjs.map +1 -1
- package/dist/ingest/data-check-codes.js +4 -2
- package/dist/ingest/data-check-codes.js.map +1 -1
- package/dist/ingest/data-checks.cjs +1 -1
- package/dist/ingest/data-checks.cjs.map +1 -1
- package/dist/ingest/data-checks.js +1 -1
- package/dist/ingest/data-checks.js.map +1 -1
- package/dist/ingest/row-key-codes.cjs +1 -0
- package/dist/ingest/row-key-codes.cjs.map +1 -1
- package/dist/ingest/row-key-codes.js +1 -0
- package/dist/ingest/row-key-codes.js.map +1 -1
- package/dist/ingest/row-key.cjs +23 -20
- package/dist/ingest/row-key.cjs.map +1 -1
- package/dist/ingest/row-key.js +23 -20
- package/dist/ingest/row-key.js.map +1 -1
- package/dist/ingest/schema-codes.cjs +1 -0
- package/dist/ingest/schema-codes.cjs.map +1 -1
- package/dist/ingest/schema-codes.js +1 -0
- package/dist/ingest/schema-codes.js.map +1 -1
- package/dist/ingest/unique-fields.cjs +2 -2
- package/dist/ingest/unique-fields.cjs.map +1 -1
- package/dist/ingest/unique-fields.js +2 -2
- package/dist/ingest/unique-fields.js.map +1 -1
- package/dist/intercept/boundary.cjs +4 -2
- package/dist/intercept/boundary.cjs.map +1 -1
- package/dist/intercept/boundary.js +4 -2
- package/dist/intercept/boundary.js.map +1 -1
- package/dist/intercept/cell-batch.cjs +4 -4
- package/dist/intercept/cell-batch.cjs.map +1 -1
- package/dist/intercept/cell-batch.js +4 -4
- package/dist/intercept/cell-batch.js.map +1 -1
- package/dist/intercept/codes.cjs +1 -0
- package/dist/intercept/codes.cjs.map +1 -1
- package/dist/intercept/codes.js +1 -0
- package/dist/intercept/codes.js.map +1 -1
- package/dist/intercept/fallbacks.cjs +12 -20
- package/dist/intercept/fallbacks.cjs.map +1 -1
- package/dist/intercept/fallbacks.js +12 -20
- package/dist/intercept/fallbacks.js.map +1 -1
- package/dist/intercept/runtime.cjs +7 -9
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +7 -9
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +4 -1
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +4 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/codes.cjs +1 -0
- package/dist/pipeline/codes.cjs.map +1 -1
- package/dist/pipeline/codes.js +1 -0
- package/dist/pipeline/codes.js.map +1 -1
- package/dist/pipeline/column-cache.cjs +94 -76
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +92 -75
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/dev-checks.cjs +1 -0
- package/dist/pipeline/dev-checks.cjs.map +1 -1
- package/dist/pipeline/dev-checks.js +1 -0
- package/dist/pipeline/dev-checks.js.map +1 -1
- package/dist/pipeline/env-codes.cjs +3 -2
- package/dist/pipeline/env-codes.cjs.map +1 -1
- package/dist/pipeline/env-codes.js +3 -2
- package/dist/pipeline/env-codes.js.map +1 -1
- package/dist/pipeline/perf-codes.cjs +1 -0
- package/dist/pipeline/perf-codes.cjs.map +1 -1
- package/dist/pipeline/perf-codes.js +1 -0
- package/dist/pipeline/perf-codes.js.map +1 -1
- package/dist/pipeline/runner.cjs +17 -9
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +17 -9
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/slice.cjs +3 -1
- package/dist/pipeline/slice.cjs.map +1 -1
- package/dist/pipeline/slice.js +3 -1
- package/dist/pipeline/slice.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-v5JdEk5Y.d.cts → search-async-entry-CuLYPfY_.d.cts} +2 -2
- package/dist/{search-async-entry-DGSw2pDL.d.ts → search-async-entry-Cx7xpuVZ.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +8 -6
- package/dist/search.d.ts +8 -6
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/codes.cjs +1 -0
- package/dist/source/codes.cjs.map +1 -1
- package/dist/source/codes.js +1 -0
- package/dist/source/codes.js.map +1 -1
- package/dist/source/fetch-codes.cjs +1 -0
- package/dist/source/fetch-codes.cjs.map +1 -1
- package/dist/source/fetch-codes.js +1 -0
- package/dist/source/fetch-codes.js.map +1 -1
- package/dist/table/codes.cjs +1 -0
- package/dist/table/codes.cjs.map +1 -1
- package/dist/table/codes.js +1 -0
- package/dist/table/codes.js.map +1 -1
- package/dist/table/create-table.cjs +39 -9
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +39 -9
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/data-mutation.cjs +1 -0
- package/dist/table/data-mutation.cjs.map +1 -1
- package/dist/table/data-mutation.js +1 -0
- package/dist/table/data-mutation.js.map +1 -1
- package/dist/table/internals.cjs.map +1 -1
- package/dist/table/internals.js.map +1 -1
- package/dist/table/ssr-codes.cjs +1 -0
- package/dist/table/ssr-codes.cjs.map +1 -1
- package/dist/table/ssr-codes.js +1 -0
- package/dist/table/ssr-codes.js.map +1 -1
- package/dist/table/ssr.cjs +4 -1
- package/dist/table/ssr.cjs.map +1 -1
- package/dist/table/ssr.js +4 -1
- package/dist/table/ssr.js.map +1 -1
- package/dist/table/state-feedback.cjs +19 -0
- package/dist/table/state-feedback.cjs.map +1 -1
- package/dist/table/state-feedback.js +19 -0
- package/dist/table/state-feedback.js.map +1 -1
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/versioning/codes.cjs +18 -0
- package/dist/versioning/codes.cjs.map +1 -1
- package/dist/versioning/codes.js +18 -0
- package/dist/versioning/codes.js.map +1 -1
- package/dist/versioning/registry.cjs +4 -2
- package/dist/versioning/registry.cjs.map +1 -1
- package/dist/versioning/registry.js +4 -2
- package/dist/versioning/registry.js.map +1 -1
- package/package.json +39 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;
|
|
1
|
+
{"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAwBP,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,MACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,MACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../../src/features/pagination/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_INVALID_PAGINATION: {\n /**\n * The API method that received the value, `'state'` for a slice written to the store, or\n * `'option'` for `pagination({ pageSize })` (checked here in production, where the option\n * validator is off).\n */\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option'\n field: 'pageIndex' | 'pageSize'\n /** The value's type only (`describeValue`), plus the number itself when it is one. */\n received: string\n /** What was done instead. */\n fallback: string\n }\n DK_ROW_COUNT_MISSING: {\n /** `data.length`, used as the total instead. */\n rows: number\n }\n }\n}\n\nconst ACCEPTED = {\n pageIndex: 'an integer ≥ 0',\n pageSize: 'an integer > 0',\n} as const\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const paginationMessages: DiagnosticMessages<\n 'DK_INVALID_PAGINATION' | 'DK_ROW_COUNT_MISSING'\n> = {\n DK_INVALID_PAGINATION: {\n problem: (d) =>\n d.source === 'option'\n ? `pagination({ pageSize: ${d.received} }); pageSize must be ${ACCEPTED.pageSize}`\n : d.source === 'state'\n ? `the pagination state has ${d.field} = ${d.received}; it must be ${ACCEPTED[d.field]}`\n : `${d.source}() received ${d.received}; ${d.field} must be ${ACCEPTED[d.field]}`,\n impact: (d) => d.fallback,\n fix: (d) =>\n d.source === 'option'\n ? [\n `pass ${ACCEPTED.pageSize} to pagination({ pageSize }), or leave it out for the ` +\n 'default of 10',\n ]\n : d.source === 'state'\n ? [\n `write pagination.${d.field} as ${ACCEPTED[d.field]} (check initialState, setState and ` +\n 'the controlled options.state)',\n d.field === 'pageIndex'\n ? 'use table.setPageIndex(i): it clamps to the existing pages'\n : 'use table.setPageSize(n): it also keeps the first visible row on screen',\n ]\n : [\n `pass ${ACCEPTED[d.field]} to ${d.source}()` +\n (d.received.startsWith('string') ? ' (convert input values with Number(...))' : ''),\n ],\n },\n DK_ROW_COUNT_MISSING: {\n problem: () =>\n 'pagination is in server mode, but there is no fetcher and no rowCount option: the ' +\n 'total number of rows is unknown',\n impact: (d) =>\n `the page count falls back to data.length (${d.rows}) as the total: one page, ` +\n 'canNextPage() is false',\n fix: () => [\n 'pass the total from your API: setOptions({ data: page.rows, rowCount: page.total })',\n \"or pass a fetcher that resolves { rows, rowCount }, or use pagination({ mode: 'client' }) \" +\n 'with all rows as data',\n ],\n },\n}\n\n/** Codes owned by the `pagination` feature (§6.5). */\nexport const paginationDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_PAGINATION',\n level: 'warn',\n where: (d) =>\n d.source === 'option'\n ? { kind: 'feature', name: 'pagination' }\n : d.source === 'state'\n ? { kind: 'option', path: `state.pagination.${d.field}` }\n : { kind: 'operation', op: `table.${d.source}` },\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_INVALID_PAGINATION,\n ),\n defineDiagnostic(\n {\n code: 'DK_ROW_COUNT_MISSING',\n level: 'warn',\n where: () => ({ kind: 'option', path: 'rowCount' }),\n },\n (process.env.NODE_ENV !== \"production\") && paginationMessages.DK_ROW_COUNT_MISSING,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAwBP,IAAM,WAAW;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AACZ;AAGO,IAAM,qBAET;AAAA,EACF,uBAAuB;AAAA,IACrB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,0BAA0B,EAAE,QAAQ,yBAAyB,SAAS,QAAQ,KAC9E,EAAE,WAAW,UACX,4BAA4B,EAAE,KAAK,MAAM,EAAE,QAAQ,gBAAgB,SAAS,EAAE,KAAK,CAAC,KACpF,GAAG,EAAE,MAAM,eAAe,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAY,SAAS,EAAE,KAAK,CAAC;AAAA,IACrF,QAAQ,CAAC,MAAM,EAAE;AAAA,IACjB,KAAK,CAAC,MACJ,EAAE,WAAW,WACT;AAAA,MACE,QAAQ,SAAS,QAAQ;AAAA,IAE3B,IACA,EAAE,WAAW,UACX;AAAA,MACE,oBAAoB,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC;AAAA,MAEnD,EAAE,UAAU,cACR,+DACA;AAAA,IACN,IACA;AAAA,MACE,QAAQ,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,QACrC,EAAE,SAAS,WAAW,QAAQ,IAAI,6CAA6C;AAAA,IACpF;AAAA,EACV;AAAA,EACA,sBAAsB;AAAA,IACpB,SAAS,MACP;AAAA,IAEF,QAAQ,CAAC,MACP,6CAA6C,EAAE,IAAI;AAAA,IAErD,KAAK,MAAM;AAAA,MACT;AAAA,MACA;AAAA,IAEF;AAAA,EACF;AACF;AAGO,IAAM,wBAAwB;AAAA,EACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,MACN,EAAE,WAAW,WACT,EAAE,MAAM,WAAW,MAAM,aAAa,IACtC,EAAE,WAAW,UACX,EAAE,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAK,GAAG,IACtD,EAAE,MAAM,aAAa,IAAI,SAAS,EAAE,MAAM,GAAG;AAAA,IACvD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AAAA,EACA;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,mBAAmB;AAAA,EAChE;AACF;","names":[]}
|
|
@@ -25,6 +25,7 @@ __export(pagination_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(pagination_exports);
|
|
26
26
|
var import_options = require("../../catalog/options.cjs");
|
|
27
27
|
var import_catalog = require("../../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
28
29
|
var import_redact = require("../../diagnostics/redact.cjs");
|
|
29
30
|
var import_define = require("../../feature/define.cjs");
|
|
30
31
|
var import_define2 = require("../../intercept/define.cjs");
|
|
@@ -90,8 +91,8 @@ var pagination = (opts = {}) => {
|
|
|
90
91
|
let missingReported = false;
|
|
91
92
|
const rowCount = () => {
|
|
92
93
|
if (mode !== "server") return table.getRowModel().filteredCount;
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
94
|
+
const n2 = internals.rowCount();
|
|
95
|
+
if (n2 !== void 0 || internals.hasFetcher()) return n2;
|
|
95
96
|
const rows = table.getRowModel().totalCount;
|
|
96
97
|
if (!missingReported) {
|
|
97
98
|
missingReported = true;
|
|
@@ -102,17 +103,17 @@ var pagination = (opts = {}) => {
|
|
|
102
103
|
function sanitize(s, prev) {
|
|
103
104
|
const fallbackSize = isSize(prev?.pageSize) ? prev.pageSize : initialSize;
|
|
104
105
|
if (s === null || typeof s !== "object") {
|
|
105
|
-
report("state", "pageIndex", s, "
|
|
106
|
+
report("state", "pageIndex", s, "page-reset");
|
|
106
107
|
return { pageIndex: 0, pageSize: initialSize };
|
|
107
108
|
}
|
|
108
109
|
const { pageIndex, pageSize } = s;
|
|
109
110
|
let fixed2;
|
|
110
111
|
if (!isIndex(pageIndex)) {
|
|
111
|
-
report("state", "pageIndex", pageIndex, "
|
|
112
|
+
report("state", "pageIndex", pageIndex, "page-index-0");
|
|
112
113
|
fixed2 = { ...s, pageIndex: 0 };
|
|
113
114
|
}
|
|
114
115
|
if (!isSize(pageSize)) {
|
|
115
|
-
report("state", "pageSize", pageSize, `
|
|
116
|
+
report("state", "pageSize", pageSize, `page-size|${fallbackSize}`);
|
|
116
117
|
fixed2 = { ...fixed2 ?? s, pageSize: fallbackSize };
|
|
117
118
|
}
|
|
118
119
|
return fixed2;
|
|
@@ -120,9 +121,8 @@ var pagination = (opts = {}) => {
|
|
|
120
121
|
function report(source, field, value, fallback) {
|
|
121
122
|
ctx.emit("DK_INVALID_PAGINATION", { source, field, received: received(value), fallback });
|
|
122
123
|
}
|
|
123
|
-
function current() {
|
|
124
|
+
function current(count = rowCount()) {
|
|
124
125
|
const s = ctx.getState();
|
|
125
|
-
const count = rowCount();
|
|
126
126
|
const index = count === void 0 ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count);
|
|
127
127
|
return { s, count, index };
|
|
128
128
|
}
|
|
@@ -152,21 +152,24 @@ var pagination = (opts = {}) => {
|
|
|
152
152
|
if (badSize) report("option", "pageSize", opts.pageSize, `pageSize ${initialSize} was used`);
|
|
153
153
|
const fixed = sanitize(ctx.getState(), void 0);
|
|
154
154
|
if (fixed) ctx.setState(fixed);
|
|
155
|
-
|
|
156
|
-
const { s, index } = current();
|
|
157
|
-
if (index
|
|
158
|
-
}
|
|
155
|
+
const clamp = (count) => {
|
|
156
|
+
const { s, index } = current(count);
|
|
157
|
+
if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index });
|
|
158
|
+
};
|
|
159
|
+
const n = mode === "server" ? internals.rowCount() : table.getOptions().data?.length;
|
|
160
|
+
if (n) clamp(n);
|
|
161
|
+
table.subscribe(() => clamp());
|
|
159
162
|
return {
|
|
160
163
|
setPageIndex(index) {
|
|
161
164
|
if (!Number.isInteger(index)) {
|
|
162
|
-
report("setPageIndex", "pageIndex", index, "
|
|
165
|
+
report("setPageIndex", "pageIndex", index, "call-ignored");
|
|
163
166
|
return;
|
|
164
167
|
}
|
|
165
168
|
goTo(index);
|
|
166
169
|
},
|
|
167
170
|
setPageSize(size) {
|
|
168
171
|
if (!isSize(size)) {
|
|
169
|
-
report("setPageSize", "pageSize", size, "
|
|
172
|
+
report("setPageSize", "pageSize", size, "call-ignored");
|
|
170
173
|
return;
|
|
171
174
|
}
|
|
172
175
|
const { s, index } = current();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'the pagination state was reset to its initial value')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'pageIndex was replaced by 0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `pageSize was replaced by ${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown. An unknown count (a server count not\n * fetched yet) clamps nothing.\n */\n function current() {\n const s = ctx.getState()\n const count = rowCount()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => {\n const { s, index } = current()\n if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n })\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'the call was ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'the call was ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["import_define","fixed"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'page-reset')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'page-index-0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `page-size|${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown (for `count` rows, default: the current\n * count). An unknown count (a server count not fetched yet) clamps nothing.\n */\n function current(count = rowCount()) {\n const s = ctx.getState()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n // ... and clamped to the last page, so the state never says \"page 100 of 3\" (#19). Client:\n // the paged rows are a subset of `data`, whose length bounds the last page without running\n // the pipeline here (restored filters leaving fewer rows: the listener below); no data yet\n // clamps nothing. Server: a known `rowCount`.\n const clamp = (count?: number) => {\n const { s, index } = current(count)\n if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n }\n const n =\n mode === 'server' ? internals.rowCount() : (table.getOptions().data as unknown[])?.length\n if (n) clamp(n)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => clamp())\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'call-ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'call-ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["import_define","n","fixed"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/features/pagination/pagination.ts
|
|
2
2
|
import { defineOptions } from "../../catalog/options.js";
|
|
3
3
|
import { keepDiagnostics } from "../../diagnostics/catalog.js";
|
|
4
|
+
import "../../diagnostics/process-guard.js";
|
|
4
5
|
import { describeValue } from "../../diagnostics/redact.js";
|
|
5
6
|
import { defineFeature } from "../../feature/define.js";
|
|
6
7
|
import { defineInterceptor } from "../../intercept/define.js";
|
|
@@ -66,8 +67,8 @@ var pagination = (opts = {}) => {
|
|
|
66
67
|
let missingReported = false;
|
|
67
68
|
const rowCount = () => {
|
|
68
69
|
if (mode !== "server") return table.getRowModel().filteredCount;
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
70
|
+
const n2 = internals.rowCount();
|
|
71
|
+
if (n2 !== void 0 || internals.hasFetcher()) return n2;
|
|
71
72
|
const rows = table.getRowModel().totalCount;
|
|
72
73
|
if (!missingReported) {
|
|
73
74
|
missingReported = true;
|
|
@@ -78,17 +79,17 @@ var pagination = (opts = {}) => {
|
|
|
78
79
|
function sanitize(s, prev) {
|
|
79
80
|
const fallbackSize = isSize(prev?.pageSize) ? prev.pageSize : initialSize;
|
|
80
81
|
if (s === null || typeof s !== "object") {
|
|
81
|
-
report("state", "pageIndex", s, "
|
|
82
|
+
report("state", "pageIndex", s, "page-reset");
|
|
82
83
|
return { pageIndex: 0, pageSize: initialSize };
|
|
83
84
|
}
|
|
84
85
|
const { pageIndex, pageSize } = s;
|
|
85
86
|
let fixed2;
|
|
86
87
|
if (!isIndex(pageIndex)) {
|
|
87
|
-
report("state", "pageIndex", pageIndex, "
|
|
88
|
+
report("state", "pageIndex", pageIndex, "page-index-0");
|
|
88
89
|
fixed2 = { ...s, pageIndex: 0 };
|
|
89
90
|
}
|
|
90
91
|
if (!isSize(pageSize)) {
|
|
91
|
-
report("state", "pageSize", pageSize, `
|
|
92
|
+
report("state", "pageSize", pageSize, `page-size|${fallbackSize}`);
|
|
92
93
|
fixed2 = { ...fixed2 ?? s, pageSize: fallbackSize };
|
|
93
94
|
}
|
|
94
95
|
return fixed2;
|
|
@@ -96,9 +97,8 @@ var pagination = (opts = {}) => {
|
|
|
96
97
|
function report(source, field, value, fallback) {
|
|
97
98
|
ctx.emit("DK_INVALID_PAGINATION", { source, field, received: received(value), fallback });
|
|
98
99
|
}
|
|
99
|
-
function current() {
|
|
100
|
+
function current(count = rowCount()) {
|
|
100
101
|
const s = ctx.getState();
|
|
101
|
-
const count = rowCount();
|
|
102
102
|
const index = count === void 0 ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count);
|
|
103
103
|
return { s, count, index };
|
|
104
104
|
}
|
|
@@ -128,21 +128,24 @@ var pagination = (opts = {}) => {
|
|
|
128
128
|
if (badSize) report("option", "pageSize", opts.pageSize, `pageSize ${initialSize} was used`);
|
|
129
129
|
const fixed = sanitize(ctx.getState(), void 0);
|
|
130
130
|
if (fixed) ctx.setState(fixed);
|
|
131
|
-
|
|
132
|
-
const { s, index } = current();
|
|
133
|
-
if (index
|
|
134
|
-
}
|
|
131
|
+
const clamp = (count) => {
|
|
132
|
+
const { s, index } = current(count);
|
|
133
|
+
if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index });
|
|
134
|
+
};
|
|
135
|
+
const n = mode === "server" ? internals.rowCount() : table.getOptions().data?.length;
|
|
136
|
+
if (n) clamp(n);
|
|
137
|
+
table.subscribe(() => clamp());
|
|
135
138
|
return {
|
|
136
139
|
setPageIndex(index) {
|
|
137
140
|
if (!Number.isInteger(index)) {
|
|
138
|
-
report("setPageIndex", "pageIndex", index, "
|
|
141
|
+
report("setPageIndex", "pageIndex", index, "call-ignored");
|
|
139
142
|
return;
|
|
140
143
|
}
|
|
141
144
|
goTo(index);
|
|
142
145
|
},
|
|
143
146
|
setPageSize(size) {
|
|
144
147
|
if (!isSize(size)) {
|
|
145
|
-
report("setPageSize", "pageSize", size, "
|
|
148
|
+
report("setPageSize", "pageSize", size, "call-ignored");
|
|
146
149
|
return;
|
|
147
150
|
}
|
|
148
151
|
const { s, index } = current();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'the pagination state was reset to its initial value')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'pageIndex was replaced by 0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `pageSize was replaced by ${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown. An unknown count (a server count not\n * fetched yet) clamps nothing.\n */\n function current() {\n const s = ctx.getState()\n const count = rowCount()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => {\n const { s, index } = current()\n if (index !== s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n })\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'the call was ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'the call was ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["fixed"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/pagination/pagination.ts"],"sourcesContent":["import { defineOptions } from '../../catalog/options'\nimport { keepDiagnostics } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { describeValue } from '../../diagnostics/redact'\nimport { defineFeature } from '../../feature/define'\nimport type { FeatureContext } from '../../feature/types'\nimport { defineInterceptor } from '../../intercept/define'\nimport { type DataMode, modeOption } from '../../source/mode'\nimport { getTableInternals } from '../../table/internals'\nimport type { Table, TableState } from '../../table/types'\nimport { paginationDiagnostics } from './codes'\n\nkeepDiagnostics(paginationDiagnostics)\n\nexport interface PaginationState {\n pageIndex: number\n pageSize: number\n}\n\nexport interface PaginationApi {\n /** Goes to page `index`, clamped to `[0, getPageCount() - 1]`. */\n setPageIndex(index: number): void\n /** Changes the page size and keeps the first visible row on screen (`floor(firstRow / size)`). */\n setPageSize(size: number): void\n /** Next page; no-op on the last page. */\n nextPage(): void\n /** Previous page; no-op on the first page. */\n prevPage(): void\n /** `ceil(rowCount / pageSize)`; `0` when there are no rows. */\n getPageCount(): number\n canNextPage(): boolean\n canPrevPage(): boolean\n}\n\nexport interface PaginationOptions {\n /** Starting page size (default 10). `options.initialState.pagination` takes precedence. */\n pageSize?: number\n /** Go back to page 0 when search, filters or sort change (default `true`). */\n autoResetPageIndex?: boolean\n /**\n * `'server'`: the server pages (its state goes to `options.fetcher`, or the app passes the\n * current page as `data`); the page count is `ceil(rowCount / pageSize)` with `rowCount` from the\n * fetcher result or the `rowCount` option (§6.5, §7.1). Default `'client'`.\n */\n mode?: DataMode\n}\n\nconst DEFAULT_PAGE_SIZE = 10\nconst NAME = 'pagination'\n\nconst isIndex = (v: unknown): v is number => Number.isInteger(v) && (v as number) >= 0\nconst isSize = (v: unknown): v is number => Number.isInteger(v) && (v as number) > 0\nconst received = (v: unknown) => (typeof v === 'number' ? String(v) : describeValue(v))\n\nconst paginationOptions = {\n pageSize: { type: 'number', check: isSize, expected: 'an integer > 0' },\n autoResetPageIndex: { type: 'boolean' },\n mode: modeOption,\n} as const\n\n/** Stages whose state changes send the table back to page 0 (`autoResetPageIndex`). */\nconst RESET_STAGES = new Set(['filter', 'search', 'sort'])\n\n/**\n * A slice change that resets the page. For a search slice only the applied `query` counts: typing\n * (`input`) and `status` alone keep the page (a debounced keystroke must not jump to page 0).\n */\nfunction changed(stage: string, a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return false\n if (stage === 'search' && hasQuery(a) && hasQuery(b)) return !Object.is(a.query, b.query)\n return true\n}\n\nconst hasQuery = (v: unknown): v is { query: unknown } =>\n v !== null && typeof v === 'object' && 'query' in v\n\n/** The page actually shown: `pageIndex` clamped to the last page (0 for no rows). */\nfunction effectiveIndex(pageIndex: number, pageSize: number, rowCount: number): number {\n const last = rowCount === 0 ? 0 : Math.ceil(rowCount / pageSize) - 1\n return pageIndex > last ? last : pageIndex\n}\n\n/** What the pagination interceptor needs of one table (registered by `createApi`). */\ninterface Host {\n /** Runs after `state:dispatch` / `table:setOptions` committed: validation, then auto-reset. */\n after(before: TableState): void\n}\n\n/**\n * Client-side pagination (§6.5). State `{ pageIndex, pageSize }`; the `paginate` step returns the\n * rows of the current page (`RowModel.filteredCount` stays the count before paging).\n *\n * - `setPageSize` keeps the first visible row on screen; `setPageIndex` clamps.\n * - `autoResetPageIndex` (default `true`): a change of a filter / search / sort slice resets\n * `pageIndex` to 0 in the same update (one notification). An update that also sets the\n * pagination slice keeps the page it sets.\n * - `pageIndex` is clamped when the row count shrinks (data change, filters with auto-reset off).\n * - Invalid values are never silent: API arguments are ignored and slices written to the store are\n * replaced, both with `DK_INVALID_PAGINATION`.\n */\nexport const pagination = (opts: PaginationOptions = {}) => {\n // Production skips the option validator: an invalid size must not reach the state, or sanitize()\n // would \"fix\" a slice with the same bad size and re-dispatch forever (#13). Reported in createApi.\n const badSize = opts.pageSize !== undefined && !isSize(opts.pageSize)\n const initialSize = badSize ? DEFAULT_PAGE_SIZE : (opts.pageSize ?? DEFAULT_PAGE_SIZE)\n const autoReset = opts.autoResetPageIndex !== false\n const mode = opts.mode ?? 'client'\n // One feature instance may serve several tables: per-table state lives here.\n const hosts = new WeakMap<object, Host>()\n\n const interceptor = defineInterceptor({\n name: NAME,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ctx, next) {\n const host = hosts.get(ctx.table as object)\n if (!host) return next()\n const before = (ctx.table as Table<unknown, TableState>).getState()\n const out = next()\n host.after(before)\n return out\n },\n })\n\n return defineFeature({\n name: NAME,\n stage: 'paginate',\n mode,\n options: defineOptions(\n // The option validator runs in development only (§9.3): production drops the schema.\n (process.env.NODE_ENV !== \"production\") ? paginationOptions : ({} as typeof paginationOptions),\n opts,\n ),\n interceptors: [interceptor],\n initialState: (): PaginationState => ({ pageIndex: 0, pageSize: initialSize }),\n // After destroy(): the getters still return their declared types (no pages, no navigation).\n destroyedResult: { getPageCount: () => 0, canNextPage: () => false, canPrevPage: () => false },\n createApi: (ctx: FeatureContext<PaginationState>): PaginationApi => {\n const table = ctx.table\n\n const internals = getTableInternals(table)\n let missingReported = false\n /**\n * Rows to page over. Server mode (§6.5, §7.1): the fetcher result's `rowCount`\n * (`undefined` until the first one applied), else the `rowCount` option, else\n * `DK_ROW_COUNT_MISSING` and `data.length` as the total.\n */\n const rowCount = (): number | undefined => {\n if (mode !== 'server') return table.getRowModel().filteredCount\n const n = internals.rowCount()\n if (n !== undefined || internals.hasFetcher()) return n\n const rows = table.getRowModel().totalCount\n if (!missingReported) {\n missingReported = true\n ctx.emit('DK_ROW_COUNT_MISSING', { rows })\n }\n return rows\n }\n\n /** The slice with invalid fields replaced (`prev` = the last valid slice, if any). */\n function sanitize(s: unknown, prev: unknown): PaginationState | undefined {\n const fallbackSize = isSize((prev as PaginationState | undefined)?.pageSize)\n ? (prev as PaginationState).pageSize\n : initialSize\n if (s === null || typeof s !== 'object') {\n report('state', 'pageIndex', s, 'page-reset')\n return { pageIndex: 0, pageSize: initialSize }\n }\n const { pageIndex, pageSize } = s as Partial<PaginationState>\n let fixed: PaginationState | undefined\n if (!isIndex(pageIndex)) {\n report('state', 'pageIndex', pageIndex, 'page-index-0')\n fixed = { ...(s as PaginationState), pageIndex: 0 }\n }\n if (!isSize(pageSize)) {\n report('state', 'pageSize', pageSize, `page-size|${fallbackSize}`)\n fixed = { ...(fixed ?? (s as PaginationState)), pageSize: fallbackSize }\n }\n return fixed\n }\n\n function report(\n source: 'setPageIndex' | 'setPageSize' | 'state' | 'option',\n field: 'pageIndex' | 'pageSize',\n value: unknown,\n fallback: string,\n ) {\n ctx.emit('DK_INVALID_PAGINATION', { source, field, received: received(value), fallback })\n }\n\n /**\n * The current slice and the page actually shown (for `count` rows, default: the current\n * count). An unknown count (a server count not fetched yet) clamps nothing.\n */\n function current(count = rowCount()) {\n const s = ctx.getState()\n const index =\n count === undefined ? s.pageIndex : effectiveIndex(s.pageIndex, s.pageSize, count)\n return { s, count, index }\n }\n\n function goTo(index: number) {\n const { s, count } = current()\n const target =\n index < 0 ? 0 : count === undefined ? index : effectiveIndex(index, s.pageSize, count)\n if (target !== s.pageIndex) ctx.setState({ ...s, pageIndex: target })\n }\n\n hosts.set(table, {\n after(before) {\n const now = table.getState()\n const slice = now[NAME]\n if (!Object.is(before[NAME], slice)) {\n const fixed = sanitize(slice, before[NAME])\n if (fixed) ctx.setState(fixed)\n return // an update that sets the pagination slice keeps its page\n }\n if (!autoReset || (slice as PaginationState).pageIndex === 0) return\n for (const f of getTableInternals(table).features) {\n if (RESET_STAGES.has(f.stage) && changed(f.stage, before[f.name], now[f.name])) {\n ctx.setState({ ...(slice as PaginationState), pageIndex: 0 })\n return\n }\n }\n },\n })\n\n if (badSize) report('option', 'pageSize', opts.pageSize, `pageSize ${initialSize} was used`)\n // A slice given at creation (initialState / controlled state) is checked once, here.\n const fixed = sanitize(ctx.getState(), undefined)\n if (fixed) ctx.setState(fixed)\n // ... and clamped to the last page, so the state never says \"page 100 of 3\" (#19). Client:\n // the paged rows are a subset of `data`, whose length bounds the last page without running\n // the pipeline here (restored filters leaving fewer rows: the listener below); no data yet\n // clamps nothing. Server: a known `rowCount`.\n const clamp = (count?: number) => {\n const { s, index } = current(count)\n if (index < s.pageIndex) ctx.setState({ ...s, pageIndex: index })\n }\n const n =\n mode === 'server' ? internals.rowCount() : (table.getOptions().data as unknown[])?.length\n if (n) clamp(n)\n\n // Clamp when the row count shrinks. A listener sees every change (data, async ingest,\n // filters with auto-reset off); its update is notified in the next round.\n table.subscribe(() => clamp())\n\n return {\n setPageIndex(index) {\n if (!Number.isInteger(index)) {\n report('setPageIndex', 'pageIndex', index, 'call-ignored')\n return\n }\n goTo(index)\n },\n setPageSize(size) {\n if (!isSize(size)) {\n report('setPageSize', 'pageSize', size, 'call-ignored')\n return\n }\n const { s, index } = current()\n if (size === s.pageSize) return\n const firstRow = index * s.pageSize\n ctx.setState({ pageIndex: Math.floor(firstRow / size), pageSize: size })\n },\n nextPage() {\n goTo(current().index + 1)\n },\n prevPage() {\n goTo(current().index - 1)\n },\n getPageCount() {\n const count = rowCount()\n return !count ? 0 : Math.ceil(count / ctx.getState().pageSize)\n },\n canNextPage() {\n const { s, count, index } = current()\n return count !== undefined && (index + 1) * s.pageSize < count\n },\n canPrevPage() {\n return current().index > 0\n },\n }\n },\n rowModel: (indices, s) => {\n const size = isSize(s?.pageSize) ? s.pageSize : initialSize\n const index = effectiveIndex(isIndex(s?.pageIndex) ? s.pageIndex : 0, size, indices.length)\n const start = index * size\n if (start === 0 && size >= indices.length) return indices\n return indices.subarray(start, start + size)\n },\n })\n}\n"],"mappings":"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","names":["n","fixed"]}
|
|
@@ -25,6 +25,7 @@ __export(codes_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(codes_exports);
|
|
27
27
|
var import_catalog = require("../../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
28
29
|
var searchMessages = {
|
|
29
30
|
DK_SEARCH_FAILED: {
|
|
30
31
|
problem: (d) => d.problem === "bad-result" ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or { keys: RowKey[] }` : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ""}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;
|
|
1
|
+
{"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n DK_SEARCH_COLUMN_SKIPPED: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in the table's columns (`options.columns`, else the derived columns). */\n index: number\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0D;AAC1D,2BAAO;AAwBA,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,MAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/features/search/codes.ts
|
|
2
2
|
import { defineDiagnostic } from "../../diagnostics/catalog.js";
|
|
3
|
+
import "../../diagnostics/process-guard.js";
|
|
3
4
|
var searchMessages = {
|
|
4
5
|
DK_SEARCH_FAILED: {
|
|
5
6
|
problem: (d) => d.problem === "bad-result" ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or { keys: RowKey[] }` : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ""}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../../src/features/search/codes.ts"],"sourcesContent":["import { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_SEARCH_FAILED: {\n /** `error.name` of the rejection / throw; the full error is `context.cause`. */\n errorName?: string\n /** `bad-result`: resolved with neither an array of rows nor `{ keys }`. */\n problem?: 'bad-result'\n /** The type of the bad result (`describeValue`), never the value. */\n received?: string\n /** Whether a previous `searchFn` result is shown (`false`: none applied yet). */\n hadResults: boolean\n }\n DK_SEARCH_COLUMN_SKIPPED: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in the table's columns (`options.columns`, else the derived columns). */\n index: number\n }\n }\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\nexport const searchMessages: DiagnosticMessages<'DK_SEARCH_FAILED'> = {\n DK_SEARCH_FAILED: {\n problem: (d) =>\n d.problem === 'bad-result'\n ? `searchFn resolved with ${d.received}, not an array of rows (from ctx.rows) or ` +\n '{ keys: RowKey[] }'\n : `searchFn rejected or threw${d.errorName ? ` ${d.errorName}` : ''}`,\n impact: (d) =>\n d.hadResults\n ? \"the previous search results are kept (status: 'error')\"\n : \"the rows are shown unsearched (status: 'error')\",\n fix: (d) => [\n d.problem === 'bad-result'\n ? 'return ctx.rows.filter(...) (the row objects themselves) or { keys: [...] } with the ' +\n 'rowKey values of the matching rows'\n : 'check the request searchFn sends (the original error is context.cause, passed to ' +\n 'onDiagnostic)',\n 'retry from the UI with flushSearch(); the next query change runs searchFn again',\n ],\n },\n}\n\n/** Codes owned by the `search` feature (spec §6.3, §8.3). */\nexport const searchDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_SEARCH_FAILED',\n level: 'error',\n where: () => ({ kind: 'option', path: 'searchFn' }),\n },\n (process.env.NODE_ENV !== \"production\") && searchMessages.DK_SEARCH_FAILED,\n ),\n]\n"],"mappings":";AAAA,SAAkC,wBAAwB;AAC1D,OAAO;AAwBA,IAAM,iBAAyD;AAAA,EACpE,kBAAkB;AAAA,IAChB,SAAS,CAAC,MACR,EAAE,YAAY,eACV,0BAA0B,EAAE,QAAQ,iEAEpC,6BAA6B,EAAE,YAAY,IAAI,EAAE,SAAS,KAAK,EAAE;AAAA,IACvE,QAAQ,CAAC,MACP,EAAE,aACE,2DACA;AAAA,IACN,KAAK,CAAC,MAAM;AAAA,MACV,EAAE,YAAY,eACV,4HAEA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW;AAAA,IACnD;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,eAAe;AAAA,EAC5D;AACF;","names":[]}
|
|
@@ -26,10 +26,14 @@ __export(search_index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(search_index_exports);
|
|
28
28
|
var import_ex_search = require("ex-search");
|
|
29
|
+
var import_get_value = require("../../column/get-value.cjs");
|
|
30
|
+
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
31
|
+
var import_column_cache = require("../../pipeline/column-cache.cjs");
|
|
29
32
|
var import_columns = require("../../pipeline/columns.cjs");
|
|
30
33
|
var import_row = require("../../pipeline/row.cjs");
|
|
31
34
|
var import_capability = require("../../versioning/capability.cjs");
|
|
32
35
|
var CHUNK = 4096;
|
|
36
|
+
var CELLS = 16384;
|
|
33
37
|
var hasExSearch = () => [import_ex_search.ExSearch.prototype.search, import_ex_search.ExSearch.prototype.setData, import_ex_search.Scorer.fuzzy].every(
|
|
34
38
|
(f) => typeof f === "function"
|
|
35
39
|
);
|
|
@@ -47,12 +51,38 @@ function probeExSearch(emit) {
|
|
|
47
51
|
) : (0, import_capability.hasCapability)(hasExSearch);
|
|
48
52
|
}
|
|
49
53
|
function numberReader(values) {
|
|
50
|
-
|
|
51
|
-
return (r) =>
|
|
54
|
+
const nullish = (0, import_column_cache.nullishOf)(values);
|
|
55
|
+
return (r) => {
|
|
56
|
+
const v = values[r];
|
|
57
|
+
return !Number.isNaN(v) ? String(v) : nullish?.[r] ? "" : "NaN";
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function* scan(size, get) {
|
|
61
|
+
const over = /* @__PURE__ */ new Map();
|
|
62
|
+
try {
|
|
63
|
+
for (let r = 0; r < size; r++) {
|
|
64
|
+
const v = get(r);
|
|
65
|
+
if (v !== null && (typeof v === "object" || typeof v === "function")) return false;
|
|
66
|
+
if (typeof v === "string" && /[\u0300-\uffff]/.test(v)) {
|
|
67
|
+
const n = v.normalize("NFC");
|
|
68
|
+
if (n !== v) over.set(r, n);
|
|
69
|
+
}
|
|
70
|
+
if (r % CHUNK === CHUNK - 1) yield;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
const read = (r) => {
|
|
76
|
+
const v = get(r);
|
|
77
|
+
return v === null || v === void 0 ? "" : String(v);
|
|
78
|
+
};
|
|
79
|
+
return over.size ? (r) => over.get(r) ?? read(r) : read;
|
|
52
80
|
}
|
|
53
81
|
function* buildIndex(ctx, config, useExSearch) {
|
|
54
82
|
const cols = (0, import_columns.resolveColumns)(ctx.columns);
|
|
55
83
|
const declared = cols.list.some((c) => c.def.search !== void 0);
|
|
84
|
+
const data = ctx.data;
|
|
85
|
+
const n = data.length;
|
|
56
86
|
const keys = [];
|
|
57
87
|
const reads = [];
|
|
58
88
|
for (const col of cols.list) {
|
|
@@ -60,15 +90,36 @@ function* buildIndex(ctx, config, useExSearch) {
|
|
|
60
90
|
const w = col.def.search;
|
|
61
91
|
const weight = declared ? typeof w === "number" && w > 0 ? w : 0 : 1;
|
|
62
92
|
if (weight === 0) continue;
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
const path = col.format ? void 0 : col.path;
|
|
94
|
+
let failed = false;
|
|
95
|
+
const onError = () => {
|
|
96
|
+
failed = true;
|
|
97
|
+
};
|
|
98
|
+
let read = path ? yield* scan(n, (r) => (0, import_get_value.getValue)(data[r], path, onError)) : void 0;
|
|
99
|
+
if (failed || read === false && declared) read = void 0;
|
|
100
|
+
if (read === void 0) {
|
|
65
101
|
const values = ctx.getColumnValues(col.id);
|
|
66
|
-
if (values
|
|
102
|
+
if (!values) continue;
|
|
103
|
+
yield;
|
|
104
|
+
const numbers = values instanceof Float64Array;
|
|
105
|
+
if (numbers && !col.format) read = numberReader(values);
|
|
106
|
+
else {
|
|
107
|
+
const own = numbers || declared && col.format ? void 0 : yield* scan(n, (r) => values[r]);
|
|
108
|
+
if (own === false && !declared) read = false;
|
|
109
|
+
else if (own && !col.format) read = own;
|
|
110
|
+
else {
|
|
111
|
+
const sliced = ctx.formattedSliced;
|
|
112
|
+
const formatted = sliced ? yield* sliced(col.id) : ctx.getFormattedValues(col.id);
|
|
113
|
+
if (!formatted) continue;
|
|
114
|
+
read = yield* scan(n, (r) => formatted[r]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
67
117
|
}
|
|
68
118
|
if (!read) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
119
|
+
if (process.env.NODE_ENV !== "production" && read === false) {
|
|
120
|
+
ctx.emit("DK_SEARCH_COLUMN_SKIPPED", { column: col.id, index: col.index });
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
72
123
|
}
|
|
73
124
|
keys.push({ name: `c${keys.length}`, weight });
|
|
74
125
|
reads.push(read);
|
|
@@ -76,14 +127,16 @@ function* buildIndex(ctx, config, useExSearch) {
|
|
|
76
127
|
}
|
|
77
128
|
if (keys.length === 0) return null;
|
|
78
129
|
const fold = (v) => config.caseSensitive ? v : v.toLowerCase();
|
|
130
|
+
const foldQuery = (q) => fold(q.normalize("NFC"));
|
|
79
131
|
const at = (k, r) => fold(reads[k](r));
|
|
80
|
-
return useExSearch ? exSearchIndex(ctx.data.length, keys, at,
|
|
132
|
+
return useExSearch ? exSearchIndex(ctx.data.length, keys, at, foldQuery, config) : containsIndex(keys, at, foldQuery, config);
|
|
81
133
|
}
|
|
82
|
-
function* chunked(rows, matchChunk) {
|
|
83
|
-
|
|
134
|
+
function* chunked(rows, columns, matchChunk) {
|
|
135
|
+
const size = Math.max(256, Math.floor(CELLS / columns));
|
|
136
|
+
if (rows.length <= size) return rankHits(matchChunk(rows));
|
|
84
137
|
const hits = [];
|
|
85
|
-
for (let k = 0; k < rows.length; k +=
|
|
86
|
-
for (const h of matchChunk(rows.subarray(k, k +
|
|
138
|
+
for (let k = 0; k < rows.length; k += size) {
|
|
139
|
+
for (const h of matchChunk(rows.subarray(k, k + size))) hits.push(h);
|
|
87
140
|
yield;
|
|
88
141
|
}
|
|
89
142
|
return rankHits(hits);
|
|
@@ -145,7 +198,7 @@ function exSearchIndex(size, keys, at, fold, config) {
|
|
|
145
198
|
return {
|
|
146
199
|
match(rows, query) {
|
|
147
200
|
const folded = fold(query);
|
|
148
|
-
return chunked(rows, (chunk) => {
|
|
201
|
+
return chunked(rows, keys.length, (chunk) => {
|
|
149
202
|
q = folded;
|
|
150
203
|
view.show(chunk);
|
|
151
204
|
try {
|
|
@@ -161,7 +214,7 @@ function containsIndex(keys, at, fold, config) {
|
|
|
161
214
|
return {
|
|
162
215
|
match(rows, query) {
|
|
163
216
|
const q = fold(query);
|
|
164
|
-
return chunked(rows, (chunk) => {
|
|
217
|
+
return chunked(rows, keys.length, (chunk) => {
|
|
165
218
|
const hits = [];
|
|
166
219
|
for (let j = 0; j < chunk.length; j++) {
|
|
167
220
|
const r = chunk[j];
|