@objectstack/formula 9.7.0 → 9.8.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +24 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +40 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cel-engine.test.ts +74 -0
- package/src/stdlib.ts +59 -0
- package/src/validate.ts +16 -5
package/src/stdlib.ts
CHANGED
|
@@ -22,6 +22,16 @@ function startOfDayUtc(d: Date): Date {
|
|
|
22
22
|
return out;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Coerce a CEL value (Date | ISO string | epoch number) to a Date. */
|
|
26
|
+
function toDate(v: unknown): Date {
|
|
27
|
+
if (v instanceof Date) return v;
|
|
28
|
+
if (typeof v === 'number' || typeof v === 'bigint') return new Date(Number(v));
|
|
29
|
+
return new Date(String(v));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One UTC day in milliseconds. */
|
|
33
|
+
const MS_PER_DAY = 86_400_000;
|
|
34
|
+
|
|
25
35
|
/** Add `n` days to a Date in UTC; returns a new Date. */
|
|
26
36
|
function addDaysUtc(d: Date, n: number): Date {
|
|
27
37
|
const out = new Date(d.getTime());
|
|
@@ -101,9 +111,58 @@ export function registerStdLib(
|
|
|
101
111
|
}
|
|
102
112
|
return parts.join(separator);
|
|
103
113
|
},
|
|
114
|
+
)
|
|
115
|
+
// ── Dates ────────────────────────────────────────────────────────────
|
|
116
|
+
// Whole days from `a` to `b` (negative if `b` is before `a`). The common
|
|
117
|
+
// shape is `daysBetween(today(), record.due)` for "days remaining". Args are
|
|
118
|
+
// coerced (Date | ISO string | epoch) so a `Field.date` that arrives as a
|
|
119
|
+
// string still works without the caller hydrating it.
|
|
120
|
+
.registerFunction(
|
|
121
|
+
'daysBetween(dyn, dyn): int',
|
|
122
|
+
(a: unknown, b: unknown) =>
|
|
123
|
+
BigInt(Math.round((toDate(b).getTime() - toDate(a).getTime()) / MS_PER_DAY)),
|
|
124
|
+
)
|
|
125
|
+
// Parse an ISO date / date-time string to a Timestamp. `date` and `datetime`
|
|
126
|
+
// are aliases — both accept either form (the field's own type decides the
|
|
127
|
+
// intent); kept distinct because authors reach for whichever reads clearer.
|
|
128
|
+
.registerFunction('date(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))
|
|
129
|
+
.registerFunction('datetime(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))
|
|
130
|
+
// ── Numbers ──────────────────────────────────────────────────────────
|
|
131
|
+
.registerFunction('abs(dyn): double', (x: unknown) => Math.abs(Number(x)))
|
|
132
|
+
.registerFunction('round(dyn): int', (x: unknown) => BigInt(Math.round(Number(x))))
|
|
133
|
+
// min/max return the smaller/larger operand verbatim (type preserved) rather
|
|
134
|
+
// than a coerced copy, so `min(record.a, record.b)` keeps int-ness when both
|
|
135
|
+
// are ints. Comparison is numeric.
|
|
136
|
+
.registerFunction('min(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) <= Number(b) ? a : b))
|
|
137
|
+
.registerFunction('max(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) >= Number(b) ? a : b))
|
|
138
|
+
// ── Strings ──────────────────────────────────────────────────────────
|
|
139
|
+
// Free-function forms of the common string ops. CEL also exposes some as
|
|
140
|
+
// receiver methods (`s.contains(x)`), but the authoring catalog advertises
|
|
141
|
+
// the bare-call form, so register it to match what authors are told to use.
|
|
142
|
+
.registerFunction('upper(dyn): string', (s: unknown) => String(s ?? '').toUpperCase())
|
|
143
|
+
.registerFunction('lower(dyn): string', (s: unknown) => String(s ?? '').toLowerCase())
|
|
144
|
+
.registerFunction('contains(dyn, dyn): bool', (s: unknown, sub: unknown) => String(s ?? '').includes(String(sub ?? '')))
|
|
145
|
+
.registerFunction('startsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').startsWith(String(p ?? '')))
|
|
146
|
+
.registerFunction('endsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').endsWith(String(p ?? '')))
|
|
147
|
+
.registerFunction('matches(dyn, dyn): bool', (s: unknown, re: unknown) => new RegExp(String(re ?? '')).test(String(s ?? '')))
|
|
148
|
+
// ── Collections ──────────────────────────────────────────────────────
|
|
149
|
+
// `len` mirrors CEL's built-in `size()` for strings/lists/maps; `isEmpty` is
|
|
150
|
+
// the inverse-of-non-empty companion to `isBlank` (true for null, '', []).
|
|
151
|
+
.registerFunction('len(dyn): int', (v: unknown) => BigInt(lengthOf(v)))
|
|
152
|
+
.registerFunction(
|
|
153
|
+
'isEmpty(dyn): bool',
|
|
154
|
+
(v: unknown) => v === null || v === undefined || lengthOf(v) === 0,
|
|
104
155
|
);
|
|
105
156
|
}
|
|
106
157
|
|
|
158
|
+
/** Length of a string / list / map (0 for scalars and null). */
|
|
159
|
+
function lengthOf(v: unknown): number {
|
|
160
|
+
if (v === null || v === undefined) return 0;
|
|
161
|
+
if (typeof v === 'string' || Array.isArray(v)) return v.length;
|
|
162
|
+
if (typeof v === 'object') return Object.keys(v as Record<string, unknown>).length;
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
107
166
|
/**
|
|
108
167
|
* Register mixed `double <op> int` / `int <op> double` arithmetic overloads.
|
|
109
168
|
*
|
package/src/validate.ts
CHANGED
|
@@ -256,10 +256,21 @@ export function introspectScope(role: FieldRole, schema?: ExprSchemaHint): {
|
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* Public catalog of CEL functions available in expressions — what `introspectScope`
|
|
261
|
+
* advertises to authors (incl. AI). Every entry MUST actually resolve at runtime:
|
|
262
|
+
* either registered in `registerStdLib` or a verified cel-js built-in. Drifting this
|
|
263
|
+
* list ahead of the runtime tells the author to call functions that fault (#1928).
|
|
264
|
+
*/
|
|
260
265
|
export const CEL_STDLIB_FUNCTIONS: string[] = [
|
|
261
|
-
|
|
262
|
-
'
|
|
263
|
-
|
|
264
|
-
'
|
|
266
|
+
// Dates (registered stdlib)
|
|
267
|
+
'now', 'today', 'daysFromNow', 'daysAgo', 'daysBetween', 'date', 'datetime',
|
|
268
|
+
// Numbers (registered stdlib)
|
|
269
|
+
'abs', 'round', 'min', 'max',
|
|
270
|
+
// Strings (registered stdlib)
|
|
271
|
+
'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches', 'joinNonEmpty',
|
|
272
|
+
// Collections / null-ish (registered stdlib)
|
|
273
|
+
'isBlank', 'isEmpty', 'coalesce', 'len',
|
|
274
|
+
// cel-js built-ins (verified to resolve)
|
|
275
|
+
'size', 'has', 'int', 'string', 'bool', 'double', 'timestamp', 'duration',
|
|
265
276
|
];
|