@marktoflow/core 2.0.0-alpha.14 → 2.0.0-alpha.15

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,697 @@
1
+ /**
2
+ * Expression Helper Library for marktoflow
3
+ *
4
+ * Provides pipeline-style filters for template expressions:
5
+ * {{ value | filter1 | filter2(arg) | filter3 }}
6
+ *
7
+ * Categories:
8
+ * - String helpers: split, join, trim, upper, lower, slugify, prefix, suffix
9
+ * - Array helpers: first, last, nth, map, filter, unique, count, sum
10
+ * - Object helpers: path, keys, values, merge, pick, omit
11
+ * - Date helpers: now, format_date, add_days, subtract_days
12
+ * - Logic helpers: default, or, and, not
13
+ * - Validation helpers: is_array, is_object, is_empty, is_null
14
+ */
15
+ // ============================================================================
16
+ // String Helpers
17
+ // ============================================================================
18
+ /**
19
+ * Split string by delimiter
20
+ * Usage: {{ "a,b,c" | split(',') }} → ['a', 'b', 'c']
21
+ */
22
+ export function split(value, delimiter = ',') {
23
+ const str = String(value);
24
+ return str.split(delimiter);
25
+ }
26
+ /**
27
+ * Join array elements
28
+ * Usage: {{ ['a', 'b', 'c'] | join(', ') }} → "a, b, c"
29
+ */
30
+ export function join(value, separator = ',') {
31
+ if (!Array.isArray(value)) {
32
+ return String(value);
33
+ }
34
+ return value.join(separator);
35
+ }
36
+ /**
37
+ * Trim whitespace
38
+ * Usage: {{ " hello " | trim }} → "hello"
39
+ */
40
+ export function trim(value) {
41
+ return String(value).trim();
42
+ }
43
+ /**
44
+ * Convert to uppercase
45
+ * Usage: {{ "hello" | upper }} → "HELLO"
46
+ */
47
+ export function upper(value) {
48
+ return String(value).toUpperCase();
49
+ }
50
+ /**
51
+ * Convert to lowercase
52
+ * Usage: {{ "HELLO" | lower }} → "hello"
53
+ */
54
+ export function lower(value) {
55
+ return String(value).toLowerCase();
56
+ }
57
+ /**
58
+ * Convert to title case
59
+ * Usage: {{ "hello world" | title }} → "Hello World"
60
+ */
61
+ export function title(value) {
62
+ const str = String(value);
63
+ return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase());
64
+ }
65
+ /**
66
+ * Capitalize first letter
67
+ * Usage: {{ "hello" | capitalize }} → "Hello"
68
+ */
69
+ export function capitalize(value) {
70
+ const str = String(value);
71
+ return str.charAt(0).toUpperCase() + str.slice(1);
72
+ }
73
+ /**
74
+ * Convert to URL-friendly slug
75
+ * Usage: {{ "Hello World!" | slugify }} → "hello-world"
76
+ */
77
+ export function slugify(value) {
78
+ return String(value)
79
+ .toLowerCase()
80
+ .replace(/[^a-z0-9]+/g, '-')
81
+ .replace(/^-|-$/g, '');
82
+ }
83
+ /**
84
+ * Add prefix to string
85
+ * Usage: {{ "hello" | prefix('@') }} → "@hello"
86
+ */
87
+ export function prefix(value, prefixStr) {
88
+ return prefixStr + String(value);
89
+ }
90
+ /**
91
+ * Add suffix to string
92
+ * Usage: {{ "hello" | suffix('!') }} → "hello!"
93
+ */
94
+ export function suffix(value, suffixStr) {
95
+ return String(value) + suffixStr;
96
+ }
97
+ /**
98
+ * Replace all occurrences
99
+ * Usage: {{ "hello world" | replace('world', 'there') }} → "hello there"
100
+ */
101
+ export function replace(value, search, replaceWith) {
102
+ return String(value).replace(new RegExp(search, 'g'), replaceWith);
103
+ }
104
+ /**
105
+ * Truncate string to length
106
+ * Usage: {{ "hello world" | truncate(5) }} → "hello..."
107
+ */
108
+ export function truncate(value, length, ellipsis = '...') {
109
+ const str = String(value);
110
+ if (str.length <= length)
111
+ return str;
112
+ return str.slice(0, length) + ellipsis;
113
+ }
114
+ /**
115
+ * Extract substring
116
+ * Usage: {{ "hello world" | substring(0, 5) }} → "hello"
117
+ */
118
+ export function substring(value, start, end) {
119
+ return String(value).substring(start, end);
120
+ }
121
+ /**
122
+ * Check if string/array contains value
123
+ * Usage: {{ "hello world" | contains('world') }} → true
124
+ */
125
+ export function contains(value, search) {
126
+ if (typeof value === 'string') {
127
+ return value.includes(String(search));
128
+ }
129
+ if (Array.isArray(value)) {
130
+ return value.includes(search);
131
+ }
132
+ return false;
133
+ }
134
+ // ============================================================================
135
+ // Array Helpers
136
+ // ============================================================================
137
+ /**
138
+ * Get first element
139
+ * Usage: {{ [1, 2, 3] | first }} → 1
140
+ */
141
+ export function first(value) {
142
+ if (Array.isArray(value)) {
143
+ return value[0];
144
+ }
145
+ return value;
146
+ }
147
+ /**
148
+ * Get last element
149
+ * Usage: {{ [1, 2, 3] | last }} → 3
150
+ */
151
+ export function last(value) {
152
+ if (Array.isArray(value)) {
153
+ return value[value.length - 1];
154
+ }
155
+ return value;
156
+ }
157
+ /**
158
+ * Get nth element (0-indexed)
159
+ * Usage: {{ [1, 2, 3] | nth(1) }} → 2
160
+ */
161
+ export function nth(value, index) {
162
+ if (Array.isArray(value)) {
163
+ return value[index];
164
+ }
165
+ return value;
166
+ }
167
+ /**
168
+ * Get array length or object property count
169
+ * Usage: {{ [1, 2, 3] | count }} → 3
170
+ */
171
+ export function count(value) {
172
+ if (Array.isArray(value)) {
173
+ return value.length;
174
+ }
175
+ if (typeof value === 'string') {
176
+ return value.length;
177
+ }
178
+ if (typeof value === 'object' && value !== null) {
179
+ // Check for common count properties
180
+ const obj = value;
181
+ if ('length' in obj && typeof obj.length === 'number')
182
+ return obj.length;
183
+ if ('total' in obj && typeof obj.total === 'number')
184
+ return obj.total;
185
+ if ('count' in obj && typeof obj.count === 'number')
186
+ return obj.count;
187
+ return Object.keys(value).length;
188
+ }
189
+ return 0;
190
+ }
191
+ /**
192
+ * Sum array of numbers
193
+ * Usage: {{ [1, 2, 3] | sum }} → 6
194
+ */
195
+ export function sum(value) {
196
+ if (!Array.isArray(value))
197
+ return 0;
198
+ return value.reduce((acc, val) => acc + Number(val), 0);
199
+ }
200
+ /**
201
+ * Get unique values
202
+ * Usage: {{ [1, 2, 2, 3] | unique }} → [1, 2, 3]
203
+ */
204
+ export function unique(value) {
205
+ if (!Array.isArray(value))
206
+ return [value];
207
+ return Array.from(new Set(value));
208
+ }
209
+ /**
210
+ * Flatten array one level
211
+ * Usage: {{ [[1, 2], [3, 4]] | flatten }} → [1, 2, 3, 4]
212
+ */
213
+ export function flatten(value) {
214
+ if (!Array.isArray(value))
215
+ return [value];
216
+ return value.flat(1);
217
+ }
218
+ /**
219
+ * Reverse array
220
+ * Usage: {{ [1, 2, 3] | reverse }} → [3, 2, 1]
221
+ */
222
+ export function reverse(value) {
223
+ if (!Array.isArray(value))
224
+ return [value];
225
+ return [...value].reverse();
226
+ }
227
+ /**
228
+ * Sort array
229
+ * Usage: {{ [3, 1, 2] | sort }} → [1, 2, 3]
230
+ * Usage: {{ [3, 1, 2] | sort(true) }} → [3, 2, 1]
231
+ */
232
+ export function sort(value, reverse = false) {
233
+ if (!Array.isArray(value))
234
+ return [value];
235
+ const sorted = [...value].sort((a, b) => {
236
+ if (typeof a === 'number' && typeof b === 'number')
237
+ return a - b;
238
+ return String(a).localeCompare(String(b));
239
+ });
240
+ return reverse ? sorted.reverse() : sorted;
241
+ }
242
+ /**
243
+ * Get array slice
244
+ * Usage: {{ [1, 2, 3, 4, 5] | slice(1, 3) }} → [2, 3]
245
+ */
246
+ export function slice(value, start, end) {
247
+ if (!Array.isArray(value))
248
+ return [value];
249
+ return value.slice(start, end);
250
+ }
251
+ // ============================================================================
252
+ // Object Helpers
253
+ // ============================================================================
254
+ /**
255
+ * Get value at path
256
+ * Usage: {{ obj | path('user.name') }} → obj.user.name
257
+ */
258
+ export function path(value, pathStr) {
259
+ if (typeof value !== 'object' || value === null) {
260
+ return undefined;
261
+ }
262
+ const keys = pathStr.split('.');
263
+ let result = value;
264
+ for (const key of keys) {
265
+ if (typeof result !== 'object' || result === null) {
266
+ return undefined;
267
+ }
268
+ result = result[key];
269
+ }
270
+ return result;
271
+ }
272
+ /**
273
+ * Get object keys
274
+ * Usage: {{ {a: 1, b: 2} | keys }} → ['a', 'b']
275
+ */
276
+ export function keys(value) {
277
+ if (typeof value !== 'object' || value === null) {
278
+ return [];
279
+ }
280
+ return Object.keys(value);
281
+ }
282
+ /**
283
+ * Get object values
284
+ * Usage: {{ {a: 1, b: 2} | values }} → [1, 2]
285
+ */
286
+ export function values(value) {
287
+ if (typeof value !== 'object' || value === null) {
288
+ return [];
289
+ }
290
+ return Object.values(value);
291
+ }
292
+ /**
293
+ * Get object entries (key-value pairs)
294
+ * Usage: {{ {a: 1, b: 2} | entries }} → [['a', 1], ['b', 2]]
295
+ */
296
+ export function entries(value) {
297
+ if (typeof value !== 'object' || value === null) {
298
+ return [];
299
+ }
300
+ return Object.entries(value);
301
+ }
302
+ /**
303
+ * Pick specific keys from object
304
+ * Usage: {{ {a: 1, b: 2, c: 3} | pick('a', 'c') }} → {a: 1, c: 3}
305
+ */
306
+ export function pick(value, ...keysToPick) {
307
+ if (typeof value !== 'object' || value === null) {
308
+ return {};
309
+ }
310
+ const obj = value;
311
+ const result = {};
312
+ for (const key of keysToPick) {
313
+ if (key in obj) {
314
+ result[key] = obj[key];
315
+ }
316
+ }
317
+ return result;
318
+ }
319
+ /**
320
+ * Omit specific keys from object
321
+ * Usage: {{ {a: 1, b: 2, c: 3} | omit('b') }} → {a: 1, c: 3}
322
+ */
323
+ export function omit(value, ...keysToOmit) {
324
+ if (typeof value !== 'object' || value === null) {
325
+ return {};
326
+ }
327
+ const obj = value;
328
+ const result = {};
329
+ const omitSet = new Set(keysToOmit);
330
+ for (const [key, val] of Object.entries(obj)) {
331
+ if (!omitSet.has(key)) {
332
+ result[key] = val;
333
+ }
334
+ }
335
+ return result;
336
+ }
337
+ /**
338
+ * Merge objects
339
+ * Usage: {{ {a: 1} | merge({b: 2}) }} → {a: 1, b: 2}
340
+ */
341
+ export function merge(value, ...objects) {
342
+ if (typeof value !== 'object' || value === null) {
343
+ return {};
344
+ }
345
+ let result = { ...value };
346
+ for (const obj of objects) {
347
+ if (typeof obj === 'object' && obj !== null) {
348
+ result = { ...result, ...obj };
349
+ }
350
+ }
351
+ return result;
352
+ }
353
+ // ============================================================================
354
+ // Date Helpers
355
+ // ============================================================================
356
+ /**
357
+ * Get current timestamp
358
+ * Usage: {{ now() }} → 1706745600000
359
+ */
360
+ export function now() {
361
+ return Date.now();
362
+ }
363
+ /**
364
+ * Format date
365
+ * Usage: {{ timestamp | format_date('YYYY-MM-DD') }} → "2025-01-31"
366
+ */
367
+ export function format_date(value, format = 'YYYY-MM-DD') {
368
+ let date;
369
+ if (value instanceof Date) {
370
+ date = value;
371
+ }
372
+ else if (typeof value === 'string' || typeof value === 'number') {
373
+ date = new Date(value);
374
+ }
375
+ else {
376
+ date = new Date();
377
+ }
378
+ if (isNaN(date.getTime())) {
379
+ return 'Invalid Date';
380
+ }
381
+ // Simple date formatting
382
+ let formatted = format;
383
+ formatted = formatted.replace('YYYY', date.getFullYear().toString());
384
+ formatted = formatted.replace('MM', String(date.getMonth() + 1).padStart(2, '0'));
385
+ formatted = formatted.replace('DD', String(date.getDate()).padStart(2, '0'));
386
+ formatted = formatted.replace('HH', String(date.getHours()).padStart(2, '0'));
387
+ formatted = formatted.replace('mm', String(date.getMinutes()).padStart(2, '0'));
388
+ formatted = formatted.replace('ss', String(date.getSeconds()).padStart(2, '0'));
389
+ return formatted;
390
+ }
391
+ /**
392
+ * Add days to date
393
+ * Usage: {{ timestamp | add_days(7) }} → timestamp + 7 days
394
+ */
395
+ export function add_days(value, days) {
396
+ const date = new Date(value);
397
+ if (isNaN(date.getTime()))
398
+ return 0;
399
+ date.setDate(date.getDate() + days);
400
+ return date.getTime();
401
+ }
402
+ /**
403
+ * Subtract days from date
404
+ * Usage: {{ timestamp | subtract_days(7) }} → timestamp - 7 days
405
+ */
406
+ export function subtract_days(value, days) {
407
+ const date = new Date(value);
408
+ if (isNaN(date.getTime()))
409
+ return 0;
410
+ date.setDate(date.getDate() - days);
411
+ return date.getTime();
412
+ }
413
+ /**
414
+ * Get date difference in days (date1 - date2)
415
+ * Usage: {{ date1 | diff_days(date2) }} → number of days
416
+ */
417
+ export function diff_days(value, compareDate) {
418
+ const date1 = new Date(value);
419
+ const date2 = new Date(compareDate);
420
+ if (isNaN(date1.getTime()) || isNaN(date2.getTime()))
421
+ return 0;
422
+ const diffMs = date1.getTime() - date2.getTime();
423
+ return Math.floor(diffMs / (1000 * 60 * 60 * 24));
424
+ }
425
+ // ============================================================================
426
+ // Logic Helpers
427
+ // ============================================================================
428
+ /**
429
+ * Return default value if input is null/undefined
430
+ * Usage: {{ null | default('N/A') }} → "N/A"
431
+ */
432
+ export function defaultValue(value, defaultVal) {
433
+ if (value === null || value === undefined) {
434
+ return defaultVal;
435
+ }
436
+ return value;
437
+ }
438
+ /**
439
+ * Return first truthy value
440
+ * Usage: {{ null | or('fallback') }} → "fallback"
441
+ */
442
+ export function or(value, ...alternatives) {
443
+ if (value)
444
+ return value;
445
+ for (const alt of alternatives) {
446
+ if (alt)
447
+ return alt;
448
+ }
449
+ return null;
450
+ }
451
+ /**
452
+ * Check if all values are truthy
453
+ * Usage: {{ true | and(1, 'value') }} → true
454
+ */
455
+ export function and(value, ...values) {
456
+ if (!value)
457
+ return false;
458
+ for (const v of values) {
459
+ if (!v)
460
+ return false;
461
+ }
462
+ return true;
463
+ }
464
+ /**
465
+ * Negate boolean value
466
+ * Usage: {{ true | not }} → false
467
+ */
468
+ export function not(value) {
469
+ return !value;
470
+ }
471
+ /**
472
+ * Ternary operator
473
+ * Usage: {{ condition | ternary('yes', 'no') }}
474
+ */
475
+ export function ternary(condition, trueVal, falseVal) {
476
+ return condition ? trueVal : falseVal;
477
+ }
478
+ // ============================================================================
479
+ // Validation Helpers
480
+ // ============================================================================
481
+ /**
482
+ * Check if value is array
483
+ * Usage: {{ value | is_array }} → true/false
484
+ */
485
+ export function is_array(value) {
486
+ return Array.isArray(value);
487
+ }
488
+ /**
489
+ * Check if value is object
490
+ * Usage: {{ value | is_object }} → true/false
491
+ */
492
+ export function is_object(value) {
493
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
494
+ }
495
+ /**
496
+ * Check if value is string
497
+ * Usage: {{ value | is_string }} → true/false
498
+ */
499
+ export function is_string(value) {
500
+ return typeof value === 'string';
501
+ }
502
+ /**
503
+ * Check if value is number
504
+ * Usage: {{ value | is_number }} → true/false
505
+ */
506
+ export function is_number(value) {
507
+ return typeof value === 'number' && !isNaN(value);
508
+ }
509
+ /**
510
+ * Check if value is empty
511
+ * Usage: {{ value | is_empty }} → true/false
512
+ */
513
+ export function is_empty(value) {
514
+ if (value === null || value === undefined)
515
+ return true;
516
+ if (typeof value === 'string')
517
+ return value.length === 0;
518
+ if (Array.isArray(value))
519
+ return value.length === 0;
520
+ if (typeof value === 'object')
521
+ return Object.keys(value).length === 0;
522
+ return false;
523
+ }
524
+ /**
525
+ * Check if value is null
526
+ * Usage: {{ value | is_null }} → true/false
527
+ */
528
+ export function is_null(value) {
529
+ return value === null;
530
+ }
531
+ // ============================================================================
532
+ // JSON Helpers
533
+ // ============================================================================
534
+ /**
535
+ * Parse JSON string
536
+ * Usage: {{ '{"a":1}' | parse_json }} → {a: 1}
537
+ */
538
+ export function parse_json(value) {
539
+ try {
540
+ return JSON.parse(String(value));
541
+ }
542
+ catch {
543
+ return null;
544
+ }
545
+ }
546
+ /**
547
+ * Stringify to JSON
548
+ * Usage: {{ {a: 1} | to_json }} → '{"a":1}'
549
+ */
550
+ export function to_json(value, pretty = false) {
551
+ return JSON.stringify(value, null, pretty ? 2 : 0);
552
+ }
553
+ // ============================================================================
554
+ // Math Helpers
555
+ // ============================================================================
556
+ /**
557
+ * Absolute value
558
+ * Usage: {{ -5 | abs }} → 5
559
+ */
560
+ export function abs(value) {
561
+ return Math.abs(Number(value));
562
+ }
563
+ /**
564
+ * Round to nearest integer
565
+ * Usage: {{ 3.7 | round }} → 4
566
+ */
567
+ export function round(value, decimals = 0) {
568
+ const num = Number(value);
569
+ const multiplier = Math.pow(10, decimals);
570
+ return Math.round(num * multiplier) / multiplier;
571
+ }
572
+ /**
573
+ * Round down
574
+ * Usage: {{ 3.7 | floor }} → 3
575
+ */
576
+ export function floor(value) {
577
+ return Math.floor(Number(value));
578
+ }
579
+ /**
580
+ * Round up
581
+ * Usage: {{ 3.1 | ceil }} → 4
582
+ */
583
+ export function ceil(value) {
584
+ return Math.ceil(Number(value));
585
+ }
586
+ /**
587
+ * Get minimum value
588
+ * Usage: {{ [1, 2, 3] | min }} → 1
589
+ * Usage: {{ 5 | min(3) }} → 3
590
+ */
591
+ export function min(value, ...values) {
592
+ if (Array.isArray(value)) {
593
+ return Math.min(...value.map(v => Number(v)));
594
+ }
595
+ const nums = [Number(value), ...values.map(v => Number(v))];
596
+ return Math.min(...nums);
597
+ }
598
+ /**
599
+ * Get maximum value
600
+ * Usage: {{ [1, 2, 3] | max }} → 3
601
+ * Usage: {{ 3 | max(5) }} → 5
602
+ */
603
+ export function max(value, ...values) {
604
+ if (Array.isArray(value)) {
605
+ return Math.max(...value.map(v => Number(v)));
606
+ }
607
+ const nums = [Number(value), ...values.map(v => Number(v))];
608
+ return Math.max(...nums);
609
+ }
610
+ // ============================================================================
611
+ // Helper Registry
612
+ // ============================================================================
613
+ /**
614
+ * Global registry of all helper functions
615
+ */
616
+ export const HELPER_REGISTRY = {
617
+ // String helpers
618
+ split: split,
619
+ join: join,
620
+ trim: trim,
621
+ upper: upper,
622
+ lower: lower,
623
+ title: title,
624
+ capitalize: capitalize,
625
+ slugify: slugify,
626
+ prefix: prefix,
627
+ suffix: suffix,
628
+ replace: replace,
629
+ truncate: truncate,
630
+ substring: substring,
631
+ contains: contains,
632
+ // Array helpers
633
+ first: first,
634
+ last: last,
635
+ nth: nth,
636
+ count: count,
637
+ sum: sum,
638
+ unique: unique,
639
+ flatten: flatten,
640
+ reverse: reverse,
641
+ sort: sort,
642
+ slice: slice,
643
+ // Object helpers
644
+ path: path,
645
+ keys: keys,
646
+ values: values,
647
+ entries: entries,
648
+ pick: pick,
649
+ omit: omit,
650
+ merge: merge,
651
+ // Date helpers
652
+ now: now,
653
+ format_date: format_date,
654
+ add_days: add_days,
655
+ subtract_days: subtract_days,
656
+ diff_days: diff_days,
657
+ // Logic helpers
658
+ default: defaultValue,
659
+ or: or,
660
+ and: and,
661
+ not: not,
662
+ ternary: ternary,
663
+ // Validation helpers
664
+ is_array: is_array,
665
+ is_object: is_object,
666
+ is_string: is_string,
667
+ is_number: is_number,
668
+ is_empty: is_empty,
669
+ is_null: is_null,
670
+ // JSON helpers
671
+ parse_json: parse_json,
672
+ to_json: to_json,
673
+ // Math helpers
674
+ abs: abs,
675
+ round: round,
676
+ floor: floor,
677
+ ceil: ceil,
678
+ min: min,
679
+ max: max,
680
+ };
681
+ /**
682
+ * Apply a helper function to a value
683
+ */
684
+ export function applyHelper(helperName, value, args) {
685
+ const helper = HELPER_REGISTRY[helperName];
686
+ if (!helper) {
687
+ throw new Error(`Unknown helper function: ${helperName}`);
688
+ }
689
+ return helper(value, ...args);
690
+ }
691
+ /**
692
+ * Check if a helper exists
693
+ */
694
+ export function hasHelper(helperName) {
695
+ return helperName in HELPER_REGISTRY;
696
+ }
697
+ //# sourceMappingURL=expression-helpers.js.map