@mintlify/common 1.0.1310 → 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 (54) 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/tsconfig.build.tsbuildinfo +1 -1
  54. package/package.json +10 -2
@@ -0,0 +1,63 @@
1
+ export const FORMULA_FUNCTIONS = [
2
+ {
3
+ name: 'if',
4
+ signature: 'if(condition, then, else)',
5
+ description: 'Returns then when condition is true, otherwise else.',
6
+ },
7
+ {
8
+ name: 'concat',
9
+ signature: 'concat(value, …)',
10
+ description: 'Joins values into one piece of text.',
11
+ },
12
+ { name: 'length', signature: 'length(text)', description: 'Number of characters in text.' },
13
+ { name: 'lower', signature: 'lower(text)', description: 'Lowercases text.' },
14
+ { name: 'upper', signature: 'upper(text)', description: 'Uppercases text.' },
15
+ {
16
+ name: 'contains',
17
+ signature: 'contains(text, text)',
18
+ description: 'Whether text contains other text, ignoring ASCII case.',
19
+ },
20
+ {
21
+ name: 'startsWith',
22
+ signature: 'startsWith(text, text)',
23
+ description: 'Whether text starts with other text, ignoring ASCII case.',
24
+ },
25
+ {
26
+ name: 'endsWith',
27
+ signature: 'endsWith(text, text)',
28
+ description: 'Whether text ends with other text, ignoring ASCII case.',
29
+ },
30
+ {
31
+ name: 'round',
32
+ signature: 'round(number, digits)',
33
+ description: 'Rounds half away from zero to digits places.',
34
+ },
35
+ { name: 'floor', signature: 'floor(number)', description: 'Rounds down.' },
36
+ { name: 'ceil', signature: 'ceil(number)', description: 'Rounds up.' },
37
+ { name: 'abs', signature: 'abs(number)', description: 'Absolute value.' },
38
+ { name: 'min', signature: 'min(number, …)', description: 'Smallest number, ignoring blanks.' },
39
+ { name: 'max', signature: 'max(number, …)', description: 'Largest number, ignoring blanks.' },
40
+ { name: 'now', signature: 'now()', description: 'The current date and time.' },
41
+ {
42
+ name: 'dateAdd',
43
+ signature: 'dateAdd(date, amount, unit)',
44
+ description: 'Adds years, months, weeks, days, hours, minutes or seconds to a date.',
45
+ },
46
+ {
47
+ name: 'dateBetween',
48
+ signature: 'dateBetween(date, date, unit)',
49
+ description: 'Whole units from the second date to the first.',
50
+ },
51
+ {
52
+ name: 'date',
53
+ signature: 'date(text)',
54
+ description: 'An ISO 8601 date. Comparisons with a date-only value match the whole UTC day.',
55
+ },
56
+ { name: 'format', signature: 'format(value)', description: 'Turns any value into text.' },
57
+ {
58
+ name: 'toNumber',
59
+ signature: 'toNumber(value)',
60
+ description: 'Turns text, booleans or dates into a number.',
61
+ },
62
+ { name: 'empty', signature: 'empty(value)', description: 'Whether a value is blank.' },
63
+ ];
@@ -0,0 +1,21 @@
1
+ import type { Span } from './ast.js';
2
+ import { type TextBudget } from './errors.js';
3
+ import { type FormulaScalar, type TypeSet } from './types.js';
4
+ export type FunctionContext = {
5
+ now: () => Date;
6
+ text: TextBudget;
7
+ };
8
+ export type Thunk = () => FormulaScalar;
9
+ export type FormulaFunction = {
10
+ min: number;
11
+ max: number;
12
+ params: (index: number) => TypeSet;
13
+ returns: (args: readonly TypeSet[]) => TypeSet;
14
+ call: (args: readonly Thunk[], span: Span, context: FunctionContext) => FormulaScalar;
15
+ apply?: (values: readonly FormulaScalar[], span: Span, context: FunctionContext) => FormulaScalar;
16
+ };
17
+ export declare const expectLogical: (name: string, value: FormulaScalar, span: Span) => boolean;
18
+ export declare const DATE_FUNCTIONS: ReadonlySet<string>;
19
+ export declare const getFunction: (name: string) => FormulaFunction | undefined;
20
+ export declare const FORMULA_FUNCTION_NAMES: readonly string[];
21
+ export declare const arityMessage: (name: string, fn: FormulaFunction) => string;
@@ -0,0 +1,161 @@
1
+ import { asciiLower } from '../query/values.js';
2
+ import { addToDate, dateDifference, parseDateUnit } from './dates.js';
3
+ import { FormulaError, joinText, limitText } from './errors.js';
4
+ import { ANY, BOOLEAN, DATE, NUMBER, STRING } from './types.js';
5
+ import { formatFormulaValue, isEmptyScalar, parseDateValue } from './values.js';
6
+ const mismatch = (name, expected, span) => new FormulaError('type', `${name} expects ${expected}`, span.start, span.end);
7
+ const expectNumber = (name, value, span) => {
8
+ if (value === null || typeof value === 'number')
9
+ return value;
10
+ throw mismatch(name, 'number', span);
11
+ };
12
+ const expectText = (name, value, span) => {
13
+ if (value === null || typeof value === 'string')
14
+ return value;
15
+ throw mismatch(name, 'text', span);
16
+ };
17
+ const expectDate = (name, value, span) => {
18
+ if (value === null || value instanceof Date)
19
+ return value;
20
+ throw mismatch(name, 'date', span);
21
+ };
22
+ export const expectLogical = (name, value, span) => {
23
+ if (value === null)
24
+ return false;
25
+ if (typeof value === 'boolean')
26
+ return value;
27
+ throw mismatch(name, 'boolean', span);
28
+ };
29
+ const all = (args) => args.map((arg) => arg());
30
+ const at = (values, index) => { var _a; return (_a = values[index]) !== null && _a !== void 0 ? _a : null; };
31
+ const fixed = (types) => () => types;
32
+ const define = (min, max, params, returns, call) => ({
33
+ min,
34
+ max,
35
+ params: (index) => { var _a; return (typeof params === 'number' ? params : ((_a = params[index]) !== null && _a !== void 0 ? _a : ANY)); },
36
+ returns: typeof returns === 'number' ? fixed(returns) : returns,
37
+ call: (args, span, context) => call(all(args), span, context),
38
+ apply: call,
39
+ });
40
+ const numeric = (name, fn) => define(1, 1, NUMBER, NUMBER, (values, span) => {
41
+ const value = expectNumber(name, at(values, 0), span);
42
+ return value === null ? null : fn(value);
43
+ });
44
+ const textual = (name, returns, fn) => define(1, 1, STRING, returns, (values, span, context) => {
45
+ const value = expectText(name, at(values, 0), span);
46
+ return value === null ? null : fn(value, span, context);
47
+ });
48
+ const matcher = (name, match) => define(2, 2, STRING, BOOLEAN, (values, span) => {
49
+ const haystack = expectText(name, at(values, 0), span);
50
+ const needle = expectText(name, at(values, 1), span);
51
+ if (!haystack || !needle)
52
+ return false;
53
+ return match(asciiLower(haystack), asciiLower(needle));
54
+ });
55
+ const extremum = (name, pick) => define(1, Infinity, NUMBER, NUMBER, (values, span) => {
56
+ const numbers = values
57
+ .map((value) => expectNumber(name, value, span))
58
+ .filter((value) => value !== null);
59
+ return numbers.length ? pick(...numbers) : null;
60
+ });
61
+ const roundTo = (value, digits) => {
62
+ const places = Math.trunc(digits);
63
+ if (places >= 0 && (Number.isInteger(value) || places > 15))
64
+ return value;
65
+ const sign = Math.sign(value);
66
+ const magnitude = Math.abs(value);
67
+ if (places < 0) {
68
+ const factor = Math.pow(10, -places);
69
+ if (!Number.isFinite(factor))
70
+ return 0;
71
+ return sign * Math.round(Number((magnitude / factor).toPrecision(15))) * factor;
72
+ }
73
+ const factor = Math.pow(10, places);
74
+ const scaled = Number((magnitude * factor).toPrecision(15));
75
+ return Number.isFinite(scaled) ? (sign * Math.round(scaled)) / factor : value;
76
+ };
77
+ const toNumber = (value) => {
78
+ if (value === null || typeof value === 'number')
79
+ return value;
80
+ if (typeof value === 'boolean')
81
+ return value ? 1 : 0;
82
+ if (value instanceof Date)
83
+ return value.getTime();
84
+ const trimmed = value.trim();
85
+ const parsed = trimmed === '' ? NaN : Number(trimmed);
86
+ return Number.isFinite(parsed) ? parsed : null;
87
+ };
88
+ export const DATE_FUNCTIONS = new Set(['dateAdd', 'dateBetween']);
89
+ const FUNCTIONS = {
90
+ if: {
91
+ min: 3,
92
+ max: 3,
93
+ params: (index) => (index === 0 ? BOOLEAN : ANY),
94
+ returns: (args) => { var _a, _b; return ((_a = args[1]) !== null && _a !== void 0 ? _a : ANY) | ((_b = args[2]) !== null && _b !== void 0 ? _b : ANY); },
95
+ call: (args, span) => {
96
+ var _a, _b;
97
+ var _c, _d;
98
+ const condition = expectLogical('if', (_c = (_a = args[0]) === null || _a === void 0 ? void 0 : _a.call(args)) !== null && _c !== void 0 ? _c : null, span);
99
+ return (_d = (_b = (condition ? args[1] : args[2])) === null || _b === void 0 ? void 0 : _b()) !== null && _d !== void 0 ? _d : null;
100
+ },
101
+ },
102
+ concat: define(1, Infinity, ANY, STRING, (values, span, context) => joinText(values.map(formatFormulaValue), span.start, span.end, context.text)),
103
+ length: textual('length', NUMBER, (value) => Array.from(value).length),
104
+ lower: textual('lower', STRING, (value, span, context) => limitText(value.toLowerCase(), span.start, span.end, context.text)),
105
+ upper: textual('upper', STRING, (value, span, context) => limitText(value.toUpperCase(), span.start, span.end, context.text)),
106
+ contains: matcher('contains', (h, n) => h.includes(n)),
107
+ startsWith: matcher('startsWith', (h, n) => h.startsWith(n)),
108
+ endsWith: matcher('endsWith', (h, n) => h.endsWith(n)),
109
+ round: define(1, 2, NUMBER, NUMBER, (values, span) => {
110
+ const value = expectNumber('round', at(values, 0), span);
111
+ const digits = values.length > 1 ? expectNumber('round', at(values, 1), span) : 0;
112
+ return value === null || digits === null ? null : roundTo(value, digits);
113
+ }),
114
+ floor: numeric('floor', Math.floor),
115
+ ceil: numeric('ceil', Math.ceil),
116
+ abs: numeric('abs', Math.abs),
117
+ min: extremum('min', Math.min),
118
+ max: extremum('max', Math.max),
119
+ now: define(0, 0, ANY, DATE, (_values, _span, context) => context.now()),
120
+ dateAdd: define(3, 3, [DATE, NUMBER, STRING], DATE, (values, span) => {
121
+ const date = expectDate('dateAdd', at(values, 0), span);
122
+ const amount = expectNumber('dateAdd', at(values, 1), span);
123
+ const unit = expectText('dateAdd', at(values, 2), span);
124
+ if (date === null || amount === null || unit === null)
125
+ return null;
126
+ return addToDate(date, amount, parseDateUnit(unit, span.start));
127
+ }),
128
+ dateBetween: define(3, 3, [DATE, DATE, STRING], NUMBER, (values, span) => {
129
+ const first = expectDate('dateBetween', at(values, 0), span);
130
+ const second = expectDate('dateBetween', at(values, 1), span);
131
+ const unit = expectText('dateBetween', at(values, 2), span);
132
+ if (first === null || second === null || unit === null)
133
+ return null;
134
+ return dateDifference(first, second, parseDateUnit(unit, span.start));
135
+ }),
136
+ date: define(1, 1, STRING | DATE, DATE, (values, span) => {
137
+ const value = at(values, 0);
138
+ if (value === null || value instanceof Date)
139
+ return value;
140
+ if (typeof value !== 'string')
141
+ throw mismatch('date', 'text', span);
142
+ const date = parseDateValue(value);
143
+ if (!date)
144
+ throw new FormulaError('type', `'${value}' is not an ISO 8601 date`, span.start, span.end);
145
+ return date;
146
+ }),
147
+ format: define(1, 1, ANY, STRING, (values, span, context) => limitText(formatFormulaValue(at(values, 0)), span.start, span.end, context.text)),
148
+ toNumber: define(1, 1, ANY, NUMBER, (values) => toNumber(at(values, 0))),
149
+ empty: define(1, 1, ANY, BOOLEAN, (values) => isEmptyScalar(at(values, 0))),
150
+ };
151
+ export const getFunction = (name) => Object.hasOwn(FUNCTIONS, name) ? FUNCTIONS[name] : undefined;
152
+ export const FORMULA_FUNCTION_NAMES = Object.keys(FUNCTIONS);
153
+ const plural = (count) => `${count} argument${count === 1 ? '' : 's'}`;
154
+ export const arityMessage = (name, fn) => {
155
+ const expected = fn.min === fn.max
156
+ ? plural(fn.min)
157
+ : fn.max === Infinity
158
+ ? `at least ${plural(fn.min)}`
159
+ : `${fn.min} to ${fn.max} arguments`;
160
+ return `${name} expects ${expected}`;
161
+ };
@@ -0,0 +1,7 @@
1
+ export type FormulaTokenKind = 'number' | 'string' | 'keyword' | 'function' | 'property' | 'operator' | 'punctuation' | 'invalid';
2
+ export type FormulaToken = {
3
+ kind: FormulaTokenKind;
4
+ start: number;
5
+ end: number;
6
+ };
7
+ export declare const highlightFormula: (source: string) => FormulaToken[];
@@ -0,0 +1,54 @@
1
+ import { FormulaError } from './errors.js';
2
+ import { getFunction } from './functions.js';
3
+ import { isKeyword, tokenize } from './tokenize.js';
4
+ const classify = (token, next) => {
5
+ switch (token.type) {
6
+ case 'number':
7
+ return 'number';
8
+ case 'string':
9
+ return 'string';
10
+ case 'columnId':
11
+ return 'property';
12
+ case 'identifier':
13
+ if (isKeyword(token.value))
14
+ return 'keyword';
15
+ if ((next === null || next === void 0 ? void 0 : next.type) === 'symbol' && next.value === '(') {
16
+ return token.value === 'prop' || getFunction(token.value) ? 'function' : 'invalid';
17
+ }
18
+ return 'property';
19
+ default:
20
+ return token.value === '(' || token.value === ')' || token.value === ','
21
+ ? 'punctuation'
22
+ : 'operator';
23
+ }
24
+ };
25
+ const lex = (source) => {
26
+ let end = source.length;
27
+ for (;;) {
28
+ try {
29
+ return {
30
+ tokens: tokenize(source.slice(0, end)),
31
+ failedAt: end === source.length ? null : end,
32
+ };
33
+ }
34
+ catch (error) {
35
+ end = Math.min(error instanceof FormulaError ? error.start : 0, end - 1);
36
+ if (end <= 0)
37
+ return { tokens: [], failedAt: 0 };
38
+ }
39
+ }
40
+ };
41
+ export const highlightFormula = (source) => {
42
+ if (typeof source !== 'string')
43
+ return [];
44
+ const { tokens, failedAt } = lex(source);
45
+ const real = tokens.filter((token) => token.type !== 'eof');
46
+ const spans = real.map((token, index) => ({
47
+ kind: classify(token, real[index + 1]),
48
+ start: token.start,
49
+ end: token.end,
50
+ }));
51
+ return failedAt === null
52
+ ? spans
53
+ : [...spans, { kind: 'invalid', start: failedAt, end: source.length }];
54
+ };
@@ -0,0 +1,16 @@
1
+ export type { BinaryOperator, FormulaNode, Span, UnaryOperator } from './ast.js';
2
+ export { compile, compileColumns, type ColumnPrograms, type CompiledFormula, type CompileOptions, } from './compile.js';
3
+ export { FormulaError, isFormulaError, type FormulaErrorCode, type FormulaSeverity, } from './errors.js';
4
+ export { columnIdReference, columnReference, formatFormula, quoteString } from './format.js';
5
+ export { formulaFilterEngine, type FormulaFilter } from './filter.js';
6
+ export { FORMULA_FUNCTIONS, type FormulaFunctionDoc } from './function-docs.js';
7
+ export { highlightFormula, type FormulaToken, type FormulaTokenKind } from './highlight.js';
8
+ export { FORMULA_LIMITS } from './limits.js';
9
+ export { toQueryIR, type QueryLowering } from './lower.js';
10
+ export { parseFormula, type FormulaParseResult, type ParseOptions } from './parse.js';
11
+ export { displayDiagnostics, isStoredFormula, referencedColumns, renameColumn, toDisplayFormula, toStoredFormula, type StoredFormulaResult, type FormulaReference, } from './references.js';
12
+ export { rewriteColumnIds } from './rewrite.js';
13
+ export { isBareName, KEYWORDS } from './tokenize.js';
14
+ export { typecheck, type TypecheckResult } from './typecheck.js';
15
+ export type { FormulaColumn, FormulaOption, FormulaRow, FormulaScalar, FormulaType, FormulaValue, } from './types.js';
16
+ export { formatFormulaValue } from './values.js';
@@ -0,0 +1,14 @@
1
+ export { compile, compileColumns, } from './compile.js';
2
+ export { FormulaError, isFormulaError, } from './errors.js';
3
+ export { columnIdReference, columnReference, formatFormula, quoteString } from './format.js';
4
+ export { formulaFilterEngine } from './filter.js';
5
+ export { FORMULA_FUNCTIONS } from './function-docs.js';
6
+ export { highlightFormula } from './highlight.js';
7
+ export { FORMULA_LIMITS } from './limits.js';
8
+ export { toQueryIR } from './lower.js';
9
+ export { parseFormula } from './parse.js';
10
+ export { displayDiagnostics, isStoredFormula, referencedColumns, renameColumn, toDisplayFormula, toStoredFormula, } from './references.js';
11
+ export { rewriteColumnIds } from './rewrite.js';
12
+ export { isBareName, KEYWORDS } from './tokenize.js';
13
+ export { typecheck } from './typecheck.js';
14
+ export { formatFormulaValue } from './values.js';
@@ -0,0 +1,8 @@
1
+ export declare const FORMULA_LIMITS: {
2
+ readonly sourceBytes: 2048;
3
+ readonly draftBytes: 16384;
4
+ readonly depth: 64;
5
+ readonly steps: 200000;
6
+ readonly textBytes: 102400;
7
+ readonly rowTextBytes: 1048576;
8
+ };
@@ -0,0 +1,8 @@
1
+ export const FORMULA_LIMITS = {
2
+ sourceBytes: 2048,
3
+ draftBytes: 16384,
4
+ depth: 64,
5
+ steps: 200000,
6
+ textBytes: 102400,
7
+ rowTextBytes: 1048576,
8
+ };
@@ -0,0 +1,9 @@
1
+ import { type QueryExpr } from '../query/types.js';
2
+ import type { FormulaNode } from './ast.js';
3
+ import type { FormulaColumn } from './types.js';
4
+ export type QueryLowering = {
5
+ ir: QueryExpr;
6
+ } | {
7
+ unsupported: string;
8
+ };
9
+ export declare const toQueryIR: (ast: FormulaNode, columns: readonly FormulaColumn[]) => QueryLowering;
@@ -0,0 +1,252 @@
1
+ import { measureQuery } from '../query/evaluate.js';
2
+ import { CLIENT_ONLY, QUERY_LIMITS, } from '../query/types.js';
3
+ import { analyzeFor } from './analyze.js';
4
+ import { BOOLEAN } from './types.js';
5
+ const OPS = {
6
+ '==': 'eq',
7
+ '!=': 'ne',
8
+ '<': 'lt',
9
+ '<=': 'lte',
10
+ '>': 'gt',
11
+ '>=': 'gte',
12
+ };
13
+ const MIRRORED = {
14
+ eq: 'eq',
15
+ ne: 'ne',
16
+ lt: 'gt',
17
+ lte: 'gte',
18
+ gt: 'lt',
19
+ gte: 'lte',
20
+ };
21
+ const TEXT_FUNCTIONS = {
22
+ contains: 'contains',
23
+ startsWith: 'startsWith',
24
+ endsWith: 'endsWith',
25
+ };
26
+ const KINDS = {
27
+ text: 'text',
28
+ url: 'text',
29
+ email: 'text',
30
+ number: 'number',
31
+ boolean: 'boolean',
32
+ date: 'date',
33
+ select: 'select',
34
+ multi_select: 'multi_select',
35
+ };
36
+ const kindOf = (type) => (Object.hasOwn(KINDS, type) ? KINDS[type] : undefined);
37
+ const idsAreLabels = (column) => { var _a; return ((_a = column.options) !== null && _a !== void 0 ? _a : []).every((option) => typeof option === 'string' || option.id === option.label); };
38
+ class Unsupported extends Error {
39
+ }
40
+ const unsupported = (reason) => new Unsupported(reason);
41
+ class Lowerer {
42
+ constructor(analysis) {
43
+ this.analysis = analysis;
44
+ }
45
+ condition(node) {
46
+ switch (node.kind) {
47
+ case 'boolean':
48
+ return { kind: 'literal', value: node.value };
49
+ case 'ref': {
50
+ const column = this.column(node);
51
+ if (column.type !== 'boolean')
52
+ throw unsupported(`'${column.name}' is not a condition`);
53
+ return { kind: 'column', id: column.id };
54
+ }
55
+ case 'unary':
56
+ if (node.operator !== 'not')
57
+ throw unsupported('Arithmetic runs on the client');
58
+ return { kind: 'not', arg: this.condition(node.operand) };
59
+ case 'binary': {
60
+ if (node.operator === 'and' || node.operator === 'or') {
61
+ const kind = node.operator;
62
+ const args = [node.left, node.right].flatMap((side) => {
63
+ const lowered = this.condition(side);
64
+ return lowered.kind === kind ? lowered.args : [lowered];
65
+ });
66
+ return { kind, args };
67
+ }
68
+ const op = OPS[node.operator];
69
+ if (!op)
70
+ throw unsupported('Arithmetic runs on the client');
71
+ return this.compare(op, node.left, node.right);
72
+ }
73
+ case 'call':
74
+ return this.call(node);
75
+ default:
76
+ throw unsupported('Not a condition');
77
+ }
78
+ }
79
+ optionOf(node) {
80
+ const option = this.analysis.options.get(node);
81
+ if (option === null)
82
+ throw unsupported('Option ids are compared on the client');
83
+ return option;
84
+ }
85
+ call(node) {
86
+ var _a;
87
+ const member = this.analysis.memberships.get(node);
88
+ if (member) {
89
+ const ids = node.args[1] ? this.optionOf(node.args[1]) : undefined;
90
+ return this.anyOption({ kind: 'column', id: this.column((_a = node.args[0]) !== null && _a !== void 0 ? _a : node).id }, ids !== null && ids !== void 0 ? ids : [], true);
91
+ }
92
+ const textOp = TEXT_FUNCTIONS[node.name];
93
+ if (textOp) {
94
+ const [subject, needle] = node.args;
95
+ if (!subject || !needle)
96
+ throw unsupported(`${node.name} needs two arguments`);
97
+ const column = this.column(subject);
98
+ if (kindOf(column.type) !== 'text') {
99
+ throw unsupported(`${node.name} on '${column.name}' runs on the client`);
100
+ }
101
+ if (needle.kind !== 'string')
102
+ throw unsupported(`${node.name} needs a text value`);
103
+ return {
104
+ kind: 'text',
105
+ op: textOp,
106
+ left: { kind: 'column', id: column.id },
107
+ right: { kind: 'literal', value: needle.value },
108
+ };
109
+ }
110
+ if (node.name === 'empty') {
111
+ const [subject] = node.args;
112
+ if (!subject)
113
+ throw unsupported('empty needs an argument');
114
+ return { kind: 'isEmpty', arg: { kind: 'column', id: this.column(subject).id } };
115
+ }
116
+ throw unsupported(`${node.name}() runs on the client`);
117
+ }
118
+ compare(op, left, right) {
119
+ var _a;
120
+ const dayOnRight = this.analysis.days.get(right);
121
+ const day = dayOnRight !== null && dayOnRight !== void 0 ? dayOnRight : this.analysis.days.get(left);
122
+ if (day) {
123
+ const side = this.operand(dayOnRight ? left : right);
124
+ if (side.kind !== 'column')
125
+ throw unsupported('Comparisons need a property');
126
+ return this.day(dayOnRight ? op : MIRRORED[op], side, day);
127
+ }
128
+ const selects = [left, right].flatMap((side) => {
129
+ const column = side.kind === 'ref' ? this.analysis.columns.get(side) : undefined;
130
+ return (column === null || column === void 0 ? void 0 : column.type) === 'select' ? [column] : [];
131
+ });
132
+ const select = selects.find((column) => !idsAreLabels(column));
133
+ if (select && (selects.length > 1 || (op !== 'eq' && op !== 'ne'))) {
134
+ throw unsupported(`'${select.name}' is ordered by option name on the client`);
135
+ }
136
+ const a = this.operand(left);
137
+ const b = this.operand(right);
138
+ if (a.kind !== 'column' && b.kind !== 'column') {
139
+ throw unsupported('Comparisons need a property');
140
+ }
141
+ const label = [left, right].find((side) => { var _a; var _b; return side.kind === 'string' && ((_b = (_a = this.analysis.options.get(side)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 1; });
142
+ if (label && (op === 'eq' || op === 'ne')) {
143
+ const subject = label === left ? right : left;
144
+ const any = this.anyOption(label === left ? b : a, (_a = this.optionOf(label)) !== null && _a !== void 0 ? _a : [], this.column(subject).type === 'multi_select');
145
+ return op === 'eq' ? any : { kind: 'not', arg: any };
146
+ }
147
+ if (a.kind === 'column' && b.kind === 'column') {
148
+ const first = this.column(left);
149
+ const second = this.column(right);
150
+ if (kindOf(first.type) !== kindOf(second.type)) {
151
+ throw unsupported(`'${first.name}' and '${second.name}' are compared on the client`);
152
+ }
153
+ }
154
+ return { kind: 'compare', op, left: a, right: b };
155
+ }
156
+ anyOption(column, ids, multi) {
157
+ const eq = (value) => ({
158
+ kind: 'compare',
159
+ op: 'eq',
160
+ left: column,
161
+ right: { kind: 'literal', value },
162
+ });
163
+ const [only] = ids;
164
+ if (ids.length <= 1)
165
+ return eq(only !== null && only !== void 0 ? only : null);
166
+ return multi ? { kind: 'or', args: ids.map(eq) } : { kind: 'in', arg: column, values: ids };
167
+ }
168
+ day(op, column, day) {
169
+ const at = (bound) => ({ kind: 'literal', value: bound.toISOString() });
170
+ const compare = (operator, bound) => ({
171
+ kind: 'compare',
172
+ op: operator,
173
+ left: column,
174
+ right: at(bound),
175
+ });
176
+ switch (op) {
177
+ case 'eq':
178
+ return { kind: 'and', args: [compare('gte', day.start), compare('lt', day.end)] };
179
+ case 'ne':
180
+ return {
181
+ kind: 'not',
182
+ arg: { kind: 'and', args: [compare('gte', day.start), compare('lt', day.end)] },
183
+ };
184
+ case 'lt':
185
+ return compare('lt', day.start);
186
+ case 'lte':
187
+ return compare('lt', day.end);
188
+ case 'gt':
189
+ return compare('gte', day.end);
190
+ case 'gte':
191
+ return compare('gte', day.start);
192
+ }
193
+ }
194
+ operand(node) {
195
+ var _a;
196
+ var _b;
197
+ const constant = node.kind === 'call' ? this.analysis.dates.get(node) : undefined;
198
+ if (constant)
199
+ return { kind: 'literal', value: constant.toISOString() };
200
+ switch (node.kind) {
201
+ case 'ref':
202
+ return { kind: 'column', id: this.column(node).id };
203
+ case 'string': {
204
+ const date = this.analysis.dates.get(node);
205
+ if (date)
206
+ return { kind: 'literal', value: date.toISOString() };
207
+ return { kind: 'literal', value: (_b = (_a = this.optionOf(node)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : node.value };
208
+ }
209
+ case 'number':
210
+ case 'boolean':
211
+ return { kind: 'literal', value: node.value };
212
+ case 'unary':
213
+ if (node.operator === '-' && node.operand.kind === 'number') {
214
+ return { kind: 'literal', value: -node.operand.value };
215
+ }
216
+ throw unsupported('Arithmetic runs on the client');
217
+ default:
218
+ throw unsupported('Calculated values run on the client');
219
+ }
220
+ }
221
+ column(node) {
222
+ const column = node.kind === 'ref' ? this.analysis.columns.get(node) : undefined;
223
+ if (!column)
224
+ throw unsupported('Expected a property');
225
+ if (column.type === 'formula')
226
+ throw unsupported(CLIENT_ONLY);
227
+ if (!kindOf(column.type))
228
+ throw unsupported(`${column.type} properties run on the client`);
229
+ return column;
230
+ }
231
+ }
232
+ export const toQueryIR = (ast, columns) => {
233
+ try {
234
+ const { analysis } = analyzeFor(ast, columns);
235
+ const failure = analysis.diagnostics.find((diagnostic) => diagnostic.severity === 'error');
236
+ if (failure)
237
+ return { unsupported: failure.message };
238
+ if (analysis.type !== BOOLEAN)
239
+ return { unsupported: 'Not a condition' };
240
+ const ir = new Lowerer(analysis).condition(ast);
241
+ const { depth, nodes } = measureQuery(ir);
242
+ if (depth > QUERY_LIMITS.depth || nodes > QUERY_LIMITS.nodes) {
243
+ return { unsupported: 'Too complex to run in the database' };
244
+ }
245
+ return { ir };
246
+ }
247
+ catch (error) {
248
+ if (error instanceof Unsupported)
249
+ return { unsupported: error.message };
250
+ return { unsupported: 'Runs on the client' };
251
+ }
252
+ };
@@ -0,0 +1,21 @@
1
+ import type { FormulaNode } from './ast.js';
2
+ import { FormulaError } from './errors.js';
3
+ export type FormulaParseResult = {
4
+ ast: FormulaNode;
5
+ } | {
6
+ error: FormulaError;
7
+ };
8
+ export declare const PRECEDENCE: {
9
+ or: number;
10
+ and: number;
11
+ not: number;
12
+ compare: number;
13
+ sum: number;
14
+ product: number;
15
+ sign: number;
16
+ power: number;
17
+ };
18
+ export type ParseOptions = {
19
+ maxBytes?: number;
20
+ };
21
+ export declare const parseFormula: (source: string, { maxBytes }?: ParseOptions) => FormulaParseResult;