@jxsuite/formulas 0.0.1
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/README.md +58 -0
- package/formulas/average/expression.json +19 -0
- package/formulas/average/formula.json +5 -0
- package/formulas/capitalize/expression.json +8 -0
- package/formulas/capitalize/formula.json +5 -0
- package/formulas/clamp/expression.json +12 -0
- package/formulas/clamp/formula.json +9 -0
- package/formulas/compact/expression.json +5 -0
- package/formulas/compact/formula.json +5 -0
- package/formulas/count/expression.json +5 -0
- package/formulas/count/formula.json +5 -0
- package/formulas/first/expression.json +5 -0
- package/formulas/first/formula.json +5 -0
- package/formulas/initials/expression.json +12 -0
- package/formulas/initials/formula.json +5 -0
- package/formulas/isEmpty/expression.json +5 -0
- package/formulas/isEmpty/formula.json +5 -0
- package/formulas/last/expression.json +5 -0
- package/formulas/last/formula.json +5 -0
- package/formulas/max/expression.json +10 -0
- package/formulas/max/formula.json +5 -0
- package/formulas/min/expression.json +10 -0
- package/formulas/min/formula.json +5 -0
- package/formulas/percent/expression.json +14 -0
- package/formulas/percent/formula.json +8 -0
- package/formulas/roundTo/expression.json +23 -0
- package/formulas/roundTo/formula.json +8 -0
- package/formulas/sum/expression.json +10 -0
- package/formulas/sum/formula.json +5 -0
- package/formulas/truncate/expression.json +18 -0
- package/formulas/truncate/formula.json +8 -0
- package/package.json +33 -0
- package/src/catalog.ts +537 -0
- package/src/index.ts +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @jxsuite/formulas
|
|
2
|
+
|
|
3
|
+
Composite **pure formulas** for Jx, authored as declarative `$expression` JSON
|
|
4
|
+
(spec §19.4c). Each formula is a named, parameterized computation — `clamp`,
|
|
5
|
+
`sum`, `average`, `truncate`, `initials`, … — whose body is built exclusively
|
|
6
|
+
from blessed operators, pure standard-library method operators (§19.4d), and
|
|
7
|
+
blessed globals. No JavaScript source anywhere: every formula is inspectable
|
|
8
|
+
data that the visual editor can open, badge with live values, and recompose.
|
|
9
|
+
|
|
10
|
+
## Consumption model: copy-in, not a project dependency
|
|
11
|
+
|
|
12
|
+
This package is a **studio/tooling dependency**, deliberately _not_ a runtime
|
|
13
|
+
or project dependency. Jx documents stay self-contained plain JSON:
|
|
14
|
+
|
|
15
|
+
- **In Jx Studio** — the formula catalog lists these formulas alongside
|
|
16
|
+
operators and blessed globals. Picking one **copies its state entry into the
|
|
17
|
+
document** (vendoring the JSON) and inserts a `call` node referencing it.
|
|
18
|
+
From that moment the project owns the formula: no package resolution at
|
|
19
|
+
build or run time, no version coupling, and the user can edit their copy
|
|
20
|
+
like any other named formula.
|
|
21
|
+
- **Programmatically** — `formulaEntries()` returns the catalog as
|
|
22
|
+
ready-to-merge `state` entries for tooling that seeds documents or
|
|
23
|
+
`project.json` state (starters, importers, AI agents):
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { catalog, formulaEntries } from "@jxsuite/formulas";
|
|
27
|
+
|
|
28
|
+
const state = { ...formulaEntries() };
|
|
29
|
+
// → { clamp: { $expression: …, parameters: […] }, sum: …, … }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Because consumption is copy-in, publishing a new package version never changes
|
|
33
|
+
existing projects — they upgrade a formula by re-inserting it.
|
|
34
|
+
|
|
35
|
+
## Authoring a formula
|
|
36
|
+
|
|
37
|
+
One folder per formula under `formulas/`:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
formulas/<name>/formula.json # metadata: name, description, parameters (CEM convention)
|
|
41
|
+
formulas/<name>/expression.json # the pure $expression body over $args/<name> refs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then regenerate the catalog module:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bun run generate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The generator (`bin/generate.ts`) emits `src/catalog.ts` and **fails the build
|
|
51
|
+
on anything impure**: unknown or mutating operators, or `call` targets outside
|
|
52
|
+
the blessed pure globals (`window#/Math/…`, `window#/JSON/…`, …). Descriptions
|
|
53
|
+
are mandatory — they double as editor tooltips and the LLM contract.
|
|
54
|
+
|
|
55
|
+
## Releases
|
|
56
|
+
|
|
57
|
+
Versioned independently via release-please (`formulas` component); published to
|
|
58
|
+
npm with provenance by the repo's publish workflow when a release is cut.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"operator": "?:",
|
|
3
|
+
"target": { "$ref": "$args/values/length" },
|
|
4
|
+
"value": {
|
|
5
|
+
"operator": "/",
|
|
6
|
+
"target": {
|
|
7
|
+
"operator": "reduce",
|
|
8
|
+
"target": { "$ref": "$args/values" },
|
|
9
|
+
"initial": 0,
|
|
10
|
+
"value": {
|
|
11
|
+
"operator": "+",
|
|
12
|
+
"target": { "$ref": "$reduce/acc" },
|
|
13
|
+
"value": { "$ref": "$map/item" }
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"value": { "$ref": "$args/values/length" }
|
|
17
|
+
},
|
|
18
|
+
"initial": 0
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clamp",
|
|
3
|
+
"description": "Bound a number to the [min, max] range (the TC39 Math.clamp shape): returns min when below, max when above, the value itself otherwise.",
|
|
4
|
+
"parameters": [
|
|
5
|
+
{ "name": "value", "type": { "text": "number" } },
|
|
6
|
+
{ "name": "min", "type": { "text": "number" } },
|
|
7
|
+
{ "name": "max", "type": { "text": "number" } }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"operator": "join",
|
|
3
|
+
"target": {
|
|
4
|
+
"operator": "map",
|
|
5
|
+
"target": { "operator": "split", "target": { "$ref": "$args/name" }, "value": " " },
|
|
6
|
+
"value": {
|
|
7
|
+
"operator": "toUpperCase",
|
|
8
|
+
"target": { "operator": "charAt", "target": { "$ref": "$map/item" }, "value": 0 }
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"value": ""
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"operator": "/",
|
|
3
|
+
"target": {
|
|
4
|
+
"operator": "call",
|
|
5
|
+
"target": { "$ref": "window#/Math/round" },
|
|
6
|
+
"value": [
|
|
7
|
+
{
|
|
8
|
+
"operator": "*",
|
|
9
|
+
"target": { "$ref": "$args/value" },
|
|
10
|
+
"value": {
|
|
11
|
+
"operator": "call",
|
|
12
|
+
"target": { "$ref": "window#/Math/pow" },
|
|
13
|
+
"value": [10, { "$ref": "$args/digits" }]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"value": {
|
|
19
|
+
"operator": "call",
|
|
20
|
+
"target": { "$ref": "window#/Math/pow" },
|
|
21
|
+
"value": [10, { "$ref": "$args/digits" }]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "roundTo",
|
|
3
|
+
"description": "Round a number to the given decimal places (Math.round against a Math.pow(10, digits) scale).",
|
|
4
|
+
"parameters": [
|
|
5
|
+
{ "name": "value", "type": { "text": "number" } },
|
|
6
|
+
{ "name": "digits", "type": { "text": "number" }, "default": 0 }
|
|
7
|
+
]
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"operator": "?:",
|
|
3
|
+
"target": {
|
|
4
|
+
"operator": ">",
|
|
5
|
+
"target": { "$ref": "$args/text/length" },
|
|
6
|
+
"value": { "$ref": "$args/length" }
|
|
7
|
+
},
|
|
8
|
+
"value": {
|
|
9
|
+
"operator": "+",
|
|
10
|
+
"target": {
|
|
11
|
+
"operator": "slice",
|
|
12
|
+
"target": { "$ref": "$args/text" },
|
|
13
|
+
"value": [0, { "$ref": "$args/length" }]
|
|
14
|
+
},
|
|
15
|
+
"value": "…"
|
|
16
|
+
},
|
|
17
|
+
"initial": { "$ref": "$args/text" }
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jxsuite/formulas",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Composite pure formulas for Jx, authored as declarative $expression JSON",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jxsuite/jx.git",
|
|
9
|
+
"directory": "packages/formulas"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"formulas/"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./src/index.ts",
|
|
18
|
+
"./catalog": "./src/catalog.ts"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"provenance": false
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"generate": "bun run bin/generate.ts",
|
|
25
|
+
"test": "bun test --isolate"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@jxsuite/schema": "workspace:^"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@jxsuite/runtime": "workspace:^"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by bin/generate.ts — do not edit. Regenerate with `bun run generate`.
|
|
3
|
+
*
|
|
4
|
+
* Every entry is a named formula (spec §19.4c): a pure $expression body over $args/<name>
|
|
5
|
+
* parameters, verified against the blessed operator and global sets at generation time.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CemParameter, JxExpressionNode } from "@jxsuite/schema/types";
|
|
9
|
+
|
|
10
|
+
export interface FormulaCatalogEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
parameters: (string | CemParameter)[];
|
|
14
|
+
expression: JxExpressionNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const catalog: FormulaCatalogEntry[] = [
|
|
18
|
+
{
|
|
19
|
+
description: "Arithmetic mean of an array of numbers; 0 for an empty array.",
|
|
20
|
+
expression: {
|
|
21
|
+
operator: "?:",
|
|
22
|
+
target: {
|
|
23
|
+
$ref: "$args/values/length",
|
|
24
|
+
},
|
|
25
|
+
value: {
|
|
26
|
+
operator: "/",
|
|
27
|
+
target: {
|
|
28
|
+
operator: "reduce",
|
|
29
|
+
target: {
|
|
30
|
+
$ref: "$args/values",
|
|
31
|
+
},
|
|
32
|
+
initial: 0,
|
|
33
|
+
value: {
|
|
34
|
+
operator: "+",
|
|
35
|
+
target: {
|
|
36
|
+
$ref: "$reduce/acc",
|
|
37
|
+
},
|
|
38
|
+
value: {
|
|
39
|
+
$ref: "$map/item",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
value: {
|
|
44
|
+
$ref: "$args/values/length",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
initial: 0,
|
|
48
|
+
},
|
|
49
|
+
name: "average",
|
|
50
|
+
parameters: [
|
|
51
|
+
{
|
|
52
|
+
name: "values",
|
|
53
|
+
type: {
|
|
54
|
+
text: "number[]",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
description:
|
|
61
|
+
"Uppercase the first character of a string, leaving the rest unchanged (the CSS text-transform: capitalize shape for a single word).",
|
|
62
|
+
expression: {
|
|
63
|
+
operator: "+",
|
|
64
|
+
target: {
|
|
65
|
+
operator: "toUpperCase",
|
|
66
|
+
target: {
|
|
67
|
+
operator: "charAt",
|
|
68
|
+
target: {
|
|
69
|
+
$ref: "$args/text",
|
|
70
|
+
},
|
|
71
|
+
value: 0,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
value: {
|
|
75
|
+
operator: "slice",
|
|
76
|
+
target: {
|
|
77
|
+
$ref: "$args/text",
|
|
78
|
+
},
|
|
79
|
+
value: 1,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
name: "capitalize",
|
|
83
|
+
parameters: [
|
|
84
|
+
{
|
|
85
|
+
name: "text",
|
|
86
|
+
type: {
|
|
87
|
+
text: "string",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
description:
|
|
94
|
+
"Bound a number to the [min, max] range (the TC39 Math.clamp shape): returns min when below, max when above, the value itself otherwise.",
|
|
95
|
+
expression: {
|
|
96
|
+
operator: "call",
|
|
97
|
+
target: {
|
|
98
|
+
$ref: "window#/Math/min",
|
|
99
|
+
},
|
|
100
|
+
value: [
|
|
101
|
+
{
|
|
102
|
+
operator: "call",
|
|
103
|
+
target: {
|
|
104
|
+
$ref: "window#/Math/max",
|
|
105
|
+
},
|
|
106
|
+
value: [
|
|
107
|
+
{
|
|
108
|
+
$ref: "$args/value",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
$ref: "$args/min",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
$ref: "$args/max",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
name: "clamp",
|
|
121
|
+
parameters: [
|
|
122
|
+
{
|
|
123
|
+
name: "value",
|
|
124
|
+
type: {
|
|
125
|
+
text: "number",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "min",
|
|
130
|
+
type: {
|
|
131
|
+
text: "number",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "max",
|
|
136
|
+
type: {
|
|
137
|
+
text: "number",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
description:
|
|
144
|
+
"A copy of an array with the falsy elements removed (Array.prototype.filter on truthiness).",
|
|
145
|
+
expression: {
|
|
146
|
+
operator: "filter",
|
|
147
|
+
target: {
|
|
148
|
+
$ref: "$args/values",
|
|
149
|
+
},
|
|
150
|
+
value: {
|
|
151
|
+
operator: "!",
|
|
152
|
+
target: {
|
|
153
|
+
operator: "!",
|
|
154
|
+
target: {
|
|
155
|
+
$ref: "$map/item",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
name: "compact",
|
|
161
|
+
parameters: [
|
|
162
|
+
{
|
|
163
|
+
name: "values",
|
|
164
|
+
type: {
|
|
165
|
+
text: "unknown[]",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
description: "The length of an array or string, or 0 when the value is missing.",
|
|
172
|
+
expression: {
|
|
173
|
+
operator: "??",
|
|
174
|
+
target: {
|
|
175
|
+
$ref: "$args/values/length",
|
|
176
|
+
},
|
|
177
|
+
value: 0,
|
|
178
|
+
},
|
|
179
|
+
name: "count",
|
|
180
|
+
parameters: [
|
|
181
|
+
{
|
|
182
|
+
name: "values",
|
|
183
|
+
type: {
|
|
184
|
+
text: "unknown[] | string",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
description: "The first element of an array (index 0), or null when empty.",
|
|
191
|
+
expression: {
|
|
192
|
+
operator: "??",
|
|
193
|
+
target: {
|
|
194
|
+
$ref: "$args/values/0",
|
|
195
|
+
},
|
|
196
|
+
value: null,
|
|
197
|
+
},
|
|
198
|
+
name: "first",
|
|
199
|
+
parameters: [
|
|
200
|
+
{
|
|
201
|
+
name: "values",
|
|
202
|
+
type: {
|
|
203
|
+
text: "unknown[]",
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
description:
|
|
210
|
+
'The uppercase first letters of each whitespace-separated word, joined (e.g. "ada lovelace" → "AL").',
|
|
211
|
+
expression: {
|
|
212
|
+
operator: "join",
|
|
213
|
+
target: {
|
|
214
|
+
operator: "map",
|
|
215
|
+
target: {
|
|
216
|
+
operator: "split",
|
|
217
|
+
target: {
|
|
218
|
+
$ref: "$args/name",
|
|
219
|
+
},
|
|
220
|
+
value: " ",
|
|
221
|
+
},
|
|
222
|
+
value: {
|
|
223
|
+
operator: "toUpperCase",
|
|
224
|
+
target: {
|
|
225
|
+
operator: "charAt",
|
|
226
|
+
target: {
|
|
227
|
+
$ref: "$map/item",
|
|
228
|
+
},
|
|
229
|
+
value: 0,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
value: "",
|
|
234
|
+
},
|
|
235
|
+
name: "initials",
|
|
236
|
+
parameters: [
|
|
237
|
+
{
|
|
238
|
+
name: "name",
|
|
239
|
+
type: {
|
|
240
|
+
text: "string",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
description:
|
|
247
|
+
"True when an array or string has no elements (length 0 — a missing value counts as empty).",
|
|
248
|
+
expression: {
|
|
249
|
+
operator: "===",
|
|
250
|
+
target: {
|
|
251
|
+
operator: "??",
|
|
252
|
+
target: {
|
|
253
|
+
$ref: "$args/value/length",
|
|
254
|
+
},
|
|
255
|
+
value: 0,
|
|
256
|
+
},
|
|
257
|
+
value: 0,
|
|
258
|
+
},
|
|
259
|
+
name: "isEmpty",
|
|
260
|
+
parameters: [
|
|
261
|
+
{
|
|
262
|
+
name: "value",
|
|
263
|
+
type: {
|
|
264
|
+
text: "unknown[] | string",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
description: "The last element of an array (Array.prototype.at(-1)), or undefined when empty.",
|
|
271
|
+
expression: {
|
|
272
|
+
operator: "at",
|
|
273
|
+
target: {
|
|
274
|
+
$ref: "$args/values",
|
|
275
|
+
},
|
|
276
|
+
value: -1,
|
|
277
|
+
},
|
|
278
|
+
name: "last",
|
|
279
|
+
parameters: [
|
|
280
|
+
{
|
|
281
|
+
name: "values",
|
|
282
|
+
type: {
|
|
283
|
+
text: "unknown[]",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
description:
|
|
290
|
+
"The largest number in an array (Math.max folded over the values); undefined for an empty array.",
|
|
291
|
+
expression: {
|
|
292
|
+
operator: "reduce",
|
|
293
|
+
target: {
|
|
294
|
+
$ref: "$args/values",
|
|
295
|
+
},
|
|
296
|
+
initial: {
|
|
297
|
+
$ref: "$args/values/0",
|
|
298
|
+
},
|
|
299
|
+
value: {
|
|
300
|
+
operator: "call",
|
|
301
|
+
target: {
|
|
302
|
+
$ref: "window#/Math/max",
|
|
303
|
+
},
|
|
304
|
+
value: [
|
|
305
|
+
{
|
|
306
|
+
$ref: "$reduce/acc",
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
$ref: "$map/item",
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
name: "max",
|
|
315
|
+
parameters: [
|
|
316
|
+
{
|
|
317
|
+
name: "values",
|
|
318
|
+
type: {
|
|
319
|
+
text: "number[]",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
description:
|
|
326
|
+
"The smallest number in an array (Math.min folded over the values); undefined for an empty array.",
|
|
327
|
+
expression: {
|
|
328
|
+
operator: "reduce",
|
|
329
|
+
target: {
|
|
330
|
+
$ref: "$args/values",
|
|
331
|
+
},
|
|
332
|
+
initial: {
|
|
333
|
+
$ref: "$args/values/0",
|
|
334
|
+
},
|
|
335
|
+
value: {
|
|
336
|
+
operator: "call",
|
|
337
|
+
target: {
|
|
338
|
+
$ref: "window#/Math/min",
|
|
339
|
+
},
|
|
340
|
+
value: [
|
|
341
|
+
{
|
|
342
|
+
$ref: "$reduce/acc",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
$ref: "$map/item",
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
name: "min",
|
|
351
|
+
parameters: [
|
|
352
|
+
{
|
|
353
|
+
name: "values",
|
|
354
|
+
type: {
|
|
355
|
+
text: "number[]",
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
description: "part / whole as a percentage (0 when the whole is 0 or missing).",
|
|
362
|
+
expression: {
|
|
363
|
+
operator: "?:",
|
|
364
|
+
target: {
|
|
365
|
+
$ref: "$args/whole",
|
|
366
|
+
},
|
|
367
|
+
value: {
|
|
368
|
+
operator: "*",
|
|
369
|
+
target: {
|
|
370
|
+
operator: "/",
|
|
371
|
+
target: {
|
|
372
|
+
$ref: "$args/part",
|
|
373
|
+
},
|
|
374
|
+
value: {
|
|
375
|
+
$ref: "$args/whole",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
value: 100,
|
|
379
|
+
},
|
|
380
|
+
initial: 0,
|
|
381
|
+
},
|
|
382
|
+
name: "percent",
|
|
383
|
+
parameters: [
|
|
384
|
+
{
|
|
385
|
+
name: "part",
|
|
386
|
+
type: {
|
|
387
|
+
text: "number",
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "whole",
|
|
392
|
+
type: {
|
|
393
|
+
text: "number",
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
description:
|
|
400
|
+
"Round a number to the given decimal places (Math.round against a Math.pow(10, digits) scale).",
|
|
401
|
+
expression: {
|
|
402
|
+
operator: "/",
|
|
403
|
+
target: {
|
|
404
|
+
operator: "call",
|
|
405
|
+
target: {
|
|
406
|
+
$ref: "window#/Math/round",
|
|
407
|
+
},
|
|
408
|
+
value: [
|
|
409
|
+
{
|
|
410
|
+
operator: "*",
|
|
411
|
+
target: {
|
|
412
|
+
$ref: "$args/value",
|
|
413
|
+
},
|
|
414
|
+
value: {
|
|
415
|
+
operator: "call",
|
|
416
|
+
target: {
|
|
417
|
+
$ref: "window#/Math/pow",
|
|
418
|
+
},
|
|
419
|
+
value: [
|
|
420
|
+
10,
|
|
421
|
+
{
|
|
422
|
+
$ref: "$args/digits",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
value: {
|
|
430
|
+
operator: "call",
|
|
431
|
+
target: {
|
|
432
|
+
$ref: "window#/Math/pow",
|
|
433
|
+
},
|
|
434
|
+
value: [
|
|
435
|
+
10,
|
|
436
|
+
{
|
|
437
|
+
$ref: "$args/digits",
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
name: "roundTo",
|
|
443
|
+
parameters: [
|
|
444
|
+
{
|
|
445
|
+
name: "value",
|
|
446
|
+
type: {
|
|
447
|
+
text: "number",
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "digits",
|
|
452
|
+
type: {
|
|
453
|
+
text: "number",
|
|
454
|
+
},
|
|
455
|
+
default: 0,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
description: "Add every number in an array (Array.prototype.reduce with +, seeded at 0).",
|
|
461
|
+
expression: {
|
|
462
|
+
operator: "reduce",
|
|
463
|
+
target: {
|
|
464
|
+
$ref: "$args/values",
|
|
465
|
+
},
|
|
466
|
+
initial: 0,
|
|
467
|
+
value: {
|
|
468
|
+
operator: "+",
|
|
469
|
+
target: {
|
|
470
|
+
$ref: "$reduce/acc",
|
|
471
|
+
},
|
|
472
|
+
value: {
|
|
473
|
+
$ref: "$map/item",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
name: "sum",
|
|
478
|
+
parameters: [
|
|
479
|
+
{
|
|
480
|
+
name: "values",
|
|
481
|
+
type: {
|
|
482
|
+
text: "number[]",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
description:
|
|
489
|
+
"Shorten a string to at most the given length, appending a horizontal ellipsis when cut.",
|
|
490
|
+
expression: {
|
|
491
|
+
operator: "?:",
|
|
492
|
+
target: {
|
|
493
|
+
operator: ">",
|
|
494
|
+
target: {
|
|
495
|
+
$ref: "$args/text/length",
|
|
496
|
+
},
|
|
497
|
+
value: {
|
|
498
|
+
$ref: "$args/length",
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
value: {
|
|
502
|
+
operator: "+",
|
|
503
|
+
target: {
|
|
504
|
+
operator: "slice",
|
|
505
|
+
target: {
|
|
506
|
+
$ref: "$args/text",
|
|
507
|
+
},
|
|
508
|
+
value: [
|
|
509
|
+
0,
|
|
510
|
+
{
|
|
511
|
+
$ref: "$args/length",
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
},
|
|
515
|
+
value: "…",
|
|
516
|
+
},
|
|
517
|
+
initial: {
|
|
518
|
+
$ref: "$args/text",
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
name: "truncate",
|
|
522
|
+
parameters: [
|
|
523
|
+
{
|
|
524
|
+
name: "text",
|
|
525
|
+
type: {
|
|
526
|
+
text: "string",
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "length",
|
|
531
|
+
type: {
|
|
532
|
+
text: "number",
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
] as unknown as FormulaCatalogEntry[];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `jxsuite formulas` package — composite pure formulas, authored as declarative $expression
|
|
3
|
+
* JSON.
|
|
4
|
+
*
|
|
5
|
+
* Each catalog entry is a named formula (spec §19.4c): a pure body over $args/<name> parameters
|
|
6
|
+
* built from blessed operators, pure standard-library methods (§19.4d), and blessed globals.
|
|
7
|
+
* `formulaEntries()` returns them as ready-to-merge `state` entries — drop them into a document or
|
|
8
|
+
* project `state` and invoke via the `call` operator.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { catalog } from "./catalog.ts";
|
|
12
|
+
|
|
13
|
+
import type { JxExpressionDef } from "@jxsuite/schema/types";
|
|
14
|
+
|
|
15
|
+
export { catalog } from "./catalog.ts";
|
|
16
|
+
export type { FormulaCatalogEntry } from "./catalog.ts";
|
|
17
|
+
|
|
18
|
+
/** The catalog as named-formula state entries, keyed by formula name. */
|
|
19
|
+
export function formulaEntries(): Record<string, JxExpressionDef> {
|
|
20
|
+
const entries: Record<string, JxExpressionDef> = {};
|
|
21
|
+
for (const formula of catalog) {
|
|
22
|
+
entries[formula.name] = {
|
|
23
|
+
$description: formula.description,
|
|
24
|
+
$expression: formula.expression,
|
|
25
|
+
parameters: formula.parameters,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return entries;
|
|
29
|
+
}
|