@jarenjs/calc 0.56.0 → 0.67.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/README.md +27 -1
- package/dist/types/component/index.d.ts +50 -5
- package/package.json +5 -5
- package/src/component/index.js +29 -1
- package/src/component/rates/index.js +24 -4
package/README.md
CHANGED
|
@@ -47,6 +47,12 @@ depreciation, bond, indicators, returns) and `@jarenjs/core/convert`
|
|
|
47
47
|
primitive). The calculator's financial mode calls `core/finance`; its
|
|
48
48
|
converter calls `core/convert`. Core is pure and never fetches.
|
|
49
49
|
|
|
50
|
+
Changing the converter dimension updates its unit selections in the same
|
|
51
|
+
transition. Existing choices survive when the new dimension offers them;
|
|
52
|
+
otherwise it selects the first two available units (the same unit for both
|
|
53
|
+
sides when only one exists). Currency choices come from the current rate
|
|
54
|
+
table. A dimension with no available units leaves the conversion unchanged.
|
|
55
|
+
|
|
50
56
|
## Live currency (the maturity example)
|
|
51
57
|
|
|
52
58
|
The converter's currency dimension takes live crypto+fiat rates from free
|
|
@@ -55,6 +61,26 @@ public tickers (CoinGecko default, Binance alternative) through an
|
|
|
55
61
|
conversion** is `@jarenjs/core/convert`'s `convertCurrency`; only the
|
|
56
62
|
*fetch* is component-side. A static fallback table keeps the converter,
|
|
57
63
|
SSR and offline tests working with no network; failures route to an error
|
|
58
|
-
action.
|
|
64
|
+
action. When refreshes overlap, only the newest request may publish success
|
|
65
|
+
or failure; an older completion cannot replace its rates or clear its
|
|
66
|
+
in-flight status.
|
|
59
67
|
|
|
60
68
|
See `docs/CALC-FORMAT.md` and `ARCHITECTURE.md` for the full contract.
|
|
69
|
+
|
|
70
|
+
## Exports
|
|
71
|
+
|
|
72
|
+
Every subpath a consumer can import, derived from the manifest by
|
|
73
|
+
`npm run docs:derive` (`npm run docs:check` fails when the two drift):
|
|
74
|
+
|
|
75
|
+
<!--fact:exports.calc-->
|
|
76
|
+
| Import | Kind | Declarations |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `@jarenjs/calc` | JavaScript | declared |
|
|
79
|
+
| `@jarenjs/calc/component` | JavaScript | declared |
|
|
80
|
+
| `@jarenjs/calc/theme` | JavaScript | declared |
|
|
81
|
+
| `@jarenjs/calc/styles/calc.css` | asset | — |
|
|
82
|
+
| `@jarenjs/calc/schemas/financial-inputs.schema.json` | schema | — |
|
|
83
|
+
| `@jarenjs/calc/schemas/jaren-calc-ast.schema.json` | schema | — |
|
|
84
|
+
| `@jarenjs/calc/schemas/jaren-calc-state.schema.json` | schema | — |
|
|
85
|
+
| `@jarenjs/calc/package.json` | metadata | — |
|
|
86
|
+
<!--/fact-->
|
|
@@ -173,11 +173,56 @@ export declare const calcActions: {
|
|
|
173
173
|
}[];
|
|
174
174
|
};
|
|
175
175
|
'calc/conv-dim': {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
$let: {
|
|
177
|
+
units: {
|
|
178
|
+
$if: ({
|
|
179
|
+
$for: {
|
|
180
|
+
unit: {
|
|
181
|
+
$entries: string;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
$return: string;
|
|
185
|
+
}[] | {
|
|
186
|
+
$eq: string[];
|
|
187
|
+
$get?: undefined;
|
|
188
|
+
} | {
|
|
189
|
+
$eq?: undefined;
|
|
190
|
+
$get: (string | {
|
|
191
|
+
$const: {
|
|
192
|
+
[k: string]: string[];
|
|
193
|
+
};
|
|
194
|
+
})[];
|
|
195
|
+
})[];
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
$return: {
|
|
199
|
+
$if: ({
|
|
200
|
+
$exists: string;
|
|
201
|
+
patch?: undefined;
|
|
202
|
+
} | {
|
|
203
|
+
$exists?: undefined;
|
|
204
|
+
patch: ({
|
|
205
|
+
op: string;
|
|
206
|
+
path: string;
|
|
207
|
+
value: string;
|
|
208
|
+
} | {
|
|
209
|
+
op: string;
|
|
210
|
+
path: string;
|
|
211
|
+
value: {
|
|
212
|
+
$if: (string | {
|
|
213
|
+
$eq: string[];
|
|
214
|
+
$default?: undefined;
|
|
215
|
+
} | {
|
|
216
|
+
$eq?: undefined;
|
|
217
|
+
$default: string[];
|
|
218
|
+
})[];
|
|
219
|
+
};
|
|
220
|
+
})[];
|
|
221
|
+
} | {
|
|
222
|
+
$exists?: undefined;
|
|
223
|
+
patch?: undefined;
|
|
224
|
+
})[];
|
|
225
|
+
};
|
|
181
226
|
};
|
|
182
227
|
'calc/conv-from': {
|
|
183
228
|
patch: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/calc",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.67.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"prepack": "npm run build:types"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@jarenjs/core": "^0.
|
|
67
|
-
"@jarenjs/view": "^0.
|
|
68
|
-
"@jarenjs/forms": "^0.
|
|
69
|
-
"@jarenjs/app": "^0.
|
|
66
|
+
"@jarenjs/core": "^0.67.0",
|
|
67
|
+
"@jarenjs/view": "^0.67.0",
|
|
68
|
+
"@jarenjs/forms": "^0.67.0",
|
|
69
|
+
"@jarenjs/app": "^0.67.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/src/component/index.js
CHANGED
|
@@ -117,6 +117,12 @@ function keypadFor(mode) {
|
|
|
117
117
|
}));
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/** Static choices come from the converter's unit registry; currency
|
|
121
|
+
* choices are read from the current rate table by the action. */
|
|
122
|
+
const CONVERTER_UNIT_IDS = Object.fromEntries(converterDimensions()
|
|
123
|
+
.filter((dimension) => dimension !== 'currency')
|
|
124
|
+
.map((dimension) => [dimension, unitOptions(dimension).map((unit) => unit.id)]));
|
|
125
|
+
|
|
120
126
|
/**
|
|
121
127
|
* The action query documents. `dataPointer` for the financial form is
|
|
122
128
|
* `/calc/fin`.
|
|
@@ -152,7 +158,29 @@ export const calcActions = {
|
|
|
152
158
|
'calc/plot-expr': { patch: [{ op: 'replace', path: '/calc/plot/expr', value: '$event.value' }] },
|
|
153
159
|
'calc/plot-kind': { patch: [{ op: 'replace', path: '/calc/plot/kind', value: '$payload.kind' }] },
|
|
154
160
|
// converter
|
|
155
|
-
'calc/conv-dim': {
|
|
161
|
+
'calc/conv-dim': {
|
|
162
|
+
$let: { units: { $if: [
|
|
163
|
+
{ $eq: ['$event.value', 'currency'] },
|
|
164
|
+
[{ $for: { unit: { $entries: '$.calc.rates.rates' } }, $return: '$unit.key' }],
|
|
165
|
+
{ $get: [{ $const: CONVERTER_UNIT_IDS }, '$event.value'] },
|
|
166
|
+
] } },
|
|
167
|
+
// Change the dimension and its unit selections in one transition,
|
|
168
|
+
// retaining a selection only when the new dimension offers it.
|
|
169
|
+
$return: { $if: [
|
|
170
|
+
{ $exists: '$units[0]' },
|
|
171
|
+
{ patch: [
|
|
172
|
+
{ op: 'replace', path: '/calc/conv/dimension', value: '$event.value' },
|
|
173
|
+
{ op: 'replace', path: '/calc/conv/from', value: { $if: [
|
|
174
|
+
{ $eq: ['$units[*]', '$.calc.conv.from'] }, '$.calc.conv.from', '$units[0]',
|
|
175
|
+
] } },
|
|
176
|
+
{ op: 'replace', path: '/calc/conv/to', value: { $if: [
|
|
177
|
+
{ $eq: ['$units[*]', '$.calc.conv.to'] }, '$.calc.conv.to',
|
|
178
|
+
{ $default: ['$units[1]', '$units[0]'] },
|
|
179
|
+
] } },
|
|
180
|
+
] },
|
|
181
|
+
{},
|
|
182
|
+
] },
|
|
183
|
+
},
|
|
156
184
|
'calc/conv-from': { patch: [{ op: 'replace', path: '/calc/conv/from', value: '$event.value' }] },
|
|
157
185
|
'calc/conv-to': { patch: [{ op: 'replace', path: '/calc/conv/to', value: '$event.value' }] },
|
|
158
186
|
'calc/conv-value': { patch: [{ op: 'replace', path: '/calc/conv/value', value: { $number: '$event.value' } }] },
|
|
@@ -70,20 +70,40 @@ export function createRatesLayer(options = {}) {
|
|
|
70
70
|
const fallbackRates = options.fallbackRates ?? FALLBACK_RATES;
|
|
71
71
|
let lastAt = -Infinity;
|
|
72
72
|
let inflight = false;
|
|
73
|
+
let generation = 0;
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* Fetch once (debounced), dispatching `calc/rates-ok` or
|
|
76
|
-
* `calc/rates-err`. `props.force` bypasses the debounce
|
|
77
|
+
* `calc/rates-err`. `props.force` bypasses the debounce; only the
|
|
78
|
+
* newest request may publish a result or clear the in-flight flag.
|
|
77
79
|
*/
|
|
78
80
|
function fetchOnce(props, dispatch) {
|
|
79
81
|
const t = now();
|
|
80
82
|
if (!props?.force && (inflight || t - lastAt < refreshMs)) return;
|
|
81
83
|
inflight = true;
|
|
82
84
|
lastAt = t;
|
|
83
|
-
|
|
85
|
+
const request = ++generation;
|
|
86
|
+
let result;
|
|
87
|
+
try {
|
|
88
|
+
result = adapter(codes, { fetch: options.fetch, endpoint: options.endpoint, at: t });
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
// A provider may throw before returning its promise. It still
|
|
92
|
+
// settles through the failure path and releases the in-flight slot.
|
|
93
|
+
result = Promise.reject(err);
|
|
94
|
+
}
|
|
95
|
+
Promise.resolve(result)
|
|
84
96
|
.then(
|
|
85
|
-
(table) => {
|
|
86
|
-
|
|
97
|
+
(table) => {
|
|
98
|
+
if (request !== generation) return;
|
|
99
|
+
inflight = false;
|
|
100
|
+
dispatch('calc/rates-ok', { ...table, at: t });
|
|
101
|
+
},
|
|
102
|
+
(err) => {
|
|
103
|
+
if (request !== generation) return;
|
|
104
|
+
inflight = false;
|
|
105
|
+
dispatch('calc/rates-err', { message: String(err?.message ?? err) });
|
|
106
|
+
},
|
|
87
107
|
);
|
|
88
108
|
}
|
|
89
109
|
|