@mintlify/common 1.0.1309 → 1.0.1311

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/databases/formula/analyze.d.ts +31 -0
  2. package/dist/databases/formula/analyze.js +296 -0
  3. package/dist/databases/formula/ast.d.ts +52 -0
  4. package/dist/databases/formula/ast.js +20 -0
  5. package/dist/databases/formula/compile.d.ts +10 -0
  6. package/dist/databases/formula/compile.js +409 -0
  7. package/dist/databases/formula/dates.d.ts +5 -0
  8. package/dist/databases/formula/dates.js +55 -0
  9. package/dist/databases/formula/errors.d.ts +18 -0
  10. package/dist/databases/formula/errors.js +33 -0
  11. package/dist/databases/formula/filter.d.ts +9 -0
  12. package/dist/databases/formula/filter.js +16 -0
  13. package/dist/databases/formula/format.d.ts +5 -0
  14. package/dist/databases/formula/format.js +66 -0
  15. package/dist/databases/formula/function-docs.d.ts +6 -0
  16. package/dist/databases/formula/function-docs.js +63 -0
  17. package/dist/databases/formula/functions.d.ts +21 -0
  18. package/dist/databases/formula/functions.js +161 -0
  19. package/dist/databases/formula/highlight.d.ts +7 -0
  20. package/dist/databases/formula/highlight.js +54 -0
  21. package/dist/databases/formula/index.d.ts +16 -0
  22. package/dist/databases/formula/index.js +14 -0
  23. package/dist/databases/formula/limits.d.ts +8 -0
  24. package/dist/databases/formula/limits.js +8 -0
  25. package/dist/databases/formula/lower.d.ts +9 -0
  26. package/dist/databases/formula/lower.js +252 -0
  27. package/dist/databases/formula/parse.d.ts +21 -0
  28. package/dist/databases/formula/parse.js +209 -0
  29. package/dist/databases/formula/references.d.ts +20 -0
  30. package/dist/databases/formula/references.js +76 -0
  31. package/dist/databases/formula/rewrite.d.ts +16 -0
  32. package/dist/databases/formula/rewrite.js +29 -0
  33. package/dist/databases/formula/schema.d.ts +29 -0
  34. package/dist/databases/formula/schema.js +117 -0
  35. package/dist/databases/formula/tokenize.d.ts +11 -0
  36. package/dist/databases/formula/tokenize.js +114 -0
  37. package/dist/databases/formula/typecheck.d.ts +11 -0
  38. package/dist/databases/formula/typecheck.js +16 -0
  39. package/dist/databases/formula/types.d.ts +26 -0
  40. package/dist/databases/formula/types.js +27 -0
  41. package/dist/databases/formula/values.d.ts +8 -0
  42. package/dist/databases/formula/values.js +100 -0
  43. package/dist/databases/query/evaluate.d.ts +25 -0
  44. package/dist/databases/query/evaluate.js +481 -0
  45. package/dist/databases/query/index.d.ts +4 -0
  46. package/dist/databases/query/index.js +4 -0
  47. package/dist/databases/query/types.d.ts +69 -0
  48. package/dist/databases/query/types.js +20 -0
  49. package/dist/databases/query/validate.d.ts +8 -0
  50. package/dist/databases/query/validate.js +138 -0
  51. package/dist/databases/query/values.d.ts +4 -0
  52. package/dist/databases/query/values.js +36 -0
  53. package/dist/mdx/snippets/resolveAllImports.d.ts +1 -0
  54. package/dist/mdx/snippets/resolveAllImports.js +9 -7
  55. package/dist/tsconfig.build.tsbuildinfo +1 -1
  56. package/package.json +10 -2
