@columenlabs/lumen-ui-types 0.1.34 → 0.1.35
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/formula-functions.d.ts +14 -0
- package/dist/formula-functions.js +269 -0
- package/dist/view-formula.d.ts +1 -1
- package/dist/view-formula.js +64 -20
- package/dist/views.d.ts +1 -0
- package/dist/views.js +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface FormulaFunction {
|
|
2
|
+
name: string;
|
|
3
|
+
/** How it is called, e.g. `left(text, n)`. */
|
|
4
|
+
call: string;
|
|
5
|
+
/** What it gives, in a few words. */
|
|
6
|
+
detail: string;
|
|
7
|
+
group: 'Date' | 'Text' | 'Number';
|
|
8
|
+
/** It gives text (a column of it shows as Text). */
|
|
9
|
+
text?: boolean;
|
|
10
|
+
fn: (...args: unknown[]) => unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare const FORMULA_FUNCTIONS: FormulaFunction[];
|
|
13
|
+
/** The functions as one line each, for an assistant's instructions. */
|
|
14
|
+
export declare const formulaFunctionsText: () => string;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// The functions a view formula can call beyond group_sum and total_sum: dates, text and numbers,
|
|
2
|
+
// e.g. `year(transactions.date)`, `left(symbol.name, 3)`, `round(holdings.weight, 1)`. Dates are
|
|
3
|
+
// `YYYY-MM-DD` or ISO text (or epoch milliseconds), read in UTC so the browser and the server agree;
|
|
4
|
+
// date math is date-fns. A function given what it cannot use gives no value (a blank cell).
|
|
5
|
+
import { UTCDate, utc } from '@date-fns/utc';
|
|
6
|
+
import { differenceInCalendarDays, format, getDate, getISODay, getMonth, getQuarter, getYear, isValid, parseISO, startOfISOWeek, startOfMonth, startOfQuarter, startOfYear, } from 'date-fns';
|
|
7
|
+
const asDate = (v) => {
|
|
8
|
+
const d = typeof v === 'number'
|
|
9
|
+
? new UTCDate(v)
|
|
10
|
+
: typeof v === 'string' && v
|
|
11
|
+
? parseISO(v, { in: utc })
|
|
12
|
+
: null;
|
|
13
|
+
return d && isValid(d) ? d : null;
|
|
14
|
+
};
|
|
15
|
+
const asText = (v) => typeof v === 'string' ? v : typeof v === 'number' && Number.isFinite(v) ? String(v) : null;
|
|
16
|
+
const asNum = (v) => {
|
|
17
|
+
const n = typeof v === 'number' ? v : typeof v === 'string' && v.trim() ? Number(v) : Number.NaN;
|
|
18
|
+
return Number.isFinite(n) ? n : null;
|
|
19
|
+
};
|
|
20
|
+
const day = (d) => format(d, 'yyyy-MM-dd');
|
|
21
|
+
/** A date part: the function reads one date and gives a number. */
|
|
22
|
+
const part = (get) => (v) => {
|
|
23
|
+
const d = asDate(v);
|
|
24
|
+
return d ? get(d) : undefined;
|
|
25
|
+
};
|
|
26
|
+
/** A text function: the first argument is the text, the rest numbers (or text, `rest`). */
|
|
27
|
+
const onText = (f) => (v, ...rest) => {
|
|
28
|
+
const s = asText(v);
|
|
29
|
+
return s === null ? undefined : (f(s, ...rest) ?? undefined);
|
|
30
|
+
};
|
|
31
|
+
const STARTS = {
|
|
32
|
+
week: startOfISOWeek,
|
|
33
|
+
month: startOfMonth,
|
|
34
|
+
quarter: startOfQuarter,
|
|
35
|
+
year: startOfYear,
|
|
36
|
+
};
|
|
37
|
+
export const FORMULA_FUNCTIONS = [
|
|
38
|
+
{
|
|
39
|
+
name: 'year',
|
|
40
|
+
call: 'year(date)',
|
|
41
|
+
detail: 'the year, 2026',
|
|
42
|
+
group: 'Date',
|
|
43
|
+
fn: part(getYear),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'month',
|
|
47
|
+
call: 'month(date)',
|
|
48
|
+
detail: 'the month, 1 to 12',
|
|
49
|
+
group: 'Date',
|
|
50
|
+
fn: part((d) => getMonth(d) + 1),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'day',
|
|
54
|
+
call: 'day(date)',
|
|
55
|
+
detail: 'the day of the month',
|
|
56
|
+
group: 'Date',
|
|
57
|
+
fn: part(getDate),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'quarter',
|
|
61
|
+
call: 'quarter(date)',
|
|
62
|
+
detail: 'the quarter, 1 to 4',
|
|
63
|
+
group: 'Date',
|
|
64
|
+
fn: part(getQuarter),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'weekday',
|
|
68
|
+
call: 'weekday(date)',
|
|
69
|
+
detail: 'the day of the week, 1 Monday to 7 Sunday',
|
|
70
|
+
group: 'Date',
|
|
71
|
+
fn: part(getISODay),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'days_between',
|
|
75
|
+
call: 'days_between(from, to)',
|
|
76
|
+
detail: 'days from one date to another',
|
|
77
|
+
group: 'Date',
|
|
78
|
+
fn: (a, b) => {
|
|
79
|
+
const from = asDate(a);
|
|
80
|
+
const to = asDate(b);
|
|
81
|
+
return from && to ? differenceInCalendarDays(to, from) : undefined;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'start_of',
|
|
86
|
+
call: "start_of(date, 'month')",
|
|
87
|
+
detail: "the first day of its 'week', 'month', 'quarter' or 'year'",
|
|
88
|
+
group: 'Date',
|
|
89
|
+
text: true,
|
|
90
|
+
fn: (v, unit) => {
|
|
91
|
+
const d = asDate(v);
|
|
92
|
+
const start = typeof unit === 'string' ? STARTS[unit] : undefined;
|
|
93
|
+
return d && start ? day(start(d)) : undefined;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'today',
|
|
98
|
+
call: 'today()',
|
|
99
|
+
detail: "today's date (UTC)",
|
|
100
|
+
group: 'Date',
|
|
101
|
+
text: true,
|
|
102
|
+
fn: () => day(new UTCDate()),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'left',
|
|
106
|
+
call: 'left(text, n)',
|
|
107
|
+
detail: 'the first n characters',
|
|
108
|
+
group: 'Text',
|
|
109
|
+
text: true,
|
|
110
|
+
fn: onText((s, n) => {
|
|
111
|
+
const k = asNum(n);
|
|
112
|
+
return k === null ? null : s.slice(0, Math.max(0, k));
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'right',
|
|
117
|
+
call: 'right(text, n)',
|
|
118
|
+
detail: 'the last n characters',
|
|
119
|
+
group: 'Text',
|
|
120
|
+
text: true,
|
|
121
|
+
fn: onText((s, n) => {
|
|
122
|
+
const k = asNum(n);
|
|
123
|
+
return k === null ? null : k <= 0 ? '' : s.slice(-k);
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'mid',
|
|
128
|
+
call: 'mid(text, start, length)',
|
|
129
|
+
detail: 'length characters from position start (the first is 1)',
|
|
130
|
+
group: 'Text',
|
|
131
|
+
text: true,
|
|
132
|
+
fn: onText((s, start, length) => {
|
|
133
|
+
const a = asNum(start);
|
|
134
|
+
const n = asNum(length);
|
|
135
|
+
return a === null || n === null ? null : s.slice(Math.max(0, a - 1), Math.max(0, a - 1 + n));
|
|
136
|
+
}),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'slice',
|
|
140
|
+
call: 'slice(text, from, to)',
|
|
141
|
+
detail: 'characters from index from up to to, the first is 0 (to left out: to the end)',
|
|
142
|
+
group: 'Text',
|
|
143
|
+
text: true,
|
|
144
|
+
fn: onText((s, from, to) => {
|
|
145
|
+
const a = asNum(from);
|
|
146
|
+
if (a === null)
|
|
147
|
+
return null;
|
|
148
|
+
return to === undefined ? s.slice(a) : s.slice(a, asNum(to) ?? undefined);
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'upper',
|
|
153
|
+
call: 'upper(text)',
|
|
154
|
+
detail: 'in capitals',
|
|
155
|
+
group: 'Text',
|
|
156
|
+
text: true,
|
|
157
|
+
fn: onText((s) => s.toUpperCase()),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'lower',
|
|
161
|
+
call: 'lower(text)',
|
|
162
|
+
detail: 'in small letters',
|
|
163
|
+
group: 'Text',
|
|
164
|
+
text: true,
|
|
165
|
+
fn: onText((s) => s.toLowerCase()),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'trim',
|
|
169
|
+
call: 'trim(text)',
|
|
170
|
+
detail: 'without spaces at either end',
|
|
171
|
+
group: 'Text',
|
|
172
|
+
text: true,
|
|
173
|
+
fn: onText((s) => s.trim()),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'len',
|
|
177
|
+
call: 'len(text)',
|
|
178
|
+
detail: 'how many characters',
|
|
179
|
+
group: 'Text',
|
|
180
|
+
fn: onText((s) => s.length),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'concat',
|
|
184
|
+
call: 'concat(a, b, ...)',
|
|
185
|
+
detail: 'the values joined into one text (blanks left out)',
|
|
186
|
+
group: 'Text',
|
|
187
|
+
text: true,
|
|
188
|
+
fn: (...args) => args
|
|
189
|
+
.map(asText)
|
|
190
|
+
.filter((s) => s !== null)
|
|
191
|
+
.join(''),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'contains',
|
|
195
|
+
call: 'contains(text, part)',
|
|
196
|
+
detail: 'whether the text holds part (any case)',
|
|
197
|
+
group: 'Text',
|
|
198
|
+
fn: onText((s, p) => {
|
|
199
|
+
const q = asText(p);
|
|
200
|
+
return q === null ? null : s.toLowerCase().includes(q.toLowerCase());
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'replace',
|
|
205
|
+
call: 'replace(text, find, with)',
|
|
206
|
+
detail: 'every find swapped for with',
|
|
207
|
+
group: 'Text',
|
|
208
|
+
text: true,
|
|
209
|
+
fn: onText((s, find, to) => {
|
|
210
|
+
const f = asText(find);
|
|
211
|
+
return f ? s.replaceAll(f, asText(to) ?? '') : null;
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'split_part',
|
|
216
|
+
call: "split_part(text, ',', n)",
|
|
217
|
+
detail: 'the nth piece between separators (the first is 1)',
|
|
218
|
+
group: 'Text',
|
|
219
|
+
text: true,
|
|
220
|
+
fn: onText((s, sep, n) => {
|
|
221
|
+
const by = asText(sep);
|
|
222
|
+
const k = asNum(n);
|
|
223
|
+
return by && k !== null && k >= 1 ? (s.split(by)[k - 1] ?? null) : null;
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'round',
|
|
228
|
+
call: 'round(x, digits)',
|
|
229
|
+
detail: 'rounded, to digits places (0 when left out)',
|
|
230
|
+
group: 'Number',
|
|
231
|
+
fn: (v, digits) => {
|
|
232
|
+
const x = asNum(v);
|
|
233
|
+
const p = 10 ** (asNum(digits) ?? 0);
|
|
234
|
+
return x === null ? undefined : Math.round(x * p) / p;
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'abs',
|
|
239
|
+
call: 'abs(x)',
|
|
240
|
+
detail: 'without its sign',
|
|
241
|
+
group: 'Number',
|
|
242
|
+
fn: (v) => {
|
|
243
|
+
const x = asNum(v);
|
|
244
|
+
return x === null ? undefined : Math.abs(x);
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'min',
|
|
249
|
+
call: 'min(a, b, ...)',
|
|
250
|
+
detail: 'the smallest (blanks left out)',
|
|
251
|
+
group: 'Number',
|
|
252
|
+
fn: (...args) => {
|
|
253
|
+
const xs = args.map(asNum).filter((x) => x !== null);
|
|
254
|
+
return xs.length ? Math.min(...xs) : undefined;
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'max',
|
|
259
|
+
call: 'max(a, b, ...)',
|
|
260
|
+
detail: 'the largest (blanks left out)',
|
|
261
|
+
group: 'Number',
|
|
262
|
+
fn: (...args) => {
|
|
263
|
+
const xs = args.map(asNum).filter((x) => x !== null);
|
|
264
|
+
return xs.length ? Math.max(...xs) : undefined;
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
/** The functions as one line each, for an assistant's instructions. */
|
|
269
|
+
export const formulaFunctionsText = () => FORMULA_FUNCTIONS.map((f) => `${f.call}: ${f.detail}`).join('; ');
|
package/dist/view-formula.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface ViewFormulaScope {
|
|
|
5
5
|
groupSum: (key: string) => number;
|
|
6
6
|
totalSum: (key: string) => number;
|
|
7
7
|
}
|
|
8
|
-
export declare const viewFormulaFunctions: readonly [
|
|
8
|
+
export declare const viewFormulaFunctions: readonly ["group_sum", "total_sum", ...string[]];
|
|
9
9
|
/** Compiles a formula (throws on a syntax error) into a row evaluator; a failed row is null. */
|
|
10
10
|
export declare function compileViewFormula(expr: string): (scope: ViewFormulaScope) => number | string | null;
|
|
11
11
|
/**
|
package/dist/view-formula.js
CHANGED
|
@@ -9,14 +9,25 @@
|
|
|
9
9
|
// `account.cash`) or a linked domain (`tax_lots.unrealized`); `frame.<frame>.<column>` for an
|
|
10
10
|
// assistant frame; and another formula by its bare key (`value_nonretire`), at any depth, never in a
|
|
11
11
|
// loop. Rows keep their flat field keys; each surface maps a name to its key (`FormulaNames`).
|
|
12
|
+
// Beyond the two sums it calls the date, text and number functions in `FORMULA_FUNCTIONS`
|
|
13
|
+
// (`year(transactions.date)`); any other function is refused by the check.
|
|
12
14
|
import jexl from 'jexl';
|
|
13
|
-
|
|
15
|
+
import { FORMULA_FUNCTIONS } from './formula-functions.js';
|
|
16
|
+
export const viewFormulaFunctions = [
|
|
17
|
+
'group_sum',
|
|
18
|
+
'total_sum',
|
|
19
|
+
...FORMULA_FUNCTIONS.map((f) => f.name),
|
|
20
|
+
];
|
|
21
|
+
const knownFunctions = new Set(viewFormulaFunctions);
|
|
22
|
+
const textFunctions = new Set(FORMULA_FUNCTIONS.filter((f) => f.text).map((f) => f.name));
|
|
14
23
|
// Functions get only their arguments, so the row being evaluated is set around each evalSync
|
|
15
24
|
// (evaluation is synchronous, so one at a time).
|
|
16
25
|
let current = null;
|
|
17
26
|
const engine = new jexl.Jexl();
|
|
18
27
|
engine.addFunction('group_sum', (key) => typeof key === 'string' && current ? current.groupSum(key) : Number.NaN);
|
|
19
28
|
engine.addFunction('total_sum', (key) => typeof key === 'string' && current ? current.totalSum(key) : Number.NaN);
|
|
29
|
+
for (const f of FORMULA_FUNCTIONS)
|
|
30
|
+
engine.addFunction(f.name, f.fn);
|
|
20
31
|
/**
|
|
21
32
|
* Folds each dotted name (`symbol.price`, Jexl's member access) into one identifier whose value is
|
|
22
33
|
* the whole name, so the scope reads `symbol.price` in one call, as it reads a bare formula key.
|
|
@@ -122,6 +133,8 @@ const endsInText = (a) => {
|
|
|
122
133
|
return false;
|
|
123
134
|
if (a.type === 'Literal')
|
|
124
135
|
return typeof a.value === 'string';
|
|
136
|
+
if (a.type === 'FunctionCall')
|
|
137
|
+
return textFunctions.has(String(a.name));
|
|
125
138
|
if (a.type === 'ConditionalExpression')
|
|
126
139
|
return endsInText(a.consequent) && endsInText(a.alternate);
|
|
127
140
|
if (a.type === 'BinaryExpression' && a.operator === '+')
|
|
@@ -141,40 +154,71 @@ export function viewFormulaIsText(expr) {
|
|
|
141
154
|
return false;
|
|
142
155
|
}
|
|
143
156
|
}
|
|
157
|
+
/** The functions a tree calls that the formula language lacks (a `|transform` is one too). */
|
|
158
|
+
function unknownFunctions(n, out) {
|
|
159
|
+
if (!n || typeof n !== 'object')
|
|
160
|
+
return out;
|
|
161
|
+
if (Array.isArray(n)) {
|
|
162
|
+
for (const x of n)
|
|
163
|
+
unknownFunctions(x, out);
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
const node = n;
|
|
167
|
+
if (node.type === 'FunctionCall' &&
|
|
168
|
+
(node.pool !== 'functions' || !knownFunctions.has(String(node.name))))
|
|
169
|
+
out.add(String(node.name));
|
|
170
|
+
for (const v of Object.values(node))
|
|
171
|
+
unknownFunctions(v, out);
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The names a tree reads, found in the tree rather than by running it, so a name on a branch that
|
|
176
|
+
* did not run, or inside a function given nothing, still counts: each name (dotted names folded)
|
|
177
|
+
* and each quoted group_sum or total_sum argument.
|
|
178
|
+
*/
|
|
179
|
+
function namesIn(n, out) {
|
|
180
|
+
if (!n || typeof n !== 'object')
|
|
181
|
+
return out;
|
|
182
|
+
if (Array.isArray(n)) {
|
|
183
|
+
for (const x of n)
|
|
184
|
+
namesIn(x, out);
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
const node = n;
|
|
188
|
+
if (node.type === 'Identifier' && !node.from && !node.relative)
|
|
189
|
+
out.add(String(node.value));
|
|
190
|
+
if (node.type === 'FunctionCall' &&
|
|
191
|
+
(node.name === 'group_sum' || node.name === 'total_sum') &&
|
|
192
|
+
node.args?.[0]?.type === 'Literal' &&
|
|
193
|
+
typeof node.args[0].value === 'string')
|
|
194
|
+
out.add(node.args[0].value);
|
|
195
|
+
for (const v of Object.values(node))
|
|
196
|
+
namesIn(v, out);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
144
199
|
/** Why a formula can't run on fields `keys` (null: any field), or null when it can. */
|
|
145
200
|
export function checkViewFormula(expr, keys) {
|
|
146
|
-
let
|
|
201
|
+
let ast;
|
|
147
202
|
try {
|
|
148
|
-
|
|
203
|
+
ast = compileNamed(expr)._getAst();
|
|
149
204
|
}
|
|
150
205
|
catch (e) {
|
|
151
206
|
return `syntax: ${e instanceof Error ? e.message : String(e)}`;
|
|
152
207
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
159
|
-
run({ value: known, groupSum: known, totalSum: known });
|
|
160
|
-
return unknown.size ? `unknown field ${[...unknown].join(', ')}` : null;
|
|
208
|
+
const missing = [...unknownFunctions(ast, new Set())];
|
|
209
|
+
if (missing.length)
|
|
210
|
+
return `unknown function ${missing.join(', ')} (functions: ${viewFormulaFunctions.join(', ')})`;
|
|
211
|
+
const unknown = keys ? [...namesIn(ast, new Set())].filter((k) => !keys.includes(k)) : [];
|
|
212
|
+
return unknown.length ? `unknown field ${unknown.join(', ')}` : null;
|
|
161
213
|
}
|
|
162
214
|
/** The names a formula reads (fields and formulas, `group_sum` arguments included); [] when it does not parse. */
|
|
163
215
|
export function formulaReads(expr) {
|
|
164
|
-
let run;
|
|
165
216
|
try {
|
|
166
|
-
|
|
217
|
+
return [...namesIn(compileNamed(expr)._getAst(), new Set())];
|
|
167
218
|
}
|
|
168
219
|
catch {
|
|
169
220
|
return [];
|
|
170
221
|
}
|
|
171
|
-
const read = new Set();
|
|
172
|
-
const note = (k) => {
|
|
173
|
-
read.add(k);
|
|
174
|
-
return 1;
|
|
175
|
-
};
|
|
176
|
-
run({ value: note, groupSum: note, totalSum: note });
|
|
177
|
-
return [...read];
|
|
178
222
|
}
|
|
179
223
|
/** A frame's part of a name: `Earnings next week` is `frame.earnings_next_week`. */
|
|
180
224
|
export function frameSpace(name) {
|
package/dist/views.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { type JoinKey } from './domains.js';
|
|
3
3
|
import { type FormulaNames } from './view-formula.js';
|
|
4
4
|
export { checkViewFormula, compileViewFormula, type FormulaNames, formulaLoops, formulaReads, frameSpace, renameFormula, viewFormulaFunctions, type ViewFormulaScope, viewFormulaIsText, whyBlank, } from './view-formula.js';
|
|
5
|
+
export { FORMULA_FUNCTIONS, type FormulaFunction, formulaFunctionsText, } from './formula-functions.js';
|
|
5
6
|
/** How a view shows its rows; the sidebar icon follows it. */
|
|
6
7
|
export declare const viewShapes: readonly ['table', 'group', 'pivot'];
|
|
7
8
|
export type ViewShape = (typeof viewShapes)[number];
|
package/dist/views.js
CHANGED
|
@@ -7,6 +7,7 @@ import { DOMAIN_KEYS, JOINS, joinFields, joinKeys } from './domains.js';
|
|
|
7
7
|
import { MARKET_KEY_RE, marketWindows } from './market.js';
|
|
8
8
|
import { checkViewFormula, formulaLoops, frameSpace, renameFormula, } from './view-formula.js';
|
|
9
9
|
export { checkViewFormula, compileViewFormula, formulaLoops, formulaReads, frameSpace, renameFormula, viewFormulaFunctions, viewFormulaIsText, whyBlank, } from './view-formula.js';
|
|
10
|
+
export { FORMULA_FUNCTIONS, formulaFunctionsText, } from './formula-functions.js';
|
|
10
11
|
const viewKey = z.string().regex(/^[a-z][a-z0-9_]{0,31}$/, 'key: lowercase letters, digits and _');
|
|
11
12
|
const MAX_VALUES = 40;
|
|
12
13
|
/** How a view shows its rows; the sidebar icon follows it. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui-types",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Lumen's view model: views, formulas, domains and joins, frames.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./dist/view-formula.d.ts",
|
|
20
20
|
"default": "./dist/view-formula.js"
|
|
21
21
|
},
|
|
22
|
+
"./formula-functions": {
|
|
23
|
+
"types": "./dist/formula-functions.d.ts",
|
|
24
|
+
"default": "./dist/formula-functions.js"
|
|
25
|
+
},
|
|
22
26
|
"./domains": {
|
|
23
27
|
"types": "./dist/domains.d.ts",
|
|
24
28
|
"default": "./dist/domains.js"
|
|
@@ -72,6 +76,8 @@
|
|
|
72
76
|
"registry": "https://registry.npmjs.org/"
|
|
73
77
|
},
|
|
74
78
|
"dependencies": {
|
|
79
|
+
"@date-fns/utc": "2.1.0",
|
|
80
|
+
"date-fns": "4.1.0",
|
|
75
81
|
"jexl": "2.3.0",
|
|
76
82
|
"zod": "^4.3.6"
|
|
77
83
|
}
|