@bpmnkit/feel 0.0.8

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.
@@ -0,0 +1,1194 @@
1
+ import { isFeelContext, isFeelDate, isFeelDateTime, isFeelDayTimeDuration, isFeelList, isFeelRange, isFeelTime, isFeelYearsMonthsDuration, } from "./types.js";
2
+ // -------------------------------------------------------------------------
3
+ // Temporal helpers
4
+ // -------------------------------------------------------------------------
5
+ const DAYS_IN_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
6
+ function isLeapYear(y) {
7
+ return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
8
+ }
9
+ function daysInMonth(y, m) {
10
+ if (m === 2 && isLeapYear(y))
11
+ return 29;
12
+ return DAYS_IN_MONTH[m] ?? 30;
13
+ }
14
+ function dateToEpochDays(d) {
15
+ // Days since 1970-01-01 (Gregorian proleptic)
16
+ const y = d.year - 1;
17
+ let days = 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400);
18
+ for (let m = 1; m < d.month; m++) {
19
+ days += daysInMonth(d.year, m);
20
+ }
21
+ days += d.day;
22
+ // Subtract epoch offset (1970-01-01 = day 719163 in this counting)
23
+ return days - 719163;
24
+ }
25
+ function epochDaysToDate(days) {
26
+ // Naive implementation: add days to 1970-01-01
27
+ let remaining = days + 719162; // days since year 0
28
+ const year400 = Math.floor(remaining / 146097);
29
+ remaining %= 146097;
30
+ const year100 = Math.min(Math.floor(remaining / 36524), 3);
31
+ remaining -= year100 * 36524;
32
+ const year4 = Math.floor(remaining / 1461);
33
+ remaining %= 1461;
34
+ const year1 = Math.min(Math.floor(remaining / 365), 3);
35
+ remaining -= year1 * 365;
36
+ const year = year400 * 400 + year100 * 100 + year4 * 4 + year1 + 1;
37
+ let month = 1;
38
+ while (month <= 12 && remaining >= daysInMonth(year, month)) {
39
+ remaining -= daysInMonth(year, month);
40
+ month++;
41
+ }
42
+ return { type: "date", year, month, day: remaining + 1 };
43
+ }
44
+ function parseDate(s) {
45
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
46
+ if (!m)
47
+ return null;
48
+ return { type: "date", year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) };
49
+ }
50
+ function parseTime(s) {
51
+ const m = /^(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)(?:([+-])(\d{2}):(\d{2})|Z)?(@(.+))?$/.exec(s);
52
+ if (!m)
53
+ return null;
54
+ const hour = Number(m[1]);
55
+ const minute = Number(m[2]);
56
+ const second = Number(m[3]);
57
+ let offsetSeconds;
58
+ let timezone;
59
+ if (m[4]) {
60
+ const sign = m[4] === "+" ? 1 : -1;
61
+ offsetSeconds = sign * (Number(m[5]) * 3600 + Number(m[6]) * 60);
62
+ }
63
+ else if (m[3] && s.includes("Z")) {
64
+ offsetSeconds = 0;
65
+ }
66
+ if (m[8])
67
+ timezone = m[8];
68
+ return { type: "time", hour, minute, second, offsetSeconds, timezone };
69
+ }
70
+ function parseDateTime(s) {
71
+ const idx = s.indexOf("T");
72
+ if (idx < 0)
73
+ return null;
74
+ const d = parseDate(s.slice(0, idx));
75
+ const t = parseTime(s.slice(idx + 1));
76
+ if (!d || !t)
77
+ return null;
78
+ return { type: "date-time", date: d, time: t };
79
+ }
80
+ function parseDuration(s) {
81
+ // P[n]Y[n]M or P[n]DT[n]H[n]M[n]S
82
+ const ymMatch = /^-?P(\d+Y)?(\d+M)?$/.exec(s);
83
+ if (ymMatch) {
84
+ const sign = s.startsWith("-") ? -1 : 1;
85
+ const years = ymMatch[1] ? Number(ymMatch[1].slice(0, -1)) : 0;
86
+ const months = ymMatch[2] ? Number(ymMatch[2].slice(0, -1)) : 0;
87
+ return { type: "years-months-duration", months: sign * (years * 12 + months) };
88
+ }
89
+ const dtMatch = /^-?P(\d+D)?(?:T(\d+H)?(\d+M)?(\d+(?:\.\d+)?S)?)?$/.exec(s);
90
+ if (dtMatch && s.length > 1) {
91
+ const sign = s.startsWith("-") ? -1 : 1;
92
+ const days = dtMatch[1] ? Number(dtMatch[1].slice(0, -1)) : 0;
93
+ const hours = dtMatch[2] ? Number(dtMatch[2].slice(0, -1)) : 0;
94
+ const minutes = dtMatch[3] ? Number(dtMatch[3].slice(0, -1)) : 0;
95
+ const seconds = dtMatch[4] ? Number(dtMatch[4].slice(0, -1)) : 0;
96
+ return {
97
+ type: "days-time-duration",
98
+ seconds: sign * (days * 86400 + hours * 3600 + minutes * 60 + seconds),
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+ function parseTemporal(raw) {
104
+ // raw = @"..."
105
+ const inner = raw.slice(2, -1);
106
+ const d = parseDate(inner);
107
+ if (d)
108
+ return d;
109
+ const dt = parseDateTime(inner);
110
+ if (dt)
111
+ return dt;
112
+ const t = parseTime(inner);
113
+ if (t)
114
+ return t;
115
+ const dur = parseDuration(inner);
116
+ if (dur)
117
+ return dur;
118
+ return null;
119
+ }
120
+ function formatDate(d) {
121
+ return `${String(d.year).padStart(4, "0")}-${String(d.month).padStart(2, "0")}-${String(d.day).padStart(2, "0")}`;
122
+ }
123
+ function formatTime(t) {
124
+ let s = `${String(t.hour).padStart(2, "0")}:${String(t.minute).padStart(2, "0")}:${String(t.second).padStart(2, "0")}`;
125
+ if (t.offsetSeconds !== undefined) {
126
+ if (t.offsetSeconds === 0) {
127
+ s += "Z";
128
+ }
129
+ else {
130
+ const sign = t.offsetSeconds >= 0 ? "+" : "-";
131
+ const abs = Math.abs(t.offsetSeconds);
132
+ s += `${sign}${String(Math.floor(abs / 3600)).padStart(2, "0")}:${String(Math.floor((abs % 3600) / 60)).padStart(2, "0")}`;
133
+ }
134
+ }
135
+ if (t.timezone)
136
+ s += `@${t.timezone}`;
137
+ return s;
138
+ }
139
+ function dayOfWeek(d) {
140
+ // 0=Sunday, 1=Monday, ... 6=Saturday → FEEL: 1=Monday ... 7=Sunday
141
+ const epochDays = dateToEpochDays(d);
142
+ return ((epochDays % 7) + 7 + 4) % 7; // 1970-01-01 was Thursday (4)
143
+ }
144
+ function dayOfYear(d) {
145
+ let n = d.day;
146
+ for (let m = 1; m < d.month; m++)
147
+ n += daysInMonth(d.year, m);
148
+ return n;
149
+ }
150
+ // -------------------------------------------------------------------------
151
+ // Helpers
152
+ // -------------------------------------------------------------------------
153
+ function toNum(v) {
154
+ if (typeof v === "number")
155
+ return v;
156
+ if (typeof v === "string") {
157
+ const n = Number(v);
158
+ return Number.isNaN(n) ? null : n;
159
+ }
160
+ return null;
161
+ }
162
+ function toStr(v) {
163
+ if (typeof v === "string")
164
+ return v;
165
+ return null;
166
+ }
167
+ function flattenToList(args) {
168
+ const first = args[0];
169
+ if (args.length === 1 && first !== undefined && isFeelList(first))
170
+ return first;
171
+ return args;
172
+ }
173
+ // Safe array element access (noUncheckedIndexedAccess compatibility)
174
+ function at(arr, i) {
175
+ return arr[i] ?? null;
176
+ }
177
+ // Unwrap single-list argument or return the flat array
178
+ function unwrapList(flat) {
179
+ const first = flat[0];
180
+ return flat.length === 1 && first !== undefined && isFeelList(first) ? first : flat;
181
+ }
182
+ function inRange(v, r) {
183
+ const cmpStart = compareValues(v, r.start);
184
+ const cmpEnd = compareValues(v, r.end);
185
+ if (cmpStart === null || cmpEnd === null)
186
+ return false;
187
+ const startOk = r.startIncluded ? cmpStart >= 0 : cmpStart > 0;
188
+ const endOk = r.endIncluded ? cmpEnd <= 0 : cmpEnd < 0;
189
+ return startOk && endOk;
190
+ }
191
+ function compareValues(a, b) {
192
+ if (typeof a === "number" && typeof b === "number")
193
+ return a - b;
194
+ if (typeof a === "string" && typeof b === "string")
195
+ return a < b ? -1 : a > b ? 1 : 0;
196
+ if (isFeelDate(a) && isFeelDate(b))
197
+ return dateToEpochDays(a) - dateToEpochDays(b);
198
+ if (isFeelDayTimeDuration(a) && isFeelDayTimeDuration(b))
199
+ return a.seconds - b.seconds;
200
+ if (isFeelYearsMonthsDuration(a) && isFeelYearsMonthsDuration(b))
201
+ return a.months - b.months;
202
+ return null;
203
+ }
204
+ const builtinMap = new Map();
205
+ function reg(name, fn) {
206
+ builtinMap.set(name, fn);
207
+ }
208
+ // -------------------------------------------------------------------------
209
+ // String functions
210
+ // -------------------------------------------------------------------------
211
+ reg("string", (v) => {
212
+ if (v === null)
213
+ return "null";
214
+ if (typeof v === "string")
215
+ return v;
216
+ if (typeof v === "number")
217
+ return String(v);
218
+ if (typeof v === "boolean")
219
+ return String(v);
220
+ if (isFeelDate(v))
221
+ return formatDate(v);
222
+ if (isFeelTime(v))
223
+ return formatTime(v);
224
+ if (isFeelDateTime(v))
225
+ return `${formatDate(v.date)}T${formatTime(v.time)}`;
226
+ if (isFeelDayTimeDuration(v)) {
227
+ const s = Math.abs(v.seconds);
228
+ const d = Math.floor(s / 86400);
229
+ const h = Math.floor((s % 86400) / 3600);
230
+ const m = Math.floor((s % 3600) / 60);
231
+ const sec = s % 60;
232
+ let r = v.seconds < 0 ? "-P" : "P";
233
+ if (d)
234
+ r += `${d}D`;
235
+ if (h || m || sec)
236
+ r += `T${h ? `${h}H` : ""}${m ? `${m}M` : ""}${sec ? `${sec}S` : ""}`;
237
+ if (r === "P" || r === "-P")
238
+ r += "T0S";
239
+ return r;
240
+ }
241
+ if (isFeelYearsMonthsDuration(v)) {
242
+ const mo = Math.abs(v.months);
243
+ const y = Math.floor(mo / 12);
244
+ const m = mo % 12;
245
+ let r = v.months < 0 ? "-P" : "P";
246
+ if (y)
247
+ r += `${y}Y`;
248
+ if (m)
249
+ r += `${m}M`;
250
+ if (r === "P" || r === "-P")
251
+ r += "0M";
252
+ return r;
253
+ }
254
+ return null;
255
+ });
256
+ reg("string length", (s) => {
257
+ const str = toStr(s);
258
+ return str === null ? null : str.length;
259
+ });
260
+ reg("substring", (str, start, length) => {
261
+ const s = toStr(str);
262
+ if (s === null)
263
+ return null;
264
+ const st = toNum(start);
265
+ if (st === null)
266
+ return null;
267
+ // FEEL substring is 1-based, negative counts from end
268
+ const idx = st > 0 ? st - 1 : Math.max(0, s.length + st);
269
+ if (length !== undefined && length !== null) {
270
+ const len = toNum(length);
271
+ if (len === null)
272
+ return null;
273
+ return s.slice(idx, idx + len);
274
+ }
275
+ return s.slice(idx);
276
+ });
277
+ reg("substring before", (str, match) => {
278
+ const s = toStr(str);
279
+ const m = toStr(match);
280
+ if (s === null || m === null)
281
+ return null;
282
+ const idx = s.indexOf(m);
283
+ return idx < 0 ? "" : s.slice(0, idx);
284
+ });
285
+ reg("substring after", (str, match) => {
286
+ const s = toStr(str);
287
+ const m = toStr(match);
288
+ if (s === null || m === null)
289
+ return null;
290
+ const idx = s.indexOf(m);
291
+ return idx < 0 ? "" : s.slice(idx + m.length);
292
+ });
293
+ reg("upper case", (s) => {
294
+ const str = toStr(s);
295
+ return str === null ? null : str.toUpperCase();
296
+ });
297
+ reg("lower case", (s) => {
298
+ const str = toStr(s);
299
+ return str === null ? null : str.toLowerCase();
300
+ });
301
+ reg("contains", (str, match) => {
302
+ const s = toStr(str);
303
+ const m = toStr(match);
304
+ if (s === null || m === null)
305
+ return null;
306
+ return s.includes(m);
307
+ });
308
+ reg("starts with", (str, match) => {
309
+ const s = toStr(str);
310
+ const m = toStr(match);
311
+ if (s === null || m === null)
312
+ return null;
313
+ return s.startsWith(m);
314
+ });
315
+ reg("ends with", (str, match) => {
316
+ const s = toStr(str);
317
+ const m = toStr(match);
318
+ if (s === null || m === null)
319
+ return null;
320
+ return s.endsWith(m);
321
+ });
322
+ reg("matches", (str, pattern, flags) => {
323
+ const s = toStr(str);
324
+ const p = toStr(pattern);
325
+ if (s === null || p === null)
326
+ return null;
327
+ const f = flags !== undefined && flags !== null ? (toStr(flags) ?? "") : "";
328
+ try {
329
+ return new RegExp(p, f).test(s);
330
+ }
331
+ catch {
332
+ return null;
333
+ }
334
+ });
335
+ reg("replace", (str, pattern, replacement, flags) => {
336
+ const s = toStr(str);
337
+ const p = toStr(pattern);
338
+ const r = toStr(replacement);
339
+ if (s === null || p === null || r === null)
340
+ return null;
341
+ const f = flags !== undefined && flags !== null ? (toStr(flags) ?? "g") : "g";
342
+ try {
343
+ return s.replace(new RegExp(p, f.includes("g") ? f : `${f}g`), r);
344
+ }
345
+ catch {
346
+ return null;
347
+ }
348
+ });
349
+ reg("split", (str, delimiter) => {
350
+ const s = toStr(str);
351
+ const d = toStr(delimiter);
352
+ if (s === null || d === null)
353
+ return null;
354
+ try {
355
+ return s.split(new RegExp(d));
356
+ }
357
+ catch {
358
+ return s.split(d);
359
+ }
360
+ });
361
+ reg("string join", (...args) => {
362
+ const flat = flattenToList(args);
363
+ // string join(list) or string join(list, delimiter) or string join(list, delimiter, prefix, suffix)
364
+ let list;
365
+ let delimiter = "";
366
+ const first = flat[0];
367
+ if (flat.length >= 1 && first !== undefined && isFeelList(first)) {
368
+ list = first;
369
+ delimiter = flat.length >= 2 ? (toStr(at(flat, 1)) ?? "") : "";
370
+ }
371
+ else {
372
+ list = flat;
373
+ }
374
+ const parts = [];
375
+ for (const v of list) {
376
+ const s = toStr(v);
377
+ if (s !== null)
378
+ parts.push(s);
379
+ }
380
+ return parts.join(delimiter);
381
+ });
382
+ // -------------------------------------------------------------------------
383
+ // Number functions
384
+ // -------------------------------------------------------------------------
385
+ reg("number", (v) => {
386
+ if (typeof v === "number")
387
+ return v;
388
+ if (typeof v === "string") {
389
+ const n = Number(v);
390
+ return Number.isNaN(n) ? null : n;
391
+ }
392
+ return null;
393
+ });
394
+ reg("decimal", (n, scale) => {
395
+ const num = toNum(n);
396
+ const sc = toNum(scale);
397
+ if (num === null || sc === null)
398
+ return null;
399
+ const factor = 10 ** sc;
400
+ return Math.round(num * factor) / factor;
401
+ });
402
+ reg("floor", (n, scale) => {
403
+ const num = toNum(n);
404
+ if (num === null)
405
+ return null;
406
+ if (scale !== undefined && scale !== null) {
407
+ const sc = toNum(scale) ?? 0;
408
+ const factor = 10 ** sc;
409
+ return Math.floor(num * factor) / factor;
410
+ }
411
+ return Math.floor(num);
412
+ });
413
+ reg("ceiling", (n, scale) => {
414
+ const num = toNum(n);
415
+ if (num === null)
416
+ return null;
417
+ if (scale !== undefined && scale !== null) {
418
+ const sc = toNum(scale) ?? 0;
419
+ const factor = 10 ** sc;
420
+ return Math.ceil(num * factor) / factor;
421
+ }
422
+ return Math.ceil(num);
423
+ });
424
+ reg("round half up", (n, scale) => {
425
+ const num = toNum(n);
426
+ const sc = toNum(scale) ?? 0;
427
+ if (num === null)
428
+ return null;
429
+ const factor = 10 ** sc;
430
+ return Math.round(num * factor) / factor;
431
+ });
432
+ reg("round half down", (n, scale) => {
433
+ const num = toNum(n);
434
+ const sc = toNum(scale) ?? 0;
435
+ if (num === null)
436
+ return null;
437
+ const factor = 10 ** sc;
438
+ const scaled = num * factor;
439
+ return (scaled > 0 ? Math.ceil(scaled - 0.5) : Math.floor(scaled + 0.5)) / factor;
440
+ });
441
+ reg("round up", (n, scale) => {
442
+ const num = toNum(n);
443
+ const sc = toNum(scale) ?? 0;
444
+ if (num === null)
445
+ return null;
446
+ const factor = 10 ** sc;
447
+ const scaled = num * factor;
448
+ return (scaled > 0 ? Math.ceil(scaled) : Math.floor(scaled)) / factor;
449
+ });
450
+ reg("round down", (n, scale) => {
451
+ const num = toNum(n);
452
+ const sc = toNum(scale) ?? 0;
453
+ if (num === null)
454
+ return null;
455
+ const factor = 10 ** sc;
456
+ return Math.trunc(num * factor) / factor;
457
+ });
458
+ reg("abs", (n) => {
459
+ if (typeof n === "number")
460
+ return Math.abs(n);
461
+ if (isFeelDayTimeDuration(n))
462
+ return { type: "days-time-duration", seconds: Math.abs(n.seconds) };
463
+ if (isFeelYearsMonthsDuration(n))
464
+ return { type: "years-months-duration", months: Math.abs(n.months) };
465
+ return null;
466
+ });
467
+ reg("modulo", (n, d) => {
468
+ const num = toNum(n);
469
+ const div = toNum(d);
470
+ if (num === null || div === null || div === 0)
471
+ return null;
472
+ return ((num % div) + div) % div;
473
+ });
474
+ reg("sqrt", (n) => {
475
+ const num = toNum(n);
476
+ return num === null || num < 0 ? null : Math.sqrt(num);
477
+ });
478
+ reg("log", (n) => {
479
+ const num = toNum(n);
480
+ return num === null || num <= 0 ? null : Math.log(num);
481
+ });
482
+ reg("exp", (n) => {
483
+ const num = toNum(n);
484
+ return num === null ? null : Math.exp(num);
485
+ });
486
+ reg("odd", (n) => {
487
+ const num = toNum(n);
488
+ return num === null ? null : Math.abs(num) % 2 === 1;
489
+ });
490
+ reg("even", (n) => {
491
+ const num = toNum(n);
492
+ return num === null ? null : num % 2 === 0;
493
+ });
494
+ reg("random number", () => Math.random());
495
+ // -------------------------------------------------------------------------
496
+ // List functions
497
+ // -------------------------------------------------------------------------
498
+ reg("count", (...args) => {
499
+ const list = flattenToList(args);
500
+ const first = list[0];
501
+ if (list.length === 1 && first !== undefined && isFeelList(first))
502
+ return first.length;
503
+ return list.length;
504
+ });
505
+ reg("list contains", (list, item) => {
506
+ if (!isFeelList(list))
507
+ return null;
508
+ return list.some((v) => v === item);
509
+ });
510
+ reg("min", (...args) => {
511
+ const list = flattenToList(args);
512
+ const listFirst = list[0];
513
+ const flat = listFirst !== undefined && isFeelList(listFirst) && list.length === 1 ? listFirst : list;
514
+ if (flat.length === 0)
515
+ return null;
516
+ let m = flat[0] ?? null;
517
+ for (let i = 1; i < flat.length; i++) {
518
+ const v = flat[i] ?? null;
519
+ const cmp = compareValues(v, m);
520
+ if (cmp !== null && cmp < 0)
521
+ m = v;
522
+ }
523
+ return m;
524
+ });
525
+ reg("max", (...args) => {
526
+ const list = flattenToList(args);
527
+ const listFirst = list[0];
528
+ const flat = listFirst !== undefined && isFeelList(listFirst) && list.length === 1 ? listFirst : list;
529
+ if (flat.length === 0)
530
+ return null;
531
+ let m = flat[0] ?? null;
532
+ for (let i = 1; i < flat.length; i++) {
533
+ const v = flat[i] ?? null;
534
+ const cmp = compareValues(v, m);
535
+ if (cmp !== null && cmp > 0)
536
+ m = v;
537
+ }
538
+ return m;
539
+ });
540
+ reg("sum", (...args) => {
541
+ const list = unwrapList(flattenToList(args));
542
+ let s = 0;
543
+ for (const v of list) {
544
+ const n = toNum(v);
545
+ if (n === null)
546
+ return null;
547
+ s += n;
548
+ }
549
+ return s;
550
+ });
551
+ reg("product", (...args) => {
552
+ const list = unwrapList(flattenToList(args));
553
+ let p = 1;
554
+ for (const v of list) {
555
+ const n = toNum(v);
556
+ if (n === null)
557
+ return null;
558
+ p *= n;
559
+ }
560
+ return p;
561
+ });
562
+ reg("mean", (...args) => {
563
+ const list = unwrapList(flattenToList(args));
564
+ if (list.length === 0)
565
+ return null;
566
+ let s = 0;
567
+ for (const v of list) {
568
+ const n = toNum(v);
569
+ if (n === null)
570
+ return null;
571
+ s += n;
572
+ }
573
+ return s / list.length;
574
+ });
575
+ reg("median", (...args) => {
576
+ const list = unwrapList(flattenToList(args));
577
+ const nums = [];
578
+ for (const v of list) {
579
+ const n = toNum(v);
580
+ if (n === null)
581
+ return null;
582
+ nums.push(n);
583
+ }
584
+ if (nums.length === 0)
585
+ return null;
586
+ nums.sort((a, b) => a - b);
587
+ const mid = Math.floor(nums.length / 2);
588
+ return nums.length % 2 === 0 ? ((nums[mid - 1] ?? 0) + (nums[mid] ?? 0)) / 2 : (nums[mid] ?? 0);
589
+ });
590
+ reg("stddev", (...args) => {
591
+ const list = unwrapList(flattenToList(args));
592
+ const nums = [];
593
+ for (const v of list) {
594
+ const n = toNum(v);
595
+ if (n === null)
596
+ return null;
597
+ nums.push(n);
598
+ }
599
+ if (nums.length <= 1)
600
+ return null;
601
+ const mean = nums.reduce((a, b) => a + b, 0) / nums.length;
602
+ const variance = nums.reduce((a, b) => a + (b - mean) ** 2, 0) / (nums.length - 1);
603
+ return Math.sqrt(variance);
604
+ });
605
+ reg("mode", (...args) => {
606
+ const list = unwrapList(flattenToList(args));
607
+ const counts = new Map();
608
+ for (const v of list) {
609
+ counts.set(v, (counts.get(v) ?? 0) + 1);
610
+ }
611
+ let maxCount = 0;
612
+ for (const cnt of counts.values()) {
613
+ if (cnt > maxCount)
614
+ maxCount = cnt;
615
+ }
616
+ const modes = [];
617
+ for (const [v, cnt] of counts) {
618
+ if (cnt === maxCount)
619
+ modes.push(v);
620
+ }
621
+ return modes;
622
+ });
623
+ reg("all", (...args) => {
624
+ const list = unwrapList(flattenToList(args));
625
+ let hasNull = false;
626
+ for (const v of list) {
627
+ if (v === false)
628
+ return false;
629
+ if (v === null)
630
+ hasNull = true;
631
+ }
632
+ return hasNull ? null : true;
633
+ });
634
+ reg("any", (...args) => {
635
+ const list = unwrapList(flattenToList(args));
636
+ let hasNull = false;
637
+ for (const v of list) {
638
+ if (v === true)
639
+ return true;
640
+ if (v === null)
641
+ hasNull = true;
642
+ }
643
+ return hasNull ? null : false;
644
+ });
645
+ reg("sublist", (list, start, length) => {
646
+ if (!isFeelList(list))
647
+ return null;
648
+ const st = toNum(start);
649
+ if (st === null)
650
+ return null;
651
+ const sliceIdx = st > 0 ? st - 1 : Math.max(0, list.length + st);
652
+ if (length !== undefined && length !== null) {
653
+ const len = toNum(length);
654
+ if (len === null)
655
+ return null;
656
+ return list.slice(sliceIdx, sliceIdx + len);
657
+ }
658
+ return list.slice(sliceIdx);
659
+ });
660
+ reg("append", (...args) => {
661
+ if (args.length < 1)
662
+ return null;
663
+ const list = at(args, 0);
664
+ if (!isFeelList(list))
665
+ return null;
666
+ return [...list, ...args.slice(1)];
667
+ });
668
+ reg("concatenate", (...args) => {
669
+ const flat = flattenToList(args);
670
+ const result = [];
671
+ for (const v of flat) {
672
+ if (isFeelList(v))
673
+ result.push(...v);
674
+ else
675
+ result.push(v);
676
+ }
677
+ return result;
678
+ });
679
+ reg("insert before", (list, pos, newItem) => {
680
+ if (!isFeelList(list))
681
+ return null;
682
+ const p = toNum(pos);
683
+ if (p === null)
684
+ return null;
685
+ const idx = p > 0 ? p - 1 : list.length + p;
686
+ const result = [...list];
687
+ result.splice(idx, 0, newItem ?? null);
688
+ return result;
689
+ });
690
+ reg("remove", (list, pos) => {
691
+ if (!isFeelList(list))
692
+ return null;
693
+ const p = toNum(pos);
694
+ if (p === null)
695
+ return null;
696
+ const idx = p > 0 ? p - 1 : list.length + p;
697
+ const result = [...list];
698
+ result.splice(idx, 1);
699
+ return result;
700
+ });
701
+ reg("reverse", (list) => {
702
+ if (!isFeelList(list))
703
+ return null;
704
+ return [...list].reverse();
705
+ });
706
+ reg("index of", (list, match) => {
707
+ if (!isFeelList(list))
708
+ return null;
709
+ const result = [];
710
+ for (let i = 0; i < list.length; i++) {
711
+ if (list[i] === match)
712
+ result.push(i + 1);
713
+ }
714
+ return result;
715
+ });
716
+ reg("union", (...args) => {
717
+ const result = [];
718
+ for (const v of args) {
719
+ if (isFeelList(v)) {
720
+ for (const item of v) {
721
+ if (!result.includes(item))
722
+ result.push(item);
723
+ }
724
+ }
725
+ else if (!result.includes(v)) {
726
+ result.push(v);
727
+ }
728
+ }
729
+ return result;
730
+ });
731
+ reg("distinct values", (list) => {
732
+ if (!isFeelList(list))
733
+ return null;
734
+ const result = [];
735
+ for (const v of list) {
736
+ if (!result.includes(v))
737
+ result.push(v);
738
+ }
739
+ return result;
740
+ });
741
+ reg("flatten", (list) => {
742
+ if (!isFeelList(list))
743
+ return null;
744
+ const flat = (arr) => {
745
+ const result = [];
746
+ for (const v of arr) {
747
+ if (isFeelList(v))
748
+ result.push(...flat(v));
749
+ else
750
+ result.push(v);
751
+ }
752
+ return result;
753
+ };
754
+ return flat(list);
755
+ });
756
+ reg("sort", (list, fn) => {
757
+ if (!isFeelList(list))
758
+ return null;
759
+ const sorted = [...list];
760
+ if (fn !== undefined && fn !== null && typeof fn === "object" && "call" in fn) {
761
+ const f = fn;
762
+ sorted.sort((a, b) => {
763
+ const r = f.call([a, b]);
764
+ return r === true ? -1 : r === false ? 1 : 0;
765
+ });
766
+ }
767
+ else {
768
+ sorted.sort((a, b) => compareValues(a, b) ?? 0);
769
+ }
770
+ return sorted;
771
+ });
772
+ // -------------------------------------------------------------------------
773
+ // Boolean functions
774
+ // -------------------------------------------------------------------------
775
+ reg("not", (v) => {
776
+ if (typeof v === "boolean")
777
+ return !v;
778
+ return null;
779
+ });
780
+ reg("is defined", (v) => v !== null && v !== undefined);
781
+ reg("get or else", (v, defaultVal) => {
782
+ return v !== null && v !== undefined ? v : (defaultVal ?? null);
783
+ });
784
+ // -------------------------------------------------------------------------
785
+ // Context functions
786
+ // -------------------------------------------------------------------------
787
+ reg("get value", (ctx, key) => {
788
+ if (!isFeelContext(ctx))
789
+ return null;
790
+ const k = toStr(key);
791
+ if (k === null)
792
+ return null;
793
+ const val = ctx[k];
794
+ return val !== undefined ? val : null;
795
+ });
796
+ reg("get entries", (ctx) => {
797
+ if (!isFeelContext(ctx))
798
+ return null;
799
+ return Object.entries(ctx).map(([k, v]) => ({ key: k, value: v }));
800
+ });
801
+ reg("context put", (ctx, key, value) => {
802
+ if (!isFeelContext(ctx))
803
+ return null;
804
+ const k = toStr(key);
805
+ if (k === null)
806
+ return null;
807
+ const result = {};
808
+ for (const [ck, cv] of Object.entries(ctx))
809
+ result[ck] = cv;
810
+ result[k] = value ?? null;
811
+ return result;
812
+ });
813
+ reg("context merge", (...args) => {
814
+ const result = {};
815
+ for (const v of args) {
816
+ if (!isFeelContext(v))
817
+ return null;
818
+ for (const [k, cv] of Object.entries(v))
819
+ result[k] = cv;
820
+ }
821
+ return result;
822
+ });
823
+ reg("context", (list) => {
824
+ if (!isFeelList(list))
825
+ return null;
826
+ const result = {};
827
+ for (const item of list) {
828
+ if (!isFeelContext(item))
829
+ return null;
830
+ const k = item.key;
831
+ const v = item.value;
832
+ if (typeof k === "string")
833
+ result[k] = v !== undefined ? v : null;
834
+ }
835
+ return result;
836
+ });
837
+ // -------------------------------------------------------------------------
838
+ // Conversion functions
839
+ // -------------------------------------------------------------------------
840
+ reg("date", (...args) => {
841
+ if (args.length === 1) {
842
+ const v = at(args, 0);
843
+ if (typeof v === "string")
844
+ return parseDate(v);
845
+ if (isFeelDateTime(v))
846
+ return v.date;
847
+ return null;
848
+ }
849
+ if (args.length === 3) {
850
+ const y = toNum(at(args, 0));
851
+ const m = toNum(at(args, 1));
852
+ const d = toNum(at(args, 2));
853
+ if (y === null || m === null || d === null)
854
+ return null;
855
+ return { type: "date", year: y, month: m, day: d };
856
+ }
857
+ return null;
858
+ });
859
+ reg("time", (...args) => {
860
+ if (args.length === 1) {
861
+ const v = at(args, 0);
862
+ if (typeof v === "string")
863
+ return parseTime(v);
864
+ if (isFeelDateTime(v))
865
+ return v.time;
866
+ return null;
867
+ }
868
+ if (args.length >= 3) {
869
+ const h = toNum(at(args, 0));
870
+ const m = toNum(at(args, 1));
871
+ const s = toNum(at(args, 2));
872
+ if (h === null || m === null || s === null)
873
+ return null;
874
+ const t = { type: "time", hour: h, minute: m, second: s };
875
+ const off = at(args, 3);
876
+ if (off !== null && isFeelDayTimeDuration(off))
877
+ t.offsetSeconds = off.seconds;
878
+ return t;
879
+ }
880
+ return null;
881
+ });
882
+ reg("date and time", (...args) => {
883
+ if (args.length === 1) {
884
+ const v = at(args, 0);
885
+ if (typeof v === "string")
886
+ return parseDateTime(v);
887
+ return null;
888
+ }
889
+ if (args.length === 2) {
890
+ const d = at(args, 0);
891
+ const t = at(args, 1);
892
+ if (isFeelDate(d) && isFeelTime(t))
893
+ return { type: "date-time", date: d, time: t };
894
+ if (isFeelDateTime(d) && isFeelTime(t))
895
+ return { type: "date-time", date: d.date, time: t };
896
+ return null;
897
+ }
898
+ return null;
899
+ });
900
+ reg("duration", (s) => {
901
+ if (typeof s !== "string")
902
+ return null;
903
+ return parseDuration(s);
904
+ });
905
+ reg("years and months duration", (from, to) => {
906
+ let d1 = null;
907
+ let d2 = null;
908
+ if (isFeelDate(from))
909
+ d1 = from;
910
+ else if (isFeelDateTime(from))
911
+ d1 = from.date;
912
+ if (isFeelDate(to))
913
+ d2 = to;
914
+ else if (isFeelDateTime(to))
915
+ d2 = to.date;
916
+ if (!d1 || !d2)
917
+ return null;
918
+ const months = (d2.year - d1.year) * 12 + (d2.month - d1.month);
919
+ return { type: "years-months-duration", months };
920
+ });
921
+ // -------------------------------------------------------------------------
922
+ // Temporal utility functions
923
+ // -------------------------------------------------------------------------
924
+ reg("now", () => {
925
+ const d = new Date();
926
+ return {
927
+ type: "date-time",
928
+ date: { type: "date", year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() },
929
+ time: {
930
+ type: "time",
931
+ hour: d.getHours(),
932
+ minute: d.getMinutes(),
933
+ second: d.getSeconds(),
934
+ offsetSeconds: -d.getTimezoneOffset() * 60,
935
+ },
936
+ };
937
+ });
938
+ reg("today", () => {
939
+ const d = new Date();
940
+ return {
941
+ type: "date",
942
+ year: d.getFullYear(),
943
+ month: d.getMonth() + 1,
944
+ day: d.getDate(),
945
+ };
946
+ });
947
+ const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
948
+ reg("day of week", (d) => {
949
+ let date = null;
950
+ if (isFeelDate(d))
951
+ date = d;
952
+ else if (isFeelDateTime(d))
953
+ date = d.date;
954
+ if (!date)
955
+ return null;
956
+ const dow = dayOfWeek(date);
957
+ return DAY_NAMES[dow] ?? null;
958
+ });
959
+ reg("day of year", (d) => {
960
+ let date = null;
961
+ if (isFeelDate(d))
962
+ date = d;
963
+ else if (isFeelDateTime(d))
964
+ date = d.date;
965
+ if (!date)
966
+ return null;
967
+ return dayOfYear(date);
968
+ });
969
+ reg("week of year", (d) => {
970
+ let date = null;
971
+ if (isFeelDate(d))
972
+ date = d;
973
+ else if (isFeelDateTime(d))
974
+ date = d.date;
975
+ if (!date)
976
+ return null;
977
+ // ISO week number
978
+ const epochDays = dateToEpochDays(date);
979
+ // 1970-01-01 was Thursday (dow=4), ISO week 1
980
+ const jan4 = dateToEpochDays({ type: "date", year: date.year, month: 1, day: 4 });
981
+ const jan4dow = ((jan4 % 7) + 7 + 4) % 7; // Monday=0
982
+ const weekStart = jan4 - ((jan4dow + 6) % 7);
983
+ const week = Math.floor((epochDays - weekStart) / 7) + 1;
984
+ if (week < 1)
985
+ return 52; // last week of previous year (simplified)
986
+ return week;
987
+ });
988
+ reg("month of year", (d) => {
989
+ const MONTH_NAMES = [
990
+ "January",
991
+ "February",
992
+ "March",
993
+ "April",
994
+ "May",
995
+ "June",
996
+ "July",
997
+ "August",
998
+ "September",
999
+ "October",
1000
+ "November",
1001
+ "December",
1002
+ ];
1003
+ let date = null;
1004
+ if (isFeelDate(d))
1005
+ date = d;
1006
+ else if (isFeelDateTime(d))
1007
+ date = d.date;
1008
+ if (!date)
1009
+ return null;
1010
+ return MONTH_NAMES[date.month - 1] ?? null;
1011
+ });
1012
+ reg("last day of month", (d) => {
1013
+ let date = null;
1014
+ if (isFeelDate(d))
1015
+ date = d;
1016
+ else if (isFeelDateTime(d))
1017
+ date = d.date;
1018
+ if (!date)
1019
+ return null;
1020
+ return daysInMonth(date.year, date.month);
1021
+ });
1022
+ // -------------------------------------------------------------------------
1023
+ // Range / interval functions
1024
+ // -------------------------------------------------------------------------
1025
+ function toRangeOrScalar(v) {
1026
+ if (isFeelRange(v))
1027
+ return v;
1028
+ return v;
1029
+ }
1030
+ function startOf(v) {
1031
+ if (isFeelRange(v))
1032
+ return { v: v.start, included: v.startIncluded };
1033
+ return { v, included: true };
1034
+ }
1035
+ function endOf(v) {
1036
+ if (isFeelRange(v))
1037
+ return { v: v.end, included: v.endIncluded };
1038
+ return { v, included: true };
1039
+ }
1040
+ function cmpPts(a, b, edge) {
1041
+ const c = compareValues(a.v, b.v) ?? 0;
1042
+ if (c !== 0)
1043
+ return c;
1044
+ if (edge === "start")
1045
+ return a.included === b.included ? 0 : a.included ? -1 : 1;
1046
+ return a.included === b.included ? 0 : a.included ? 1 : -1;
1047
+ }
1048
+ reg("before", (a, b) => {
1049
+ const ae = endOf(a);
1050
+ const bs = startOf(b);
1051
+ const c = compareValues(ae.v, bs.v) ?? 0;
1052
+ if (c < 0)
1053
+ return true;
1054
+ if (c === 0)
1055
+ return !ae.included || !bs.included;
1056
+ return false;
1057
+ });
1058
+ reg("after", (a, b) => {
1059
+ const as_ = startOf(a);
1060
+ const be = endOf(b);
1061
+ const c = compareValues(as_.v, be.v) ?? 0;
1062
+ if (c > 0)
1063
+ return true;
1064
+ if (c === 0)
1065
+ return !as_.included || !be.included;
1066
+ return false;
1067
+ });
1068
+ reg("meets", (a, b) => {
1069
+ if (!isFeelRange(a) || !isFeelRange(b))
1070
+ return null;
1071
+ const c = compareValues(a.end, b.start) ?? 1;
1072
+ return c === 0 && a.endIncluded && b.startIncluded;
1073
+ });
1074
+ reg("met by", (a, b) => {
1075
+ if (!isFeelRange(a) || !isFeelRange(b))
1076
+ return null;
1077
+ const c = compareValues(a.start, b.end) ?? 1;
1078
+ return c === 0 && a.startIncluded && b.endIncluded;
1079
+ });
1080
+ reg("overlaps", (a, b) => {
1081
+ if (!isFeelRange(a) || !isFeelRange(b))
1082
+ return null;
1083
+ const ae = endOf(a);
1084
+ const bs = startOf(b);
1085
+ const as_ = startOf(a);
1086
+ const be = endOf(b);
1087
+ const c1 = compareValues(ae.v, bs.v) ?? -1;
1088
+ const c2 = compareValues(as_.v, be.v) ?? 1;
1089
+ if (c1 < 0 || c2 > 0)
1090
+ return false;
1091
+ if (c1 === 0 && (!ae.included || !bs.included))
1092
+ return false;
1093
+ if (c2 === 0 && (!as_.included || !be.included))
1094
+ return false;
1095
+ return true;
1096
+ });
1097
+ reg("overlaps before", (a, b) => {
1098
+ if (!isFeelRange(a) || !isFeelRange(b))
1099
+ return null;
1100
+ const as_ = startOf(a);
1101
+ const bs = startOf(b);
1102
+ const ae = endOf(a);
1103
+ const be = endOf(b);
1104
+ return cmpPts(as_, bs, "start") < 0 && cmpPts(ae, be, "end") < 0 && cmpPts(ae, bs, "end") >= 0;
1105
+ });
1106
+ reg("overlaps after", (a, b) => {
1107
+ if (!isFeelRange(a) || !isFeelRange(b))
1108
+ return null;
1109
+ const as_ = startOf(a);
1110
+ const bs = startOf(b);
1111
+ const ae = endOf(a);
1112
+ const be = endOf(b);
1113
+ return cmpPts(as_, bs, "start") > 0 && cmpPts(ae, be, "end") > 0 && cmpPts(as_, be, "end") <= 0;
1114
+ });
1115
+ reg("during", (a, b) => {
1116
+ if (!isFeelRange(b))
1117
+ return null;
1118
+ const as_ = startOf(a);
1119
+ const ae = endOf(a);
1120
+ const bs = startOf(b);
1121
+ const be = endOf(b);
1122
+ return cmpPts(bs, as_, "start") <= 0 && cmpPts(ae, be, "end") <= 0;
1123
+ });
1124
+ reg("includes", (a, b) => {
1125
+ if (!isFeelRange(a))
1126
+ return null;
1127
+ const as_ = startOf(a);
1128
+ const ae = endOf(a);
1129
+ const bs = startOf(b);
1130
+ const be = endOf(b);
1131
+ return cmpPts(as_, bs, "start") <= 0 && cmpPts(be, ae, "end") <= 0;
1132
+ });
1133
+ reg("starts", (a, b) => {
1134
+ if (!isFeelRange(a) || !isFeelRange(b))
1135
+ return null;
1136
+ const as_ = startOf(a);
1137
+ const bs = startOf(b);
1138
+ const ae = endOf(a);
1139
+ const be = endOf(b);
1140
+ return cmpPts(as_, bs, "start") === 0 && cmpPts(ae, be, "end") <= 0;
1141
+ });
1142
+ reg("started by", (a, b) => {
1143
+ if (!isFeelRange(a) || !isFeelRange(b))
1144
+ return null;
1145
+ const as_ = startOf(a);
1146
+ const bs = startOf(b);
1147
+ const ae = endOf(a);
1148
+ const be = endOf(b);
1149
+ return cmpPts(as_, bs, "start") === 0 && cmpPts(be, ae, "end") <= 0;
1150
+ });
1151
+ reg("finishes", (a, b) => {
1152
+ if (!isFeelRange(a) || !isFeelRange(b))
1153
+ return null;
1154
+ const ae = endOf(a);
1155
+ const be = endOf(b);
1156
+ const as_ = startOf(a);
1157
+ const bs = startOf(b);
1158
+ return cmpPts(ae, be, "end") === 0 && cmpPts(bs, as_, "start") <= 0;
1159
+ });
1160
+ reg("finished by", (a, b) => {
1161
+ if (!isFeelRange(a) || !isFeelRange(b))
1162
+ return null;
1163
+ const ae = endOf(a);
1164
+ const be = endOf(b);
1165
+ const as_ = startOf(a);
1166
+ const bs = startOf(b);
1167
+ return cmpPts(ae, be, "end") === 0 && cmpPts(as_, bs, "start") <= 0;
1168
+ });
1169
+ reg("coincides", (a, b) => {
1170
+ if (isFeelRange(a) && isFeelRange(b)) {
1171
+ return cmpPts(startOf(a), startOf(b), "start") === 0 && cmpPts(endOf(a), endOf(b), "end") === 0;
1172
+ }
1173
+ if (!isFeelRange(a) && !isFeelRange(b)) {
1174
+ return compareValues(a, b) === 0;
1175
+ }
1176
+ return null;
1177
+ });
1178
+ // -------------------------------------------------------------------------
1179
+ // Exports
1180
+ // -------------------------------------------------------------------------
1181
+ /** Look up a built-in function by name. Returns undefined if not found. */
1182
+ export function getBuiltin(name) {
1183
+ const fn = builtinMap.get(name);
1184
+ if (!fn)
1185
+ return undefined;
1186
+ return { type: "function", call: (args) => fn(...args) };
1187
+ }
1188
+ /** All built-in names. */
1189
+ export function builtinNames() {
1190
+ return [...builtinMap.keys()];
1191
+ }
1192
+ /** Parse a @"..." temporal literal to a FeelValue. */
1193
+ export { parseTemporal, compareValues };
1194
+ //# sourceMappingURL=builtins.js.map