@@ -0,0 +1,26 @@
1
+ import type { FormulaError } from './errors.js';
2
+ export type FormulaScalar = number | string | boolean | Date | null;
3
+ export type FormulaValue = FormulaScalar | FormulaError;
4
+ export type FormulaType = 'number' | 'string' | 'boolean' | 'date' | 'any';
5
+ export type FormulaRow = Readonly<Record<string, unknown>>;
6
+ export type FormulaOption = string | {
7
+ id: string;
8
+ label: string;
9
+ };
10
+ export type FormulaColumn = {
11
+ id: string;
12
+ name: string;
13
+ type: string;
14
+ options?: readonly FormulaOption[];
15
+ config?: {
16
+ expression?: string;
17
+ } | null;
18
+ };
19
+ export declare const NUMBER = 1;
20
+ export declare const STRING = 2;
21
+ export declare const BOOLEAN = 4;
22
+ export declare const DATE = 8;
23
+ export declare const ANY: number;
24
+ export type TypeSet = number;
25
+ export declare const typeName: (types: TypeSet) => FormulaType;
26
+ export declare const describeTypes: (types: TypeSet) => string;
@@ -0,0 +1,27 @@
1
+ export const NUMBER = 1;
2
+ export const STRING = 2;
3
+ export const BOOLEAN = 4;
4
+ export const DATE = 8;
5
+ export const ANY = NUMBER | STRING | BOOLEAN | DATE;
6
+ export const typeName = (types) => {
7
+ if (types === NUMBER)
8
+ return 'number';
9
+ if (types === STRING)
10
+ return 'string';
11
+ if (types === BOOLEAN)
12
+ return 'boolean';
13
+ if (types === DATE)
14
+ return 'date';
15
+ return 'any';
16
+ };
17
+ export const describeTypes = (types) => {
18
+ const names = [
19
+ [NUMBER, 'number'],
20
+ [STRING, 'text'],
21
+ [BOOLEAN, 'boolean'],
22
+ [DATE, 'date'],
23
+ ]
24
+ .filter(([bit]) => types & bit)
25
+ .map(([, name]) => name);
26
+ return names.length === 4 ? 'any value' : names.join(' or ');
27
+ };
@@ -0,0 +1,8 @@
1
+ import type { OptionMaps } from './schema.js';
2
+ import type { FormulaColumn, FormulaScalar, FormulaValue } from './types.js';
3
+ export declare const formatFormulaValue: (value: FormulaValue) => string;
4
+ export declare const isEmptyScalar: (value: FormulaScalar) => value is "" | null;
5
+ export declare const parseDateValue: (raw: unknown) => Date | null;
6
+ export declare const optionIds: (raw: unknown) => string[];
7
+ export type CellReader = (raw: unknown) => FormulaScalar;
8
+ export declare const cellReader: (column: FormulaColumn, options: OptionMaps) => CellReader;
@@ -0,0 +1,100 @@
1
+ import { normalizeDate } from '../query/values.js';
2
+ import { isFormulaError } from './errors.js';
3
+ const isValidDate = (value) => value instanceof Date && !Number.isNaN(value.getTime());
4
+ const formatNumber = (value) => {
5
+ if (!Number.isFinite(value))
6
+ return String(value);
7
+ const rounded = Math.abs(value) < 1e21 ? Number(value.toFixed(10)) : value;
8
+ return String(rounded === 0 && value !== 0 ? Number(value.toPrecision(10)) : rounded);
9
+ };
10
+ const formatDate = (value) => {
11
+ if (!isValidDate(value))
12
+ return '';
13
+ const iso = value.toISOString();
14
+ return iso.endsWith('T00:00:00.000Z') ? iso.slice(0, 10) : iso;
15
+ };
16
+ export const formatFormulaValue = (value) => {
17
+ if (value === null)
18
+ return '';
19
+ if (isFormulaError(value))
20
+ return 'Error';
21
+ if (value instanceof Date)
22
+ return formatDate(value);
23
+ if (typeof value === 'number')
24
+ return formatNumber(value);
25
+ return String(value);
26
+ };
27
+ export const isEmptyScalar = (value) => value === null || value === '';
28
+ export const parseDateValue = (raw) => {
29
+ if (raw instanceof Date)
30
+ return isValidDate(raw) ? raw : null;
31
+ const normalized = normalizeDate(raw);
32
+ return normalized === null ? null : new Date(normalized);
33
+ };
34
+ const DATE_CACHE_LIMIT = 10000;
35
+ const cachedDates = () => {
36
+ const cache = new Map();
37
+ return (raw) => {
38
+ if (typeof raw !== 'string')
39
+ return parseDateValue(raw);
40
+ const known = cache.get(raw);
41
+ if (known !== undefined)
42
+ return known;
43
+ const parsed = parseDateValue(raw);
44
+ if (cache.size >= DATE_CACHE_LIMIT)
45
+ cache.clear();
46
+ cache.set(raw, parsed);
47
+ return parsed;
48
+ };
49
+ };
50
+ const toText = (raw) => typeof raw === 'string'
51
+ ? raw
52
+ : typeof raw === 'number' || typeof raw === 'boolean'
53
+ ? String(raw)
54
+ : null;
55
+ const toJson = (raw) => {
56
+ var _a;
57
+ if (raw === null || raw === undefined)
58
+ return null;
59
+ try {
60
+ return (_a = JSON.stringify(raw)) !== null && _a !== void 0 ? _a : null;
61
+ }
62
+ catch (_b) {
63
+ return null;
64
+ }
65
+ };
66
+ export const optionIds = (raw) => Array.isArray(raw) ? raw.filter((item) => typeof item === 'string') : [];
67
+ export const cellReader = (column, options) => {
68
+ switch (column.type) {
69
+ case 'number':
70
+ return (raw) => (typeof raw === 'number' && Number.isFinite(raw) ? raw : null);
71
+ case 'boolean':
72
+ return (raw) => (typeof raw === 'boolean' ? raw : null);
73
+ case 'date':
74
+ return cachedDates();
75
+ case 'select':
76
+ return (raw) => { var _a; return (typeof raw === 'string' ? ((_a = options.labelById.get(raw)) !== null && _a !== void 0 ? _a : raw) : null); };
77
+ case 'multi_select':
78
+ return (raw) => optionIds(raw)
79
+ .map((id) => { var _a; return (_a = options.labelById.get(id)) !== null && _a !== void 0 ? _a : id; })
80
+ .join(', ');
81
+ case 'json':
82
+ return toJson;
83
+ case 'text':
84
+ case 'url':
85
+ case 'email':
86
+ return toText;
87
+ default:
88
+ return (raw) => raw instanceof Date
89
+ ? isValidDate(raw)
90
+ ? raw
91
+ : null
92
+ : typeof raw === 'number'
93
+ ? Number.isFinite(raw)
94
+ ? raw
95
+ : null
96
+ : typeof raw === 'string' || typeof raw === 'boolean'
97
+ ? raw
98
+ : null;
99
+ }
100
+ };
@@ -0,0 +1,25 @@
1
+ import { type QueryColumn, type QueryExpr, type QueryRequest, type QueryRow } from './types.js';
2
+ import { type QueryError } from './validate.js';
3
+ export type { QueryError } from './validate.js';
4
+ export type QueryResult = {
5
+ ids: string[];
6
+ total: number;
7
+ aggregates?: Record<string, number | null>;
8
+ };
9
+ export type RowPredicate = (row: QueryRow) => boolean;
10
+ export declare const compensatedSum: (values: readonly number[]) => number;
11
+ export declare const measureQuery: (root: QueryExpr) => {
12
+ depth: number;
13
+ nodes: number;
14
+ };
15
+ export declare const compileQueryFilter: (filter: QueryExpr, columns: readonly QueryColumn[]) => {
16
+ test: RowPredicate;
17
+ } | {
18
+ error: QueryError;
19
+ };
20
+ export type EvaluateQueryOptions = {
21
+ ids?: boolean;
22
+ };
23
+ export declare const evaluateQuery: (request: QueryRequest, rows: readonly QueryRow[], columns: readonly QueryColumn[], options?: EvaluateQueryOptions) => QueryResult | {
24
+ error: QueryError;
25
+ };
@@ -0,0 +1,481 @@
1
+ import { CLIENT_ONLY, QUERY_LIMITS, } from './types.js';
2
+ import { validateQueryExpr, validateQueryRequest } from './validate.js';
3
+ import { asciiLower, compareCodePoints, normalizeDate } from './values.js';
4
+ const KINDS = {
5
+ text: 'text',
6
+ url: 'text',
7
+ email: 'text',
8
+ number: 'number',
9
+ boolean: 'boolean',
10
+ date: 'date',
11
+ select: 'select',
12
+ multi_select: 'multi_select',
13
+ };
14
+ const kindOf = (type) => (Object.hasOwn(KINDS, type) ? KINDS[type] : undefined);
15
+ const CLIENT_ONLY_TYPES = new Set(['formula']);
16
+ const MIRRORED = new Map([
17
+ ['eq', 'eq'],
18
+ ['ne', 'ne'],
19
+ ['lt', 'gt'],
20
+ ['lte', 'gte'],
21
+ ['gt', 'lt'],
22
+ ['gte', 'lte'],
23
+ ]);
24
+ const NEVER = () => false;
25
+ const ALWAYS = () => true;
26
+ const readScalar = (kind, raw) => {
27
+ switch (kind) {
28
+ case 'number':
29
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
30
+ case 'boolean':
31
+ return typeof raw === 'boolean' ? (raw ? 1 : 0) : null;
32
+ case 'date':
33
+ return normalizeDate(raw);
34
+ case 'select':
35
+ return typeof raw === 'string' && raw !== '' ? raw : null;
36
+ case 'text':
37
+ return typeof raw === 'string'
38
+ ? raw
39
+ : (typeof raw === 'number' && Number.isFinite(raw)) || typeof raw === 'boolean'
40
+ ? String(raw)
41
+ : null;
42
+ default:
43
+ return typeof raw === 'string' ? raw : null;
44
+ }
45
+ };
46
+ const elementsOf = (row, id) => {
47
+ const raw = row.columns[id];
48
+ return Array.isArray(raw) ? raw.filter((item) => typeof item === 'string') : [];
49
+ };
50
+ const compareScalars = (a, b) => typeof a === 'number' && typeof b === 'number'
51
+ ? Math.sign(a - b)
52
+ : compareCodePoints(String(a), String(b));
53
+ const holds = (op, order) => {
54
+ switch (op) {
55
+ case 'eq':
56
+ return order === 0;
57
+ case 'ne':
58
+ return order !== 0;
59
+ case 'lt':
60
+ return order < 0;
61
+ case 'lte':
62
+ return order <= 0;
63
+ case 'gt':
64
+ return order > 0;
65
+ case 'gte':
66
+ return order >= 0;
67
+ }
68
+ };
69
+ export const compensatedSum = (values) => {
70
+ let sum = 0;
71
+ let compensation = 0;
72
+ for (const value of values) {
73
+ const next = sum + value;
74
+ compensation += Math.abs(sum) >= Math.abs(value) ? sum - next + value : value - next + sum;
75
+ sum = next;
76
+ }
77
+ return sum + compensation;
78
+ };
79
+ const isFilledText = (value) => typeof value === 'string' && value !== '';
80
+ const emptyOf = (ref) => ref.kind === 'multi_select'
81
+ ? (row) => elementsOf(row, ref.id).length === 0
82
+ : (row) => {
83
+ const value = ref.read(row);
84
+ return value === null || (ref.kind === 'text' && value === '');
85
+ };
86
+ const filledOf = (ref) => {
87
+ const empty = emptyOf(ref);
88
+ return (row) => !empty(row);
89
+ };
90
+ export const measureQuery = (root) => {
91
+ let depth = 0;
92
+ let nodes = 1;
93
+ const pending = [{ node: root, level: 1 }];
94
+ for (let next = pending.pop(); next && nodes <= QUERY_LIMITS.nodes; next = pending.pop()) {
95
+ depth = Math.max(depth, next.level);
96
+ if (depth > QUERY_LIMITS.depth)
97
+ break;
98
+ const { node } = next;
99
+ const children = node.kind === 'and' || node.kind === 'or'
100
+ ? node.args.slice(0, QUERY_LIMITS.nodes + 1 - nodes)
101
+ : node.kind === 'not' || node.kind === 'isEmpty' || node.kind === 'in'
102
+ ? [node.arg]
103
+ : node.kind === 'compare' || node.kind === 'text'
104
+ ? [node.left, node.right]
105
+ : [];
106
+ for (const child of children) {
107
+ if (++nodes > QUERY_LIMITS.nodes)
108
+ break;
109
+ pending.push({ node: child, level: next.level + 1 });
110
+ }
111
+ }
112
+ return { depth, nodes };
113
+ };
114
+ class Compiler {
115
+ constructor(columns) {
116
+ this.columns = columns;
117
+ this.fields = {};
118
+ this.clientOnly = false;
119
+ }
120
+ condition(expr, path) {
121
+ switch (expr.kind) {
122
+ case 'and':
123
+ case 'or': {
124
+ const args = expr.args.map((arg, index) => this.condition(arg, `${path}.args.${index}`));
125
+ if (expr.kind === 'and')
126
+ return (row) => args.every((arg) => arg(row));
127
+ return (row) => args.some((arg) => arg(row));
128
+ }
129
+ case 'not': {
130
+ const arg = this.condition(expr.arg, `${path}.arg`);
131
+ return (row) => !arg(row);
132
+ }
133
+ case 'compare':
134
+ return this.compare(expr.op, expr.left, expr.right, path);
135
+ case 'text':
136
+ return this.text(expr, path);
137
+ case 'isEmpty': {
138
+ const ref = this.operand(expr.arg, `${path}.arg`);
139
+ return ref ? emptyOf(ref) : NEVER;
140
+ }
141
+ case 'in':
142
+ return this.in(expr, path);
143
+ case 'column': {
144
+ const ref = this.ref(expr.id, path);
145
+ if (!ref)
146
+ return NEVER;
147
+ if (ref.kind !== 'boolean')
148
+ return this.fail(path, 'only checkbox columns are conditions');
149
+ return (row) => ref.read(row) === 1;
150
+ }
151
+ case 'literal':
152
+ if (typeof expr.value !== 'boolean')
153
+ return this.fail(path, 'expected a condition');
154
+ return expr.value ? ALWAYS : NEVER;
155
+ }
156
+ }
157
+ sortKeys(sort) {
158
+ if (!(sort === null || sort === void 0 ? void 0 : sort.length))
159
+ return [];
160
+ if (sort.length > QUERY_LIMITS.sortKeys) {
161
+ this.fail('sort', `at most ${QUERY_LIMITS.sortKeys} sort keys`);
162
+ }
163
+ const seen = new Set();
164
+ return sort.map(({ columnId }, index) => {
165
+ const path = `sort.${index}.columnId`;
166
+ const ref = this.ref(columnId, path);
167
+ if (seen.has(columnId))
168
+ this.fail(path, 'column is already sorted');
169
+ seen.add(columnId);
170
+ if (!ref)
171
+ return () => null;
172
+ if (ref.kind === 'multi_select')
173
+ this.fail(path, 'multi_select columns cannot be sorted');
174
+ return ref.kind === 'text'
175
+ ? (row) => {
176
+ const value = ref.read(row);
177
+ return value === '' ? null : value;
178
+ }
179
+ : ref.read;
180
+ });
181
+ }
182
+ aggregate({ columnId, fn }, path) {
183
+ const ref = this.ref(columnId, `${path}.columnId`);
184
+ if (!ref)
185
+ return () => null;
186
+ switch (fn) {
187
+ case 'count':
188
+ return (rows) => rows.length;
189
+ case 'count_empty': {
190
+ const empty = emptyOf(ref);
191
+ return (rows) => rows.filter(empty).length;
192
+ }
193
+ case 'count_filled': {
194
+ const filled = filledOf(ref);
195
+ return (rows) => rows.filter(filled).length;
196
+ }
197
+ default: {
198
+ if (ref.kind !== 'number') {
199
+ this.fail(`${path}.fn`, `${fn} needs a number column`);
200
+ return () => null;
201
+ }
202
+ return (rows) => {
203
+ const numbers = rows
204
+ .map(ref.read)
205
+ .filter((value) => typeof value === 'number');
206
+ if (fn === 'sum')
207
+ return compensatedSum(numbers);
208
+ if (!numbers.length)
209
+ return null;
210
+ if (fn === 'avg')
211
+ return compensatedSum(numbers) / numbers.length;
212
+ let extreme = numbers[0];
213
+ for (const value of numbers) {
214
+ if (fn === 'min' ? value < extreme : value > extreme)
215
+ extreme = value;
216
+ }
217
+ return extreme;
218
+ };
219
+ }
220
+ }
221
+ }
222
+ error() {
223
+ if (this.clientOnly)
224
+ return { message: CLIENT_ONLY, fields: this.fields };
225
+ return Object.keys(this.fields).length
226
+ ? { message: 'query is invalid', fields: this.fields }
227
+ : null;
228
+ }
229
+ fail(path, message) {
230
+ var _a;
231
+ var _b;
232
+ (_a = (_b = this.fields)[path]) !== null && _a !== void 0 ? _a : (_b[path] = message);
233
+ return NEVER;
234
+ }
235
+ ref(columnId, path) {
236
+ const column = this.columns.get(columnId);
237
+ if (!column) {
238
+ this.fail(path, 'unknown column');
239
+ return undefined;
240
+ }
241
+ if (CLIENT_ONLY_TYPES.has(column.type)) {
242
+ this.clientOnly = true;
243
+ this.fail(path, CLIENT_ONLY);
244
+ return undefined;
245
+ }
246
+ const kind = kindOf(column.type);
247
+ if (!kind) {
248
+ this.fail(path, `${column.type} columns cannot be queried`);
249
+ return undefined;
250
+ }
251
+ return {
252
+ id: column.id,
253
+ type: column.type,
254
+ kind,
255
+ read: (row) => readScalar(kind, row.columns[column.id]),
256
+ };
257
+ }
258
+ operand(expr, path) {
259
+ if (expr.kind === 'column')
260
+ return this.ref(expr.id, path);
261
+ this.fail(path, 'expected a column');
262
+ return undefined;
263
+ }
264
+ compare(op, left, right, path, sides = { left: `${path}.left`, right: `${path}.right` }) {
265
+ var _a;
266
+ if (left.kind !== 'column' && right.kind === 'column') {
267
+ return this.compare((_a = MIRRORED.get(op)) !== null && _a !== void 0 ? _a : op, right, left, path, {
268
+ left: sides.right,
269
+ right: sides.left,
270
+ });
271
+ }
272
+ if (left.kind !== 'column')
273
+ return this.fail(path, 'compare needs a column');
274
+ const ref = this.ref(left.id, sides.left);
275
+ if (right.kind === 'column') {
276
+ const other = this.ref(right.id, sides.right);
277
+ return ref && other ? this.compareColumns(op, ref, other, path) : NEVER;
278
+ }
279
+ if (right.kind !== 'literal')
280
+ return this.fail(sides.right, 'expected a column or a value');
281
+ if (!ref)
282
+ return NEVER;
283
+ if ((ref.kind === 'multi_select' || ref.kind === 'boolean') && op !== 'eq' && op !== 'ne') {
284
+ return this.fail(path, `${ref.type} columns only support eq and ne`);
285
+ }
286
+ const value = this.literal(ref, right.value, sides.right);
287
+ if (value === undefined)
288
+ return NEVER;
289
+ if (ref.kind === 'multi_select') {
290
+ const has = (row) => elementsOf(row, ref.id).includes(String(value));
291
+ return op === 'eq' ? has : (row) => !has(row);
292
+ }
293
+ const guarded = ref.kind === 'text' && (op === 'ne' || op === 'lt' || op === 'lte');
294
+ return (row) => {
295
+ const cell = ref.read(row);
296
+ if (cell === null || (guarded && !isFilledText(cell)))
297
+ return op === 'ne';
298
+ return holds(op, compareScalars(cell, value));
299
+ };
300
+ }
301
+ compareColumns(op, a, b, path) {
302
+ if (a.kind === 'multi_select' || b.kind === 'multi_select' || a.kind !== b.kind) {
303
+ return this.fail(path, `${a.type} and ${b.type} columns cannot be compared`);
304
+ }
305
+ if (a.kind === 'boolean' && op !== 'eq' && op !== 'ne') {
306
+ return this.fail(path, 'boolean columns only support eq and ne');
307
+ }
308
+ const text = a.kind === 'text';
309
+ return (row) => {
310
+ const left = a.read(row);
311
+ const right = b.read(row);
312
+ if (left === null || right === null)
313
+ return op === 'ne';
314
+ if (text && (!isFilledText(left) || !isFilledText(right)))
315
+ return op === 'ne';
316
+ return holds(op, compareScalars(left, right));
317
+ };
318
+ }
319
+ text(expr, path) {
320
+ const ref = this.operand(expr.left, `${path}.left`);
321
+ const needle = expr.right.kind === 'literal' ? expr.right.value : undefined;
322
+ if (needle !== null && typeof needle !== 'string') {
323
+ return this.fail(`${path}.right`, 'expected a string');
324
+ }
325
+ if (!ref)
326
+ return NEVER;
327
+ if (ref.kind !== 'text')
328
+ return this.fail(`${path}.left`, `${ref.type} columns are not text`);
329
+ if (!needle)
330
+ return NEVER;
331
+ const lowered = asciiLower(needle);
332
+ const match = expr.op === 'contains'
333
+ ? (value) => value.includes(lowered)
334
+ : expr.op === 'startsWith'
335
+ ? (value) => value.startsWith(lowered)
336
+ : (value) => value.endsWith(lowered);
337
+ return (row) => {
338
+ const cell = ref.read(row);
339
+ return typeof cell === 'string' && match(asciiLower(cell));
340
+ };
341
+ }
342
+ in(expr, path) {
343
+ const ref = this.operand(expr.arg, `${path}.arg`);
344
+ if (expr.values.length > QUERY_LIMITS.inValues) {
345
+ return this.fail(`${path}.values`, `at most ${QUERY_LIMITS.inValues} values`);
346
+ }
347
+ if (!ref)
348
+ return NEVER;
349
+ const values = expr.values.flatMap((value, index) => {
350
+ const bound = this.literal(ref, value, `${path}.values.${index}`);
351
+ return bound === undefined ? [] : [bound];
352
+ });
353
+ if (!values.length)
354
+ return NEVER;
355
+ const set = new Set(values);
356
+ if (ref.kind === 'multi_select') {
357
+ return (row) => elementsOf(row, ref.id).some((item) => set.has(item));
358
+ }
359
+ return (row) => {
360
+ const cell = ref.read(row);
361
+ return cell !== null && set.has(cell);
362
+ };
363
+ }
364
+ literal(ref, value, path) {
365
+ if (value === null || value === '')
366
+ return undefined;
367
+ if (typeof value === 'string' && value.length > QUERY_LIMITS.literalLength) {
368
+ this.fail(path, `at most ${QUERY_LIMITS.literalLength} characters`);
369
+ return undefined;
370
+ }
371
+ switch (ref.kind) {
372
+ case 'number':
373
+ if (typeof value === 'number' && Number.isFinite(value))
374
+ return value;
375
+ this.fail(path, 'expected a number');
376
+ return undefined;
377
+ case 'boolean':
378
+ if (typeof value === 'boolean')
379
+ return value ? 1 : 0;
380
+ this.fail(path, 'expected true or false');
381
+ return undefined;
382
+ case 'date': {
383
+ const normalized = normalizeDate(value);
384
+ if (normalized !== null)
385
+ return normalized;
386
+ this.fail(path, 'expected an ISO 8601 date string');
387
+ return undefined;
388
+ }
389
+ default:
390
+ if (typeof value === 'string')
391
+ return value;
392
+ this.fail(path, 'expected a string');
393
+ return undefined;
394
+ }
395
+ }
396
+ }
397
+ const UNEXPECTED = {
398
+ message: 'query is invalid',
399
+ fields: { request: 'could not be evaluated' },
400
+ };
401
+ const columnMap = (columns) => new Map(columns.map((column) => [column.id, column]));
402
+ export const compileQueryFilter = (filter, columns) => {
403
+ const invalid = validateQueryExpr(filter);
404
+ if (invalid)
405
+ return { error: invalid };
406
+ try {
407
+ const compiler = new Compiler(columnMap(columns));
408
+ const test = compiler.condition(filter, 'filter');
409
+ const error = compiler.error();
410
+ return error ? { error } : { test };
411
+ }
412
+ catch (_a) {
413
+ return { error: UNEXPECTED };
414
+ }
415
+ };
416
+ const byKeys = (sort) => (a, b) => {
417
+ var _a;
418
+ var _b;
419
+ if (!a.keys.length) {
420
+ const order = Math.sign(a.created - b.created);
421
+ if (order)
422
+ return order;
423
+ }
424
+ for (const [index, left] of a.keys.entries()) {
425
+ const right = (_b = b.keys[index]) !== null && _b !== void 0 ? _b : null;
426
+ if (left === null || right === null) {
427
+ if (left !== right)
428
+ return left === null ? 1 : -1;
429
+ continue;
430
+ }
431
+ const order = compareScalars(left, right);
432
+ if (order)
433
+ return ((_a = sort === null || sort === void 0 ? void 0 : sort[index]) === null || _a === void 0 ? void 0 : _a.direction) === 'desc' ? -order : order;
434
+ }
435
+ return compareCodePoints(a.row.id, b.row.id);
436
+ };
437
+ const ordered = (rows, keys, sort) => rows
438
+ .map((row) => ({
439
+ row,
440
+ keys: keys.map((key) => key(row)),
441
+ created: keys.length ? 0 : Date.parse(row.createdAt),
442
+ }))
443
+ .sort(byKeys(sort))
444
+ .map(({ row }) => row);
445
+ export const evaluateQuery = (request, rows, columns, options = {}) => {
446
+ const invalid = validateQueryRequest(request);
447
+ if (invalid)
448
+ return { error: invalid };
449
+ try {
450
+ return run(request, rows, columns, options);
451
+ }
452
+ catch (_a) {
453
+ return { error: UNEXPECTED };
454
+ }
455
+ };
456
+ const run = (request, rows, columns, options) => {
457
+ var _a, _b;
458
+ var _c;
459
+ const compiler = new Compiler(columnMap(columns));
460
+ const test = request.filter ? compiler.condition(request.filter, 'filter') : ALWAYS;
461
+ const keys = compiler.sortKeys(request.sort);
462
+ if (((_c = (_a = request.aggregates) === null || _a === void 0 ? void 0 : _a.length) !== null && _c !== void 0 ? _c : 0) > QUERY_LIMITS.aggregates) {
463
+ compiler.fail('aggregates', `at most ${QUERY_LIMITS.aggregates} aggregates`);
464
+ }
465
+ const aggregates = new Map();
466
+ (_b = request.aggregates) === null || _b === void 0 ? void 0 : _b.forEach((aggregate, index) => {
467
+ const key = `${aggregate.columnId}:${aggregate.fn}`;
468
+ if (!aggregates.has(key))
469
+ aggregates.set(key, compiler.aggregate(aggregate, `aggregates.${index}`));
470
+ });
471
+ const error = compiler.error();
472
+ if (error)
473
+ return { error };
474
+ const filtered = rows.filter(test);
475
+ const matched = options.ids === false ? filtered : ordered(filtered, keys, request.sort);
476
+ return Object.assign({ ids: options.ids === false ? [] : matched.map((row) => row.id), total: matched.length }, (aggregates.size
477
+ ? {
478
+ aggregates: Object.fromEntries([...aggregates].map(([key, compute]) => [key, compute(matched)])),
479
+ }
480
+ : {}));
481
+ };
@@ -0,0 +1,4 @@
1
+ export { compensatedSum, compileQueryFilter, evaluateQuery, type EvaluateQueryOptions, measureQuery, type QueryError, type QueryResult, type RowPredicate, } from './evaluate.js';
2
+ export * from './types.js';
3
+ export { isValidQueryRequest, validateQueryExpr, validateQueryRequest } from './validate.js';
4
+ export { asciiLower, compareCodePoints, isEmptyValue, normalizeDate } from './values.js';
@@ -0,0 +1,4 @@
1
+ export { compensatedSum, compileQueryFilter, evaluateQuery, measureQuery, } from './evaluate.js';
2
+ export * from './types.js';
3
+ export { isValidQueryRequest, validateQueryExpr, validateQueryRequest } from './validate.js';
4
+ export { asciiLower, compareCodePoints, isEmptyValue, normalizeDate } from './values.js';