@pythia-software/query-table-core 0.1.0 → 0.2.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/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -71,10 +71,11 @@ type RowId = string | number;
|
|
|
71
71
|
* Empty `select`/`orderBy`/`where` mean "defer to the schema", which also keeps
|
|
72
72
|
* encoded `?q=` tokens short (defaults are omitted). */
|
|
73
73
|
declare const EMPTY_QUERY: QueryState;
|
|
74
|
-
/**
|
|
75
|
-
* storage, an imperative setQuery call, or a server projection).
|
|
76
|
-
*
|
|
77
|
-
|
|
74
|
+
/** Structural limits applied whenever query state crosses a trust boundary
|
|
75
|
+
* (URL, storage, an imperative setQuery call, or a server projection). Row
|
|
76
|
+
* limits may use the full safe-integer range; consumers and their backends own
|
|
77
|
+
* any smaller operational cap appropriate for their dataset. */
|
|
78
|
+
declare const MAX_QUERY_LIMIT: number;
|
|
78
79
|
declare const MAX_QUERY_OFFSET = 1000000;
|
|
79
80
|
declare const MAX_SELECT_COLUMNS = 200;
|
|
80
81
|
declare const MAX_WHERE_CLAUSES = 100;
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/query.ts","../src/schema.ts","../src/ops.ts","../src/agg.ts","../src/encode.ts","../src/apply.ts","../src/adapters.ts"],"sourcesContent":["// query.ts — the canonical query state shape.\n//\n// We lean into SQL vocabulary at every layer (per design feedback): a QueryState\n// reads top-to-bottom like the statement it compiles to —\n//\n// SELECT <select> WHERE <where> ORDER BY <orderBy> LIMIT <limit> OFFSET <offset>\n//\n// Both \"what data\" (where/orderBy/limit/offset) and \"how viewed\" (select +\n// per-column width) can be serialized together when a consumer explicitly opts\n// into URL synchronization.\n\n/** All filter operators across both source projects, unioned. Every value can\n * be NULL, so `is_null`/`is_not_null` are valid for every field type. */\nexport type FilterOp =\n // comparison — number / datetime (and =,!= for any scalar)\n | \"=\"\n | \"!=\"\n | \">\"\n | \">=\"\n | \"<\"\n | \"<=\"\n // text matching\n | \"contains\"\n | \"starts_with\"\n | \"ends_with\"\n // array (textarray) containment\n | \"includes\"\n // nullity — valid for EVERY type; for arrays, \"empty\" vs \"non-empty\"\n | \"is_null\"\n | \"is_not_null\";\n\n/** A single AND-combined filter. `value` is always the raw string the UI\n * captured; coercion to number/bool/date happens at apply/compile time based on\n * the field's declared type (never on the value's runtime shape). Unused for\n * the nullary ops (`is_null`/`is_not_null`). */\nexport interface WhereClause {\n field: string; // FieldDef.name\n op: FilterOp;\n value: string;\n}\n\n/** One ORDER BY term. Array order in `QueryState.orderBy` is the sort priority. */\nexport interface OrderByClause {\n field: string; // FieldDef.name (server may sort a different expr via FieldDef.sort.field)\n dir: \"asc\" | \"desc\";\n /** NULL placement. Omitted = \"last\" (the historical default both projects used,\n * preserved so old `?q=` links round-trip identically). */\n nulls?: \"first\" | \"last\";\n}\n\n/** One column in the SELECT list, plus the view state that travels with it.\n * Order in `QueryState.select` is the left-to-right display order. */\nexport interface SelectColumn {\n field: string; // FieldDef.name\n /** Pixel width override. Omitted = FieldDef.select.width, else the type default. */\n width?: number;\n}\n\n/** A single aggregate operation. `count` is the only op that needs no measure\n * column (it counts rows); the rest reduce one column's values. The server-side\n * matrix (backends/go aggOpAllowed + the TS AGG_OPS_BY_TYPE) decides which ops a\n * field's type permits. */\nexport type AggOp = \"count\" | \"count_distinct\" | \"sum\" | \"avg\" | \"min\" | \"max\";\n\n/** One optional \"metric\" pinned above the table: a single aggregate over a single\n * measure column, optionally broken down by one or more group columns.\n *\n * Scope is deliberately the *whole filtered set* — the same WHERE as the table,\n * but NOT its ORDER BY / LIMIT / OFFSET. It is always evaluated on the server\n * (a real GROUP BY), so it reflects every matching row, not just the visible\n * page. Lives inside QueryState so it round-trips through `?q=`, saved queries,\n * and undo/redo for free — a saved query is a saved dashboard. */\nexport interface AggregationClause {\n /** Stable id; keys the metric panel and survives a `?q=` round-trip. */\n id: string;\n op: AggOp;\n /** Measure column (FieldDef.name). Omit only for `count` (⇒ COUNT(*)). */\n field?: string;\n /** Group-by columns, in axis order. `[]` = a single grand-total value.\n * One ⇒ a list/bar breakdown; two ⇒ an x/y pivot; three+ ⇒ a flat table. */\n groupBy: string[];\n /** Panel header override; defaults to a derived label like \"avg total\". */\n label?: string;\n}\n\nexport interface QueryState {\n /** Ordered SELECT list + per-column widths. Empty = the schema's default columns. */\n select: SelectColumn[];\n /** AND-combined filters. */\n where: WhereClause[];\n /** Multi-sort terms in priority order. Empty = the schema's default sort. */\n orderBy: OrderByClause[];\n /** Page size. */\n limit: number;\n /** Page offset (rows). */\n offset: number;\n /** Optional aggregate metrics shown above the table. Omitted/empty = none.\n * Each runs as its own server GROUP BY over the WHERE-filtered set (no paging). */\n aggregations?: AggregationClause[];\n}\n\n/** A row's stable identity, used for selection and per-row refresh. */\nexport type RowId = string | number;\n\n/** The query a fresh table starts from before schema defaults are layered on.\n * Empty `select`/`orderBy`/`where` mean \"defer to the schema\", which also keeps\n * encoded `?q=` tokens short (defaults are omitted). */\nexport const EMPTY_QUERY: QueryState = {\n select: [],\n where: [],\n orderBy: [],\n limit: 100,\n offset: 0,\n};\n\n/** Resource limits applied whenever query state crosses a trust boundary (URL,\n * storage, an imperative setQuery call, or a server projection). They keep a\n * malformed bookmark from becoming an unexpectedly expensive request. */\nexport const MAX_QUERY_LIMIT = 1_000;\nexport const MAX_QUERY_OFFSET = 1_000_000;\nexport const MAX_SELECT_COLUMNS = 200;\nexport const MAX_WHERE_CLAUSES = 100;\nexport const MAX_ORDER_BY_TERMS = 20;\nexport const MAX_AGGREGATIONS = 20;\nexport const MAX_GROUP_BY_FIELDS = 20;\nexport const MAX_QUERY_TOKEN_LENGTH = 2 * 1024 * 1024;\n\nconst MAX_FIELD_NAME_LENGTH = 256;\nconst MAX_FILTER_VALUE_LENGTH = 10_000;\nconst MAX_LABEL_LENGTH = 1_000;\nconst MIN_COLUMN_WIDTH = 24;\nconst MAX_COLUMN_WIDTH = 2_000;\n\nconst FILTER_OPS: ReadonlySet<string> = new Set([\n \"=\",\n \"!=\",\n \">\",\n \">=\",\n \"<\",\n \"<=\",\n \"contains\",\n \"starts_with\",\n \"ends_with\",\n \"includes\",\n \"is_null\",\n \"is_not_null\",\n]);\n\nconst AGG_OPS: ReadonlySet<string> = new Set([\"count\", \"count_distinct\", \"sum\", \"avg\", \"min\", \"max\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | null {\n return typeof value === \"string\" && value.length > 0 && value.length <= maxLength ? value : null;\n}\n\nfunction boundedInteger(value: unknown, fallback: number, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\n return Math.max(min, Math.min(max, Math.round(value)));\n}\n\n/** Convert unknown input into a bounded, structurally valid QueryState.\n * Invalid members are dropped; invalid scalar paging values use the supplied\n * fallback. This function is intentionally schema-agnostic—field allowlisting\n * still happens in toServerQuery and in the backend compiler. */\nexport function normalizeQueryState(input: unknown, fallback: QueryState = EMPTY_QUERY): QueryState {\n const raw = isRecord(input) ? input : {};\n\n const select: SelectColumn[] = [];\n if (Array.isArray(raw.select)) {\n for (const item of raw.select.slice(0, MAX_SELECT_COLUMNS)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n if (!field) continue;\n const column: SelectColumn = { field };\n if (typeof item.width === \"number\" && Number.isFinite(item.width)) {\n column.width = boundedInteger(item.width, MIN_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);\n }\n select.push(column);\n }\n } else {\n select.push(...fallback.select.map((column) => ({ ...column })));\n }\n\n const where: WhereClause[] = [];\n if (Array.isArray(raw.where)) {\n for (const item of raw.where.slice(0, MAX_WHERE_CLAUSES)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n const value = typeof item.value === \"string\" && item.value.length <= MAX_FILTER_VALUE_LENGTH ? item.value : null;\n if (!field || typeof item.op !== \"string\" || !FILTER_OPS.has(item.op) || value == null) continue;\n where.push({ field, op: item.op as FilterOp, value });\n }\n } else {\n where.push(...fallback.where.map((clause) => ({ ...clause })));\n }\n\n const orderBy: OrderByClause[] = [];\n if (Array.isArray(raw.orderBy)) {\n for (const item of raw.orderBy.slice(0, MAX_ORDER_BY_TERMS)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n if (!field || (item.dir !== \"asc\" && item.dir !== \"desc\")) continue;\n const term: OrderByClause = { field, dir: item.dir };\n if (item.nulls === \"first\" || item.nulls === \"last\") term.nulls = item.nulls;\n orderBy.push(term);\n }\n } else {\n orderBy.push(...fallback.orderBy.map((term) => ({ ...term })));\n }\n\n const out: QueryState = {\n select,\n where,\n orderBy,\n limit: boundedInteger(raw.limit, fallback.limit, 1, MAX_QUERY_LIMIT),\n offset: boundedInteger(raw.offset, fallback.offset, 0, MAX_QUERY_OFFSET),\n };\n\n if (Array.isArray(raw.aggregations)) {\n const aggregations: AggregationClause[] = [];\n for (const item of raw.aggregations.slice(0, MAX_AGGREGATIONS)) {\n if (!isRecord(item)) continue;\n const id = boundedString(item.id, MAX_FIELD_NAME_LENGTH);\n if (!id || typeof item.op !== \"string\" || !AGG_OPS.has(item.op) || !Array.isArray(item.groupBy)) continue;\n const groupBy = item.groupBy\n .slice(0, MAX_GROUP_BY_FIELDS)\n .map((field) => boundedString(field, MAX_FIELD_NAME_LENGTH))\n .filter((field): field is string => field != null);\n const aggregation: AggregationClause = { id, op: item.op as AggOp, groupBy };\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n const label = boundedString(item.label, MAX_LABEL_LENGTH);\n if (field) aggregation.field = field;\n if (label) aggregation.label = label;\n aggregations.push(aggregation);\n }\n if (aggregations.length > 0) out.aggregations = aggregations;\n } else if (fallback.aggregations?.length) {\n out.aggregations = fallback.aggregations.map((aggregation) => ({ ...aggregation, groupBy: [...aggregation.groupBy] }));\n }\n\n return out;\n}\n\n/** Structural equality on two queries — used to decide whether a `?q=` write or a\n * refetch is actually needed (avoids history spam / redundant fetches). */\nexport function queriesEqual(a: QueryState, b: QueryState): boolean {\n if (a === b) return true;\n if (a.limit !== b.limit || a.offset !== b.offset) return false;\n if (!sameArray(a.where, b.where)) return false;\n if (!sameArray(a.orderBy, b.orderBy)) return false;\n if (!sameArray(a.select, b.select)) return false;\n if (!sameArray(a.aggregations ?? [], b.aggregations ?? [])) return false;\n return true;\n}\n\nfunction sameArray<T>(a: readonly T[], b: readonly T[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n // Members are flat records of primitives — JSON compare is correct and cheap here.\n if (JSON.stringify(a[i]) !== JSON.stringify(b[i])) return false;\n }\n return true;\n}\n","// schema.ts — the field schema: the contract that binds frontend and backend.\n//\n// A `FieldSchema` is the runtime, frontend-facing projection of a JSON schema\n// document (schema/query-table.schema.json). The same document is projected on\n// the backend into a Go `Schema` of SQL bindings. Codegen keeps the two in sync.\n//\n// `FieldDef` is organized to make two things obvious (per design feedback):\n//\n// 1. WHAT KIND of field it is — the `source` discriminated union. A `backend`\n// field is a column the server returns and can filter/sort; a `derived`\n// field is computed on the client (or render-only) and is never pushed to\n// the server. Each kind uses its own property subset, so you can't, say,\n// mark a client-only column as server-filterable.\n//\n// 2. CONFIG vs CODE. Everything except `source.accessor` and `render` is\n// declarative, serializable config grouped by capability (`filter`, `sort`,\n// `select`). The only code-bearing members are the derived `accessor` and\n// the `render` function — kept apart from the config so the two don't mix.\n\nimport type { AggOp, FilterOp, OrderByClause, SelectColumn } from \"./query\";\n\nexport type FieldType =\n | \"text\"\n | \"number\"\n | \"enum\"\n | \"datetime\"\n | \"bool\"\n | \"textarray\"; // array-of-text; supports `includes`, and null-as-empty semantics\n\nexport type Align = \"left\" | \"right\" | \"center\";\n\n/** A cell renderer is resolved by name against a `RenderRegistry` (in\n * @pythia-software/query-table-ui) or supplied inline. The type lives in core so FieldDef can\n * reference it, but core never imports React — `unknown` stands in for the node\n * and @pythia-software/query-table-ui narrows it to `React.ReactNode`. */\nexport type CellRenderer<Row = any, V = unknown> = (ctx: {\n value: V;\n row: Row;\n field: FieldDef<Row, V>;\n}) => unknown;\n\n// ---- source: WHAT KIND of field (discriminated union) ---------------------\n\n/** A column the backend returns and can filter/sort. Its SQL expression lives in\n * the JSON schema doc's `bindings` (and the Go Schema), never on the frontend. */\nexport interface BackendField {\n kind: \"backend\";\n /** Dotted path into the API row when it differs from `name` (e.g. \"enqueued_at.Time\"). */\n path?: string;\n /** Computed SQL with no backing column (e.g. is_starred). Documentation hint\n * surfaced to the picker; the actual expression is server-side. */\n synthetic?: boolean;\n}\n\n/** A value computed on the client, or a purely render-driven column. Never\n * pushed to the server; client-side filtering/sorting (if any) reads `accessor`. */\nexport interface DerivedField<Row = any, V = unknown> {\n kind: \"derived\";\n /** Compute the value from the row for client filter/sort. Omit for render-only\n * columns (the renderer reads the whole row from CellContext instead). */\n accessor?: (row: Row) => V;\n}\n\nexport type FieldSource<Row = any, V = unknown> = BackendField | DerivedField<Row, V>;\n\n// ---- capability config (declarative; serializable; shared with backend) ----\n\n/** How filter-value candidates are offered. Autocomplete is the DEFAULT for\n * every field (design feedback): the value input is a combobox backed by\n * Transport.fetchDistinctValues, which takes a search string so large domains\n * refine per keystroke on the backend. `static` is for closed domains you don't\n * want a round-trip for; `freeform` opts out of suggestions entirely. */\nexport type FilterValues =\n | { source: \"autocomplete\" }\n | { source: \"static\"; options: string[] }\n | { source: \"freeform\" };\n\nexport interface FilterConfig {\n /** Whether this field is filterable. Default: backend ⇒ true, derived ⇒ false. */\n enabled?: boolean;\n /** Evaluate on the backend (predicate pushdown)? Default: backend ⇒ true.\n * false ⇒ the clause is stripped from the server request and applied locally\n * by applyQuery (the \"serverFilter:false\" derived/JSONB-less columns). */\n pushdown?: boolean;\n /** Override the default operator set for the field's type. */\n ops?: FilterOp[];\n /** Value-suggestion strategy. Default { source: \"autocomplete\" }. */\n values?: FilterValues;\n}\n\nexport interface SortConfig {\n /** Whether this field is sortable. Default: backend ⇒ true, derived ⇒ false. */\n enabled?: boolean;\n /** Server field to ORDER BY when it differs from the display field\n * (e.g. a \"★\" column whose server sort key is `is_starred`). */\n field?: string;\n}\n\nexport interface SelectConfig {\n /** Can this appear as a visible column? Default true. false ⇒ filter-only field. */\n enabled?: boolean;\n /** Shown by default when QueryState.select is empty. */\n default?: boolean;\n /** Default column width (px); a per-column override rides in QueryState.select. */\n width?: number;\n align?: Align;\n}\n\nexport interface AggregateConfig {\n /** Can this field be a metric's MEASURE (sum/avg/min/max/count_distinct)?\n * Default: backend ⇒ true, derived ⇒ false. */\n measure?: boolean;\n /** Can this field be a GROUP BY key? Default: enum/text/bool ⇒ true. */\n groupable?: boolean;\n /** Override the default aggregate-op set for the field's type. */\n ops?: AggOp[];\n}\n\n// ---- the field ------------------------------------------------------------\n\nexport interface FieldDef<Row = any, V = unknown> {\n // identity\n name: string; // canonical id; matches the backend binding key + Where/OrderBy `field`\n label: string;\n type: FieldType;\n\n // WHAT KIND of field (value origin + server capability)\n source: FieldSource<Row, V>;\n\n // capabilities (declarative config; omit for sensible per-source defaults)\n filter?: FilterConfig;\n sort?: SortConfig;\n select?: SelectConfig;\n aggregate?: AggregateConfig;\n\n // discovery (picker UX, declarative)\n group?: string; // grouping bucket\n alias?: string; // single alternative search term\n aliases?: string[]; // extra search terms\n\n // presentation (the only code-bearing field): registry key or inline fn\n render?: string | CellRenderer<Row, V>;\n}\n\n/** The frontend's loaded schema for one dataset. */\nexport interface FieldSchema<Row = any> {\n /** Dataset id, also the StorageAdapter namespace for saved queries. */\n name: string;\n /** Field that yields a row's stable id for selection / per-row refresh. */\n idField: string;\n fields: FieldDef<Row>[];\n /** Sort applied when QueryState.orderBy is empty. */\n defaultSort?: OrderByClause[];\n /** Columns shown when QueryState.select is empty. */\n defaultSelect?: SelectColumn[];\n /** Page size for a fresh query. */\n defaultLimit?: number;\n}\n\n// ---- derived capability helpers (the per-source defaults, in one place) ----\n\nexport function isFilterable(f: FieldDef): boolean {\n return f.filter?.enabled ?? f.source.kind === \"backend\";\n}\nexport function isPushdownFilter(f: FieldDef): boolean {\n return isFilterable(f) && (f.filter?.pushdown ?? f.source.kind === \"backend\");\n}\nexport function isSortable(f: FieldDef): boolean {\n return f.sort?.enabled ?? f.source.kind === \"backend\";\n}\nexport function isSelectable(f: FieldDef): boolean {\n return f.select?.enabled ?? true;\n}\nexport function filterValues(f: FieldDef): FilterValues {\n return f.filter?.values ?? { source: \"autocomplete\" };\n}\n\n// ---- schema operations ----------------------------------------------------\n\n/** Index fields by name for O(1) lookup. */\nexport function indexFields<Row>(schema: FieldSchema<Row>): Map<string, FieldDef<Row>> {\n return new Map(schema.fields.map((f) => [f.name, f]));\n}\n\n/** Resolve the ordered, selectable FieldDefs for a query, falling back to schema\n * defaults, then to every `select.default` field. Unknown/duplicate/unselectable\n * names in `select` are dropped. */\nexport function selectedFields<Row>(\n schema: FieldSchema<Row>,\n q: { select: SelectColumn[] },\n): FieldDef<Row>[] {\n const order =\n q.select.length > 0\n ? q.select.map((c) => c.field)\n : (schema.defaultSelect ?? schema.fields.filter((f) => f.select?.default).map((f) => ({ field: f.name }))).map(\n (c) => c.field,\n );\n const seen = new Set<string>();\n const out: FieldDef<Row>[] = [];\n for (const name of order) {\n if (seen.has(name)) continue;\n const f = resolveField(schema, name);\n if (!f || !isSelectable(f)) continue;\n seen.add(name);\n seen.add(f.name);\n out.push(f);\n }\n return out;\n}\n\n/** Resolve a field token to a concrete field by canonical name, case-insensitive\n * name match, or alias match. */\nexport function resolveField<Row>(schema: FieldSchema<Row>, field: string): FieldDef<Row> | undefined {\n const token = field.trim();\n if (!token) return undefined;\n\n const byName = indexFields(schema);\n const byExactName = byName.get(token);\n if (byExactName) return byExactName;\n\n const needle = token.toLowerCase();\n for (const f of schema.fields) {\n if (f.name.toLowerCase() === needle) return f;\n if (aliasMatches(f, needle)) return f;\n }\n return undefined;\n}\n\n/** Return the canonical name for a query token, or undefined when unresolved. */\nexport function resolveFieldName<Row>(schema: FieldSchema<Row>, field: string): string | undefined {\n return resolveField(schema, field)?.name;\n}\n\n/** All configured aliases for a field (deduped, preserving display shape). */\nexport function aliasTermsFor<Row>(field: FieldDef<Row>): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n\n const add = (raw: unknown) => {\n if (typeof raw !== \"string\") return;\n const value = raw.trim();\n if (!value) return;\n const key = value.toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n out.push(value);\n };\n\n add(field.alias);\n if (field.aliases) {\n for (const alias of field.aliases) add(alias);\n }\n\n return out;\n}\n\nfunction aliasMatches(field: FieldDef, needle: string): boolean {\n for (const alias of aliasTermsFor(field)) {\n if (alias.toLowerCase() === needle) return true;\n }\n return false;\n}\n\n/** Read a field's value from a row: derived `accessor` if present, else the\n * backend `path` (dotted) / `name`. Returns null when any path segment is missing. */\nexport function readFieldValue<Row, V = unknown>(field: FieldDef<Row, V>, row: Row): V | null {\n if (row == null) return null;\n if (field.source.kind === \"derived\" && field.source.accessor) {\n return field.source.accessor(row);\n }\n const path = (field.source.kind === \"backend\" && field.source.path) || field.name;\n const r = row as Record<string, unknown>;\n if (path in r) return (r[path] ?? null) as V | null;\n let cur: unknown = row;\n for (const part of path.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") return null;\n cur = (cur as Record<string, unknown>)[part];\n }\n return (cur ?? null) as V | null;\n}\n\n/** Parse + validate a raw JSON schema document into a FieldSchema. The doc is the\n * same one the Go backend loads; here we drop backend `bindings` (the frontend\n * never sees SQL) and leave `render` as a string key the consumer maps to a\n * renderer at runtime. Throws with a readable message on a malformed document. */\nexport function loadSchema<Row = any>(doc: unknown): FieldSchema<Row> {\n const d = doc as Record<string, any>;\n if (!d || typeof d !== \"object\") throw new Error(\"schema: document is not an object\");\n if (typeof d.name !== \"string\") throw new Error(\"schema: missing `name`\");\n if (typeof d.idField !== \"string\") throw new Error(\"schema: missing `idField`\");\n if (!Array.isArray(d.fields) || d.fields.length === 0) throw new Error(\"schema: `fields` must be a non-empty array\");\n\n const fields: FieldDef<Row>[] = d.fields.map((raw: any, i: number) => projectField<Row>(raw, i));\n return {\n name: d.name,\n idField: d.idField,\n fields,\n defaultSort: d.defaultSort,\n defaultSelect: d.defaultSelect,\n defaultLimit: d.defaultLimit,\n };\n}\n\nfunction projectField<Row>(raw: any, i: number): FieldDef<Row> {\n if (!raw || typeof raw.name !== \"string\") throw new Error(`schema: field[${i}] missing \\`name\\``);\n if (typeof raw.label !== \"string\") throw new Error(`schema: field ${raw.name} missing \\`label\\``);\n if (typeof raw.type !== \"string\") throw new Error(`schema: field ${raw.name} missing \\`type\\``);\n\n // `source` accepts a string shorthand (\"backend\"|\"derived\") or an object.\n const rs = raw.source ?? (raw.bindings ? \"backend\" : \"derived\");\n let source: FieldSource<Row>;\n if (typeof rs === \"string\") {\n source = rs === \"derived\" ? { kind: \"derived\" } : { kind: \"backend\" };\n } else if (rs.kind === \"derived\") {\n source = { kind: \"derived\" }; // accessor is code → attached at runtime, never from JSON\n } else {\n source = { kind: \"backend\", path: rs.path, synthetic: rs.synthetic };\n }\n\n return {\n name: raw.name,\n label: raw.label,\n type: raw.type,\n source,\n filter: raw.filter,\n sort: raw.sort,\n select: raw.select,\n aggregate: raw.aggregate,\n group: raw.group,\n alias: raw.alias,\n aliases: normalizeAliases(raw.aliases, raw.alias),\n render: raw.render, // string key from JSON; consumer may override with a fn\n };\n}\n\nfunction normalizeAliases(rawAliases: unknown, rawAlias: unknown): string[] {\n const aliases: string[] = [];\n const seen = new Set<string>();\n const add = (raw: unknown) => {\n if (typeof raw !== \"string\") return;\n const value = raw.trim();\n if (!value) return;\n const key = value.toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n aliases.push(value);\n };\n\n add(rawAlias);\n if (Array.isArray(rawAliases)) {\n for (const item of rawAliases) add(item);\n }\n return aliases;\n}\n","// ops.ts — which operators a field type allows, and value coercion.\n//\n// Single source of truth for \"what filters can I build on this field\", shared by\n// the QueryBuilder UI, the CellMenu quick-filters, and client-side applyQuery.\n// The backend (backends/go) enforces the same matrix independently — never trust\n// the client.\n//\n// Every value can be NULL, so `is_null`/`is_not_null` are available on EVERY\n// type, including bool (design feedback).\n\nimport type { FilterOp, FieldType, FieldDef } from \"./index\";\n\nconst NULLITY: FilterOp[] = [\"is_null\", \"is_not_null\"];\n\n/** Default operator set per type. A FieldDef.filter.ops overrides this. */\nexport const OPS_BY_TYPE: Record<FieldType, FilterOp[]> = {\n text: [\"=\", \"!=\", \"contains\", \"starts_with\", \"ends_with\", ...NULLITY],\n enum: [\"=\", \"!=\", ...NULLITY],\n number: [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", ...NULLITY],\n datetime: [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", ...NULLITY],\n bool: [\"=\", \"!=\", ...NULLITY],\n textarray: [\"includes\", ...NULLITY],\n};\n\n/** Operators that take no value (the value input is hidden). */\nexport const NULLARY_OPS: ReadonlySet<FilterOp> = new Set<FilterOp>(NULLITY);\n\n/** Effective operators for a field (its override, else the type default). */\nexport function opsForField(field: Pick<FieldDef, \"type\" | \"filter\">): FilterOp[] {\n return field.filter?.ops ?? OPS_BY_TYPE[field.type];\n}\n\n/** Whether an op is valid for a type per the default matrix (server mirror lives\n * in Go opAllowed; keep them in lockstep). */\nexport function opAllowedForType(type: FieldType, op: FilterOp): boolean {\n return OPS_BY_TYPE[type].includes(op);\n}\n\n/** Coerce a raw string value to the type the field expects, for client-side\n * comparison. Throws on invalid input (e.g. a non-numeric value on a number\n * field) so callers can surface it rather than silently mis-filtering. */\nexport function coerceValue(type: FieldType, raw: string): string | number | boolean {\n switch (type) {\n case \"number\": {\n const n = Number(raw);\n if (raw.trim() === \"\" || Number.isNaN(n)) throw new Error(`not a number: ${JSON.stringify(raw)}`);\n return n;\n }\n case \"bool\": {\n const v = raw.toLowerCase();\n if (v === \"true\" || v === \"1\" || v === \"t\") return true;\n if (v === \"false\" || v === \"0\" || v === \"f\") return false;\n throw new Error(`not a bool: ${JSON.stringify(raw)}`);\n }\n case \"datetime\": {\n const ms = Date.parse(raw);\n if (Number.isNaN(ms)) throw new Error(`not a datetime: ${JSON.stringify(raw)}`);\n return ms;\n }\n default:\n return raw;\n }\n}\n","// agg.ts — which aggregate ops a field type allows, and which fields can be a\n// measure or a group-by key. The single source of truth shared by the\n// QueryBuilder's metric editor, the client-side applyAggregations, and (mirrored\n// independently in backends/go) the SQL compiler. Never trust the client: the Go\n// `aggOpAllowed` enforces the same matrix before any GROUP BY is built.\n\nimport type { AggOp } from \"./query\";\nimport type { FieldType, FieldDef } from \"./schema\";\n\n/** Default aggregate ops per field type. A FieldDef.aggregate.ops overrides this.\n * `count`/`count_distinct` work on anything countable; sum/avg are numeric-only;\n * min/max need an ordered domain (numbers, datetimes, and lexical text/enum). */\nexport const AGG_OPS_BY_TYPE: Record<FieldType, AggOp[]> = {\n number: [\"count\", \"count_distinct\", \"sum\", \"avg\", \"min\", \"max\"],\n datetime: [\"count\", \"count_distinct\", \"min\", \"max\"],\n enum: [\"count\", \"count_distinct\", \"min\", \"max\"],\n text: [\"count\", \"count_distinct\", \"min\", \"max\"],\n bool: [\"count\", \"count_distinct\"],\n textarray: [\"count\"],\n};\n\n/** Ops that reduce a measure column. `count` is the only op that may omit a\n * field (counting rows), so it's the only one absent here. */\nexport const AGG_OPS_NEEDING_FIELD: ReadonlySet<AggOp> = new Set<AggOp>([\n \"count_distinct\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n]);\n\n/** Whether an op requires a measure column. */\nexport function aggOpNeedsField(op: AggOp): boolean {\n return AGG_OPS_NEEDING_FIELD.has(op);\n}\n\n/** Effective aggregate ops for a field (its override, else the type default). */\nexport function aggOpsForField(field: Pick<FieldDef, \"type\" | \"aggregate\">): AggOp[] {\n return field.aggregate?.ops ?? AGG_OPS_BY_TYPE[field.type];\n}\n\n/** Whether an op is valid for a type per the default matrix (Go aggOpAllowed is\n * the server mirror; keep them in lockstep). */\nexport function aggOpAllowedForType(type: FieldType, op: AggOp): boolean {\n return AGG_OPS_BY_TYPE[type].includes(op);\n}\n\n/** Can this field be the MEASURE of a metric? Only backend fields (they have SQL\n * to aggregate); derived/render-only columns are never pushed to the server. An\n * explicit `aggregate.measure` wins over the type default. */\nexport function isMeasurable(field: FieldDef): boolean {\n if (field.source.kind !== \"backend\") return false;\n if (field.aggregate?.measure != null) return field.aggregate.measure;\n return aggOpsForField(field).length > 0;\n}\n\n/** Can this field be a GROUP BY key? Backend fields only. Default: low-cardinality\n * types (enum/text/bool); numbers/datetimes need bucketing (out of scope) so they\n * default off, but `aggregate.groupable: true` opts any backend field in. */\nexport function isGroupable(field: FieldDef): boolean {\n if (field.source.kind !== \"backend\") return false;\n if (field.aggregate?.groupable != null) return field.aggregate.groupable;\n return field.type === \"enum\" || field.type === \"text\" || field.type === \"bool\";\n}\n","// encode.ts — URL serialization + the server-bound query subset.\n//\n// Two distinct serializations, deliberately kept apart:\n//\n// 1. encodeQuery / decodeQuery — the *whole* QueryState (incl. view state:\n// select columns + widths) ⇄ a base64url `?q=` token. This is what makes\n// reload, back/forward, bookmarks, and \"send a coworker your view\" all work\n// (decisions #4 + #5). Defaults are omitted to keep the token short.\n//\n// 2. toServerQuery — the subset the backend acts on: pushdown\n// WHERE clauses, server-sortable ORDER BY (remapped through sort.field), the\n// page window, and the field names to SELECT (so the backend evaluates the\n// computed/synthetic columns the view needs — requested ∪ always-needed,\n// design decision #2). Per-column widths never leave the client.\n//\n// Back-compat: decodeQuery accepts legacy compact shapes: `o` as a single\n// object (pre-multi-sort) and `c`/`s` as a string[] (pre-width).\n\nimport { EMPTY_QUERY, MAX_QUERY_TOKEN_LENGTH, normalizeQueryState } from \"./query\";\nimport type { QueryState, WhereClause, OrderByClause, SelectColumn, AggregationClause } from \"./query\";\nimport type { FieldSchema } from \"./schema\";\nimport { indexFields, isFilterable, isPushdownFilter, isSortable, resolveFieldName, selectedFields } from \"./schema\";\n\n// ---- base64url (browser + node) -------------------------------------------\n\nfunction toBase64Url(json: string): string {\n let b64: string;\n if (typeof Buffer !== \"undefined\") {\n b64 = Buffer.from(json, \"utf8\").toString(\"base64\");\n } else {\n const bytes = new TextEncoder().encode(json);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n b64 = btoa(bin);\n }\n return b64.replace(/=+$/, \"\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n}\n\nfunction fromBase64Url(s: string): string {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof Buffer !== \"undefined\") return Buffer.from(b64, \"base64\").toString(\"utf8\");\n const bin = atob(b64);\n const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n}\n\n// ---- compact wire form ----------------------------------------------------\n// Keys are short to keep `?q=` small; any field at its default is omitted.\n// s = select (as [field] or [field, width] tuples)\n// w = where, o = orderBy, l = limit, f = offset\n\ninterface CompactQuery {\n s?: Array<[string] | [string, number]> | string[]; // string[] = legacy\n w?: WhereClause[];\n o?: OrderByClause[] | OrderByClause; // object accepted for legacy decode\n l?: number;\n f?: number;\n g?: AggregationClause[]; // aggregation metrics (omitted when none)\n c?: string[]; // legacy alias for select column names\n}\n\n/** QueryState → base64url token (omitting defaults). All-default query → \"\". */\nexport function encodeQuery(q: QueryState): string {\n q = normalizeQueryState(q);\n const c: CompactQuery = {};\n if (q.select.length)\n c.s = q.select.map((col): [string] | [string, number] => (col.width != null ? [col.field, col.width] : [col.field]));\n if (q.where.length) c.w = q.where;\n if (q.orderBy.length) c.o = q.orderBy;\n c.l = q.limit;\n if (q.offset) c.f = q.offset;\n if (q.aggregations?.length) c.g = q.aggregations;\n if (Object.keys(c).length === 0) return \"\";\n return toBase64Url(JSON.stringify(c));\n}\n\n/** base64url token → QueryState. Tolerant: bad input and legacy shapes both\n * normalize to a valid QueryState (never throws). */\nexport function decodeQuery(token: string): QueryState {\n if (!token) return { ...EMPTY_QUERY };\n if (token.length > MAX_QUERY_TOKEN_LENGTH) return { ...EMPTY_QUERY };\n try {\n const c = JSON.parse(fromBase64Url(token)) as CompactQuery;\n const out = normalizeQueryState({\n select: normalizeSelect(c.s ?? c.c),\n where: Array.isArray(c.w) ? c.w : [],\n orderBy: normalizeOrderBy(c.o),\n limit: c.l,\n offset: c.f,\n aggregations: c.g,\n });\n return out;\n } catch {\n return { ...EMPTY_QUERY };\n }\n}\n\nfunction normalizeSelect(s: CompactQuery[\"s\"] | CompactQuery[\"c\"]): SelectColumn[] {\n if (!Array.isArray(s)) return [];\n return s\n .map((item): SelectColumn | null => {\n if (typeof item === \"string\") return { field: item }; // legacy string[]\n if (Array.isArray(item) && typeof item[0] === \"string\") {\n return typeof item[1] === \"number\" ? { field: item[0], width: item[1] } : { field: item[0] };\n }\n return null;\n })\n .filter((x): x is SelectColumn => x != null);\n}\n\nfunction normalizeOrderBy(o: CompactQuery[\"o\"]): OrderByClause[] {\n if (!o) return [];\n if (Array.isArray(o)) return o;\n return [o]; // legacy single-object form → one-element array\n}\n\n// ---- server-bound subset --------------------------------------------------\n\n/** The backend request derived from a QueryState + its schema. Mirrors the Go\n * `WireQuery`. View-only state (column widths) is intentionally absent. */\nexport interface ServerQuery {\n /** field names to return — visible columns ∪ fields referenced by where/orderBy. */\n select: string[];\n /** only clauses on pushdown-filterable fields. */\n where: WhereClause[];\n /** only terms on server-sortable fields, with `field` already remapped to the\n * field's server sort key (FieldDef.sort.field) when set. */\n orderBy: OrderByClause[];\n limit: number;\n offset: number;\n}\n\n/** Project a QueryState into the server request, dropping client-only filters,\n * client-only sorts, and remapping sort fields through `sort.field`. */\nexport function toServerQuery<Row>(q: QueryState, schema: FieldSchema<Row>): ServerQuery {\n q = normalizeQueryState(q);\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n\n const where = q.where.filter((cl) => {\n const field = resolveField(cl.field);\n const f = byName.get(field);\n return f != null && isPushdownFilter(f);\n });\n\n const orderBy: OrderByClause[] = [];\n for (const term of q.orderBy) {\n const field = resolveField(term.field);\n const f = byName.get(field);\n if (!f || !isSortable(f) || f.source.kind !== \"backend\") continue;\n orderBy.push({ ...term, field: f.sort?.field ?? f.name });\n }\n\n // SELECT = the BACKEND columns the response rows must carry: visible backend\n // columns, the id (for selection/refresh), and any backend column a\n // CLIENT-SIDE filter reads. Derived/render-only columns are computed on the\n // client and have no SQL to select; pushdown filters/sorts run in SQL and need\n // not be returned (design decision #2: requested ∪ always-needed).\n const select = new Set<string>(\n selectedFields(schema, q)\n .filter((f) => f.source.kind === \"backend\")\n .map((f) => f.name),\n );\n select.add(schema.idField);\n for (const cl of q.where) {\n const field = resolveField(cl.field);\n const f = byName.get(field);\n if (f && isFilterable(f) && !isPushdownFilter(f) && f.source.kind === \"backend\") select.add(field);\n }\n\n return { select: [...select], where, orderBy, limit: q.limit, offset: q.offset };\n}\n\n// ---- aggregation server subset --------------------------------------------\n\n/** The backend request for the metric panel. Scope is the whole filtered set:\n * the SAME pushdown WHERE as the rows query, but NO ORDER BY / LIMIT / OFFSET —\n * metrics describe every matching row, not the visible page. Mirrors the Go\n * AggSpec list. */\nexport interface AggregationRequest {\n where: WhereClause[];\n aggregations: AggregationClause[];\n}\n\n/** One group's result. `keys` has one entry per AggregationClause.groupBy field,\n * in axis order (`[]` for a grand total); a null key is the NULL/empty bucket. */\nexport interface AggregationBucket {\n keys: (string | null)[];\n /** The metric: a number for count/sum/avg, or the column's value for min/max\n * (which may be a string for text/datetime). null when undefined (e.g. avg of\n * an all-null column). */\n value: number | string | null;\n /** COUNT(*) of rows in the group — always present, even when `value` isn't a\n * count, so the panel can show group sizes / shares. */\n count: number;\n}\n\nexport interface AggregationResultEntry {\n /** Echoes AggregationClause.id. */\n id: string;\n buckets: AggregationBucket[];\n}\n\nexport interface AggregationResult {\n /** One entry per requested aggregation, in request order. */\n metrics: AggregationResultEntry[];\n}\n\n/** Project a QueryState into the metric request: the pushdown WHERE subset (same\n * rule as toServerQuery) plus the aggregations whose measure + group fields are\n * all server-capable backend columns. Aggregations referencing a derived /\n * unknown field are dropped — the server has no SQL for them. */\nexport function toAggregationQuery<Row>(q: QueryState, schema: FieldSchema<Row>): AggregationRequest {\n q = normalizeQueryState(q);\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n\n const where = q.where.filter((cl) => {\n const f = byName.get(resolveField(cl.field));\n return f != null && isPushdownFilter(f);\n });\n\n const isBackend = (name: string | undefined): boolean => {\n if (name == null) return true; // omitted measure (count(*)) is fine\n const field = resolveField(name);\n const f = byName.get(field);\n return f != null && f.source.kind === \"backend\";\n };\n\n const aggregations = (q.aggregations ?? []).filter(\n (a) => isBackend(a.field) && a.groupBy.every((g) => isBackend(g)),\n );\n\n return { where, aggregations };\n}\n","// apply.ts — client-side filter + multi-sort + paginate.\n//\n// applyQuery is the client mirror of the backend compiler. Three jobs:\n// 1. The executor for purely client-side datasets (no backend at all).\n// 2. The executor for derived / non-pushdown columns the backend can't filter.\n// 3. Idempotent defense-in-depth over server-filtered fields (re-applying a\n// server-handled clause must not change the result).\n//\n// It must agree with backends/go's Compile on operator semantics:\n// - `contains`/`starts_with`/`ends_with` are case-insensitive\n// - textarray `is_null` = empty-or-missing; `is_not_null` = any element;\n// `includes` = case-insensitive membership\n// - NULLs sort per OrderByClause.nulls (default \"last\")\n// - multi-sort is a stable lexicographic fold over orderBy in priority order\n\nimport type { AggOp, AggregationClause, QueryState, WhereClause } from \"./query\";\nimport type { FieldSchema, FieldDef } from \"./schema\";\nimport type { AggregationBucket, AggregationResult } from \"./encode\";\nimport { indexFields, readFieldValue, resolveFieldName } from \"./schema\";\nimport { coerceValue } from \"./ops\";\n\nexport interface ApplyResult<Row> {\n /** The page slice (after filter + sort + offset/limit). */\n rows: Row[];\n /** Total matching rows *before* pagination — powers \"showing N of M\". */\n total: number;\n}\n\n/** Filter + multi-sort + paginate `rows` per `q`, resolving field types/paths\n * from `schema`. Pure; never mutates `rows`. Clauses/sorts on unknown fields are\n * ignored (the backend already enforced its own allowlist). */\nexport function applyQuery<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): ApplyResult<Row> {\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n const whereClauses = q.where.map((clause) => ({ ...clause, field: resolveField(clause.field) }));\n const orderBy = q.orderBy.map((term) => ({ ...term, field: resolveField(term.field) }));\n\n let out = rows.filter((row) => whereClauses.every((cl) => matchesWith(byName, row, cl)));\n const total = out.length;\n\n if (orderBy.length) {\n const terms = orderBy\n .map((t) => ({ field: byName.get(t.field), dir: t.dir, nullsLast: (t.nulls ?? \"last\") === \"last\" }))\n .filter((t): t is { field: FieldDef<Row>; dir: \"asc\" | \"desc\"; nullsLast: boolean } => t.field != null);\n out = [...out].sort((a, b) => {\n for (const t of terms) {\n const av = readFieldValue(t.field, a);\n const bv = readFieldValue(t.field, b);\n const aNull = av == null;\n const bNull = bv == null;\n if (aNull || bNull) {\n if (aNull && bNull) continue;\n return (aNull ? 1 : -1) * (t.nullsLast ? 1 : -1);\n }\n const cmp = compare(av, bv);\n if (cmp !== 0) return t.dir === \"asc\" ? cmp : -cmp;\n }\n return 0;\n });\n }\n\n const start = q.offset > 0 ? q.offset : 0;\n const end = q.limit > 0 ? start + q.limit : out.length;\n return { rows: out.slice(start, end), total };\n}\n\n/** Does one row satisfy one clause? Exposed for the CellMenu preview + tests. */\nexport function matchesClause<Row>(row: Row, clause: WhereClause, schema: FieldSchema<Row>): boolean {\n return matchesWith(indexFields(schema), row, clause);\n}\n\n/** Client-side mirror of the backend GROUP BY (the executor for `clientRows`\n * mode / the demo). Scope matches the server contract: the WHERE-filtered set\n * only — ORDER BY / LIMIT / OFFSET are intentionally ignored, so a metric\n * reflects every matching row, not the visible page. Must agree with\n * backends/go's aggregate compile on op semantics. */\nexport function applyAggregations<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): AggregationResult {\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n const whereClauses = q.where.map((clause) => ({ ...clause, field: resolveField(clause.field) }));\n const filtered = rows.filter((row) => whereClauses.every((cl) => matchesWith(byName, row, cl)));\n const metrics = (q.aggregations ?? []).map((agg) => ({\n id: agg.id,\n buckets: computeBuckets(filtered, agg, byName, resolveField),\n }));\n return { metrics };\n}\n\nfunction computeBuckets<Row>(\n rows: Row[],\n agg: AggregationClause,\n byName: Map<string, FieldDef<Row>>,\n resolveField: (field: string) => string,\n): AggregationBucket[] {\n // One slot per groupBy entry, preserving position. An unresolved field stays\n // in the array as `undefined` so every bucket's `keys` has exactly one element\n // per requested group field — the panel renders keys positionally against\n // `clause.groupBy`, so dropping an entry here would shift every later column.\n const groupFields = agg.groupBy.map((n) => byName.get(resolveField(n)));\n const measure = agg.field ? byName.get(resolveField(agg.field)) : undefined;\n\n const groups = new Map<string, { keys: (string | null)[]; rows: Row[] }>();\n for (const row of rows) {\n const keys = groupFields.map((f) => {\n if (!f) return null; // unresolved group field → NULL bucket, never a missing column\n const v = readFieldValue(f, row);\n return v == null || v === \"\" ? null : String(v);\n });\n const k = JSON.stringify(keys);\n let g = groups.get(k);\n if (!g) {\n g = { keys, rows: [] };\n groups.set(k, g);\n }\n g.rows.push(row);\n }\n\n const buckets = [...groups.values()].map((g) => ({\n keys: g.keys,\n count: g.rows.length,\n value: aggValue(agg.op, measure, g.rows),\n }));\n return sortBuckets(buckets);\n}\n\nfunction aggValue<Row>(op: AggOp, measure: FieldDef<Row> | undefined, rows: Row[]): number | string | null {\n if (op === \"count\") {\n if (!measure) return rows.length; // COUNT(*)\n let n = 0;\n for (const r of rows) if (readFieldValue(measure, r) != null) n++; // COUNT(expr) = non-null\n return n;\n }\n if (!measure) return null;\n const values = rows.map((r) => readFieldValue(measure, r)).filter((v) => v != null);\n\n switch (op) {\n case \"count_distinct\":\n return new Set(values.map((v) => String(v))).size;\n case \"sum\":\n case \"avg\": {\n let sum = 0;\n let n = 0;\n for (const v of values) {\n const num = typeof v === \"number\" ? v : Number(v);\n if (Number.isFinite(num)) {\n sum += num;\n n++;\n }\n }\n if (op === \"avg\") return n ? sum / n : null;\n return n ? sum : null;\n }\n case \"min\":\n case \"max\": {\n let best: unknown;\n for (const v of values) {\n if (best === undefined) {\n best = v;\n continue;\n }\n const cmp = compare(v, best);\n if (op === \"min\" ? cmp < 0 : cmp > 0) best = v;\n }\n return (best as number | string | undefined) ?? null;\n }\n default:\n return null;\n }\n}\n\n/** Stable display order: descending by the metric (or group size when the metric\n * isn't numeric), then by group key. The 2D pivot ignores this; the 1D bar list\n * and the flat N-column table read top-to-bottom from it. */\nfunction sortBuckets(buckets: AggregationBucket[]): AggregationBucket[] {\n return [...buckets].sort((a, b) => {\n const av = typeof a.value === \"number\" ? a.value : a.count;\n const bv = typeof b.value === \"number\" ? b.value : b.count;\n if (av !== bv) return bv - av;\n return JSON.stringify(a.keys).localeCompare(JSON.stringify(b.keys));\n });\n}\n\nfunction matchesWith<Row>(byName: Map<string, FieldDef<Row>>, row: Row, clause: WhereClause): boolean {\n const field = byName.get(clause.field);\n if (!field) return true; // unknown field → no client opinion\n const v = readFieldValue(field, row);\n\n // Nullity, with array-aware semantics.\n if (clause.op === \"is_null\") return Array.isArray(v) ? v.length === 0 : v == null || v === \"\";\n if (clause.op === \"is_not_null\") return Array.isArray(v) ? v.length > 0 : v != null && v !== \"\";\n\n // Empty value on a non-equality op is a \"no filter\" signal (matches the UI +\n // the Go compiler, which skip such clauses).\n if (clause.value === \"\" && clause.op !== \"=\" && clause.op !== \"!=\") return true;\n\n // `includes` is array membership (ARRAY_HAS): a null/missing array contains\n // nothing. Handle before the array/scalar split so null can't fall through.\n if (clause.op === \"includes\") {\n const arr = Array.isArray(v) ? v : [];\n const needle = clause.value.toLowerCase();\n return arr.some((x) => String(x).toLowerCase() === needle);\n }\n\n if (Array.isArray(v)) {\n const needle = clause.value.toLowerCase();\n const hay = v.map((x) => String(x).toLowerCase());\n switch (clause.op) {\n case \"contains\":\n return hay.some((s) => s.includes(needle));\n case \"=\":\n return hay.includes(needle);\n case \"!=\":\n return !hay.includes(needle);\n default:\n return true;\n }\n }\n\n switch (clause.op) {\n case \"=\":\n return String(v ?? \"\") === String(coerceSafe(field, clause.value));\n case \"!=\":\n return String(v ?? \"\") !== String(coerceSafe(field, clause.value));\n case \">\":\n return compare(v, coerceSafe(field, clause.value)) > 0;\n case \">=\":\n return compare(v, coerceSafe(field, clause.value)) >= 0;\n case \"<\":\n return compare(v, coerceSafe(field, clause.value)) < 0;\n case \"<=\":\n return compare(v, coerceSafe(field, clause.value)) <= 0;\n case \"contains\":\n return String(v ?? \"\").toLowerCase().includes(clause.value.toLowerCase());\n case \"starts_with\":\n return String(v ?? \"\").toLowerCase().startsWith(clause.value.toLowerCase());\n case \"ends_with\":\n return String(v ?? \"\").toLowerCase().endsWith(clause.value.toLowerCase());\n default:\n return true;\n }\n}\n\n/** Coerce per the field type, falling back to the raw string if coercion fails\n * (so a malformed value degrades to a string compare rather than throwing). */\nfunction coerceSafe(field: FieldDef, raw: string): string | number | boolean {\n try {\n return coerceValue(field.type, raw);\n } catch {\n return raw;\n }\n}\n\nfunction compare(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return a === b ? 0 : a ? 1 : -1;\n return String(a).localeCompare(String(b));\n}\n","// adapters.ts — the pluggable seams. Everything project-specific is injected\n// here so the package core stays generic.\n//\n// - Transport : how rows + filter-value suggestions are fetched.\n// - StorageAdapter: where saved/last queries live (in-memory by default in\n// the React hook; explicitly durable when callers opt in).\n\nimport { normalizeQueryState } from \"./query\";\nimport type { QueryState, RowId } from \"./query\";\nimport type { ServerQuery, AggregationRequest, AggregationResult } from \"./encode\";\n\n// ---- Transport ------------------------------------------------------------\n\nexport interface FetchRowsResult<Row> {\n rows: Row[];\n /** Total matching rows before pagination (for \"N of M\" + paging bounds). */\n total: number;\n}\n\n/** A keystroke-driven distinct-value lookup. Autocomplete is the DEFAULT value\n * source for every field (design feedback), so this is how the filter combobox\n * finds options. `search` is the user's current input; the backend returns the\n * best matches and whether it truncated (so the UI can prompt \"keep typing\"). */\nexport interface DistinctValuesQuery {\n field: string;\n /** Current substring/prefix the user has typed (empty = top values). */\n search: string;\n /** Max suggestions to return (default chosen by the adapter, ~50). */\n limit?: number;\n}\n\nexport interface DistinctValuesResult {\n values: string[];\n /** true when more matches exist than were returned — refine by typing. */\n hasMore: boolean;\n /** True if the selected field has at least one NULL in the source dataset.\n * False if that field is guaranteed non-null. Omitted when the backend does\n * not compute this metadata. */\n hasNull?: boolean;\n}\n\n/** Per-field metadata for the field picker. */\nexport interface FieldStats {\n distinct?: number;\n min?: string | number;\n max?: string | number;\n}\n\nexport interface Transport<Row = any> {\n /** Run a server query. The only required method — a purely client-side table\n * can omit a Transport entirely and rely on applyQuery over local rows. */\n fetchRows(query: ServerQuery, signal?: AbortSignal): Promise<FetchRowsResult<Row>>;\n\n /** Distinct values for filter autocomplete. Strongly recommended: without it,\n * fields fall back to freeform input. The backend should match `search`\n * server-side (e.g. ILIKE prefix) and cap at `limit` so large domains stay\n * fast and refine per keystroke. */\n fetchDistinctValues?(q: DistinctValuesQuery, signal?: AbortSignal): Promise<DistinctValuesResult>;\n\n /** Run the metric panel's GROUP BY queries (one per requested aggregation),\n * scoped to the WHERE-filtered set with no paging. Optional: without it, the\n * metric panel falls back to applyAggregations over local rows (client mode).\n * Implementations compile each AggSpec with backends/go CompileAggregation,\n * sharing the same WHERE as fetchRows. */\n fetchAggregations?(q: AggregationRequest, signal?: AbortSignal): Promise<AggregationResult>;\n\n /** Re-fetch a single row by id, for in-place updates without a full re-query\n * after a mutation. Optional. */\n fetchRow?(id: RowId, signal?: AbortSignal): Promise<Row | null>;\n\n /** Stats for the field picker. Optional. */\n fetchFieldStats?(fields: string[], signal?: AbortSignal): Promise<Record<string, FieldStats>>;\n}\n\n// ---- Saved queries --------------------------------------------------------\n\nexport interface SavedQuery {\n id: string;\n name: string;\n /** Epoch millis; injected by the caller so core stays deterministic/testable. */\n savedAt: number;\n query: QueryState;\n}\n\n/** Persistence for the \"last\" query (auto-restored) and named saved queries. All\n * methods are namespaced by `key` (the schema/dataset name) so multiple tables\n * on one origin don't collide. Async to allow backend implementations; the\n * browser adapters can still resolve synchronously. */\nexport interface StorageAdapter {\n loadLast(key: string): Promise<QueryState | null>;\n saveLast(key: string, query: QueryState): Promise<void>;\n\n listSaved(key: string): Promise<SavedQuery[]>;\n /** Load the saved query selected as the default view for this key. Optional so\n * existing backend adapters keep working until they add first-class support. */\n loadDefaultSaved?(key: string): Promise<SavedQuery | null>;\n /** Set or clear the saved query used as the default view for this key. */\n setDefaultSaved?(key: string, id: string | null): Promise<void>;\n /** Persist a named query snapshot. Rejects when a saved query with the same\n * name already exists in the key namespace. */\n saveNamed(key: string, name: string, query: QueryState, savedAt: number): Promise<SavedQuery>;\n deleteSaved(key: string, id: string): Promise<void>;\n}\n\nconst LAST_PREFIX = \"query-table:last:\";\nconst SAVED_PREFIX = \"query-table:saved:\";\nconst DEFAULT_PREFIX = \"query-table:default:\";\nconst MAX_SAVED_QUERIES = 100;\nconst MAX_SAVED_NAME_LENGTH = 200;\n\nfunction normalizeSavedQuery(value: unknown): SavedQuery | null {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) return null;\n const raw = value as Record<string, unknown>;\n if (\n typeof raw.id !== \"string\" ||\n !raw.id ||\n raw.id.length > 256 ||\n typeof raw.name !== \"string\" ||\n !raw.name ||\n raw.name.length > MAX_SAVED_NAME_LENGTH ||\n typeof raw.savedAt !== \"number\" ||\n !Number.isFinite(raw.savedAt) ||\n raw.query == null ||\n typeof raw.query !== \"object\"\n ) {\n return null;\n }\n return { id: raw.id, name: raw.name, savedAt: raw.savedAt, query: normalizeQueryState(raw.query) };\n}\n\nfunction normalizeSavedQueries(value: unknown): SavedQuery[] {\n if (!Array.isArray(value)) return [];\n return value\n .slice(0, MAX_SAVED_QUERIES)\n .map(normalizeSavedQuery)\n .filter((item): item is SavedQuery => item != null);\n}\n\n/** In-memory persistence used by default by the React package. It supports the\n * full saved-query UI for the lifetime of a mounted table without writing\n * filter values to durable browser storage. */\nexport function memoryStorageAdapter(): StorageAdapter {\n const last = new Map<string, QueryState>();\n const saved = new Map<string, SavedQuery[]>();\n const defaults = new Map<string, string>();\n\n return {\n async loadLast(key) {\n const query = last.get(key);\n return query ? normalizeQueryState(query) : null;\n },\n async saveLast(key, query) {\n last.set(key, normalizeQueryState(query));\n },\n async listSaved(key) {\n return (saved.get(key) ?? []).map((item) => ({ ...item, query: normalizeQueryState(item.query) }));\n },\n async loadDefaultSaved(key) {\n const id = defaults.get(key);\n return id ? (saved.get(key) ?? []).find((item) => item.id === id) ?? null : null;\n },\n async setDefaultSaved(key, id) {\n if (id == null) {\n defaults.delete(key);\n return;\n }\n if (!(saved.get(key) ?? []).some((item) => item.id === id)) throw new Error(`Saved query \"${id}\" does not exist.`);\n defaults.set(key, id);\n },\n async saveNamed(key, name, query, savedAt) {\n const normalizedName = name.trim();\n if (!normalizedName || normalizedName.length > MAX_SAVED_NAME_LENGTH) {\n throw new Error(`Saved query name must be between 1 and ${MAX_SAVED_NAME_LENGTH} characters.`);\n }\n const items = saved.get(key) ?? [];\n if (items.length >= MAX_SAVED_QUERIES) throw new Error(`At most ${MAX_SAVED_QUERIES} saved queries are allowed.`);\n if (items.some((item) => item.name === normalizedName)) throw new Error(`Saved query \"${normalizedName}\" already exists.`);\n const item: SavedQuery = {\n id: `${savedAt}-${Math.round((savedAt * 9301 + 49297) % 233280)}`,\n name: normalizedName,\n savedAt,\n query: normalizeQueryState(query),\n };\n saved.set(key, [...items, item]);\n return item;\n },\n async deleteSaved(key, id) {\n saved.set(key, (saved.get(key) ?? []).filter((item) => item.id !== id));\n if (defaults.get(key) === id) defaults.delete(key);\n },\n };\n}\n\n/** Opt-in StorageAdapter over window.localStorage. Query state includes raw\n * filter values and is stored as cleartext JSON; do not use it for sensitive\n * datasets. Falls back to no persistence when storage is unavailable. */\nexport function localStorageAdapter(): StorageAdapter {\n const ls: Storage | null = (() => {\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n return null; // access can throw in some sandboxes\n }\n })();\n\n const readSaved = (key: string): SavedQuery[] => {\n if (!ls) return [];\n try {\n const raw = ls.getItem(SAVED_PREFIX + key);\n return normalizeSavedQueries(raw ? JSON.parse(raw) : []);\n } catch {\n return [];\n }\n };\n const writeSaved = (key: string, items: SavedQuery[]): void => {\n if (ls) ls.setItem(SAVED_PREFIX + key, JSON.stringify(items));\n };\n const readDefaultId = (key: string): string | null => {\n if (!ls) return null;\n return ls.getItem(DEFAULT_PREFIX + key);\n };\n const writeDefaultId = (key: string, id: string | null): void => {\n if (!ls) return;\n if (id) ls.setItem(DEFAULT_PREFIX + key, id);\n else ls.removeItem(DEFAULT_PREFIX + key);\n };\n\n return {\n async loadLast(key) {\n if (!ls) return null;\n try {\n const raw = ls.getItem(LAST_PREFIX + key);\n return raw ? normalizeQueryState(JSON.parse(raw)) : null;\n } catch {\n return null;\n }\n },\n async saveLast(key, query) {\n if (ls) ls.setItem(LAST_PREFIX + key, JSON.stringify(normalizeQueryState(query)));\n },\n async listSaved(key) {\n return readSaved(key).sort((a, b) => b.savedAt - a.savedAt);\n },\n async loadDefaultSaved(key) {\n const id = readDefaultId(key);\n if (!id) return null;\n const found = readSaved(key).find((q) => q.id === id);\n if (!found) writeDefaultId(key, null);\n return found ?? null;\n },\n async setDefaultSaved(key, id) {\n if (id && !readSaved(key).some((q) => q.id === id)) {\n throw new Error(`Saved query \"${id}\" does not exist.`);\n }\n writeDefaultId(key, id);\n },\n async saveNamed(key, name, query, savedAt) {\n const normalizedName = name.trim();\n if (!normalizedName || normalizedName.length > MAX_SAVED_NAME_LENGTH) {\n throw new Error(`Saved query name must be between 1 and ${MAX_SAVED_NAME_LENGTH} characters.`);\n }\n const items = readSaved(key);\n if (items.length >= MAX_SAVED_QUERIES) throw new Error(`At most ${MAX_SAVED_QUERIES} saved queries are allowed.`);\n if (items.some((q) => q.name === normalizedName)) {\n throw new Error(`Saved query \"${normalizedName}\" already exists.`);\n }\n const item: SavedQuery = {\n id: `${savedAt}-${Math.round((savedAt * 9301 + 49297) % 233280)}`,\n name: normalizedName,\n savedAt,\n query: normalizeQueryState(query),\n };\n items.push(item);\n writeSaved(key, items);\n return item;\n },\n async deleteSaved(key, id) {\n writeSaved(\n key,\n readSaved(key).filter((q) => q.id !== id),\n );\n if (readDefaultId(key) === id) writeDefaultId(key, null);\n },\n };\n}\n"],"mappings":"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","names":["field","resolveField","resolveField","item"]}
|
|
1
|
+
{"version":3,"sources":["../src/query.ts","../src/schema.ts","../src/ops.ts","../src/agg.ts","../src/encode.ts","../src/apply.ts","../src/adapters.ts"],"sourcesContent":["// query.ts — the canonical query state shape.\n//\n// We lean into SQL vocabulary at every layer (per design feedback): a QueryState\n// reads top-to-bottom like the statement it compiles to —\n//\n// SELECT <select> WHERE <where> ORDER BY <orderBy> LIMIT <limit> OFFSET <offset>\n//\n// Both \"what data\" (where/orderBy/limit/offset) and \"how viewed\" (select +\n// per-column width) can be serialized together when a consumer explicitly opts\n// into URL synchronization.\n\n/** All filter operators across both source projects, unioned. Every value can\n * be NULL, so `is_null`/`is_not_null` are valid for every field type. */\nexport type FilterOp =\n // comparison — number / datetime (and =,!= for any scalar)\n | \"=\"\n | \"!=\"\n | \">\"\n | \">=\"\n | \"<\"\n | \"<=\"\n // text matching\n | \"contains\"\n | \"starts_with\"\n | \"ends_with\"\n // array (textarray) containment\n | \"includes\"\n // nullity — valid for EVERY type; for arrays, \"empty\" vs \"non-empty\"\n | \"is_null\"\n | \"is_not_null\";\n\n/** A single AND-combined filter. `value` is always the raw string the UI\n * captured; coercion to number/bool/date happens at apply/compile time based on\n * the field's declared type (never on the value's runtime shape). Unused for\n * the nullary ops (`is_null`/`is_not_null`). */\nexport interface WhereClause {\n field: string; // FieldDef.name\n op: FilterOp;\n value: string;\n}\n\n/** One ORDER BY term. Array order in `QueryState.orderBy` is the sort priority. */\nexport interface OrderByClause {\n field: string; // FieldDef.name (server may sort a different expr via FieldDef.sort.field)\n dir: \"asc\" | \"desc\";\n /** NULL placement. Omitted = \"last\" (the historical default both projects used,\n * preserved so old `?q=` links round-trip identically). */\n nulls?: \"first\" | \"last\";\n}\n\n/** One column in the SELECT list, plus the view state that travels with it.\n * Order in `QueryState.select` is the left-to-right display order. */\nexport interface SelectColumn {\n field: string; // FieldDef.name\n /** Pixel width override. Omitted = FieldDef.select.width, else the type default. */\n width?: number;\n}\n\n/** A single aggregate operation. `count` is the only op that needs no measure\n * column (it counts rows); the rest reduce one column's values. The server-side\n * matrix (backends/go aggOpAllowed + the TS AGG_OPS_BY_TYPE) decides which ops a\n * field's type permits. */\nexport type AggOp = \"count\" | \"count_distinct\" | \"sum\" | \"avg\" | \"min\" | \"max\";\n\n/** One optional \"metric\" pinned above the table: a single aggregate over a single\n * measure column, optionally broken down by one or more group columns.\n *\n * Scope is deliberately the *whole filtered set* — the same WHERE as the table,\n * but NOT its ORDER BY / LIMIT / OFFSET. It is always evaluated on the server\n * (a real GROUP BY), so it reflects every matching row, not just the visible\n * page. Lives inside QueryState so it round-trips through `?q=`, saved queries,\n * and undo/redo for free — a saved query is a saved dashboard. */\nexport interface AggregationClause {\n /** Stable id; keys the metric panel and survives a `?q=` round-trip. */\n id: string;\n op: AggOp;\n /** Measure column (FieldDef.name). Omit only for `count` (⇒ COUNT(*)). */\n field?: string;\n /** Group-by columns, in axis order. `[]` = a single grand-total value.\n * One ⇒ a list/bar breakdown; two ⇒ an x/y pivot; three+ ⇒ a flat table. */\n groupBy: string[];\n /** Panel header override; defaults to a derived label like \"avg total\". */\n label?: string;\n}\n\nexport interface QueryState {\n /** Ordered SELECT list + per-column widths. Empty = the schema's default columns. */\n select: SelectColumn[];\n /** AND-combined filters. */\n where: WhereClause[];\n /** Multi-sort terms in priority order. Empty = the schema's default sort. */\n orderBy: OrderByClause[];\n /** Page size. */\n limit: number;\n /** Page offset (rows). */\n offset: number;\n /** Optional aggregate metrics shown above the table. Omitted/empty = none.\n * Each runs as its own server GROUP BY over the WHERE-filtered set (no paging). */\n aggregations?: AggregationClause[];\n}\n\n/** A row's stable identity, used for selection and per-row refresh. */\nexport type RowId = string | number;\n\n/** The query a fresh table starts from before schema defaults are layered on.\n * Empty `select`/`orderBy`/`where` mean \"defer to the schema\", which also keeps\n * encoded `?q=` tokens short (defaults are omitted). */\nexport const EMPTY_QUERY: QueryState = {\n select: [],\n where: [],\n orderBy: [],\n limit: 100,\n offset: 0,\n};\n\n/** Structural limits applied whenever query state crosses a trust boundary\n * (URL, storage, an imperative setQuery call, or a server projection). Row\n * limits may use the full safe-integer range; consumers and their backends own\n * any smaller operational cap appropriate for their dataset. */\nexport const MAX_QUERY_LIMIT = Number.MAX_SAFE_INTEGER;\nexport const MAX_QUERY_OFFSET = 1_000_000;\nexport const MAX_SELECT_COLUMNS = 200;\nexport const MAX_WHERE_CLAUSES = 100;\nexport const MAX_ORDER_BY_TERMS = 20;\nexport const MAX_AGGREGATIONS = 20;\nexport const MAX_GROUP_BY_FIELDS = 20;\nexport const MAX_QUERY_TOKEN_LENGTH = 2 * 1024 * 1024;\n\nconst MAX_FIELD_NAME_LENGTH = 256;\nconst MAX_FILTER_VALUE_LENGTH = 10_000;\nconst MAX_LABEL_LENGTH = 1_000;\nconst MIN_COLUMN_WIDTH = 24;\nconst MAX_COLUMN_WIDTH = 2_000;\n\nconst FILTER_OPS: ReadonlySet<string> = new Set([\n \"=\",\n \"!=\",\n \">\",\n \">=\",\n \"<\",\n \"<=\",\n \"contains\",\n \"starts_with\",\n \"ends_with\",\n \"includes\",\n \"is_null\",\n \"is_not_null\",\n]);\n\nconst AGG_OPS: ReadonlySet<string> = new Set([\"count\", \"count_distinct\", \"sum\", \"avg\", \"min\", \"max\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | null {\n return typeof value === \"string\" && value.length > 0 && value.length <= maxLength ? value : null;\n}\n\nfunction boundedInteger(value: unknown, fallback: number, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\n return Math.max(min, Math.min(max, Math.round(value)));\n}\n\n/** Convert unknown input into a bounded, structurally valid QueryState.\n * Invalid members are dropped; invalid scalar paging values use the supplied\n * fallback. This function is intentionally schema-agnostic—field allowlisting\n * still happens in toServerQuery and in the backend compiler. */\nexport function normalizeQueryState(input: unknown, fallback: QueryState = EMPTY_QUERY): QueryState {\n const raw = isRecord(input) ? input : {};\n\n const select: SelectColumn[] = [];\n if (Array.isArray(raw.select)) {\n for (const item of raw.select.slice(0, MAX_SELECT_COLUMNS)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n if (!field) continue;\n const column: SelectColumn = { field };\n if (typeof item.width === \"number\" && Number.isFinite(item.width)) {\n column.width = boundedInteger(item.width, MIN_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);\n }\n select.push(column);\n }\n } else {\n select.push(...fallback.select.map((column) => ({ ...column })));\n }\n\n const where: WhereClause[] = [];\n if (Array.isArray(raw.where)) {\n for (const item of raw.where.slice(0, MAX_WHERE_CLAUSES)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n const value = typeof item.value === \"string\" && item.value.length <= MAX_FILTER_VALUE_LENGTH ? item.value : null;\n if (!field || typeof item.op !== \"string\" || !FILTER_OPS.has(item.op) || value == null) continue;\n where.push({ field, op: item.op as FilterOp, value });\n }\n } else {\n where.push(...fallback.where.map((clause) => ({ ...clause })));\n }\n\n const orderBy: OrderByClause[] = [];\n if (Array.isArray(raw.orderBy)) {\n for (const item of raw.orderBy.slice(0, MAX_ORDER_BY_TERMS)) {\n if (!isRecord(item)) continue;\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n if (!field || (item.dir !== \"asc\" && item.dir !== \"desc\")) continue;\n const term: OrderByClause = { field, dir: item.dir };\n if (item.nulls === \"first\" || item.nulls === \"last\") term.nulls = item.nulls;\n orderBy.push(term);\n }\n } else {\n orderBy.push(...fallback.orderBy.map((term) => ({ ...term })));\n }\n\n const out: QueryState = {\n select,\n where,\n orderBy,\n limit: boundedInteger(raw.limit, fallback.limit, 1, MAX_QUERY_LIMIT),\n offset: boundedInteger(raw.offset, fallback.offset, 0, MAX_QUERY_OFFSET),\n };\n\n if (Array.isArray(raw.aggregations)) {\n const aggregations: AggregationClause[] = [];\n for (const item of raw.aggregations.slice(0, MAX_AGGREGATIONS)) {\n if (!isRecord(item)) continue;\n const id = boundedString(item.id, MAX_FIELD_NAME_LENGTH);\n if (!id || typeof item.op !== \"string\" || !AGG_OPS.has(item.op) || !Array.isArray(item.groupBy)) continue;\n const groupBy = item.groupBy\n .slice(0, MAX_GROUP_BY_FIELDS)\n .map((field) => boundedString(field, MAX_FIELD_NAME_LENGTH))\n .filter((field): field is string => field != null);\n const aggregation: AggregationClause = { id, op: item.op as AggOp, groupBy };\n const field = boundedString(item.field, MAX_FIELD_NAME_LENGTH);\n const label = boundedString(item.label, MAX_LABEL_LENGTH);\n if (field) aggregation.field = field;\n if (label) aggregation.label = label;\n aggregations.push(aggregation);\n }\n if (aggregations.length > 0) out.aggregations = aggregations;\n } else if (fallback.aggregations?.length) {\n out.aggregations = fallback.aggregations.map((aggregation) => ({ ...aggregation, groupBy: [...aggregation.groupBy] }));\n }\n\n return out;\n}\n\n/** Structural equality on two queries — used to decide whether a `?q=` write or a\n * refetch is actually needed (avoids history spam / redundant fetches). */\nexport function queriesEqual(a: QueryState, b: QueryState): boolean {\n if (a === b) return true;\n if (a.limit !== b.limit || a.offset !== b.offset) return false;\n if (!sameArray(a.where, b.where)) return false;\n if (!sameArray(a.orderBy, b.orderBy)) return false;\n if (!sameArray(a.select, b.select)) return false;\n if (!sameArray(a.aggregations ?? [], b.aggregations ?? [])) return false;\n return true;\n}\n\nfunction sameArray<T>(a: readonly T[], b: readonly T[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n // Members are flat records of primitives — JSON compare is correct and cheap here.\n if (JSON.stringify(a[i]) !== JSON.stringify(b[i])) return false;\n }\n return true;\n}\n","// schema.ts — the field schema: the contract that binds frontend and backend.\n//\n// A `FieldSchema` is the runtime, frontend-facing projection of a JSON schema\n// document (schema/query-table.schema.json). The same document is projected on\n// the backend into a Go `Schema` of SQL bindings. Codegen keeps the two in sync.\n//\n// `FieldDef` is organized to make two things obvious (per design feedback):\n//\n// 1. WHAT KIND of field it is — the `source` discriminated union. A `backend`\n// field is a column the server returns and can filter/sort; a `derived`\n// field is computed on the client (or render-only) and is never pushed to\n// the server. Each kind uses its own property subset, so you can't, say,\n// mark a client-only column as server-filterable.\n//\n// 2. CONFIG vs CODE. Everything except `source.accessor` and `render` is\n// declarative, serializable config grouped by capability (`filter`, `sort`,\n// `select`). The only code-bearing members are the derived `accessor` and\n// the `render` function — kept apart from the config so the two don't mix.\n\nimport type { AggOp, FilterOp, OrderByClause, SelectColumn } from \"./query\";\n\nexport type FieldType =\n | \"text\"\n | \"number\"\n | \"enum\"\n | \"datetime\"\n | \"bool\"\n | \"textarray\"; // array-of-text; supports `includes`, and null-as-empty semantics\n\nexport type Align = \"left\" | \"right\" | \"center\";\n\n/** A cell renderer is resolved by name against a `RenderRegistry` (in\n * @pythia-software/query-table-ui) or supplied inline. The type lives in core so FieldDef can\n * reference it, but core never imports React — `unknown` stands in for the node\n * and @pythia-software/query-table-ui narrows it to `React.ReactNode`. */\nexport type CellRenderer<Row = any, V = unknown> = (ctx: {\n value: V;\n row: Row;\n field: FieldDef<Row, V>;\n}) => unknown;\n\n// ---- source: WHAT KIND of field (discriminated union) ---------------------\n\n/** A column the backend returns and can filter/sort. Its SQL expression lives in\n * the JSON schema doc's `bindings` (and the Go Schema), never on the frontend. */\nexport interface BackendField {\n kind: \"backend\";\n /** Dotted path into the API row when it differs from `name` (e.g. \"enqueued_at.Time\"). */\n path?: string;\n /** Computed SQL with no backing column (e.g. is_starred). Documentation hint\n * surfaced to the picker; the actual expression is server-side. */\n synthetic?: boolean;\n}\n\n/** A value computed on the client, or a purely render-driven column. Never\n * pushed to the server; client-side filtering/sorting (if any) reads `accessor`. */\nexport interface DerivedField<Row = any, V = unknown> {\n kind: \"derived\";\n /** Compute the value from the row for client filter/sort. Omit for render-only\n * columns (the renderer reads the whole row from CellContext instead). */\n accessor?: (row: Row) => V;\n}\n\nexport type FieldSource<Row = any, V = unknown> = BackendField | DerivedField<Row, V>;\n\n// ---- capability config (declarative; serializable; shared with backend) ----\n\n/** How filter-value candidates are offered. Autocomplete is the DEFAULT for\n * every field (design feedback): the value input is a combobox backed by\n * Transport.fetchDistinctValues, which takes a search string so large domains\n * refine per keystroke on the backend. `static` is for closed domains you don't\n * want a round-trip for; `freeform` opts out of suggestions entirely. */\nexport type FilterValues =\n | { source: \"autocomplete\" }\n | { source: \"static\"; options: string[] }\n | { source: \"freeform\" };\n\nexport interface FilterConfig {\n /** Whether this field is filterable. Default: backend ⇒ true, derived ⇒ false. */\n enabled?: boolean;\n /** Evaluate on the backend (predicate pushdown)? Default: backend ⇒ true.\n * false ⇒ the clause is stripped from the server request and applied locally\n * by applyQuery (the \"serverFilter:false\" derived/JSONB-less columns). */\n pushdown?: boolean;\n /** Override the default operator set for the field's type. */\n ops?: FilterOp[];\n /** Value-suggestion strategy. Default { source: \"autocomplete\" }. */\n values?: FilterValues;\n}\n\nexport interface SortConfig {\n /** Whether this field is sortable. Default: backend ⇒ true, derived ⇒ false. */\n enabled?: boolean;\n /** Server field to ORDER BY when it differs from the display field\n * (e.g. a \"★\" column whose server sort key is `is_starred`). */\n field?: string;\n}\n\nexport interface SelectConfig {\n /** Can this appear as a visible column? Default true. false ⇒ filter-only field. */\n enabled?: boolean;\n /** Shown by default when QueryState.select is empty. */\n default?: boolean;\n /** Default column width (px); a per-column override rides in QueryState.select. */\n width?: number;\n align?: Align;\n}\n\nexport interface AggregateConfig {\n /** Can this field be a metric's MEASURE (sum/avg/min/max/count_distinct)?\n * Default: backend ⇒ true, derived ⇒ false. */\n measure?: boolean;\n /** Can this field be a GROUP BY key? Default: enum/text/bool ⇒ true. */\n groupable?: boolean;\n /** Override the default aggregate-op set for the field's type. */\n ops?: AggOp[];\n}\n\n// ---- the field ------------------------------------------------------------\n\nexport interface FieldDef<Row = any, V = unknown> {\n // identity\n name: string; // canonical id; matches the backend binding key + Where/OrderBy `field`\n label: string;\n type: FieldType;\n\n // WHAT KIND of field (value origin + server capability)\n source: FieldSource<Row, V>;\n\n // capabilities (declarative config; omit for sensible per-source defaults)\n filter?: FilterConfig;\n sort?: SortConfig;\n select?: SelectConfig;\n aggregate?: AggregateConfig;\n\n // discovery (picker UX, declarative)\n group?: string; // grouping bucket\n alias?: string; // single alternative search term\n aliases?: string[]; // extra search terms\n\n // presentation (the only code-bearing field): registry key or inline fn\n render?: string | CellRenderer<Row, V>;\n}\n\n/** The frontend's loaded schema for one dataset. */\nexport interface FieldSchema<Row = any> {\n /** Dataset id, also the StorageAdapter namespace for saved queries. */\n name: string;\n /** Field that yields a row's stable id for selection / per-row refresh. */\n idField: string;\n fields: FieldDef<Row>[];\n /** Sort applied when QueryState.orderBy is empty. */\n defaultSort?: OrderByClause[];\n /** Columns shown when QueryState.select is empty. */\n defaultSelect?: SelectColumn[];\n /** Page size for a fresh query. */\n defaultLimit?: number;\n}\n\n// ---- derived capability helpers (the per-source defaults, in one place) ----\n\nexport function isFilterable(f: FieldDef): boolean {\n return f.filter?.enabled ?? f.source.kind === \"backend\";\n}\nexport function isPushdownFilter(f: FieldDef): boolean {\n return isFilterable(f) && (f.filter?.pushdown ?? f.source.kind === \"backend\");\n}\nexport function isSortable(f: FieldDef): boolean {\n return f.sort?.enabled ?? f.source.kind === \"backend\";\n}\nexport function isSelectable(f: FieldDef): boolean {\n return f.select?.enabled ?? true;\n}\nexport function filterValues(f: FieldDef): FilterValues {\n return f.filter?.values ?? { source: \"autocomplete\" };\n}\n\n// ---- schema operations ----------------------------------------------------\n\n/** Index fields by name for O(1) lookup. */\nexport function indexFields<Row>(schema: FieldSchema<Row>): Map<string, FieldDef<Row>> {\n return new Map(schema.fields.map((f) => [f.name, f]));\n}\n\n/** Resolve the ordered, selectable FieldDefs for a query, falling back to schema\n * defaults, then to every `select.default` field. Unknown/duplicate/unselectable\n * names in `select` are dropped. */\nexport function selectedFields<Row>(\n schema: FieldSchema<Row>,\n q: { select: SelectColumn[] },\n): FieldDef<Row>[] {\n const order =\n q.select.length > 0\n ? q.select.map((c) => c.field)\n : (schema.defaultSelect ?? schema.fields.filter((f) => f.select?.default).map((f) => ({ field: f.name }))).map(\n (c) => c.field,\n );\n const seen = new Set<string>();\n const out: FieldDef<Row>[] = [];\n for (const name of order) {\n if (seen.has(name)) continue;\n const f = resolveField(schema, name);\n if (!f || !isSelectable(f)) continue;\n seen.add(name);\n seen.add(f.name);\n out.push(f);\n }\n return out;\n}\n\n/** Resolve a field token to a concrete field by canonical name, case-insensitive\n * name match, or alias match. */\nexport function resolveField<Row>(schema: FieldSchema<Row>, field: string): FieldDef<Row> | undefined {\n const token = field.trim();\n if (!token) return undefined;\n\n const byName = indexFields(schema);\n const byExactName = byName.get(token);\n if (byExactName) return byExactName;\n\n const needle = token.toLowerCase();\n for (const f of schema.fields) {\n if (f.name.toLowerCase() === needle) return f;\n if (aliasMatches(f, needle)) return f;\n }\n return undefined;\n}\n\n/** Return the canonical name for a query token, or undefined when unresolved. */\nexport function resolveFieldName<Row>(schema: FieldSchema<Row>, field: string): string | undefined {\n return resolveField(schema, field)?.name;\n}\n\n/** All configured aliases for a field (deduped, preserving display shape). */\nexport function aliasTermsFor<Row>(field: FieldDef<Row>): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n\n const add = (raw: unknown) => {\n if (typeof raw !== \"string\") return;\n const value = raw.trim();\n if (!value) return;\n const key = value.toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n out.push(value);\n };\n\n add(field.alias);\n if (field.aliases) {\n for (const alias of field.aliases) add(alias);\n }\n\n return out;\n}\n\nfunction aliasMatches(field: FieldDef, needle: string): boolean {\n for (const alias of aliasTermsFor(field)) {\n if (alias.toLowerCase() === needle) return true;\n }\n return false;\n}\n\n/** Read a field's value from a row: derived `accessor` if present, else the\n * backend `path` (dotted) / `name`. Returns null when any path segment is missing. */\nexport function readFieldValue<Row, V = unknown>(field: FieldDef<Row, V>, row: Row): V | null {\n if (row == null) return null;\n if (field.source.kind === \"derived\" && field.source.accessor) {\n return field.source.accessor(row);\n }\n const path = (field.source.kind === \"backend\" && field.source.path) || field.name;\n const r = row as Record<string, unknown>;\n if (path in r) return (r[path] ?? null) as V | null;\n let cur: unknown = row;\n for (const part of path.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") return null;\n cur = (cur as Record<string, unknown>)[part];\n }\n return (cur ?? null) as V | null;\n}\n\n/** Parse + validate a raw JSON schema document into a FieldSchema. The doc is the\n * same one the Go backend loads; here we drop backend `bindings` (the frontend\n * never sees SQL) and leave `render` as a string key the consumer maps to a\n * renderer at runtime. Throws with a readable message on a malformed document. */\nexport function loadSchema<Row = any>(doc: unknown): FieldSchema<Row> {\n const d = doc as Record<string, any>;\n if (!d || typeof d !== \"object\") throw new Error(\"schema: document is not an object\");\n if (typeof d.name !== \"string\") throw new Error(\"schema: missing `name`\");\n if (typeof d.idField !== \"string\") throw new Error(\"schema: missing `idField`\");\n if (!Array.isArray(d.fields) || d.fields.length === 0) throw new Error(\"schema: `fields` must be a non-empty array\");\n\n const fields: FieldDef<Row>[] = d.fields.map((raw: any, i: number) => projectField<Row>(raw, i));\n return {\n name: d.name,\n idField: d.idField,\n fields,\n defaultSort: d.defaultSort,\n defaultSelect: d.defaultSelect,\n defaultLimit: d.defaultLimit,\n };\n}\n\nfunction projectField<Row>(raw: any, i: number): FieldDef<Row> {\n if (!raw || typeof raw.name !== \"string\") throw new Error(`schema: field[${i}] missing \\`name\\``);\n if (typeof raw.label !== \"string\") throw new Error(`schema: field ${raw.name} missing \\`label\\``);\n if (typeof raw.type !== \"string\") throw new Error(`schema: field ${raw.name} missing \\`type\\``);\n\n // `source` accepts a string shorthand (\"backend\"|\"derived\") or an object.\n const rs = raw.source ?? (raw.bindings ? \"backend\" : \"derived\");\n let source: FieldSource<Row>;\n if (typeof rs === \"string\") {\n source = rs === \"derived\" ? { kind: \"derived\" } : { kind: \"backend\" };\n } else if (rs.kind === \"derived\") {\n source = { kind: \"derived\" }; // accessor is code → attached at runtime, never from JSON\n } else {\n source = { kind: \"backend\", path: rs.path, synthetic: rs.synthetic };\n }\n\n return {\n name: raw.name,\n label: raw.label,\n type: raw.type,\n source,\n filter: raw.filter,\n sort: raw.sort,\n select: raw.select,\n aggregate: raw.aggregate,\n group: raw.group,\n alias: raw.alias,\n aliases: normalizeAliases(raw.aliases, raw.alias),\n render: raw.render, // string key from JSON; consumer may override with a fn\n };\n}\n\nfunction normalizeAliases(rawAliases: unknown, rawAlias: unknown): string[] {\n const aliases: string[] = [];\n const seen = new Set<string>();\n const add = (raw: unknown) => {\n if (typeof raw !== \"string\") return;\n const value = raw.trim();\n if (!value) return;\n const key = value.toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n aliases.push(value);\n };\n\n add(rawAlias);\n if (Array.isArray(rawAliases)) {\n for (const item of rawAliases) add(item);\n }\n return aliases;\n}\n","// ops.ts — which operators a field type allows, and value coercion.\n//\n// Single source of truth for \"what filters can I build on this field\", shared by\n// the QueryBuilder UI, the CellMenu quick-filters, and client-side applyQuery.\n// The backend (backends/go) enforces the same matrix independently — never trust\n// the client.\n//\n// Every value can be NULL, so `is_null`/`is_not_null` are available on EVERY\n// type, including bool (design feedback).\n\nimport type { FilterOp, FieldType, FieldDef } from \"./index\";\n\nconst NULLITY: FilterOp[] = [\"is_null\", \"is_not_null\"];\n\n/** Default operator set per type. A FieldDef.filter.ops overrides this. */\nexport const OPS_BY_TYPE: Record<FieldType, FilterOp[]> = {\n text: [\"=\", \"!=\", \"contains\", \"starts_with\", \"ends_with\", ...NULLITY],\n enum: [\"=\", \"!=\", ...NULLITY],\n number: [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", ...NULLITY],\n datetime: [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", ...NULLITY],\n bool: [\"=\", \"!=\", ...NULLITY],\n textarray: [\"includes\", ...NULLITY],\n};\n\n/** Operators that take no value (the value input is hidden). */\nexport const NULLARY_OPS: ReadonlySet<FilterOp> = new Set<FilterOp>(NULLITY);\n\n/** Effective operators for a field (its override, else the type default). */\nexport function opsForField(field: Pick<FieldDef, \"type\" | \"filter\">): FilterOp[] {\n return field.filter?.ops ?? OPS_BY_TYPE[field.type];\n}\n\n/** Whether an op is valid for a type per the default matrix (server mirror lives\n * in Go opAllowed; keep them in lockstep). */\nexport function opAllowedForType(type: FieldType, op: FilterOp): boolean {\n return OPS_BY_TYPE[type].includes(op);\n}\n\n/** Coerce a raw string value to the type the field expects, for client-side\n * comparison. Throws on invalid input (e.g. a non-numeric value on a number\n * field) so callers can surface it rather than silently mis-filtering. */\nexport function coerceValue(type: FieldType, raw: string): string | number | boolean {\n switch (type) {\n case \"number\": {\n const n = Number(raw);\n if (raw.trim() === \"\" || Number.isNaN(n)) throw new Error(`not a number: ${JSON.stringify(raw)}`);\n return n;\n }\n case \"bool\": {\n const v = raw.toLowerCase();\n if (v === \"true\" || v === \"1\" || v === \"t\") return true;\n if (v === \"false\" || v === \"0\" || v === \"f\") return false;\n throw new Error(`not a bool: ${JSON.stringify(raw)}`);\n }\n case \"datetime\": {\n const ms = Date.parse(raw);\n if (Number.isNaN(ms)) throw new Error(`not a datetime: ${JSON.stringify(raw)}`);\n return ms;\n }\n default:\n return raw;\n }\n}\n","// agg.ts — which aggregate ops a field type allows, and which fields can be a\n// measure or a group-by key. The single source of truth shared by the\n// QueryBuilder's metric editor, the client-side applyAggregations, and (mirrored\n// independently in backends/go) the SQL compiler. Never trust the client: the Go\n// `aggOpAllowed` enforces the same matrix before any GROUP BY is built.\n\nimport type { AggOp } from \"./query\";\nimport type { FieldType, FieldDef } from \"./schema\";\n\n/** Default aggregate ops per field type. A FieldDef.aggregate.ops overrides this.\n * `count`/`count_distinct` work on anything countable; sum/avg are numeric-only;\n * min/max need an ordered domain (numbers, datetimes, and lexical text/enum). */\nexport const AGG_OPS_BY_TYPE: Record<FieldType, AggOp[]> = {\n number: [\"count\", \"count_distinct\", \"sum\", \"avg\", \"min\", \"max\"],\n datetime: [\"count\", \"count_distinct\", \"min\", \"max\"],\n enum: [\"count\", \"count_distinct\", \"min\", \"max\"],\n text: [\"count\", \"count_distinct\", \"min\", \"max\"],\n bool: [\"count\", \"count_distinct\"],\n textarray: [\"count\"],\n};\n\n/** Ops that reduce a measure column. `count` is the only op that may omit a\n * field (counting rows), so it's the only one absent here. */\nexport const AGG_OPS_NEEDING_FIELD: ReadonlySet<AggOp> = new Set<AggOp>([\n \"count_distinct\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n]);\n\n/** Whether an op requires a measure column. */\nexport function aggOpNeedsField(op: AggOp): boolean {\n return AGG_OPS_NEEDING_FIELD.has(op);\n}\n\n/** Effective aggregate ops for a field (its override, else the type default). */\nexport function aggOpsForField(field: Pick<FieldDef, \"type\" | \"aggregate\">): AggOp[] {\n return field.aggregate?.ops ?? AGG_OPS_BY_TYPE[field.type];\n}\n\n/** Whether an op is valid for a type per the default matrix (Go aggOpAllowed is\n * the server mirror; keep them in lockstep). */\nexport function aggOpAllowedForType(type: FieldType, op: AggOp): boolean {\n return AGG_OPS_BY_TYPE[type].includes(op);\n}\n\n/** Can this field be the MEASURE of a metric? Only backend fields (they have SQL\n * to aggregate); derived/render-only columns are never pushed to the server. An\n * explicit `aggregate.measure` wins over the type default. */\nexport function isMeasurable(field: FieldDef): boolean {\n if (field.source.kind !== \"backend\") return false;\n if (field.aggregate?.measure != null) return field.aggregate.measure;\n return aggOpsForField(field).length > 0;\n}\n\n/** Can this field be a GROUP BY key? Backend fields only. Default: low-cardinality\n * types (enum/text/bool); numbers/datetimes need bucketing (out of scope) so they\n * default off, but `aggregate.groupable: true` opts any backend field in. */\nexport function isGroupable(field: FieldDef): boolean {\n if (field.source.kind !== \"backend\") return false;\n if (field.aggregate?.groupable != null) return field.aggregate.groupable;\n return field.type === \"enum\" || field.type === \"text\" || field.type === \"bool\";\n}\n","// encode.ts — URL serialization + the server-bound query subset.\n//\n// Two distinct serializations, deliberately kept apart:\n//\n// 1. encodeQuery / decodeQuery — the *whole* QueryState (incl. view state:\n// select columns + widths) ⇄ a base64url `?q=` token. This is what makes\n// reload, back/forward, bookmarks, and \"send a coworker your view\" all work\n// (decisions #4 + #5). Defaults are omitted to keep the token short.\n//\n// 2. toServerQuery — the subset the backend acts on: pushdown\n// WHERE clauses, server-sortable ORDER BY (remapped through sort.field), the\n// page window, and the field names to SELECT (so the backend evaluates the\n// computed/synthetic columns the view needs — requested ∪ always-needed,\n// design decision #2). Per-column widths never leave the client.\n//\n// Back-compat: decodeQuery accepts legacy compact shapes: `o` as a single\n// object (pre-multi-sort) and `c`/`s` as a string[] (pre-width).\n\nimport { EMPTY_QUERY, MAX_QUERY_TOKEN_LENGTH, normalizeQueryState } from \"./query\";\nimport type { QueryState, WhereClause, OrderByClause, SelectColumn, AggregationClause } from \"./query\";\nimport type { FieldSchema } from \"./schema\";\nimport { indexFields, isFilterable, isPushdownFilter, isSortable, resolveFieldName, selectedFields } from \"./schema\";\n\n// ---- base64url (browser + node) -------------------------------------------\n\nfunction toBase64Url(json: string): string {\n let b64: string;\n if (typeof Buffer !== \"undefined\") {\n b64 = Buffer.from(json, \"utf8\").toString(\"base64\");\n } else {\n const bytes = new TextEncoder().encode(json);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n b64 = btoa(bin);\n }\n return b64.replace(/=+$/, \"\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n}\n\nfunction fromBase64Url(s: string): string {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof Buffer !== \"undefined\") return Buffer.from(b64, \"base64\").toString(\"utf8\");\n const bin = atob(b64);\n const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n}\n\n// ---- compact wire form ----------------------------------------------------\n// Keys are short to keep `?q=` small; any field at its default is omitted.\n// s = select (as [field] or [field, width] tuples)\n// w = where, o = orderBy, l = limit, f = offset\n\ninterface CompactQuery {\n s?: Array<[string] | [string, number]> | string[]; // string[] = legacy\n w?: WhereClause[];\n o?: OrderByClause[] | OrderByClause; // object accepted for legacy decode\n l?: number;\n f?: number;\n g?: AggregationClause[]; // aggregation metrics (omitted when none)\n c?: string[]; // legacy alias for select column names\n}\n\n/** QueryState → base64url token (omitting defaults). All-default query → \"\". */\nexport function encodeQuery(q: QueryState): string {\n q = normalizeQueryState(q);\n const c: CompactQuery = {};\n if (q.select.length)\n c.s = q.select.map((col): [string] | [string, number] => (col.width != null ? [col.field, col.width] : [col.field]));\n if (q.where.length) c.w = q.where;\n if (q.orderBy.length) c.o = q.orderBy;\n c.l = q.limit;\n if (q.offset) c.f = q.offset;\n if (q.aggregations?.length) c.g = q.aggregations;\n if (Object.keys(c).length === 0) return \"\";\n return toBase64Url(JSON.stringify(c));\n}\n\n/** base64url token → QueryState. Tolerant: bad input and legacy shapes both\n * normalize to a valid QueryState (never throws). */\nexport function decodeQuery(token: string): QueryState {\n if (!token) return { ...EMPTY_QUERY };\n if (token.length > MAX_QUERY_TOKEN_LENGTH) return { ...EMPTY_QUERY };\n try {\n const c = JSON.parse(fromBase64Url(token)) as CompactQuery;\n const out = normalizeQueryState({\n select: normalizeSelect(c.s ?? c.c),\n where: Array.isArray(c.w) ? c.w : [],\n orderBy: normalizeOrderBy(c.o),\n limit: c.l,\n offset: c.f,\n aggregations: c.g,\n });\n return out;\n } catch {\n return { ...EMPTY_QUERY };\n }\n}\n\nfunction normalizeSelect(s: CompactQuery[\"s\"] | CompactQuery[\"c\"]): SelectColumn[] {\n if (!Array.isArray(s)) return [];\n return s\n .map((item): SelectColumn | null => {\n if (typeof item === \"string\") return { field: item }; // legacy string[]\n if (Array.isArray(item) && typeof item[0] === \"string\") {\n return typeof item[1] === \"number\" ? { field: item[0], width: item[1] } : { field: item[0] };\n }\n return null;\n })\n .filter((x): x is SelectColumn => x != null);\n}\n\nfunction normalizeOrderBy(o: CompactQuery[\"o\"]): OrderByClause[] {\n if (!o) return [];\n if (Array.isArray(o)) return o;\n return [o]; // legacy single-object form → one-element array\n}\n\n// ---- server-bound subset --------------------------------------------------\n\n/** The backend request derived from a QueryState + its schema. Mirrors the Go\n * `WireQuery`. View-only state (column widths) is intentionally absent. */\nexport interface ServerQuery {\n /** field names to return — visible columns ∪ fields referenced by where/orderBy. */\n select: string[];\n /** only clauses on pushdown-filterable fields. */\n where: WhereClause[];\n /** only terms on server-sortable fields, with `field` already remapped to the\n * field's server sort key (FieldDef.sort.field) when set. */\n orderBy: OrderByClause[];\n limit: number;\n offset: number;\n}\n\n/** Project a QueryState into the server request, dropping client-only filters,\n * client-only sorts, and remapping sort fields through `sort.field`. */\nexport function toServerQuery<Row>(q: QueryState, schema: FieldSchema<Row>): ServerQuery {\n q = normalizeQueryState(q);\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n\n const where = q.where.filter((cl) => {\n const field = resolveField(cl.field);\n const f = byName.get(field);\n return f != null && isPushdownFilter(f);\n });\n\n const orderBy: OrderByClause[] = [];\n for (const term of q.orderBy) {\n const field = resolveField(term.field);\n const f = byName.get(field);\n if (!f || !isSortable(f) || f.source.kind !== \"backend\") continue;\n orderBy.push({ ...term, field: f.sort?.field ?? f.name });\n }\n\n // SELECT = the BACKEND columns the response rows must carry: visible backend\n // columns, the id (for selection/refresh), and any backend column a\n // CLIENT-SIDE filter reads. Derived/render-only columns are computed on the\n // client and have no SQL to select; pushdown filters/sorts run in SQL and need\n // not be returned (design decision #2: requested ∪ always-needed).\n const select = new Set<string>(\n selectedFields(schema, q)\n .filter((f) => f.source.kind === \"backend\")\n .map((f) => f.name),\n );\n select.add(schema.idField);\n for (const cl of q.where) {\n const field = resolveField(cl.field);\n const f = byName.get(field);\n if (f && isFilterable(f) && !isPushdownFilter(f) && f.source.kind === \"backend\") select.add(field);\n }\n\n return { select: [...select], where, orderBy, limit: q.limit, offset: q.offset };\n}\n\n// ---- aggregation server subset --------------------------------------------\n\n/** The backend request for the metric panel. Scope is the whole filtered set:\n * the SAME pushdown WHERE as the rows query, but NO ORDER BY / LIMIT / OFFSET —\n * metrics describe every matching row, not the visible page. Mirrors the Go\n * AggSpec list. */\nexport interface AggregationRequest {\n where: WhereClause[];\n aggregations: AggregationClause[];\n}\n\n/** One group's result. `keys` has one entry per AggregationClause.groupBy field,\n * in axis order (`[]` for a grand total); a null key is the NULL/empty bucket. */\nexport interface AggregationBucket {\n keys: (string | null)[];\n /** The metric: a number for count/sum/avg, or the column's value for min/max\n * (which may be a string for text/datetime). null when undefined (e.g. avg of\n * an all-null column). */\n value: number | string | null;\n /** COUNT(*) of rows in the group — always present, even when `value` isn't a\n * count, so the panel can show group sizes / shares. */\n count: number;\n}\n\nexport interface AggregationResultEntry {\n /** Echoes AggregationClause.id. */\n id: string;\n buckets: AggregationBucket[];\n}\n\nexport interface AggregationResult {\n /** One entry per requested aggregation, in request order. */\n metrics: AggregationResultEntry[];\n}\n\n/** Project a QueryState into the metric request: the pushdown WHERE subset (same\n * rule as toServerQuery) plus the aggregations whose measure + group fields are\n * all server-capable backend columns. Aggregations referencing a derived /\n * unknown field are dropped — the server has no SQL for them. */\nexport function toAggregationQuery<Row>(q: QueryState, schema: FieldSchema<Row>): AggregationRequest {\n q = normalizeQueryState(q);\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n\n const where = q.where.filter((cl) => {\n const f = byName.get(resolveField(cl.field));\n return f != null && isPushdownFilter(f);\n });\n\n const isBackend = (name: string | undefined): boolean => {\n if (name == null) return true; // omitted measure (count(*)) is fine\n const field = resolveField(name);\n const f = byName.get(field);\n return f != null && f.source.kind === \"backend\";\n };\n\n const aggregations = (q.aggregations ?? []).filter(\n (a) => isBackend(a.field) && a.groupBy.every((g) => isBackend(g)),\n );\n\n return { where, aggregations };\n}\n","// apply.ts — client-side filter + multi-sort + paginate.\n//\n// applyQuery is the client mirror of the backend compiler. Three jobs:\n// 1. The executor for purely client-side datasets (no backend at all).\n// 2. The executor for derived / non-pushdown columns the backend can't filter.\n// 3. Idempotent defense-in-depth over server-filtered fields (re-applying a\n// server-handled clause must not change the result).\n//\n// It must agree with backends/go's Compile on operator semantics:\n// - `contains`/`starts_with`/`ends_with` are case-insensitive\n// - textarray `is_null` = empty-or-missing; `is_not_null` = any element;\n// `includes` = case-insensitive membership\n// - NULLs sort per OrderByClause.nulls (default \"last\")\n// - multi-sort is a stable lexicographic fold over orderBy in priority order\n\nimport type { AggOp, AggregationClause, QueryState, WhereClause } from \"./query\";\nimport type { FieldSchema, FieldDef } from \"./schema\";\nimport type { AggregationBucket, AggregationResult } from \"./encode\";\nimport { indexFields, readFieldValue, resolveFieldName } from \"./schema\";\nimport { coerceValue } from \"./ops\";\n\nexport interface ApplyResult<Row> {\n /** The page slice (after filter + sort + offset/limit). */\n rows: Row[];\n /** Total matching rows *before* pagination — powers \"showing N of M\". */\n total: number;\n}\n\n/** Filter + multi-sort + paginate `rows` per `q`, resolving field types/paths\n * from `schema`. Pure; never mutates `rows`. Clauses/sorts on unknown fields are\n * ignored (the backend already enforced its own allowlist). */\nexport function applyQuery<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): ApplyResult<Row> {\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n const whereClauses = q.where.map((clause) => ({ ...clause, field: resolveField(clause.field) }));\n const orderBy = q.orderBy.map((term) => ({ ...term, field: resolveField(term.field) }));\n\n let out = rows.filter((row) => whereClauses.every((cl) => matchesWith(byName, row, cl)));\n const total = out.length;\n\n if (orderBy.length) {\n const terms = orderBy\n .map((t) => ({ field: byName.get(t.field), dir: t.dir, nullsLast: (t.nulls ?? \"last\") === \"last\" }))\n .filter((t): t is { field: FieldDef<Row>; dir: \"asc\" | \"desc\"; nullsLast: boolean } => t.field != null);\n out = [...out].sort((a, b) => {\n for (const t of terms) {\n const av = readFieldValue(t.field, a);\n const bv = readFieldValue(t.field, b);\n const aNull = av == null;\n const bNull = bv == null;\n if (aNull || bNull) {\n if (aNull && bNull) continue;\n return (aNull ? 1 : -1) * (t.nullsLast ? 1 : -1);\n }\n const cmp = compare(av, bv);\n if (cmp !== 0) return t.dir === \"asc\" ? cmp : -cmp;\n }\n return 0;\n });\n }\n\n const start = q.offset > 0 ? q.offset : 0;\n const end = q.limit > 0 ? start + q.limit : out.length;\n return { rows: out.slice(start, end), total };\n}\n\n/** Does one row satisfy one clause? Exposed for the CellMenu preview + tests. */\nexport function matchesClause<Row>(row: Row, clause: WhereClause, schema: FieldSchema<Row>): boolean {\n return matchesWith(indexFields(schema), row, clause);\n}\n\n/** Client-side mirror of the backend GROUP BY (the executor for `clientRows`\n * mode / the demo). Scope matches the server contract: the WHERE-filtered set\n * only — ORDER BY / LIMIT / OFFSET are intentionally ignored, so a metric\n * reflects every matching row, not the visible page. Must agree with\n * backends/go's aggregate compile on op semantics. */\nexport function applyAggregations<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): AggregationResult {\n const byName = indexFields(schema);\n const resolveField = (name: string) => resolveFieldName(schema, name) ?? name;\n const whereClauses = q.where.map((clause) => ({ ...clause, field: resolveField(clause.field) }));\n const filtered = rows.filter((row) => whereClauses.every((cl) => matchesWith(byName, row, cl)));\n const metrics = (q.aggregations ?? []).map((agg) => ({\n id: agg.id,\n buckets: computeBuckets(filtered, agg, byName, resolveField),\n }));\n return { metrics };\n}\n\nfunction computeBuckets<Row>(\n rows: Row[],\n agg: AggregationClause,\n byName: Map<string, FieldDef<Row>>,\n resolveField: (field: string) => string,\n): AggregationBucket[] {\n // One slot per groupBy entry, preserving position. An unresolved field stays\n // in the array as `undefined` so every bucket's `keys` has exactly one element\n // per requested group field — the panel renders keys positionally against\n // `clause.groupBy`, so dropping an entry here would shift every later column.\n const groupFields = agg.groupBy.map((n) => byName.get(resolveField(n)));\n const measure = agg.field ? byName.get(resolveField(agg.field)) : undefined;\n\n const groups = new Map<string, { keys: (string | null)[]; rows: Row[] }>();\n for (const row of rows) {\n const keys = groupFields.map((f) => {\n if (!f) return null; // unresolved group field → NULL bucket, never a missing column\n const v = readFieldValue(f, row);\n return v == null || v === \"\" ? null : String(v);\n });\n const k = JSON.stringify(keys);\n let g = groups.get(k);\n if (!g) {\n g = { keys, rows: [] };\n groups.set(k, g);\n }\n g.rows.push(row);\n }\n\n const buckets = [...groups.values()].map((g) => ({\n keys: g.keys,\n count: g.rows.length,\n value: aggValue(agg.op, measure, g.rows),\n }));\n return sortBuckets(buckets);\n}\n\nfunction aggValue<Row>(op: AggOp, measure: FieldDef<Row> | undefined, rows: Row[]): number | string | null {\n if (op === \"count\") {\n if (!measure) return rows.length; // COUNT(*)\n let n = 0;\n for (const r of rows) if (readFieldValue(measure, r) != null) n++; // COUNT(expr) = non-null\n return n;\n }\n if (!measure) return null;\n const values = rows.map((r) => readFieldValue(measure, r)).filter((v) => v != null);\n\n switch (op) {\n case \"count_distinct\":\n return new Set(values.map((v) => String(v))).size;\n case \"sum\":\n case \"avg\": {\n let sum = 0;\n let n = 0;\n for (const v of values) {\n const num = typeof v === \"number\" ? v : Number(v);\n if (Number.isFinite(num)) {\n sum += num;\n n++;\n }\n }\n if (op === \"avg\") return n ? sum / n : null;\n return n ? sum : null;\n }\n case \"min\":\n case \"max\": {\n let best: unknown;\n for (const v of values) {\n if (best === undefined) {\n best = v;\n continue;\n }\n const cmp = compare(v, best);\n if (op === \"min\" ? cmp < 0 : cmp > 0) best = v;\n }\n return (best as number | string | undefined) ?? null;\n }\n default:\n return null;\n }\n}\n\n/** Stable display order: descending by the metric (or group size when the metric\n * isn't numeric), then by group key. The 2D pivot ignores this; the 1D bar list\n * and the flat N-column table read top-to-bottom from it. */\nfunction sortBuckets(buckets: AggregationBucket[]): AggregationBucket[] {\n return [...buckets].sort((a, b) => {\n const av = typeof a.value === \"number\" ? a.value : a.count;\n const bv = typeof b.value === \"number\" ? b.value : b.count;\n if (av !== bv) return bv - av;\n return JSON.stringify(a.keys).localeCompare(JSON.stringify(b.keys));\n });\n}\n\nfunction matchesWith<Row>(byName: Map<string, FieldDef<Row>>, row: Row, clause: WhereClause): boolean {\n const field = byName.get(clause.field);\n if (!field) return true; // unknown field → no client opinion\n const v = readFieldValue(field, row);\n\n // Nullity, with array-aware semantics.\n if (clause.op === \"is_null\") return Array.isArray(v) ? v.length === 0 : v == null || v === \"\";\n if (clause.op === \"is_not_null\") return Array.isArray(v) ? v.length > 0 : v != null && v !== \"\";\n\n // Empty value on a non-equality op is a \"no filter\" signal (matches the UI +\n // the Go compiler, which skip such clauses).\n if (clause.value === \"\" && clause.op !== \"=\" && clause.op !== \"!=\") return true;\n\n // `includes` is array membership (ARRAY_HAS): a null/missing array contains\n // nothing. Handle before the array/scalar split so null can't fall through.\n if (clause.op === \"includes\") {\n const arr = Array.isArray(v) ? v : [];\n const needle = clause.value.toLowerCase();\n return arr.some((x) => String(x).toLowerCase() === needle);\n }\n\n if (Array.isArray(v)) {\n const needle = clause.value.toLowerCase();\n const hay = v.map((x) => String(x).toLowerCase());\n switch (clause.op) {\n case \"contains\":\n return hay.some((s) => s.includes(needle));\n case \"=\":\n return hay.includes(needle);\n case \"!=\":\n return !hay.includes(needle);\n default:\n return true;\n }\n }\n\n switch (clause.op) {\n case \"=\":\n return String(v ?? \"\") === String(coerceSafe(field, clause.value));\n case \"!=\":\n return String(v ?? \"\") !== String(coerceSafe(field, clause.value));\n case \">\":\n return compare(v, coerceSafe(field, clause.value)) > 0;\n case \">=\":\n return compare(v, coerceSafe(field, clause.value)) >= 0;\n case \"<\":\n return compare(v, coerceSafe(field, clause.value)) < 0;\n case \"<=\":\n return compare(v, coerceSafe(field, clause.value)) <= 0;\n case \"contains\":\n return String(v ?? \"\").toLowerCase().includes(clause.value.toLowerCase());\n case \"starts_with\":\n return String(v ?? \"\").toLowerCase().startsWith(clause.value.toLowerCase());\n case \"ends_with\":\n return String(v ?? \"\").toLowerCase().endsWith(clause.value.toLowerCase());\n default:\n return true;\n }\n}\n\n/** Coerce per the field type, falling back to the raw string if coercion fails\n * (so a malformed value degrades to a string compare rather than throwing). */\nfunction coerceSafe(field: FieldDef, raw: string): string | number | boolean {\n try {\n return coerceValue(field.type, raw);\n } catch {\n return raw;\n }\n}\n\nfunction compare(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return a === b ? 0 : a ? 1 : -1;\n return String(a).localeCompare(String(b));\n}\n","// adapters.ts — the pluggable seams. Everything project-specific is injected\n// here so the package core stays generic.\n//\n// - Transport : how rows + filter-value suggestions are fetched.\n// - StorageAdapter: where saved/last queries live (in-memory by default in\n// the React hook; explicitly durable when callers opt in).\n\nimport { normalizeQueryState } from \"./query\";\nimport type { QueryState, RowId } from \"./query\";\nimport type { ServerQuery, AggregationRequest, AggregationResult } from \"./encode\";\n\n// ---- Transport ------------------------------------------------------------\n\nexport interface FetchRowsResult<Row> {\n rows: Row[];\n /** Total matching rows before pagination (for \"N of M\" + paging bounds). */\n total: number;\n}\n\n/** A keystroke-driven distinct-value lookup. Autocomplete is the DEFAULT value\n * source for every field (design feedback), so this is how the filter combobox\n * finds options. `search` is the user's current input; the backend returns the\n * best matches and whether it truncated (so the UI can prompt \"keep typing\"). */\nexport interface DistinctValuesQuery {\n field: string;\n /** Current substring/prefix the user has typed (empty = top values). */\n search: string;\n /** Max suggestions to return (default chosen by the adapter, ~50). */\n limit?: number;\n}\n\nexport interface DistinctValuesResult {\n values: string[];\n /** true when more matches exist than were returned — refine by typing. */\n hasMore: boolean;\n /** True if the selected field has at least one NULL in the source dataset.\n * False if that field is guaranteed non-null. Omitted when the backend does\n * not compute this metadata. */\n hasNull?: boolean;\n}\n\n/** Per-field metadata for the field picker. */\nexport interface FieldStats {\n distinct?: number;\n min?: string | number;\n max?: string | number;\n}\n\nexport interface Transport<Row = any> {\n /** Run a server query. The only required method — a purely client-side table\n * can omit a Transport entirely and rely on applyQuery over local rows. */\n fetchRows(query: ServerQuery, signal?: AbortSignal): Promise<FetchRowsResult<Row>>;\n\n /** Distinct values for filter autocomplete. Strongly recommended: without it,\n * fields fall back to freeform input. The backend should match `search`\n * server-side (e.g. ILIKE prefix) and cap at `limit` so large domains stay\n * fast and refine per keystroke. */\n fetchDistinctValues?(q: DistinctValuesQuery, signal?: AbortSignal): Promise<DistinctValuesResult>;\n\n /** Run the metric panel's GROUP BY queries (one per requested aggregation),\n * scoped to the WHERE-filtered set with no paging. Optional: without it, the\n * metric panel falls back to applyAggregations over local rows (client mode).\n * Implementations compile each AggSpec with backends/go CompileAggregation,\n * sharing the same WHERE as fetchRows. */\n fetchAggregations?(q: AggregationRequest, signal?: AbortSignal): Promise<AggregationResult>;\n\n /** Re-fetch a single row by id, for in-place updates without a full re-query\n * after a mutation. Optional. */\n fetchRow?(id: RowId, signal?: AbortSignal): Promise<Row | null>;\n\n /** Stats for the field picker. Optional. */\n fetchFieldStats?(fields: string[], signal?: AbortSignal): Promise<Record<string, FieldStats>>;\n}\n\n// ---- Saved queries --------------------------------------------------------\n\nexport interface SavedQuery {\n id: string;\n name: string;\n /** Epoch millis; injected by the caller so core stays deterministic/testable. */\n savedAt: number;\n query: QueryState;\n}\n\n/** Persistence for the \"last\" query (auto-restored) and named saved queries. All\n * methods are namespaced by `key` (the schema/dataset name) so multiple tables\n * on one origin don't collide. Async to allow backend implementations; the\n * browser adapters can still resolve synchronously. */\nexport interface StorageAdapter {\n loadLast(key: string): Promise<QueryState | null>;\n saveLast(key: string, query: QueryState): Promise<void>;\n\n listSaved(key: string): Promise<SavedQuery[]>;\n /** Load the saved query selected as the default view for this key. Optional so\n * existing backend adapters keep working until they add first-class support. */\n loadDefaultSaved?(key: string): Promise<SavedQuery | null>;\n /** Set or clear the saved query used as the default view for this key. */\n setDefaultSaved?(key: string, id: string | null): Promise<void>;\n /** Persist a named query snapshot. Rejects when a saved query with the same\n * name already exists in the key namespace. */\n saveNamed(key: string, name: string, query: QueryState, savedAt: number): Promise<SavedQuery>;\n deleteSaved(key: string, id: string): Promise<void>;\n}\n\nconst LAST_PREFIX = \"query-table:last:\";\nconst SAVED_PREFIX = \"query-table:saved:\";\nconst DEFAULT_PREFIX = \"query-table:default:\";\nconst MAX_SAVED_QUERIES = 100;\nconst MAX_SAVED_NAME_LENGTH = 200;\n\nfunction normalizeSavedQuery(value: unknown): SavedQuery | null {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) return null;\n const raw = value as Record<string, unknown>;\n if (\n typeof raw.id !== \"string\" ||\n !raw.id ||\n raw.id.length > 256 ||\n typeof raw.name !== \"string\" ||\n !raw.name ||\n raw.name.length > MAX_SAVED_NAME_LENGTH ||\n typeof raw.savedAt !== \"number\" ||\n !Number.isFinite(raw.savedAt) ||\n raw.query == null ||\n typeof raw.query !== \"object\"\n ) {\n return null;\n }\n return { id: raw.id, name: raw.name, savedAt: raw.savedAt, query: normalizeQueryState(raw.query) };\n}\n\nfunction normalizeSavedQueries(value: unknown): SavedQuery[] {\n if (!Array.isArray(value)) return [];\n return value\n .slice(0, MAX_SAVED_QUERIES)\n .map(normalizeSavedQuery)\n .filter((item): item is SavedQuery => item != null);\n}\n\n/** In-memory persistence used by default by the React package. It supports the\n * full saved-query UI for the lifetime of a mounted table without writing\n * filter values to durable browser storage. */\nexport function memoryStorageAdapter(): StorageAdapter {\n const last = new Map<string, QueryState>();\n const saved = new Map<string, SavedQuery[]>();\n const defaults = new Map<string, string>();\n\n return {\n async loadLast(key) {\n const query = last.get(key);\n return query ? normalizeQueryState(query) : null;\n },\n async saveLast(key, query) {\n last.set(key, normalizeQueryState(query));\n },\n async listSaved(key) {\n return (saved.get(key) ?? []).map((item) => ({ ...item, query: normalizeQueryState(item.query) }));\n },\n async loadDefaultSaved(key) {\n const id = defaults.get(key);\n return id ? (saved.get(key) ?? []).find((item) => item.id === id) ?? null : null;\n },\n async setDefaultSaved(key, id) {\n if (id == null) {\n defaults.delete(key);\n return;\n }\n if (!(saved.get(key) ?? []).some((item) => item.id === id)) throw new Error(`Saved query \"${id}\" does not exist.`);\n defaults.set(key, id);\n },\n async saveNamed(key, name, query, savedAt) {\n const normalizedName = name.trim();\n if (!normalizedName || normalizedName.length > MAX_SAVED_NAME_LENGTH) {\n throw new Error(`Saved query name must be between 1 and ${MAX_SAVED_NAME_LENGTH} characters.`);\n }\n const items = saved.get(key) ?? [];\n if (items.length >= MAX_SAVED_QUERIES) throw new Error(`At most ${MAX_SAVED_QUERIES} saved queries are allowed.`);\n if (items.some((item) => item.name === normalizedName)) throw new Error(`Saved query \"${normalizedName}\" already exists.`);\n const item: SavedQuery = {\n id: `${savedAt}-${Math.round((savedAt * 9301 + 49297) % 233280)}`,\n name: normalizedName,\n savedAt,\n query: normalizeQueryState(query),\n };\n saved.set(key, [...items, item]);\n return item;\n },\n async deleteSaved(key, id) {\n saved.set(key, (saved.get(key) ?? []).filter((item) => item.id !== id));\n if (defaults.get(key) === id) defaults.delete(key);\n },\n };\n}\n\n/** Opt-in StorageAdapter over window.localStorage. Query state includes raw\n * filter values and is stored as cleartext JSON; do not use it for sensitive\n * datasets. Falls back to no persistence when storage is unavailable. */\nexport function localStorageAdapter(): StorageAdapter {\n const ls: Storage | null = (() => {\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n return null; // access can throw in some sandboxes\n }\n })();\n\n const readSaved = (key: string): SavedQuery[] => {\n if (!ls) return [];\n try {\n const raw = ls.getItem(SAVED_PREFIX + key);\n return normalizeSavedQueries(raw ? JSON.parse(raw) : []);\n } catch {\n return [];\n }\n };\n const writeSaved = (key: string, items: SavedQuery[]): void => {\n if (ls) ls.setItem(SAVED_PREFIX + key, JSON.stringify(items));\n };\n const readDefaultId = (key: string): string | null => {\n if (!ls) return null;\n return ls.getItem(DEFAULT_PREFIX + key);\n };\n const writeDefaultId = (key: string, id: string | null): void => {\n if (!ls) return;\n if (id) ls.setItem(DEFAULT_PREFIX + key, id);\n else ls.removeItem(DEFAULT_PREFIX + key);\n };\n\n return {\n async loadLast(key) {\n if (!ls) return null;\n try {\n const raw = ls.getItem(LAST_PREFIX + key);\n return raw ? normalizeQueryState(JSON.parse(raw)) : null;\n } catch {\n return null;\n }\n },\n async saveLast(key, query) {\n if (ls) ls.setItem(LAST_PREFIX + key, JSON.stringify(normalizeQueryState(query)));\n },\n async listSaved(key) {\n return readSaved(key).sort((a, b) => b.savedAt - a.savedAt);\n },\n async loadDefaultSaved(key) {\n const id = readDefaultId(key);\n if (!id) return null;\n const found = readSaved(key).find((q) => q.id === id);\n if (!found) writeDefaultId(key, null);\n return found ?? null;\n },\n async setDefaultSaved(key, id) {\n if (id && !readSaved(key).some((q) => q.id === id)) {\n throw new Error(`Saved query \"${id}\" does not exist.`);\n }\n writeDefaultId(key, id);\n },\n async saveNamed(key, name, query, savedAt) {\n const normalizedName = name.trim();\n if (!normalizedName || normalizedName.length > MAX_SAVED_NAME_LENGTH) {\n throw new Error(`Saved query name must be between 1 and ${MAX_SAVED_NAME_LENGTH} characters.`);\n }\n const items = readSaved(key);\n if (items.length >= MAX_SAVED_QUERIES) throw new Error(`At most ${MAX_SAVED_QUERIES} saved queries are allowed.`);\n if (items.some((q) => q.name === normalizedName)) {\n throw new Error(`Saved query \"${normalizedName}\" already exists.`);\n }\n const item: SavedQuery = {\n id: `${savedAt}-${Math.round((savedAt * 9301 + 49297) % 233280)}`,\n name: normalizedName,\n savedAt,\n query: normalizeQueryState(query),\n };\n items.push(item);\n writeSaved(key, items);\n return item;\n },\n async deleteSaved(key, id) {\n writeSaved(\n key,\n readSaved(key).filter((q) => q.id !== id),\n );\n if (readDefaultId(key) === id) writeDefaultId(key, null);\n },\n };\n}\n"],"mappings":"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","names":["field","resolveField","resolveField","item"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pythia-software/query-table-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Schema-driven data table — framework-agnostic core types and logic.",
|
|
5
5
|
"keywords": ["data-table", "query-builder", "schema", "typescript"],
|
|
6
6
|
"license": "MIT",
|