@datakable/core 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{codes-AX8XkMNY.d.cts → codes-4sYQv_7J.d.cts} +36 -10
- package/dist/{codes-AX8XkMNY.d.ts → codes-4sYQv_7J.d.ts} +36 -10
- package/dist/column-filters.d.cts +2 -2
- package/dist/column-filters.d.ts +2 -2
- package/dist/diagnostics/build.cjs +1 -1
- package/dist/diagnostics/build.cjs.map +1 -1
- package/dist/diagnostics/build.js +2 -2
- package/dist/diagnostics/build.js.map +1 -1
- package/dist/diagnostics/console.cjs +15 -4
- package/dist/diagnostics/console.cjs.map +1 -1
- package/dist/diagnostics/console.js +15 -4
- package/dist/diagnostics/console.js.map +1 -1
- package/dist/diagnostics/prose.cjs +10 -1
- package/dist/diagnostics/prose.cjs.map +1 -1
- package/dist/diagnostics/prose.js +9 -1
- package/dist/diagnostics/prose.js.map +1 -1
- package/dist/{explain-pKAnnxEW.d.cts → explain-DmwN_RG9.d.cts} +11 -4
- package/dist/{explain-BVE3ln3G.d.ts → explain-Dr3SmMNY.d.ts} +11 -4
- package/dist/feature/install.cjs +2 -1
- package/dist/feature/install.cjs.map +1 -1
- package/dist/feature/install.js +2 -1
- package/dist/feature/install.js.map +1 -1
- package/dist/feature/slice-guard.cjs +76 -0
- package/dist/feature/slice-guard.cjs.map +1 -0
- package/dist/feature/slice-guard.js +50 -0
- package/dist/feature/slice-guard.js.map +1 -0
- package/dist/features/column-filters/codes.cjs +2 -19
- package/dist/features/column-filters/codes.cjs.map +1 -1
- package/dist/features/column-filters/codes.js +2 -19
- package/dist/features/column-filters/codes.js.map +1 -1
- package/dist/features/column-filters/column-filters.cjs +37 -22
- package/dist/features/column-filters/column-filters.cjs.map +1 -1
- package/dist/features/column-filters/column-filters.js +37 -22
- package/dist/features/column-filters/column-filters.js.map +1 -1
- package/dist/features/column-filters/match.cjs +6 -2
- package/dist/features/column-filters/match.cjs.map +1 -1
- package/dist/features/column-filters/match.js +6 -2
- package/dist/features/column-filters/match.js.map +1 -1
- package/dist/features/pagination/pagination.cjs +2 -2
- package/dist/features/pagination/pagination.cjs.map +1 -1
- package/dist/features/pagination/pagination.js +2 -2
- package/dist/features/pagination/pagination.js.map +1 -1
- package/dist/features/search/codes.cjs +18 -2
- package/dist/features/search/codes.cjs.map +1 -1
- package/dist/features/search/codes.js +16 -1
- package/dist/features/search/codes.js.map +1 -1
- package/dist/features/search/search-engine.cjs +24 -5
- package/dist/features/search/search-engine.cjs.map +1 -1
- package/dist/features/search/search-engine.js +25 -6
- package/dist/features/search/search-engine.js.map +1 -1
- package/dist/features/search/search-index.cjs +23 -44
- package/dist/features/search/search-index.cjs.map +1 -1
- package/dist/features/search/search-index.js +24 -45
- package/dist/features/search/search-index.js.map +1 -1
- package/dist/features/sorting/ranks.cjs +117 -50
- package/dist/features/sorting/ranks.cjs.map +1 -1
- package/dist/features/sorting/ranks.js +117 -50
- package/dist/features/sorting/ranks.js.map +1 -1
- package/dist/features/sorting/sorting.cjs +87 -32
- package/dist/features/sorting/sorting.cjs.map +1 -1
- package/dist/features/sorting/sorting.js +87 -32
- package/dist/features/sorting/sorting.js.map +1 -1
- package/dist/index.d.cts +26 -17
- package/dist/index.d.ts +26 -17
- package/dist/intercept/runtime.cjs +3 -2
- package/dist/intercept/runtime.cjs.map +1 -1
- package/dist/intercept/runtime.js +3 -2
- package/dist/intercept/runtime.js.map +1 -1
- package/dist/messages/index.cjs +2 -0
- package/dist/messages/index.cjs.map +1 -1
- package/dist/messages/index.js +2 -0
- package/dist/messages/index.js.map +1 -1
- package/dist/pagination.d.cts +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pipeline/column-cache.cjs +6 -2
- package/dist/pipeline/column-cache.cjs.map +1 -1
- package/dist/pipeline/column-cache.js +6 -2
- package/dist/pipeline/column-cache.js.map +1 -1
- package/dist/pipeline/release.cjs +34 -0
- package/dist/pipeline/release.cjs.map +1 -0
- package/dist/pipeline/release.js +8 -0
- package/dist/pipeline/release.js.map +1 -0
- package/dist/pipeline/runner.cjs +15 -1
- package/dist/pipeline/runner.cjs.map +1 -1
- package/dist/pipeline/runner.js +15 -1
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/{search-async-entry-ByMJbHjc.d.cts → search-async-entry-BiemO3JT.d.cts} +2 -2
- package/dist/{search-async-entry-CFQODE08.d.ts → search-async-entry-GRj0E06n.d.ts} +2 -2
- package/dist/search-async.d.cts +3 -3
- package/dist/search-async.d.ts +3 -3
- package/dist/search.d.cts +4 -4
- package/dist/search.d.ts +4 -4
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sorting.d.cts +2 -2
- package/dist/sorting.d.ts +2 -2
- package/dist/source/async-control.cjs +2 -1
- package/dist/source/async-control.cjs.map +1 -1
- package/dist/source/async-control.js +2 -1
- package/dist/source/async-control.js.map +1 -1
- package/dist/source/fetcher.cjs.map +1 -1
- package/dist/source/fetcher.js.map +1 -1
- package/dist/source/server.cjs +58 -5
- package/dist/source/server.cjs.map +1 -1
- package/dist/source/server.js +58 -5
- package/dist/source/server.js.map +1 -1
- package/dist/table/create-table.cjs +33 -20
- package/dist/table/create-table.cjs.map +1 -1
- package/dist/table/create-table.js +33 -20
- package/dist/table/create-table.js.map +1 -1
- package/dist/table/index.cjs +3 -0
- package/dist/table/index.cjs.map +1 -1
- package/dist/table/index.js +5 -1
- package/dist/table/index.js.map +1 -1
- package/dist/table/state-codes.cjs +72 -0
- package/dist/table/state-codes.cjs.map +1 -0
- package/dist/table/state-codes.js +45 -0
- package/dist/table/state-codes.js.map +1 -0
- package/dist/table/types.cjs.map +1 -1
- package/dist/table/types.js.map +1 -1
- package/dist/validity.d.cts +2 -2
- package/dist/validity.d.ts +2 -2
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/dist/ingest/test-schemas.cjs +0 -73
- package/dist/ingest/test-schemas.cjs.map +0 -1
- package/dist/ingest/test-schemas.js +0 -46
- package/dist/ingest/test-schemas.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/diagnostics/prose.ts"],"sourcesContent":["import { installedText } from './catalog'\nimport './process-guard'\n\n/**\n * Production detail prose (§8.2, §9.3): the sentences some details carry (`fallback`, `reason`,\n * `problem`) are written as short keys at the emit site, e.g. `fallback: 'cells-undefined'`, or\n * `'page-size|10'` with arguments. A diagnostic built in development, or in production with\n * `@datakable/core/messages` installed, reads the full sentence (the key is expanded when it is\n * built); production without the messages keeps the key, so the bundle carries no prose. A value\n * that is not a known key (a plugin's own `registerFallback` text) is kept as written.\n *\n * `{0}`, `{1}` are the arguments after the key, separated by
|
|
1
|
+
{"version":3,"sources":["../../src/diagnostics/prose.ts"],"sourcesContent":["import { installedText } from './catalog'\nimport './process-guard'\n\n/**\n * Production detail prose (§8.2, §9.3): the sentences some details carry (`fallback`, `reason`,\n * `problem`) are written as short keys at the emit site, e.g. `fallback: 'cells-undefined'`, or\n * `'page-size|10'` with arguments. A diagnostic built in development, or in production with\n * `@datakable/core/messages` installed, reads the full sentence (the key is expanded when it is\n * built); production without the messages keeps the key, so the bundle carries no prose. A value\n * that is not a known key (a plugin's own `registerFallback` text) is kept as written.\n *\n * `{0}`, `{1}` are the arguments after the key, separated by `|`. A value the library does not\n * write itself (a user's option, a column id, a plugin's name) is packed with {@link proseArg}, so\n * a `|` inside it does not split it (#92).\n */\nexport const proseMessages: Readonly<Record<string, string>> = {\n // Cell slots (`DK_CALLBACK_FAILED` / `DK_CALLBACK_BAD_RETURN` fallback).\n 'cells-coerced': 'those cells were coerced with the default formatter',\n 'cells-warn': 'those cells rendered ⚠', // `<DataTable />` (@datakable/react)\n 'cells-undefined': 'the failed cells read as undefined',\n 'cells-empty': \"the failed cells render ''\",\n 'rows-unmatched': 'the failed rows are treated as non-matching',\n 'rows-last': 'the failed rows sort as missing values (last)',\n 'cell-undefined': 'the cell reads as undefined',\n 'row-key-index': \"those rows use the fallback key '#<index>'\",\n 'sorted-native': 'the rows were sorted with the native algorithm',\n 'sorted-default': 'the column was sorted with the default comparator',\n // Operation fallbacks (`intercept/fallbacks.ts`, the boundary's reports).\n 'init-continued': 'table creation continued without the failed step',\n 'options-kept': 'the previous options were kept',\n 'destroy-continued': 'teardown continued; the table is destroyed',\n 'update-dropped': 'the update was dropped (state unchanged)',\n 'data-kept': 'the previous data was kept',\n 'row-model-kept': 'the last good row model was kept',\n 'step-passthrough': 'the step passed its input through',\n 'batch-fallback':\n \"the column's cells use the slot fallback ('' for format, undefined for accessor, \" +\n 'non-matching for filterFn, missing (sorted last) for sortBy)',\n 'results-kept': \"the previous results were kept (status: 'error')\",\n 'diagnostic-dropped': 'the diagnostic was dropped (the logger never kills the table)',\n 'returned-undefined': 'the operation returned undefined',\n // Features.\n 'slice-undefined': 'the slice starts as undefined',\n 'api-not-installed': 'its API was not installed',\n 'call-ignored': 'the call was ignored',\n 'api-missing': 'table.{0}() did nothing (the API of that feature was not installed)',\n 'apis-missing': 'table.{0}() did nothing (the API of those features was not installed)',\n 'page-reset': 'the pagination state was reset to its initial value',\n 'page-index-0': 'pageIndex was replaced by 0',\n 'page-size': 'pageSize was replaced by {0}',\n 'validity-ignored': 'the call was ignored; the validity filter is unchanged',\n 'validity-all': \"the table shows every row, as with 'all'\",\n 'validity-needs-flag':\n 'validity filters rows flagged by schema validation, which needs `schema` and ' +\n \"`invalidRows: 'flag'` (without a schema no row is invalid; with 'drop' invalid rows \" +\n \"are removed, with 'throw' they throw)\",\n 'not-searchable': 'no column is searchable: give the columns to search a `search` weight',\n collation: 'the runtime\\'s collation for \"{0}\"',\n 'not-algorithm':\n '{0} is not a sort algorithm (accepted: auto, tim, radix, counting, native, or a function); ' +\n 'auto was used',\n 'exsorted-failed': 'exsorted {0}Sort could not sort these keys; auto was used',\n 'exsorted-range':\n 'exsorted {0}Sort could not sort these keys (key range above 1,000,000); auto was used',\n 'needs-int-keys':\n '{0} sorts a single column of safe integer keys (numbers or Dates, no sortFn); auto was used',\n // Sorting reasons (DK_SORT_STRATEGY, `debug: true`).\n 'custom-threw': 'the custom algorithm threw',\n 'custom-bad': 'the custom algorithm returned a bad value',\n 'not-safe-int': 'keys are not all safe integers',\n ranks: '{0}, strings as collation ranks',\n 'int-keys': '{0}, safe-integer keys',\n 'auto-nearly': 'auto: safe integers, nearly sorted ({0} descents)',\n 'auto-range': 'auto: safe integers, key range {0} ≤ 4n',\n 'auto-wide': 'auto: safe integers, wide range, n ≥ 10k',\n 'auto-wide-small': 'auto: safe integers, wide range, n < 10k',\n 'auto-ranked-nearly':\n 'auto: collation ranks of a string column, safe integers, nearly sorted ({0} descents)',\n 'auto-ranked-range':\n 'auto: collation ranks of a string column, safe integers, key range {0} ≤ 4n',\n 'auto-ranked-wide':\n 'auto: collation ranks of a string column, safe integers, wide range, n ≥ 10k',\n 'auto-ranked-wide-small':\n 'auto: collation ranks of a string column, safe integers, wide range, n < 10k',\n // Filter values (`DK_FILTER_TYPE_MISMATCH` expected).\n 'prims-or-dates': 'an array of primitives or Dates',\n 'prim-or-date': 'a primitive or a Date',\n 'range-bound': 'number | bigint or a valid Date bound',\n 'not-nan': 'a value other than NaN (NaN equals no cell)',\n 'same-type': 'min and max of the same type ({0})',\n // An option given without its wrapper (`DK_INVALID_OPTION` expected).\n wrapped: \"{0}({1}) from '@datakable/core/{2}'\",\n // DK_INTERNAL problems.\n escaped: 'an exception escaped DataKable code',\n 'unknown-code': 'unknown diagnostic code \"{0}\": define it with defineDiagnostic() first',\n 'unknown-op': 'Unknown operation \"{0}\": register it with registerOperation()',\n 'boundary-twice': 'A boundary interceptor is already installed (\"{0}\"); \"{1}\" was rejected',\n}\n\n/**\n * A user value as one prose argument (`` `unknown-op|${proseArg(op)}` ``): `%` and `|` are\n * escaped (`%25`, `%7C`), so a value holding them is read back whole, not split or expanded.\n */\nexport function proseArg(v: string): string {\n return v.replace(/[%|]/g, encodeURIComponent)\n}\n\n/**\n * The sentence of a prose key (`'page-size|10'` → `'pageSize was replaced by 10'`): from\n * {@link proseMessages} in development (`dev`), else from the installed messages; `v` itself when\n * neither has the key.\n */\nexport function expandProse(v: string, dev: boolean): string {\n const [key, ...args] = v.split('|') as [string, ...string[]]\n const text = ((process.env.NODE_ENV !== \"production\") && dev ? proseMessages[key] : undefined) ?? installedText(key)\n // An argument may be a key itself (`'ranks|custom-threw'`); a missing one reads as ''. An\n // escaped one ({@link proseArg}) is a value. (`n` is the digit string: `args['0']` indexes like\n // `args[0]`.)\n return typeof text === 'string'\n ? text.replace(/\\{(\\d)\\}/g, (_, n: number) => {\n const a = args[n] ?? ''\n return a.includes('%') ? decodeURIComponent(a) : expandProse(a, dev)\n })\n : v\n}\n\n/** The detail fields that may hold a prose key. */\nconst FIELDS = ['fallback', 'reason', 'problem', 'expected'] as const\n\n/** A copy of `details` with its prose keys expanded. */\nexport function expandDetails(\n details: Record<string, unknown>,\n dev: boolean,\n): Record<string, unknown> {\n const out = { ...details }\n for (const f of FIELDS) {\n const v = details[f]\n if (typeof v === 'string') out[f] = expandProse(v, dev)\n }\n return out\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,OAAO;AAcA,IAAM,gBAAkD;AAAA;AAAA,EAE7D,iBAAiB;AAAA,EACjB,cAAc;AAAA;AAAA,EACd,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA;AAAA,EAElB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,kBACE;AAAA,EAEF,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,sBAAsB;AAAA;AAAA,EAEtB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,uBACE;AAAA,EAGF,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,iBACE;AAAA,EAEF,mBAAmB;AAAA,EACnB,kBACE;AAAA,EACF,kBACE;AAAA;AAAA,EAEF,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc;AAAA,EACd,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,sBACE;AAAA,EACF,qBACE;AAAA,EACF,oBACE;AAAA,EACF,0BACE;AAAA;AAAA,EAEF,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,aAAa;AAAA;AAAA,EAEb,SAAS;AAAA;AAAA,EAET,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,kBAAkB;AACpB;AAMO,SAAS,SAAS,GAAmB;AAC1C,SAAO,EAAE,QAAQ,SAAS,kBAAkB;AAC9C;AAOO,SAAS,YAAY,GAAW,KAAsB;AAC3D,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI,EAAE,MAAM,GAAG;AAClC,QAAM,QAAS,QAAQ,IAAI,aAAa,gBAAiB,MAAM,cAAc,GAAG,IAAI,WAAc,cAAc,GAAG;AAInH,SAAO,OAAO,SAAS,WACnB,KAAK,QAAQ,aAAa,CAAC,GAAG,MAAc;AAC1C,UAAM,IAAI,KAAK,CAAC,KAAK;AACrB,WAAO,EAAE,SAAS,GAAG,IAAI,mBAAmB,CAAC,IAAI,YAAY,GAAG,GAAG;AAAA,EACrE,CAAC,IACD;AACN;AAGA,IAAM,SAAS,CAAC,YAAY,UAAU,WAAW,UAAU;AAGpD,SAAS,cACd,SACA,KACyB;AACzB,QAAM,MAAM,EAAE,GAAG,QAAQ;AACzB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,OAAO,MAAM,SAAU,KAAI,CAAC,IAAI,YAAY,GAAG,GAAG;AAAA,EACxD;AACA,SAAO;AACT;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-
|
|
1
|
+
import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-4sYQv_7J.cjs';
|
|
2
2
|
|
|
3
3
|
/** Runtime types an option can accept (`'array'` is checked before `'object'`). */
|
|
4
4
|
type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
|
|
@@ -463,7 +463,8 @@ type ColumnValues = Float64Array | readonly unknown[];
|
|
|
463
463
|
|
|
464
464
|
/**
|
|
465
465
|
* What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the
|
|
466
|
-
* request's `signal` (aborted when superseded, timed out or the table is destroyed).
|
|
466
|
+
* request's `signal` (aborted when superseded, timed out or the table is destroyed). The state may
|
|
467
|
+
* be restored (URL, storage): treat these params as untrusted input on the backend (#100).
|
|
467
468
|
*/
|
|
468
469
|
interface FetchParams {
|
|
469
470
|
/** The applied search query (`search({ mode: 'server' })`). */
|
|
@@ -492,6 +493,8 @@ interface FetchResult<T> {
|
|
|
492
493
|
* What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;
|
|
493
494
|
* `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed
|
|
494
495
|
* (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.
|
|
496
|
+
* On a `createDeferred` table it is the outcome of the request that runs after `startTable`
|
|
497
|
+
* (`'superseded'` when the table is destroyed before it starts, #90).
|
|
495
498
|
*/
|
|
496
499
|
interface FetchOutcome {
|
|
497
500
|
status: 'applied' | 'superseded' | 'error';
|
|
@@ -598,7 +601,9 @@ declare function emitTableDiagnostic<C extends DiagnosticCode>(table: object, co
|
|
|
598
601
|
interface ServerSource extends TableExtension {
|
|
599
602
|
/**
|
|
600
603
|
* Starts a fetcher request when the server-mode state differs from the last request's (or
|
|
601
|
-
* `force`: `refetch()`). Resolves with the outcome; never rejects.
|
|
604
|
+
* `force`: `refetch()`). Resolves with the outcome; never rejects. Deferred (`createDeferred`),
|
|
605
|
+
* `force` resolves with the request that runs after `startTable` (`'superseded'` when the table
|
|
606
|
+
* is destroyed first, #90).
|
|
602
607
|
*/
|
|
603
608
|
sync(force?: boolean): Promise<FetchOutcome> | undefined;
|
|
604
609
|
/** `rowCount` of the last applied fetcher result (§6.5). */
|
|
@@ -757,7 +762,9 @@ In = T> {
|
|
|
757
762
|
explainRow(key: RowKey): RowExplanation;
|
|
758
763
|
/**
|
|
759
764
|
* Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after
|
|
760
|
-
* `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.
|
|
765
|
+
* `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once. On a
|
|
766
|
+
* `createDeferred` table it settles with the request that runs after `startTable`
|
|
767
|
+
* (`'superseded'` when the table is destroyed first, #90).
|
|
761
768
|
*/
|
|
762
769
|
refetch(): Promise<FetchOutcome>;
|
|
763
770
|
/** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-
|
|
1
|
+
import { R as RowIssue, E as Emit, a as EmitOptions, D as Diagnostic, T as Trigger, P as Phase, I as InvalidRowsMode, F as FlaggedRow, b as Row, c as DiagnosticCode, d as DiagnosticDetails, e as TableSchema, f as RedactLevel, g as RedactFn, h as InvalidRow, i as RowKey } from './codes-4sYQv_7J.js';
|
|
2
2
|
|
|
3
3
|
/** Runtime types an option can accept (`'array'` is checked before `'object'`). */
|
|
4
4
|
type OptionType = 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array';
|
|
@@ -463,7 +463,8 @@ type ColumnValues = Float64Array | readonly unknown[];
|
|
|
463
463
|
|
|
464
464
|
/**
|
|
465
465
|
* What `options.fetcher` receives (§7.1): only the state of the server-mode steps, plus the
|
|
466
|
-
* request's `signal` (aborted when superseded, timed out or the table is destroyed).
|
|
466
|
+
* request's `signal` (aborted when superseded, timed out or the table is destroyed). The state may
|
|
467
|
+
* be restored (URL, storage): treat these params as untrusted input on the backend (#100).
|
|
467
468
|
*/
|
|
468
469
|
interface FetchParams {
|
|
469
470
|
/** The applied search query (`search({ mode: 'server' })`). */
|
|
@@ -492,6 +493,8 @@ interface FetchResult<T> {
|
|
|
492
493
|
* What `table.refetch()` resolves to; it never rejects. `'applied'`: the rows were loaded;
|
|
493
494
|
* `'superseded'`: a newer request (or `destroy()`) replaced it; `'error'`: it failed
|
|
494
495
|
* (`DK_FETCH_FAILED`, `DK_FETCH_BAD_RESPONSE`, `DK_ASYNC_TIMEOUT`) and the previous rows are kept.
|
|
496
|
+
* On a `createDeferred` table it is the outcome of the request that runs after `startTable`
|
|
497
|
+
* (`'superseded'` when the table is destroyed before it starts, #90).
|
|
495
498
|
*/
|
|
496
499
|
interface FetchOutcome {
|
|
497
500
|
status: 'applied' | 'superseded' | 'error';
|
|
@@ -598,7 +601,9 @@ declare function emitTableDiagnostic<C extends DiagnosticCode>(table: object, co
|
|
|
598
601
|
interface ServerSource extends TableExtension {
|
|
599
602
|
/**
|
|
600
603
|
* Starts a fetcher request when the server-mode state differs from the last request's (or
|
|
601
|
-
* `force`: `refetch()`). Resolves with the outcome; never rejects.
|
|
604
|
+
* `force`: `refetch()`). Resolves with the outcome; never rejects. Deferred (`createDeferred`),
|
|
605
|
+
* `force` resolves with the request that runs after `startTable` (`'superseded'` when the table
|
|
606
|
+
* is destroyed first, #90).
|
|
602
607
|
*/
|
|
603
608
|
sync(force?: boolean): Promise<FetchOutcome> | undefined;
|
|
604
609
|
/** `rowCount` of the last applied fetcher result (§6.5). */
|
|
@@ -757,7 +762,9 @@ In = T> {
|
|
|
757
762
|
explainRow(key: RowKey): RowExplanation;
|
|
758
763
|
/**
|
|
759
764
|
* Runs `options.fetcher` again with the current params (§7.1), e.g. a Retry button after
|
|
760
|
-
* `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once.
|
|
765
|
+
* `DK_FETCH_FAILED`. Never rejects. Without a fetcher: `{ status: 'applied' }` at once. On a
|
|
766
|
+
* `createDeferred` table it settles with the request that runs after `startTable`
|
|
767
|
+
* (`'superseded'` when the table is destroyed first, #90).
|
|
761
768
|
*/
|
|
762
769
|
refetch(): Promise<FetchOutcome>;
|
|
763
770
|
/** Releases listeners. Later mutating calls are no-ops that report `DK_TABLE_DESTROYED`. */
|
package/dist/feature/install.cjs
CHANGED
|
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(install_exports);
|
|
|
27
27
|
var import_build = require("../diagnostics/build.cjs");
|
|
28
28
|
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
29
29
|
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
30
|
+
var import_prose = require("../diagnostics/prose.cjs");
|
|
30
31
|
var import_classify = require("../intercept/classify.cjs");
|
|
31
32
|
var import_codes = require("./codes.cjs");
|
|
32
33
|
(0, import_catalog.keepDiagnostics)(import_codes.featureDiagnostics);
|
|
@@ -121,7 +122,7 @@ function missingApi(failed, host) {
|
|
|
121
122
|
feature,
|
|
122
123
|
hook: "createApi",
|
|
123
124
|
method: key,
|
|
124
|
-
fallback: `${failed.length === 1 ? "api" : "apis"}-missing|${key}`
|
|
125
|
+
fallback: `${failed.length === 1 ? "api" : "apis"}-missing|${(0, import_prose.proseArg)(key)}`
|
|
125
126
|
})
|
|
126
127
|
);
|
|
127
128
|
return void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'slice-undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'api-not-installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `${failed.length === 1 ? 'api' : 'apis'}-missing|${key}`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAiC;AACjC,qBAAgC;AAChC,2BAAO;
|
|
1
|
+
{"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { proseArg } from '../diagnostics/prose'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'slice-undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'api-not-installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `${failed.length === 1 ? 'api' : 'apis'}-missing|${proseArg(key)}`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":"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","names":[]}
|
package/dist/feature/install.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { throwConfigError } from "../diagnostics/build.js";
|
|
3
3
|
import { keepDiagnostics } from "../diagnostics/catalog.js";
|
|
4
4
|
import "../diagnostics/process-guard.js";
|
|
5
|
+
import { proseArg } from "../diagnostics/prose.js";
|
|
5
6
|
import { errorName } from "../intercept/classify.js";
|
|
6
7
|
import { featureDiagnostics } from "./codes.js";
|
|
7
8
|
keepDiagnostics(featureDiagnostics);
|
|
@@ -96,7 +97,7 @@ function missingApi(failed, host) {
|
|
|
96
97
|
feature,
|
|
97
98
|
hook: "createApi",
|
|
98
99
|
method: key,
|
|
99
|
-
fallback: `${failed.length === 1 ? "api" : "apis"}-missing|${key}`
|
|
100
|
+
fallback: `${failed.length === 1 ? "api" : "apis"}-missing|${proseArg(key)}`
|
|
100
101
|
})
|
|
101
102
|
);
|
|
102
103
|
return void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'slice-undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'api-not-installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `${failed.length === 1 ? 'api' : 'apis'}-missing|${key}`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../src/feature/install.ts"],"sourcesContent":["import { throwConfigError } from '../diagnostics/build'\nimport { keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { proseArg } from '../diagnostics/prose'\nimport type { Emit } from '../diagnostics/types'\nimport { errorName } from '../intercept/classify'\nimport type { Table, TableState } from '../table/types'\nimport { featureDiagnostics } from './codes'\nimport type { AnyFeature, FeatureContext, SliceUpdater } from './types'\n\nkeepDiagnostics(featureDiagnostics)\n\n/** What `createTable` gives the installer: the store operations a feature context needs. */\nexport interface FeatureHost {\n readonly table: Table<unknown, TableState>\n /** The table name, for config errors thrown before the first emit. */\n readonly tableName: string\n readonly emit: Emit\n getSlice(name: string): unknown\n /** `state:dispatch` of one slice (where = the feature name); notifies unless inside an API call. */\n setSlice(name: string, updater: SliceUpdater<unknown>): void\n /**\n * Runs an API method: no-op + `DK_TABLE_DESTROYED` after destroy (returning `destroyed?.()`),\n * trigger `api:<method>`, listeners notified once when the outermost call returns.\n */\n call(method: string, fn: () => unknown, destroyed?: () => unknown): unknown\n}\n\nfunction reportFailure(\n emit: Emit,\n feature: string,\n hook: 'initialState' | 'createApi',\n fallback: string,\n e: unknown,\n) {\n const name = errorName(e)\n emit(\n 'DK_FEATURE_FAILED',\n { feature, hook, fallback, ...(name === undefined ? {} : { errorName: name }) },\n { cause: e },\n )\n}\n\n/**\n * The starting snapshot: each feature's `initialState()` under its name, then `given`\n * (`options.initialState`) per slice on top. `initialState()` is not called for a given slice.\n */\nexport function initialSlices(\n features: readonly AnyFeature[],\n given: object | undefined,\n emit: Emit,\n): TableState {\n const out: TableState = {}\n for (const f of features) {\n if (given && Object.hasOwn(given, f.name)) continue\n try {\n out[f.name] = f.initialState()\n } catch (e) {\n out[f.name] = undefined\n reportFailure(emit, f.name, 'initialState', 'slice-undefined', e)\n }\n }\n return given ? Object.assign(out, given) : out\n}\n\n/**\n * Creates every feature API (in the order the features were listed) and merges it onto the table.\n * Throws `DatakableConfigError` `DK_FEATURE_API_CONFLICT` when a member name is already taken by a\n * core table method or by an earlier feature. Methods are wrapped once here (cold path).\n */\nexport function installApis(features: readonly AnyFeature[], host: FeatureHost): void {\n const target = host.table as unknown as Record<string, unknown>\n const owners = new Map<string, string | null>()\n for (const k of Object.keys(target)) owners.set(k, null)\n /** Features whose `createApi()` threw: their methods are missing from the table. */\n const failed: string[] = []\n\n for (const f of features) {\n const name = f.name\n const ctx: FeatureContext<unknown> = {\n name,\n getState: () => host.getSlice(name),\n setState: (updater) => host.setSlice(name, updater),\n table: host.table,\n emit: host.emit,\n }\n let api: unknown\n try {\n api = f.createApi(ctx)\n } catch (e) {\n reportFailure(host.emit, name, 'createApi', 'api-not-installed', e)\n failed.push(name)\n continue\n }\n if (api === null || typeof api !== 'object') continue\n const members = api as Record<string, unknown>\n const keys = Object.keys(members)\n for (const method of keys) {\n if (owners.has(method)) {\n throwConfigError(\n 'DK_FEATURE_API_CONFLICT',\n { method, feature: name, owner: owners.get(method) ?? null },\n { table: host.tableName },\n )\n }\n }\n for (const method of keys) {\n owners.set(method, name)\n const member = members[method]\n const destroyed = f.destroyedResult?.[method]\n target[method] =\n typeof member === 'function'\n ? (...args: unknown[]) => host.call(method, () => member.apply(members, args), destroyed)\n : member\n }\n }\n if ((process.env.NODE_ENV !== \"production\") && failed.length > 0) Object.setPrototypeOf(target, missingApi(failed, host))\n}\n\n/**\n * Names generic code probes on any object (await, JSON, React, test runners, inspectors): never a\n * feature method, so never stubbed.\n */\nconst PROBES = new Set([\n 'then',\n 'catch',\n 'finally',\n 'toJSON',\n 'asymmetricMatch',\n 'nodeType',\n 'tagName',\n])\nconst isProbe = (k: string) => PROBES.has(k) || /^[_$@]/.test(k)\n\n/**\n * Dev (R4): the prototype of a table where a feature's `createApi()` threw. The types still promise\n * that feature's methods, so a call to a name the table does not have is a no-op that reports\n * `DK_FEATURE_FAILED` (with the method) instead of a raw TypeError at the caller. Only reads of\n * missing names reach it: the table keeps its identity and own members, `in` / `hasOwn` stay\n * false, and `Object.prototype` members, symbols and generic probes (`then`, `toJSON`, …) resolve\n * as usual.\n */\nfunction missingApi(failed: readonly string[], host: FeatureHost): object {\n const feature = failed.join(', ')\n const stubs = new Map<string, () => undefined>()\n return new Proxy(Object.prototype, {\n get(proto, key, receiver) {\n if (typeof key !== 'string' || key in proto || isProbe(key)) {\n return Reflect.get(proto, key, receiver)\n }\n let stub = stubs.get(key)\n if (!stub) {\n stub = () => {\n host.call(key, () =>\n host.emit('DK_FEATURE_FAILED', {\n feature,\n hook: 'createApi',\n method: key,\n fallback: `${failed.length === 1 ? 'api' : 'apis'}-missing|${proseArg(key)}`,\n }),\n )\n return undefined\n }\n stubs.set(key, stub)\n }\n return stub\n },\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/feature/slice-guard.ts
|
|
21
|
+
var slice_guard_exports = {};
|
|
22
|
+
__export(slice_guard_exports, {
|
|
23
|
+
sliceGuard: () => sliceGuard,
|
|
24
|
+
sliceGuardDiagnostics: () => sliceGuardDiagnostics
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(slice_guard_exports);
|
|
27
|
+
var import_catalog = require("../diagnostics/catalog.cjs");
|
|
28
|
+
var import_process_guard = require("../diagnostics/process-guard.cjs");
|
|
29
|
+
var import_define = require("../intercept/define.cjs");
|
|
30
|
+
var import_state_codes = require("../table/state-codes.cjs");
|
|
31
|
+
var sliceGuardDiagnostics = [
|
|
32
|
+
(0, import_catalog.defineDiagnostic)(
|
|
33
|
+
{
|
|
34
|
+
code: "DK_INVALID_STATE",
|
|
35
|
+
level: "warn",
|
|
36
|
+
where: (d) => ({ kind: "option", path: `state.${d.slice}` })
|
|
37
|
+
},
|
|
38
|
+
process.env.NODE_ENV !== "production" && import_state_codes.stateMessages.DK_INVALID_STATE
|
|
39
|
+
)
|
|
40
|
+
];
|
|
41
|
+
(0, import_catalog.keepDiagnostics)(sliceGuardDiagnostics);
|
|
42
|
+
function sliceGuard(name, fix) {
|
|
43
|
+
const hosts = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
const check = (ctx, s) => {
|
|
45
|
+
const out = fix(s);
|
|
46
|
+
if (!out) return;
|
|
47
|
+
ctx.emit("DK_INVALID_STATE", { slice: name, received: out[1], fallback: out[2] });
|
|
48
|
+
ctx.setState(out[0]);
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
interceptor: (0, import_define.defineInterceptor)({
|
|
52
|
+
name: `${name}.state`,
|
|
53
|
+
on: ["state:dispatch", "table:setOptions"],
|
|
54
|
+
intercept(ictx, next) {
|
|
55
|
+
const ctx = hosts.get(ictx.table);
|
|
56
|
+
if (!ctx) return next();
|
|
57
|
+
const before = ictx.table.getState()[name];
|
|
58
|
+
const out = next();
|
|
59
|
+
const now = ctx.getState();
|
|
60
|
+
if (!Object.is(before, now)) check(ctx, now);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
/** Registers the table (call from `createApi`) and checks the slice it starts with. */
|
|
65
|
+
install(ctx) {
|
|
66
|
+
hosts.set(ctx.table, ctx);
|
|
67
|
+
check(ctx, ctx.getState());
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
72
|
+
0 && (module.exports = {
|
|
73
|
+
sliceGuard,
|
|
74
|
+
sliceGuardDiagnostics
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=slice-guard.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/feature/slice-guard.ts"],"sourcesContent":["import { defineDiagnostic, keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { defineInterceptor } from '../intercept/define'\nimport { stateMessages } from '../table/state-codes'\nimport type { Table, TableState } from '../table/types'\nimport type { FeatureContext } from './types'\n\n/** Defined here: only the development guard reports it, so production bundles drop it. */\nexport const sliceGuardDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_STATE',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}` }),\n },\n (process.env.NODE_ENV !== \"production\") && stateMessages.DK_INVALID_STATE,\n ),\n]\n\nkeepDiagnostics(sliceGuardDiagnostics)\n\n/** A replacement for a malformed slice: the valid slice, what was wrong, what was stored. */\nexport type SliceFix<S> = readonly [slice: S, received: string, fallback: string]\n\n/**\n * Replaces a malformed slice written to the store (`initialState`, `setState`, the controlled\n * `options.state`), like pagination's sanitize, with `DK_INVALID_STATE` (#89): restored state (URL,\n * storage) is untrusted. Development only: production reads a malformed slice as empty in the\n * feature's API and row model instead. `fix(s)` returns `undefined` for a valid slice. One guard per\n * feature instance; the instance may serve several tables.\n */\nexport function sliceGuard<S>(name: string, fix: (s: unknown) => SliceFix<S> | undefined) {\n const hosts = new WeakMap<object, FeatureContext<S>>()\n const check = (ctx: FeatureContext<S>, s: unknown) => {\n const out = fix(s)\n if (!out) return\n ctx.emit('DK_INVALID_STATE', { slice: name, received: out[1], fallback: out[2] })\n ctx.setState(out[0])\n }\n return {\n interceptor: defineInterceptor({\n name: `${name}.state`,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ictx, next) {\n const ctx = hosts.get(ictx.table as object)\n if (!ctx) return next()\n const before = (ictx.table as Table<unknown, TableState>).getState()[name]\n const out = next()\n const now = ctx.getState()\n if (!Object.is(before, now)) check(ctx, now)\n return out\n },\n }),\n /** Registers the table (call from `createApi`) and checks the slice it starts with. */\n install(ctx: FeatureContext<S>) {\n hosts.set(ctx.table, ctx)\n check(ctx, ctx.getState())\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAkD;AAClD,2BAAO;AACP,oBAAkC;AAClC,yBAA8B;AAKvB,IAAM,wBAAwB;AAAA,MACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS,EAAE,KAAK,GAAG;AAAA,IAC5D;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,iCAAc;AAAA,EAC3D;AACF;AAAA,IAEA,gCAAgB,qBAAqB;AAY9B,SAAS,WAAc,MAAc,KAA8C;AACxF,QAAM,QAAQ,oBAAI,QAAmC;AACrD,QAAM,QAAQ,CAAC,KAAwB,MAAe;AACpD,UAAM,MAAM,IAAI,CAAC;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,KAAK,oBAAoB,EAAE,OAAO,MAAM,UAAU,IAAI,CAAC,GAAG,UAAU,IAAI,CAAC,EAAE,CAAC;AAChF,QAAI,SAAS,IAAI,CAAC,CAAC;AAAA,EACrB;AACA,SAAO;AAAA,IACL,iBAAa,iCAAkB;AAAA,MAC7B,MAAM,GAAG,IAAI;AAAA,MACb,IAAI,CAAC,kBAAkB,kBAAkB;AAAA,MACzC,UAAU,MAAM,MAAM;AACpB,cAAM,MAAM,MAAM,IAAI,KAAK,KAAe;AAC1C,YAAI,CAAC,IAAK,QAAO,KAAK;AACtB,cAAM,SAAU,KAAK,MAAqC,SAAS,EAAE,IAAI;AACzE,cAAM,MAAM,KAAK;AACjB,cAAM,MAAM,IAAI,SAAS;AACzB,YAAI,CAAC,OAAO,GAAG,QAAQ,GAAG,EAAG,OAAM,KAAK,GAAG;AAC3C,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA;AAAA,IAED,QAAQ,KAAwB;AAC9B,YAAM,IAAI,IAAI,OAAO,GAAG;AACxB,YAAM,KAAK,IAAI,SAAS,CAAC;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/feature/slice-guard.ts
|
|
2
|
+
import { defineDiagnostic, keepDiagnostics } from "../diagnostics/catalog.js";
|
|
3
|
+
import "../diagnostics/process-guard.js";
|
|
4
|
+
import { defineInterceptor } from "../intercept/define.js";
|
|
5
|
+
import { stateMessages } from "../table/state-codes.js";
|
|
6
|
+
var sliceGuardDiagnostics = [
|
|
7
|
+
defineDiagnostic(
|
|
8
|
+
{
|
|
9
|
+
code: "DK_INVALID_STATE",
|
|
10
|
+
level: "warn",
|
|
11
|
+
where: (d) => ({ kind: "option", path: `state.${d.slice}` })
|
|
12
|
+
},
|
|
13
|
+
process.env.NODE_ENV !== "production" && stateMessages.DK_INVALID_STATE
|
|
14
|
+
)
|
|
15
|
+
];
|
|
16
|
+
keepDiagnostics(sliceGuardDiagnostics);
|
|
17
|
+
function sliceGuard(name, fix) {
|
|
18
|
+
const hosts = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
const check = (ctx, s) => {
|
|
20
|
+
const out = fix(s);
|
|
21
|
+
if (!out) return;
|
|
22
|
+
ctx.emit("DK_INVALID_STATE", { slice: name, received: out[1], fallback: out[2] });
|
|
23
|
+
ctx.setState(out[0]);
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
interceptor: defineInterceptor({
|
|
27
|
+
name: `${name}.state`,
|
|
28
|
+
on: ["state:dispatch", "table:setOptions"],
|
|
29
|
+
intercept(ictx, next) {
|
|
30
|
+
const ctx = hosts.get(ictx.table);
|
|
31
|
+
if (!ctx) return next();
|
|
32
|
+
const before = ictx.table.getState()[name];
|
|
33
|
+
const out = next();
|
|
34
|
+
const now = ctx.getState();
|
|
35
|
+
if (!Object.is(before, now)) check(ctx, now);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
/** Registers the table (call from `createApi`) and checks the slice it starts with. */
|
|
40
|
+
install(ctx) {
|
|
41
|
+
hosts.set(ctx.table, ctx);
|
|
42
|
+
check(ctx, ctx.getState());
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
sliceGuard,
|
|
48
|
+
sliceGuardDiagnostics
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=slice-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/feature/slice-guard.ts"],"sourcesContent":["import { defineDiagnostic, keepDiagnostics } from '../diagnostics/catalog'\nimport '../diagnostics/process-guard'\nimport { defineInterceptor } from '../intercept/define'\nimport { stateMessages } from '../table/state-codes'\nimport type { Table, TableState } from '../table/types'\nimport type { FeatureContext } from './types'\n\n/** Defined here: only the development guard reports it, so production bundles drop it. */\nexport const sliceGuardDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_INVALID_STATE',\n level: 'warn',\n where: (d) => ({ kind: 'option', path: `state.${d.slice}` }),\n },\n (process.env.NODE_ENV !== \"production\") && stateMessages.DK_INVALID_STATE,\n ),\n]\n\nkeepDiagnostics(sliceGuardDiagnostics)\n\n/** A replacement for a malformed slice: the valid slice, what was wrong, what was stored. */\nexport type SliceFix<S> = readonly [slice: S, received: string, fallback: string]\n\n/**\n * Replaces a malformed slice written to the store (`initialState`, `setState`, the controlled\n * `options.state`), like pagination's sanitize, with `DK_INVALID_STATE` (#89): restored state (URL,\n * storage) is untrusted. Development only: production reads a malformed slice as empty in the\n * feature's API and row model instead. `fix(s)` returns `undefined` for a valid slice. One guard per\n * feature instance; the instance may serve several tables.\n */\nexport function sliceGuard<S>(name: string, fix: (s: unknown) => SliceFix<S> | undefined) {\n const hosts = new WeakMap<object, FeatureContext<S>>()\n const check = (ctx: FeatureContext<S>, s: unknown) => {\n const out = fix(s)\n if (!out) return\n ctx.emit('DK_INVALID_STATE', { slice: name, received: out[1], fallback: out[2] })\n ctx.setState(out[0])\n }\n return {\n interceptor: defineInterceptor({\n name: `${name}.state`,\n on: ['state:dispatch', 'table:setOptions'],\n intercept(ictx, next) {\n const ctx = hosts.get(ictx.table as object)\n if (!ctx) return next()\n const before = (ictx.table as Table<unknown, TableState>).getState()[name]\n const out = next()\n const now = ctx.getState()\n if (!Object.is(before, now)) check(ctx, now)\n return out\n },\n }),\n /** Registers the table (call from `createApi`) and checks the slice it starts with. */\n install(ctx: FeatureContext<S>) {\n hosts.set(ctx.table, ctx)\n check(ctx, ctx.getState())\n },\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB,uBAAuB;AAClD,OAAO;AACP,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAKvB,IAAM,wBAAwB;AAAA,EACnC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,SAAS,EAAE,KAAK,GAAG;AAAA,IAC5D;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,cAAc;AAAA,EAC3D;AACF;AAEA,gBAAgB,qBAAqB;AAY9B,SAAS,WAAc,MAAc,KAA8C;AACxF,QAAM,QAAQ,oBAAI,QAAmC;AACrD,QAAM,QAAQ,CAAC,KAAwB,MAAe;AACpD,UAAM,MAAM,IAAI,CAAC;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,KAAK,oBAAoB,EAAE,OAAO,MAAM,UAAU,IAAI,CAAC,GAAG,UAAU,IAAI,CAAC,EAAE,CAAC;AAChF,QAAI,SAAS,IAAI,CAAC,CAAC;AAAA,EACrB;AACA,SAAO;AAAA,IACL,aAAa,kBAAkB;AAAA,MAC7B,MAAM,GAAG,IAAI;AAAA,MACb,IAAI,CAAC,kBAAkB,kBAAkB;AAAA,MACzC,UAAU,MAAM,MAAM;AACpB,cAAM,MAAM,MAAM,IAAI,KAAK,KAAe;AAC1C,YAAI,CAAC,IAAK,QAAO,KAAK;AACtB,cAAM,SAAU,KAAK,MAAqC,SAAS,EAAE,IAAI;AACzE,cAAM,MAAM,KAAK;AACjB,cAAM,MAAM,IAAI,SAAS;AACzB,YAAI,CAAC,OAAO,GAAG,QAAQ,GAAG,EAAG,OAAM,KAAK,GAAG;AAC3C,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA;AAAA,IAED,QAAQ,KAAwB;AAC9B,YAAM,IAAI,IAAI,OAAO,GAAG;AACxB,YAAM,KAAK,IAAI,SAAS,CAAC;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
|
|
@@ -28,6 +28,7 @@ module.exports = __toCommonJS(codes_exports);
|
|
|
28
28
|
var import_suggest = require("../../catalog/suggest.cjs");
|
|
29
29
|
var import_catalog = require("../../diagnostics/catalog.cjs");
|
|
30
30
|
var import_process_guard = require("../../diagnostics/process-guard.cjs");
|
|
31
|
+
var import_state_codes = require("../../table/state-codes.cjs");
|
|
31
32
|
var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
|
|
32
33
|
var rowList = (rows, count) => {
|
|
33
34
|
if (rows.length === 0) return "";
|
|
@@ -42,17 +43,6 @@ function convert(expected, received) {
|
|
|
42
43
|
return void 0;
|
|
43
44
|
}
|
|
44
45
|
var columnFiltersMessages = {
|
|
45
|
-
DK_STATE_UNKNOWN_KEY: {
|
|
46
|
-
problem: (d) => `state.${d.slice} has an entry for "${d.key}", which is not a column`,
|
|
47
|
-
impact: () => "the entry is ignored (the other entries still apply); it may come from an older URL or saved state, or the column was renamed or removed",
|
|
48
|
-
fix: (d) => {
|
|
49
|
-
const near = (0, import_suggest.suggest)(d.key, d.known);
|
|
50
|
-
return [
|
|
51
|
-
near.length > 0 ? `did you mean "${near[0]}"? (state.${d.slice}["${near[0]}"])` : `use one of the column ids: ${d.known.map((k) => `"${k}"`).join(", ")}`,
|
|
52
|
-
`drop "${d.key}" when parsing initialState / state (e.g. from the URL), or add the column`
|
|
53
|
-
];
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
46
|
DK_FILTER_TYPE_MISMATCH: {
|
|
57
47
|
problem: (d) => d.source === "filter" ? `the "${d.filter}" filter on column "${d.column}" has a value of type ${d.received}; it expects ${d.expected}` : `the "${d.filter}" filter on column "${d.column}" compares ${d.expected} values, but ${plural(d.count, "cell holds", "cells hold")} another type (first: ${d.received})${rowList(d.rows, d.count)}`,
|
|
58
48
|
impact: (d) => d.source === "filter" ? "the filter matches no rows until its value is fixed" : "those rows are treated as non-matching; the other rows are filtered normally",
|
|
@@ -98,14 +88,7 @@ var columnFiltersDiagnostics = [
|
|
|
98
88
|
},
|
|
99
89
|
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH
|
|
100
90
|
),
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
code: "DK_STATE_UNKNOWN_KEY",
|
|
104
|
-
level: "warn",
|
|
105
|
-
where: (d) => ({ kind: "option", path: `state.${d.slice}.${d.key}` })
|
|
106
|
-
},
|
|
107
|
-
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_STATE_UNKNOWN_KEY
|
|
108
|
-
)
|
|
91
|
+
...import_state_codes.stateDiagnostics
|
|
109
92
|
];
|
|
110
93
|
// Annotate the CommonJS export names for ESM import in node:
|
|
111
94
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { stateDiagnostics } from '../../table/state-codes'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n }\n}\n\n/** The filter kinds of §6.1. */\nexport const FILTER_KINDS = ['equals', 'contains', 'in', 'range', 'custom'] as const\n\nconst rowList = (rows: readonly number[], count: number) => {\n if (rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? ` (first: ${list}, …)` : ` (rows ${list})`\n}\n\nconst plural = (n: number, one: string, many: string) => (n === 1 ? `1 ${one}` : `${n} ${many}`)\n\n/** A conversion from `received` to `expected`, as code, when one is obvious. */\nfunction convert(expected: string, received: string): string | undefined {\n if (expected.includes('number') && received === 'string') return 'Number(x)'\n if (expected === 'string' && (received === 'number' || received === 'bigint')) return 'String(x)'\n if (expected === 'Date' && (received === 'string' || received === 'number')) return 'new Date(x)'\n return undefined\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\n// DK_STATE_UNKNOWN_KEY is shared with core (a `__proto__` key, #102): table/state-codes.ts.\nexport const columnFiltersMessages: DiagnosticMessages<'DK_FILTER_TYPE_MISMATCH'> = {\n DK_FILTER_TYPE_MISMATCH: {\n problem: (d) =>\n d.source === 'filter'\n ? `the \"${d.filter}\" filter on column \"${d.column}\" has a value of type ${d.received}; ` +\n `it expects ${d.expected}`\n : `the \"${d.filter}\" filter on column \"${d.column}\" compares ${d.expected} values, but ` +\n `${plural(d.count, 'cell holds', 'cells hold')} another type (first: ` +\n `${d.received})${rowList(d.rows, d.count)}`,\n impact: (d) =>\n d.source === 'filter'\n ? 'the filter matches no rows until its value is fixed'\n : 'those rows are treated as non-matching; the other rows are filtered normally',\n fix: (d) => {\n const at = `state.columnFilters[\"${d.column}\"]`\n if (d.source === 'filter') {\n if (!(FILTER_KINDS as readonly string[]).includes(d.filter)) {\n const near = suggest(d.filter, FILTER_KINDS)\n return [\n near.length > 0\n ? `did you mean type: '${near[0]}'? (${at})`\n : `use one of the filter kinds ${FILTER_KINDS.join(', ')} in ${at}`,\n ]\n }\n return [\n `give ${at} a value of type ${d.expected}: { type: '${d.filter}', value: … }`,\n 'remove the filter (setColumnFilter(id, undefined)) while there is no value',\n ]\n }\n const out: string[] = []\n const c = convert(d.expected, d.received)\n if (c) {\n out.push(\n `if the filter value is the wrong type, convert it before setting it: ${c.replace('x', 'input')}`,\n `if the data is the wrong type, normalise the column: ` +\n `{ id: '${d.column}', accessor: (row) => ${c.replace('x', 'row.<field>')} } ` +\n `(columns[${d.index}])`,\n )\n } else {\n out.push(\n `normalise the column so every cell is ${d.expected}: an accessor on columns[${d.index}]`,\n )\n }\n out.push(\n `or filter with a kind that fits ${d.received} cells, or a custom filter: ` +\n `{ type: 'custom', value, fn: (cell, value, row) => boolean }`,\n )\n return out\n },\n },\n}\n\n/** Codes owned by the `columnFilters` feature (§6.1, §8.3). */\nexport const columnFiltersDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_FILTER_TYPE_MISMATCH',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH,\n ),\n ...stateDiagnostics,\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,qBAA0D;AAC1D,2BAAO;AACP,yBAAiC;AA0B1B,IAAM,eAAe,CAAC,UAAU,YAAY,MAAM,SAAS,QAAQ;AAE1E,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,YAAY,IAAI,cAAS,UAAU,IAAI;AACtE;AAEA,IAAM,SAAS,CAAC,GAAW,KAAa,SAAkB,MAAM,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,IAAI,IAAI;AAG7F,SAAS,QAAQ,UAAkB,UAAsC;AACvE,MAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,SAAU,QAAO;AACjE,MAAI,aAAa,aAAa,aAAa,YAAY,aAAa,UAAW,QAAO;AACtF,MAAI,aAAa,WAAW,aAAa,YAAY,aAAa,UAAW,QAAO;AACpF,SAAO;AACT;AAIO,IAAM,wBAAuE;AAAA,EAClF,yBAAyB;AAAA,IACvB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,yBAAyB,EAAE,QAAQ,gBACpE,EAAE,QAAQ,KACxB,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,cAAc,EAAE,QAAQ,gBACpE,OAAO,EAAE,OAAO,cAAc,YAAY,CAAC,yBAC3C,EAAE,QAAQ,IAAI,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC/C,QAAQ,CAAC,MACP,EAAE,WAAW,WACT,wDACA;AAAA,IACN,KAAK,CAAC,MAAM;AACV,YAAM,KAAK,wBAAwB,EAAE,MAAM;AAC3C,UAAI,EAAE,WAAW,UAAU;AACzB,YAAI,CAAE,aAAmC,SAAS,EAAE,MAAM,GAAG;AAC3D,gBAAM,WAAO,wBAAQ,EAAE,QAAQ,YAAY;AAC3C,iBAAO;AAAA,YACL,KAAK,SAAS,IACV,uBAAuB,KAAK,CAAC,CAAC,OAAO,EAAE,MACvC,+BAA+B,aAAa,KAAK,IAAI,CAAC,OAAO,EAAE;AAAA,UACrE;AAAA,QACF;AACA,eAAO;AAAA,UACL,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE,MAAM;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAgB,CAAC;AACvB,YAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,QAAQ;AACxC,UAAI,GAAG;AACL,YAAI;AAAA,UACF,wEAAwE,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,UAC/F,+DACY,EAAE,MAAM,yBAAyB,EAAE,QAAQ,KAAK,aAAa,CAAC,eAC5D,EAAE,KAAK;AAAA,QACvB;AAAA,MACF,OAAO;AACL,YAAI;AAAA,UACF,yCAAyC,EAAE,QAAQ,4BAA4B,EAAE,KAAK;AAAA,QACxF;AAAA,MACF;AACA,UAAI;AAAA,QACF,mCAAmC,EAAE,QAAQ;AAAA,MAE/C;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGO,IAAM,2BAA2B;AAAA,MACtC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,sBAAsB;AAAA,EACnE;AAAA,EACA,GAAG;AACL;","names":[]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { suggest } from "../../catalog/suggest.js";
|
|
3
3
|
import { defineDiagnostic } from "../../diagnostics/catalog.js";
|
|
4
4
|
import "../../diagnostics/process-guard.js";
|
|
5
|
+
import { stateDiagnostics } from "../../table/state-codes.js";
|
|
5
6
|
var FILTER_KINDS = ["equals", "contains", "in", "range", "custom"];
|
|
6
7
|
var rowList = (rows, count) => {
|
|
7
8
|
if (rows.length === 0) return "";
|
|
@@ -16,17 +17,6 @@ function convert(expected, received) {
|
|
|
16
17
|
return void 0;
|
|
17
18
|
}
|
|
18
19
|
var columnFiltersMessages = {
|
|
19
|
-
DK_STATE_UNKNOWN_KEY: {
|
|
20
|
-
problem: (d) => `state.${d.slice} has an entry for "${d.key}", which is not a column`,
|
|
21
|
-
impact: () => "the entry is ignored (the other entries still apply); it may come from an older URL or saved state, or the column was renamed or removed",
|
|
22
|
-
fix: (d) => {
|
|
23
|
-
const near = suggest(d.key, d.known);
|
|
24
|
-
return [
|
|
25
|
-
near.length > 0 ? `did you mean "${near[0]}"? (state.${d.slice}["${near[0]}"])` : `use one of the column ids: ${d.known.map((k) => `"${k}"`).join(", ")}`,
|
|
26
|
-
`drop "${d.key}" when parsing initialState / state (e.g. from the URL), or add the column`
|
|
27
|
-
];
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
20
|
DK_FILTER_TYPE_MISMATCH: {
|
|
31
21
|
problem: (d) => d.source === "filter" ? `the "${d.filter}" filter on column "${d.column}" has a value of type ${d.received}; it expects ${d.expected}` : `the "${d.filter}" filter on column "${d.column}" compares ${d.expected} values, but ${plural(d.count, "cell holds", "cells hold")} another type (first: ${d.received})${rowList(d.rows, d.count)}`,
|
|
32
22
|
impact: (d) => d.source === "filter" ? "the filter matches no rows until its value is fixed" : "those rows are treated as non-matching; the other rows are filtered normally",
|
|
@@ -72,14 +62,7 @@ var columnFiltersDiagnostics = [
|
|
|
72
62
|
},
|
|
73
63
|
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH
|
|
74
64
|
),
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
code: "DK_STATE_UNKNOWN_KEY",
|
|
78
|
-
level: "warn",
|
|
79
|
-
where: (d) => ({ kind: "option", path: `state.${d.slice}.${d.key}` })
|
|
80
|
-
},
|
|
81
|
-
process.env.NODE_ENV !== "production" && columnFiltersMessages.DK_STATE_UNKNOWN_KEY
|
|
82
|
-
)
|
|
65
|
+
...stateDiagnostics
|
|
83
66
|
];
|
|
84
67
|
export {
|
|
85
68
|
FILTER_KINDS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n
|
|
1
|
+
{"version":3,"sources":["../../../src/features/column-filters/codes.ts"],"sourcesContent":["import { suggest } from '../../catalog/suggest'\nimport { type DiagnosticMessages, defineDiagnostic } from '../../diagnostics/catalog'\nimport '../../diagnostics/process-guard'\nimport { stateDiagnostics } from '../../table/state-codes'\n\ndeclare module '../../diagnostics/catalog' {\n interface DiagnosticCatalog {\n DK_FILTER_TYPE_MISMATCH: {\n /** Column id (`key` or `id`). */\n column: string\n /** Position in `options.columns`. */\n index: number\n /** The filter kind (`type`) as given, e.g. `contains`. */\n filter: string\n /** `cell`: cell values of the wrong type; `filter`: the filter value itself is malformed. */\n source: 'cell' | 'filter'\n /** What the filter accepts, e.g. `string`, `number | bigint`, `Date`, `array`. */\n expected: string\n /** The type of the first offending value (`string`, `number`, `Date`, `array`, …). */\n received: string\n /** Cells of the wrong type (0 for `source: 'filter'`). */\n count: number\n /** A sample of their row indices (first few, in order). */\n rows: readonly number[]\n }\n }\n}\n\n/** The filter kinds of §6.1. */\nexport const FILTER_KINDS = ['equals', 'contains', 'in', 'range', 'custom'] as const\n\nconst rowList = (rows: readonly number[], count: number) => {\n if (rows.length === 0) return ''\n const list = rows.map((r) => `#${r}`).join(', ')\n return count > rows.length ? ` (first: ${list}, …)` : ` (rows ${list})`\n}\n\nconst plural = (n: number, one: string, many: string) => (n === 1 ? `1 ${one}` : `${n} ${many}`)\n\n/** A conversion from `received` to `expected`, as code, when one is obvious. */\nfunction convert(expected: string, received: string): string | undefined {\n if (expected.includes('number') && received === 'string') return 'Number(x)'\n if (expected === 'string' && (received === 'number' || received === 'bigint')) return 'String(x)'\n if (expected === 'Date' && (received === 'string' || received === 'number')) return 'new Date(x)'\n return undefined\n}\n\n/** Message text (§8.2): development builds, or production via `@datakable/core/messages`. */\n// DK_STATE_UNKNOWN_KEY is shared with core (a `__proto__` key, #102): table/state-codes.ts.\nexport const columnFiltersMessages: DiagnosticMessages<'DK_FILTER_TYPE_MISMATCH'> = {\n DK_FILTER_TYPE_MISMATCH: {\n problem: (d) =>\n d.source === 'filter'\n ? `the \"${d.filter}\" filter on column \"${d.column}\" has a value of type ${d.received}; ` +\n `it expects ${d.expected}`\n : `the \"${d.filter}\" filter on column \"${d.column}\" compares ${d.expected} values, but ` +\n `${plural(d.count, 'cell holds', 'cells hold')} another type (first: ` +\n `${d.received})${rowList(d.rows, d.count)}`,\n impact: (d) =>\n d.source === 'filter'\n ? 'the filter matches no rows until its value is fixed'\n : 'those rows are treated as non-matching; the other rows are filtered normally',\n fix: (d) => {\n const at = `state.columnFilters[\"${d.column}\"]`\n if (d.source === 'filter') {\n if (!(FILTER_KINDS as readonly string[]).includes(d.filter)) {\n const near = suggest(d.filter, FILTER_KINDS)\n return [\n near.length > 0\n ? `did you mean type: '${near[0]}'? (${at})`\n : `use one of the filter kinds ${FILTER_KINDS.join(', ')} in ${at}`,\n ]\n }\n return [\n `give ${at} a value of type ${d.expected}: { type: '${d.filter}', value: … }`,\n 'remove the filter (setColumnFilter(id, undefined)) while there is no value',\n ]\n }\n const out: string[] = []\n const c = convert(d.expected, d.received)\n if (c) {\n out.push(\n `if the filter value is the wrong type, convert it before setting it: ${c.replace('x', 'input')}`,\n `if the data is the wrong type, normalise the column: ` +\n `{ id: '${d.column}', accessor: (row) => ${c.replace('x', 'row.<field>')} } ` +\n `(columns[${d.index}])`,\n )\n } else {\n out.push(\n `normalise the column so every cell is ${d.expected}: an accessor on columns[${d.index}]`,\n )\n }\n out.push(\n `or filter with a kind that fits ${d.received} cells, or a custom filter: ` +\n `{ type: 'custom', value, fn: (cell, value, row) => boolean }`,\n )\n return out\n },\n },\n}\n\n/** Codes owned by the `columnFilters` feature (§6.1, §8.3). */\nexport const columnFiltersDiagnostics = [\n defineDiagnostic(\n {\n code: 'DK_FILTER_TYPE_MISMATCH',\n level: 'warn',\n where: (d) => ({ kind: 'column', index: d.index, id: d.column }),\n },\n (process.env.NODE_ENV !== \"production\") && columnFiltersMessages.DK_FILTER_TYPE_MISMATCH,\n ),\n ...stateDiagnostics,\n]\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAkC,wBAAwB;AAC1D,OAAO;AACP,SAAS,wBAAwB;AA0B1B,IAAM,eAAe,CAAC,UAAU,YAAY,MAAM,SAAS,QAAQ;AAE1E,IAAM,UAAU,CAAC,MAAyB,UAAkB;AAC1D,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC/C,SAAO,QAAQ,KAAK,SAAS,YAAY,IAAI,cAAS,UAAU,IAAI;AACtE;AAEA,IAAM,SAAS,CAAC,GAAW,KAAa,SAAkB,MAAM,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,IAAI,IAAI;AAG7F,SAAS,QAAQ,UAAkB,UAAsC;AACvE,MAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,SAAU,QAAO;AACjE,MAAI,aAAa,aAAa,aAAa,YAAY,aAAa,UAAW,QAAO;AACtF,MAAI,aAAa,WAAW,aAAa,YAAY,aAAa,UAAW,QAAO;AACpF,SAAO;AACT;AAIO,IAAM,wBAAuE;AAAA,EAClF,yBAAyB;AAAA,IACvB,SAAS,CAAC,MACR,EAAE,WAAW,WACT,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,yBAAyB,EAAE,QAAQ,gBACpE,EAAE,QAAQ,KACxB,QAAQ,EAAE,MAAM,uBAAuB,EAAE,MAAM,cAAc,EAAE,QAAQ,gBACpE,OAAO,EAAE,OAAO,cAAc,YAAY,CAAC,yBAC3C,EAAE,QAAQ,IAAI,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC/C,QAAQ,CAAC,MACP,EAAE,WAAW,WACT,wDACA;AAAA,IACN,KAAK,CAAC,MAAM;AACV,YAAM,KAAK,wBAAwB,EAAE,MAAM;AAC3C,UAAI,EAAE,WAAW,UAAU;AACzB,YAAI,CAAE,aAAmC,SAAS,EAAE,MAAM,GAAG;AAC3D,gBAAM,OAAO,QAAQ,EAAE,QAAQ,YAAY;AAC3C,iBAAO;AAAA,YACL,KAAK,SAAS,IACV,uBAAuB,KAAK,CAAC,CAAC,OAAO,EAAE,MACvC,+BAA+B,aAAa,KAAK,IAAI,CAAC,OAAO,EAAE;AAAA,UACrE;AAAA,QACF;AACA,eAAO;AAAA,UACL,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE,MAAM;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAgB,CAAC;AACvB,YAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,QAAQ;AACxC,UAAI,GAAG;AACL,YAAI;AAAA,UACF,wEAAwE,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,UAC/F,+DACY,EAAE,MAAM,yBAAyB,EAAE,QAAQ,KAAK,aAAa,CAAC,eAC5D,EAAE,KAAK;AAAA,QACvB;AAAA,MACF,OAAO;AACL,YAAI;AAAA,UACF,yCAAyC,EAAE,QAAQ,4BAA4B,EAAE,KAAK;AAAA,QACxF;AAAA,MACF;AACA,UAAI;AAAA,QACF,mCAAmC,EAAE,QAAQ;AAAA,MAE/C;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGO,IAAM,2BAA2B;AAAA,EACtC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO,CAAC,OAAO,EAAE,MAAM,UAAU,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AAAA,IAChE;AAAA,IACC,QAAQ,IAAI,aAAa,gBAAiB,sBAAsB;AAAA,EACnE;AAAA,EACA,GAAG;AACL;","names":[]}
|