jprx 1.0.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/LICENSE +21 -0
- package/README.md +130 -0
- package/helpers/array.js +75 -0
- package/helpers/compare.js +26 -0
- package/helpers/conditional.js +34 -0
- package/helpers/datetime.js +54 -0
- package/helpers/format.js +20 -0
- package/helpers/logic.js +24 -0
- package/helpers/lookup.js +25 -0
- package/helpers/math.js +34 -0
- package/helpers/network.js +41 -0
- package/helpers/state.js +80 -0
- package/helpers/stats.js +39 -0
- package/helpers/string.js +49 -0
- package/index.js +69 -0
- package/package.json +24 -0
- package/parser.js +1517 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 - 2026 Simon Y. Blackwell, AnyWhichWay LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# JPRX (JSON Reactive Path eXpressions)
|
|
2
|
+
|
|
3
|
+
**JPRX** is a declarative, reactive expression syntax designed for JSON-based data structures. It extends [JSON Pointer (RFC 6901)](https://www.rfc-editor.org/rfc/rfc6901) with reactivity, relative paths, operator syntax, and a rich library of helper functions.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
JPRX is a **syntax** and an **expression engine**. While this repository provides the parser and core helper functions, JPRX is intended to be integrated into UI libraries or state management systems that can "hydrate" these expressions into active reactive bindings.
|
|
8
|
+
|
|
9
|
+
### Why JPRX?
|
|
10
|
+
|
|
11
|
+
- **Declarative Power**: Define relationships between data points as easily as writing an Excel formula.
|
|
12
|
+
- **Security**: JPRX strictly avoids `eval()`. Expressions are handled by a custom high-performance Pratt parser and a registry of pre-defined helpers, making it safe for dynamic content.
|
|
13
|
+
- **Portability**: Because JPRX expressions are strings within JSON structures, they are easily serialized, stored, and sent over the wire.
|
|
14
|
+
- **Platform Agnostic**: While Lightview is the first implementation, JPRX can be used in any environment that manages reactive state.
|
|
15
|
+
|
|
16
|
+
## Syntax & Features
|
|
17
|
+
|
|
18
|
+
JPRX extends the base JSON Pointer syntax with:
|
|
19
|
+
|
|
20
|
+
| Feature | Syntax | Description |
|
|
21
|
+
|---------|--------|-------------|
|
|
22
|
+
| **Global Path** | `$/user/name` | Access global state via an absolute path. |
|
|
23
|
+
| **Relative Path** | `./count` | Access properties relative to the current context. |
|
|
24
|
+
| **Parent Path** | `../id` | Traverse up the state hierarchy. |
|
|
25
|
+
| **Functions** | `$sum(/items...price)` | Call registered core helpers. |
|
|
26
|
+
| **Explosion** | `/items...name` | Extract a property from every object in an array (spread). |
|
|
27
|
+
| **Operators** | `$++/count`, `$/a + $/b` | Familiar JS-style prefix, postfix, and infix operators. |
|
|
28
|
+
| **Placeholders** | `_` (item), `$event` | Context-aware placeholders for iteration and interaction. |
|
|
29
|
+
|
|
30
|
+
## Human & AI Utility
|
|
31
|
+
|
|
32
|
+
JPRX is uniquely positioned to bridge the gap between human developers and AI coding assistants:
|
|
33
|
+
|
|
34
|
+
### For Humans: "The Excel Paradigm"
|
|
35
|
+
Humans are often familiar with the "recalculation" model of spreadsheets. JPRX brings this to UI development. Instead of writing complex "glue code" (event listeners, state updates, DOM manipulation), developers specify the *formula* for a UI element once, and it stays updated forever.
|
|
36
|
+
|
|
37
|
+
### For AI: Structured & Concise
|
|
38
|
+
Large Language Models (LLMs) are exceptionally good at generating structured data (JSON) and formulaic expressions. They are often prone to errors when generating large blocks of imperative JavaScript logic. JPRX provides a high-level, declarative "target" for AI to aim at, resulting in:
|
|
39
|
+
- **Higher Accuracy**: Less boilerplate means fewer places for the AI to hallucinate.
|
|
40
|
+
- **Safety**: AI can generate UI logic that remains sandboxed and secure.
|
|
41
|
+
- **Compactness**: Entire interactive components can be described in a few lines of JSON.
|
|
42
|
+
|
|
43
|
+
## Operators
|
|
44
|
+
|
|
45
|
+
JPRX supports a wide range of operators that provide a more concise and familiar syntax than function calls.
|
|
46
|
+
|
|
47
|
+
### Arithmetic & Logic (Infix)
|
|
48
|
+
Infix operators require surrounding whitespace in JPRX to avoid ambiguity with path separators.
|
|
49
|
+
|
|
50
|
+
- **Arithmetic**: `+`, `-`, `*`, `/`, `mod`, `pow`
|
|
51
|
+
- **Comparison**: `>`, `<`, `>=`, `<=`, `==`, `!=`
|
|
52
|
+
- **Logic**: `&&`, `||`
|
|
53
|
+
|
|
54
|
+
*Example:* `$/a + $/b * 10 > $/threshold`
|
|
55
|
+
|
|
56
|
+
### Mutation & Unary (Prefix/Postfix)
|
|
57
|
+
These operators are typically used in event handlers or for immediate state transformation.
|
|
58
|
+
|
|
59
|
+
- **Increment**: `$++/count` (prefix) or `$/count++` (postfix)
|
|
60
|
+
- **Decrement**: `$--/count` (prefix) or `$/count--` (postfix)
|
|
61
|
+
- **Toggle**: `$!!/enabled` (logical NOT/toggle)
|
|
62
|
+
|
|
63
|
+
## Helper Functions
|
|
64
|
+
|
|
65
|
+
JPRX includes a comprehensive library of built-in helpers. For security, only registered helpers are available—there is no access to the global JavaScript environment.
|
|
66
|
+
|
|
67
|
+
### Math
|
|
68
|
+
`add`, `sub`, `mul`, `div`, `mod`, `pow`, `sqrt`, `abs`, `round`, `ceil`, `floor`, `min`, `max`
|
|
69
|
+
|
|
70
|
+
### Stats
|
|
71
|
+
`sum`, `avg`, `min`, `max`, `median`, `stdev`, `var`
|
|
72
|
+
|
|
73
|
+
### String
|
|
74
|
+
`upper`, `lower`, `trim`, `capitalize`, `titleCase`, `contains`, `startsWith`, `endsWith`, `replace`, `split`, `join`, `concat`, `len`, `default`
|
|
75
|
+
|
|
76
|
+
### Array
|
|
77
|
+
`count`, `map`, `filter`, `find`, `unique`, `sort`, `reverse`, `first`, `last`, `slice`, `flatten`, `join`, `len`
|
|
78
|
+
|
|
79
|
+
### Logic & Comparison
|
|
80
|
+
`if`, `and`, `or`, `not`, `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `between`, `in`
|
|
81
|
+
|
|
82
|
+
### Formatting
|
|
83
|
+
`number`, `currency`, `percent`, `thousands`
|
|
84
|
+
|
|
85
|
+
### DateTime
|
|
86
|
+
`now`, `today`, `date`, `formatDate`, `year`, `month`, `day`, `weekday`, `addDays`, `dateDiff`
|
|
87
|
+
|
|
88
|
+
### Lookup
|
|
89
|
+
`lookup`, `vlookup`, `index`, `match`
|
|
90
|
+
|
|
91
|
+
### State Mutation
|
|
92
|
+
`set`, `increment`, `decrement`, `toggle`, `push`, `pop`, `assign`, `clear`
|
|
93
|
+
|
|
94
|
+
### Network
|
|
95
|
+
`fetch(url, options?)` - *Auto-serializes JSON bodies and handles content-types.*
|
|
96
|
+
|
|
97
|
+
## Example
|
|
98
|
+
|
|
99
|
+
A simple reactive counter described in JPRX syntax:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"div": {
|
|
104
|
+
"cdom-state": { "count": 0 },
|
|
105
|
+
"children": [
|
|
106
|
+
{ "h2": "Counter" },
|
|
107
|
+
{ "p": ["Current Count: ", "$/count"] },
|
|
108
|
+
{ "button": { "onclick": "$increment(/count)", "children": ["+"] } },
|
|
109
|
+
{ "button": { "onclick": "$decrement(/count)", "children": ["-"] } }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Reference Implementation: Lightview
|
|
116
|
+
|
|
117
|
+
JPRX was originally developed for [Lightview](https://github.com/anywhichway/lightview) to power its **Computational DOM (cDOM)**. Lightview serves as the primary example of how a UI library can hydrate JPRX expressions into a live, reactive interface.
|
|
118
|
+
|
|
119
|
+
If you are building a UI library and want to support reactive JSON structures, this parser provides the foundation.
|
|
120
|
+
|
|
121
|
+
## Getting Started
|
|
122
|
+
|
|
123
|
+
The JPRX package contains:
|
|
124
|
+
1. `parser.js`: The core Pratt parser and path resolution logic.
|
|
125
|
+
2. `helpers/`: A comprehensive library of math, logic, string, array, formatting, and state helpers.
|
|
126
|
+
|
|
127
|
+
To use JPRX, you typically register your state-management primitives (like Signals or Proxies) with the parser's registry, and then call `hydrate()` or `resolveExpression()` to activate the logic.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
© 2026 Simon Y. Blackwell, AnyWhichWay LLC. Licensed under MIT.
|
package/helpers/array.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom ARRAY HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const count = (...args) => args.length;
|
|
6
|
+
|
|
7
|
+
export const filter = (arr, predicate) => {
|
|
8
|
+
if (!Array.isArray(arr)) return [];
|
|
9
|
+
if (typeof predicate === 'function' && predicate.isLazy) {
|
|
10
|
+
return arr.filter(item => predicate.resolve(item));
|
|
11
|
+
}
|
|
12
|
+
return arr.filter(item => !!item);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const map = (arr, transform) => {
|
|
16
|
+
if (!Array.isArray(arr)) return [];
|
|
17
|
+
if (typeof transform === 'string') {
|
|
18
|
+
return arr.map(item => (item && typeof item === 'object') ? item[transform] : item);
|
|
19
|
+
}
|
|
20
|
+
// Check for LazyValue (has isLazy property and resolve method)
|
|
21
|
+
if (transform && transform.isLazy && typeof transform.resolve === 'function') {
|
|
22
|
+
return arr.map(item => transform.resolve(item));
|
|
23
|
+
}
|
|
24
|
+
// If it's a plain function
|
|
25
|
+
if (typeof transform === 'function') {
|
|
26
|
+
return arr.map(transform);
|
|
27
|
+
}
|
|
28
|
+
return arr;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const find = (arr, predicate) => {
|
|
32
|
+
if (!Array.isArray(arr)) return undefined;
|
|
33
|
+
if (predicate && predicate.isLazy) {
|
|
34
|
+
return arr.find(item => predicate.resolve(item));
|
|
35
|
+
}
|
|
36
|
+
return arr.find(item => !!item);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const unique = (arr) => Array.isArray(arr) ? [...new Set(arr)] : [];
|
|
40
|
+
|
|
41
|
+
export const sort = (arr, order = 'asc') => {
|
|
42
|
+
if (!Array.isArray(arr)) return [];
|
|
43
|
+
const sorted = [...arr];
|
|
44
|
+
sorted.sort((a, b) => {
|
|
45
|
+
if (a < b) return order === 'asc' ? -1 : 1;
|
|
46
|
+
if (a > b) return order === 'asc' ? 1 : -1;
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
return sorted;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const reverse = (arr) => Array.isArray(arr) ? [...arr].reverse() : [];
|
|
53
|
+
export const first = (arr) => Array.isArray(arr) ? arr[0] : undefined;
|
|
54
|
+
export const last = (arr) => Array.isArray(arr) ? arr[arr.length - 1] : undefined;
|
|
55
|
+
export const slice = (arr, start, end) => Array.isArray(arr) ? arr.slice(start, end) : [];
|
|
56
|
+
export const flatten = (arr) => Array.isArray(arr) ? arr.flat(Infinity) : [];
|
|
57
|
+
export const join = (arr, sep = ',') => Array.isArray(arr) ? arr.join(String(sep)) : '';
|
|
58
|
+
export const length = (arg) => Array.isArray(arg) ? arg.length : (arg ? String(arg).length : 0);
|
|
59
|
+
|
|
60
|
+
export const registerArrayHelpers = (register) => {
|
|
61
|
+
register('count', count);
|
|
62
|
+
register('filter', filter, { lazyAware: true });
|
|
63
|
+
register('map', map, { lazyAware: true });
|
|
64
|
+
register('find', find, { lazyAware: true });
|
|
65
|
+
register('unique', unique);
|
|
66
|
+
register('sort', sort);
|
|
67
|
+
register('reverse', reverse);
|
|
68
|
+
register('first', first);
|
|
69
|
+
register('last', last);
|
|
70
|
+
register('slice', slice);
|
|
71
|
+
register('flatten', flatten);
|
|
72
|
+
register('join', join);
|
|
73
|
+
register('len', length);
|
|
74
|
+
register('length', length);
|
|
75
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom COMPARISON HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const gt = (a, b) => a > b;
|
|
6
|
+
export const lt = (a, b) => a < b;
|
|
7
|
+
export const gte = (a, b) => a >= b;
|
|
8
|
+
export const lte = (a, b) => a <= b;
|
|
9
|
+
export const neq = (a, b) => a !== b;
|
|
10
|
+
export const between = (val, min, max) => val >= min && val <= max;
|
|
11
|
+
export const contains = (arr, val) => Array.isArray(arr) && arr.includes(val);
|
|
12
|
+
|
|
13
|
+
export const registerCompareHelpers = (register) => {
|
|
14
|
+
register('gt', gt);
|
|
15
|
+
register('>', gt);
|
|
16
|
+
register('lt', lt);
|
|
17
|
+
register('<', lt);
|
|
18
|
+
register('gte', gte);
|
|
19
|
+
register('>=', gte);
|
|
20
|
+
register('lte', lte);
|
|
21
|
+
register('<=', lte);
|
|
22
|
+
register('neq', neq);
|
|
23
|
+
register('!=', neq);
|
|
24
|
+
register('between', between);
|
|
25
|
+
register('in', contains);
|
|
26
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom CONDITIONAL AGGREGATE HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const sumIf = (arr, predicate) => {
|
|
6
|
+
if (!Array.isArray(arr)) return 0;
|
|
7
|
+
const filtered = (predicate && predicate.isLazy)
|
|
8
|
+
? arr.filter(item => predicate.resolve(item))
|
|
9
|
+
: arr;
|
|
10
|
+
return filtered.reduce((a, b) => a + (Number(b) || 0), 0);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const countIf = (arr, predicate) => {
|
|
14
|
+
if (!Array.isArray(arr)) return 0;
|
|
15
|
+
if (predicate && predicate.isLazy) {
|
|
16
|
+
return arr.filter(item => predicate.resolve(item)).length;
|
|
17
|
+
}
|
|
18
|
+
return arr.filter(item => !!item).length;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const avgIf = (arr, predicate) => {
|
|
22
|
+
if (!Array.isArray(arr)) return 0;
|
|
23
|
+
const filtered = (predicate && predicate.isLazy)
|
|
24
|
+
? arr.filter(item => predicate.resolve(item))
|
|
25
|
+
: arr;
|
|
26
|
+
if (filtered.length === 0) return 0;
|
|
27
|
+
return filtered.reduce((a, b) => a + (Number(b) || 0), 0) / filtered.length;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const registerConditionalHelpers = (register) => {
|
|
31
|
+
register('sumIf', sumIf);
|
|
32
|
+
register('countIf', countIf);
|
|
33
|
+
register('avgIf', avgIf);
|
|
34
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom DATE/TIME HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const now = () => new Date().getTime();
|
|
6
|
+
export const today = () => {
|
|
7
|
+
const d = new Date();
|
|
8
|
+
d.setHours(0, 0, 0, 0);
|
|
9
|
+
return d.getTime();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const date = (val) => new Date(val).getTime();
|
|
13
|
+
|
|
14
|
+
export const formatDate = (val, format) => {
|
|
15
|
+
const d = new Date(val);
|
|
16
|
+
if (isNaN(d.getTime())) return '';
|
|
17
|
+
|
|
18
|
+
// Minimal formatter, can be expanded
|
|
19
|
+
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
20
|
+
if (format === 'long') options.month = 'long';
|
|
21
|
+
return d.toLocaleDateString(undefined, options);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const year = (val) => new Date(val).getFullYear();
|
|
25
|
+
export const month = (val) => new Date(val).getMonth() + 1;
|
|
26
|
+
export const day = (val) => new Date(val).getDate();
|
|
27
|
+
export const weekday = (val) => new Date(val).getDay();
|
|
28
|
+
|
|
29
|
+
export const addDays = (val, days) => {
|
|
30
|
+
const d = new Date(val);
|
|
31
|
+
d.setDate(d.getDate() + Number(days));
|
|
32
|
+
return d.getTime();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const dateDiff = (d1, d2, unit = 'days') => {
|
|
36
|
+
const diff = Math.abs(new Date(d1) - new Date(d2));
|
|
37
|
+
if (unit === 'seconds') return diff / 1000;
|
|
38
|
+
if (unit === 'minutes') return diff / (1000 * 60);
|
|
39
|
+
if (unit === 'hours') return diff / (1000 * 60 * 60);
|
|
40
|
+
return diff / (1000 * 60 * 60 * 24);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const registerDateTimeHelpers = (register) => {
|
|
44
|
+
register('now', now);
|
|
45
|
+
register('today', today);
|
|
46
|
+
register('date', date);
|
|
47
|
+
register('formatDate', formatDate);
|
|
48
|
+
register('year', year);
|
|
49
|
+
register('month', month);
|
|
50
|
+
register('day', day);
|
|
51
|
+
register('weekday', weekday);
|
|
52
|
+
register('addDays', addDays);
|
|
53
|
+
register('dateDiff', dateDiff);
|
|
54
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom FORMATTING HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const number = (val, decimals = 2) => Number(val).toFixed(decimals);
|
|
6
|
+
|
|
7
|
+
export const currency = (val, symbol = '$', decimals = 2) => {
|
|
8
|
+
return symbol + Number(val).toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const percent = (val, decimals = 0) => (Number(val) * 100).toFixed(decimals) + '%';
|
|
12
|
+
|
|
13
|
+
export const thousands = (val) => String(val).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
14
|
+
|
|
15
|
+
export const registerFormatHelpers = (register) => {
|
|
16
|
+
register('number', number);
|
|
17
|
+
register('currency', currency);
|
|
18
|
+
register('percent', percent);
|
|
19
|
+
register('thousands', thousands);
|
|
20
|
+
};
|
package/helpers/logic.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom LOGIC HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const ifHelper = (condition, thenVal, elseVal) => condition ? thenVal : elseVal;
|
|
6
|
+
export const andHelper = (...args) => args.every(Boolean);
|
|
7
|
+
export const orHelper = (...args) => args.some(Boolean);
|
|
8
|
+
export const notHelper = (val) => !val;
|
|
9
|
+
export const eqHelper = (a, b) => a === b;
|
|
10
|
+
export const neqHelper = (a, b) => a !== b;
|
|
11
|
+
|
|
12
|
+
export const registerLogicHelpers = (register) => {
|
|
13
|
+
register('if', ifHelper);
|
|
14
|
+
register('and', andHelper);
|
|
15
|
+
register('&&', andHelper);
|
|
16
|
+
register('or', orHelper);
|
|
17
|
+
register('||', orHelper);
|
|
18
|
+
register('not', notHelper);
|
|
19
|
+
register('!', notHelper);
|
|
20
|
+
register('eq', eqHelper);
|
|
21
|
+
register('==', eqHelper);
|
|
22
|
+
register('===', eqHelper);
|
|
23
|
+
register('neq', neqHelper);
|
|
24
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom LOOKUP HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const lookup = (val, searchArr, resultArr) => {
|
|
6
|
+
if (!Array.isArray(searchArr)) return undefined;
|
|
7
|
+
const idx = searchArr.indexOf(val);
|
|
8
|
+
return idx !== -1 && Array.isArray(resultArr) ? resultArr[idx] : undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const vlookup = (val, table, colIdx) => {
|
|
12
|
+
if (!Array.isArray(table)) return undefined;
|
|
13
|
+
const row = table.find(r => Array.isArray(r) && r[0] === val);
|
|
14
|
+
return row ? row[colIdx - 1] : undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const index = (arr, idx) => Array.isArray(arr) ? arr[idx] : undefined;
|
|
18
|
+
export const match = (val, arr) => Array.isArray(arr) ? arr.indexOf(val) : -1;
|
|
19
|
+
|
|
20
|
+
export const registerLookupHelpers = (register) => {
|
|
21
|
+
register('lookup', lookup);
|
|
22
|
+
register('vlookup', vlookup);
|
|
23
|
+
register('index', index);
|
|
24
|
+
register('match', match);
|
|
25
|
+
};
|
package/helpers/math.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom MATH HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const add = (...args) => args.reduce((a, b) => Number(a) + Number(b), 0);
|
|
6
|
+
export const subtract = (a, b) => Number(a) - Number(b);
|
|
7
|
+
export const multiply = (...args) => args.reduce((a, b) => Number(a) * Number(b), 1);
|
|
8
|
+
export const divide = (a, b) => Number(a) / Number(b);
|
|
9
|
+
|
|
10
|
+
export const round = (val, decimals = 0) => Number(Math.round(val + 'e' + decimals) + 'e-' + decimals);
|
|
11
|
+
export const ceil = (val) => Math.ceil(val);
|
|
12
|
+
export const floor = (val) => Math.floor(val);
|
|
13
|
+
export const abs = (val) => Math.abs(val);
|
|
14
|
+
export const mod = (a, b) => a % b;
|
|
15
|
+
export const pow = (a, b) => Math.pow(a, b);
|
|
16
|
+
export const sqrt = (val) => Math.sqrt(val);
|
|
17
|
+
|
|
18
|
+
export const registerMathHelpers = (register) => {
|
|
19
|
+
register('+', add);
|
|
20
|
+
register('add', add);
|
|
21
|
+
register('-', subtract);
|
|
22
|
+
register('sub', subtract);
|
|
23
|
+
register('*', multiply);
|
|
24
|
+
register('mul', multiply);
|
|
25
|
+
register('/', divide);
|
|
26
|
+
register('div', divide);
|
|
27
|
+
register('round', round);
|
|
28
|
+
register('ceil', ceil);
|
|
29
|
+
register('floor', floor);
|
|
30
|
+
register('abs', abs);
|
|
31
|
+
register('mod', mod);
|
|
32
|
+
register('pow', pow);
|
|
33
|
+
register('sqrt', sqrt);
|
|
34
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom NETWORK HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A wrapper around the native fetch API that handles body serialization and
|
|
7
|
+
* Content-Type headers based on the body type.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} url - The URL to fetch
|
|
10
|
+
* @param {object} options - Fetch options (method, headers, body)
|
|
11
|
+
*/
|
|
12
|
+
export const fetchHelper = (url, options = {}) => {
|
|
13
|
+
const fetchOptions = { ...options };
|
|
14
|
+
const headers = { ...fetchOptions.headers };
|
|
15
|
+
|
|
16
|
+
let body = fetchOptions.body;
|
|
17
|
+
if (body !== undefined) {
|
|
18
|
+
if (body !== null && typeof body === 'object') {
|
|
19
|
+
// Automatically stringify objects
|
|
20
|
+
body = JSON.stringify(body);
|
|
21
|
+
if (!headers['Content-Type']) {
|
|
22
|
+
headers['Content-Type'] = 'application/json';
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
// Convert everything else to string
|
|
26
|
+
body = String(body);
|
|
27
|
+
if (!headers['Content-Type']) {
|
|
28
|
+
headers['Content-Type'] = 'text/plain';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fetchOptions.body = body;
|
|
34
|
+
fetchOptions.headers = headers;
|
|
35
|
+
|
|
36
|
+
return globalThis.fetch(url, fetchOptions);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const registerNetworkHelpers = (register) => {
|
|
40
|
+
register('fetch', fetchHelper);
|
|
41
|
+
};
|
package/helpers/state.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom STATE/MUTATION HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const set = (target, val) => {
|
|
6
|
+
if (target && typeof target === 'object' && 'value' in target) {
|
|
7
|
+
target.value = val;
|
|
8
|
+
} else if (target && typeof target === 'function' && 'value' in target) {
|
|
9
|
+
target.value = val;
|
|
10
|
+
} else if (target && typeof target === 'object' && val && typeof val === 'object') {
|
|
11
|
+
Object.assign(target, val);
|
|
12
|
+
}
|
|
13
|
+
return val;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const increment = (target, by = 1) => {
|
|
17
|
+
const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
|
|
18
|
+
const current = hasValue ? target.value : 0;
|
|
19
|
+
const next = Number(current) + Number(by);
|
|
20
|
+
return set(target, next);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const decrement = (target, by = 1) => {
|
|
24
|
+
const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
|
|
25
|
+
const current = hasValue ? target.value : 0;
|
|
26
|
+
const next = Number(current) - Number(by);
|
|
27
|
+
return set(target, next);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const toggle = (target) => {
|
|
31
|
+
const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
|
|
32
|
+
const current = hasValue ? target.value : false;
|
|
33
|
+
return set(target, !current);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const push = (target, item) => {
|
|
37
|
+
const current = (target && typeof target === 'object' && 'value' in target) ? target.value : [];
|
|
38
|
+
if (Array.isArray(current)) {
|
|
39
|
+
const next = [...current, item];
|
|
40
|
+
return set(target, next);
|
|
41
|
+
}
|
|
42
|
+
return current;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const pop = (target) => {
|
|
46
|
+
const current = (target && typeof target === 'object' && 'value' in target) ? target.value : [];
|
|
47
|
+
if (Array.isArray(current) && current.length > 0) {
|
|
48
|
+
const next = current.slice(0, -1);
|
|
49
|
+
set(target, next);
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const assign = (target, obj) => {
|
|
55
|
+
const current = (target && typeof target === 'object' && 'value' in target) ? target.value : {};
|
|
56
|
+
const next = { ...current, ...obj };
|
|
57
|
+
return set(target, next);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const clear = (target) => {
|
|
61
|
+
const current = (target && typeof target === 'object' && 'value' in target) ? target.value : null;
|
|
62
|
+
if (Array.isArray(current)) return set(target, []);
|
|
63
|
+
if (typeof current === 'object' && current !== null) return set(target, {});
|
|
64
|
+
return set(target, null);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const registerStateHelpers = (register) => {
|
|
68
|
+
const opts = { pathAware: true };
|
|
69
|
+
register('set', set, opts);
|
|
70
|
+
register('increment', increment, opts);
|
|
71
|
+
register('++', increment, opts);
|
|
72
|
+
register('decrement', decrement, opts);
|
|
73
|
+
register('--', decrement, opts);
|
|
74
|
+
register('toggle', toggle, opts);
|
|
75
|
+
register('!!', toggle, opts);
|
|
76
|
+
register('push', push, opts);
|
|
77
|
+
register('pop', pop, opts);
|
|
78
|
+
register('assign', assign, opts);
|
|
79
|
+
register('clear', clear, opts);
|
|
80
|
+
};
|
package/helpers/stats.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom STATISTICAL HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const sum = (...args) => args.reduce((a, b) => a + (Number(b) || 0), 0);
|
|
6
|
+
export const avg = (...args) => args.length === 0 ? 0 : sum(...args) / args.length;
|
|
7
|
+
export const min = (...args) => Math.min(...args);
|
|
8
|
+
export const max = (...args) => Math.max(...args);
|
|
9
|
+
|
|
10
|
+
export const median = (...args) => {
|
|
11
|
+
if (args.length === 0) return 0;
|
|
12
|
+
const sorted = [...args].sort((a, b) => a - b);
|
|
13
|
+
const mid = Math.floor(sorted.length / 2);
|
|
14
|
+
return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const stdev = (...args) => {
|
|
18
|
+
if (args.length === 0) return 0;
|
|
19
|
+
const mean = avg(...args);
|
|
20
|
+
const squareDiffs = args.map(value => Math.pow(value - mean, 2));
|
|
21
|
+
return Math.sqrt(avg(...squareDiffs));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const variance = (...args) => {
|
|
25
|
+
if (args.length === 0) return 0;
|
|
26
|
+
const mean = avg(...args);
|
|
27
|
+
const squareDiffs = args.map(value => Math.pow(value - mean, 2));
|
|
28
|
+
return avg(...squareDiffs);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const registerStatsHelpers = (register) => {
|
|
32
|
+
register('sum', sum);
|
|
33
|
+
register('avg', avg);
|
|
34
|
+
register('min', min);
|
|
35
|
+
register('max', max);
|
|
36
|
+
register('median', median);
|
|
37
|
+
register('stdev', stdev);
|
|
38
|
+
register('var', variance);
|
|
39
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdom STRING HELPERS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const join = (...args) => {
|
|
6
|
+
const separator = args[args.length - 1];
|
|
7
|
+
const items = args.slice(0, -1);
|
|
8
|
+
return items.join(separator);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const concat = (...args) => args.join('');
|
|
12
|
+
export const upper = (s) => String(s).toUpperCase();
|
|
13
|
+
export const lower = (s) => String(s).toLowerCase();
|
|
14
|
+
export const trim = (s) => String(s).trim();
|
|
15
|
+
export const len = (s) => String(s).length;
|
|
16
|
+
export const replace = (s, search, replacement) => String(s).replace(search, replacement);
|
|
17
|
+
export const split = (s, separator) => String(s).split(separator);
|
|
18
|
+
|
|
19
|
+
export const capitalize = (s) => {
|
|
20
|
+
const str = String(s);
|
|
21
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const titleCase = (s) => {
|
|
25
|
+
return String(s).toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const contains = (s, search) => String(s).includes(search);
|
|
29
|
+
export const startsWith = (s, prefix) => String(s).startsWith(prefix);
|
|
30
|
+
export const endsWith = (s, suffix) => String(s).endsWith(suffix);
|
|
31
|
+
|
|
32
|
+
export const defaultHelper = (val, fallback) => (val !== undefined && val !== null) ? val : fallback;
|
|
33
|
+
|
|
34
|
+
export const registerStringHelpers = (register) => {
|
|
35
|
+
register('join', join);
|
|
36
|
+
register('concat', concat);
|
|
37
|
+
register('upper', upper);
|
|
38
|
+
register('lower', lower);
|
|
39
|
+
register('trim', trim);
|
|
40
|
+
register('len', len);
|
|
41
|
+
register('replace', replace);
|
|
42
|
+
register('split', split);
|
|
43
|
+
register('capitalize', capitalize);
|
|
44
|
+
register('titleCase', titleCase);
|
|
45
|
+
register('contains', contains);
|
|
46
|
+
register('startsWith', startsWith);
|
|
47
|
+
register('endsWith', endsWith);
|
|
48
|
+
register('default', defaultHelper);
|
|
49
|
+
};
|