@jarenjs/calc 0.34.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 +60 -0
- package/dist/types/ast.d.ts +65 -0
- package/dist/types/compile.d.ts +52 -0
- package/dist/types/component/index.d.ts +259 -0
- package/dist/types/component/rates/binance.d.ts +34 -0
- package/dist/types/component/rates/coingecko.d.ts +47 -0
- package/dist/types/component/rates/index.d.ts +84 -0
- package/dist/types/component/rules.d.ts +180 -0
- package/dist/types/component/schema.d.ts +38 -0
- package/dist/types/env.d.ts +24 -0
- package/dist/types/errors.d.ts +18 -0
- package/dist/types/index.d.ts +42 -0
- package/dist/types/modes/converter.d.ts +46 -0
- package/dist/types/modes/financial.d.ts +59 -0
- package/dist/types/modes/index.d.ts +38 -0
- package/dist/types/modes/programmer.d.ts +50 -0
- package/dist/types/modes/scientific.d.ts +28 -0
- package/dist/types/modes/standard.d.ts +32 -0
- package/dist/types/parser/index.d.ts +32 -0
- package/dist/types/plot/plot2d.d.ts +70 -0
- package/dist/types/plot/plot3d.d.ts +68 -0
- package/dist/types/render/error.d.ts +19 -0
- package/dist/types/theme.d.ts +35 -0
- package/dist/types/to-expr.d.ts +13 -0
- package/dist/types/utils.d.ts +9 -0
- package/docs/CALC-FORMAT.md +79 -0
- package/package.json +71 -0
- package/schemas/financial-inputs.schema.json +15 -0
- package/schemas/jaren-calc-ast.schema.json +91 -0
- package/schemas/jaren-calc-state.schema.json +52 -0
- package/src/ast.js +96 -0
- package/src/compile.js +119 -0
- package/src/component/index.js +352 -0
- package/src/component/rates/binance.js +44 -0
- package/src/component/rates/coingecko.js +63 -0
- package/src/component/rates/index.js +116 -0
- package/src/component/rules.js +118 -0
- package/src/component/schema.js +24 -0
- package/src/env.js +163 -0
- package/src/errors.js +23 -0
- package/src/index.js +85 -0
- package/src/modes/converter.js +73 -0
- package/src/modes/financial.js +89 -0
- package/src/modes/index.js +24 -0
- package/src/modes/programmer.js +69 -0
- package/src/modes/scientific.js +31 -0
- package/src/modes/standard.js +39 -0
- package/src/parser/index.js +220 -0
- package/src/plot/plot2d.js +221 -0
- package/src/plot/plot3d.js +177 -0
- package/src/render/error.js +25 -0
- package/src/theme.js +76 -0
- package/src/to-expr.js +84 -0
- package/src/utils.js +11 -0
- package/styles/calc.css +128 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @jarenjs/calc
|
|
2
|
+
|
|
3
|
+
A native, headless **multi-mode calculator** for the jarenjs stack:
|
|
4
|
+
standard / scientific / programmer / financial / converter, with **x·y and
|
|
5
|
+
x·y·z plotting rendered as pure-vnode SVG** through `@jarenjs/view`. The
|
|
6
|
+
calculator *is* an `@jarenjs/app` document (state is JSON, keys are data
|
|
7
|
+
bindings, transitions are copy-on-write RFC-6902 patches), and it is built
|
|
8
|
+
on `@jarenjs/core` — it holds no formulas or unit factors of its own.
|
|
9
|
+
|
|
10
|
+
Zero runtime dependencies beyond `@jarenjs/*`. No `eval`, no `new
|
|
11
|
+
Function` (CSP-safe). SSR-able and deterministic.
|
|
12
|
+
|
|
13
|
+
## Two layers
|
|
14
|
+
|
|
15
|
+
- **Engine** (`@jarenjs/calc`) — pure functions over data. A two-stage
|
|
16
|
+
expression compiler `parseExpression(text) → ExprAST → compileExpr →
|
|
17
|
+
(scope) => value`, its canonical printer `toExpression` (a round-trip
|
|
18
|
+
fixed point), and `plot2d`/`plot3d`/`calcToVnode` (pure-vnode SVG).
|
|
19
|
+
Imports only `@jarenjs/core` + `@jarenjs/view`.
|
|
20
|
+
- **Component** (`@jarenjs/calc/component`) — the `@jarenjs/app` glue:
|
|
21
|
+
`createCalcComponent()` → `{ initialState, actions, mode, rules,
|
|
22
|
+
effects, subs, viewModel, createApp }`, the keypad/panel JSLT view, the
|
|
23
|
+
financial `@jarenjs/forms` panel, and the live crypto/fiat rates layer.
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { evaluate, parseExpression, toExpression, plot2d, toSvgString } from '@jarenjs/calc';
|
|
27
|
+
|
|
28
|
+
evaluate('sin(pi/2) + 2^10').value; // 1025
|
|
29
|
+
toExpression(parseExpression('a-(b-c)')); // "a - (b - c)"
|
|
30
|
+
toSvgString(plot2d('sin(x)', { domain: [-6.28, 6.28] })); // standalone SVG
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { createCalcComponent } from '@jarenjs/calc/component';
|
|
35
|
+
const calc = createCalcComponent({ provider: 'coingecko' });
|
|
36
|
+
const app = calc.createApp({ node: document.getElementById('calc') });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## The numeric kernel lives in `@jarenjs/core`
|
|
40
|
+
|
|
41
|
+
Reusable primitives were pushed *down* into core, so any package can use
|
|
42
|
+
them: `@jarenjs/core/math` (transcendentals, `word.js` BigInt word math,
|
|
43
|
+
`solve.js` root finders, `mat4`/`project.js` 3D kernel, `format.js`),
|
|
44
|
+
`@jarenjs/core/finance` (TVM, cash-flow, amortization, interest,
|
|
45
|
+
depreciation, bond, indicators, returns) and `@jarenjs/core/convert`
|
|
46
|
+
(fixed-factor dimensional conversion + the pure `convertCurrency` rate-table
|
|
47
|
+
primitive). The calculator's financial mode calls `core/finance`; its
|
|
48
|
+
converter calls `core/convert`. Core is pure and never fetches.
|
|
49
|
+
|
|
50
|
+
## Live currency (the maturity example)
|
|
51
|
+
|
|
52
|
+
The converter's currency dimension takes live crypto+fiat rates from free
|
|
53
|
+
public tickers (CoinGecko default, Binance alternative) through an
|
|
54
|
+
`@jarenjs/app` effect + a `when`-gated polling subscription. The **pure
|
|
55
|
+
conversion** is `@jarenjs/core/convert`'s `convertCurrency`; only the
|
|
56
|
+
*fetch* is component-side. A static fallback table keeps the converter,
|
|
57
|
+
SSR and offline tests working with no network; failures route to an error
|
|
58
|
+
action.
|
|
59
|
+
|
|
60
|
+
See `docs/CALC-FORMAT.md` and `ARCHITECTURE.md` for the full contract.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The expression AST. Monomorphic node
|
|
3
|
+
* constructors — one plain-object shape per kind — so the compiler and
|
|
4
|
+
* printer branch on a single `type` tag and the reconciler/`deepEqual`
|
|
5
|
+
* round-trip stays cheap. The AST is **geometry-free**: it carries no
|
|
6
|
+
* layout, no source formatting (not even a literal's original radix), so
|
|
7
|
+
* that `parseExpression(toExpression(ast))` deep-equals `ast`. Meaning is
|
|
8
|
+
* imposed later — by `compileExpr` (evaluation) or the plotter
|
|
9
|
+
* (projection), never by the parser.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CALC_AST_VERSION = "0.13.0";
|
|
12
|
+
/** Numeric literal (value only — the radix/format is not preserved). */
|
|
13
|
+
export declare function num(value: any): {
|
|
14
|
+
type: string;
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
/** A named constant (`pi`, `e`, `phi`, `tau`, `inf`, `nan`). */
|
|
18
|
+
export declare function constant(name: any): {
|
|
19
|
+
type: string;
|
|
20
|
+
name: any;
|
|
21
|
+
};
|
|
22
|
+
/** A variable / free identifier (`x`, `y`, `ans`, `mem`). */
|
|
23
|
+
export declare function variable(name: any): {
|
|
24
|
+
type: string;
|
|
25
|
+
name: any;
|
|
26
|
+
};
|
|
27
|
+
/** A prefix unary node (`-`, `+`, `~`). */
|
|
28
|
+
export declare function unary(op: any, arg: any): {
|
|
29
|
+
type: string;
|
|
30
|
+
op: any;
|
|
31
|
+
arg: any;
|
|
32
|
+
};
|
|
33
|
+
/** A postfix node (`!` factorial, `%` percent). */
|
|
34
|
+
export declare function postfix(op: any, arg: any): {
|
|
35
|
+
type: string;
|
|
36
|
+
op: any;
|
|
37
|
+
arg: any;
|
|
38
|
+
};
|
|
39
|
+
/** A binary node (`+ - * / ^ & | << >>`). */
|
|
40
|
+
export declare function binary(op: any, left: any, right: any): {
|
|
41
|
+
type: string;
|
|
42
|
+
op: any;
|
|
43
|
+
left: any;
|
|
44
|
+
right: any;
|
|
45
|
+
};
|
|
46
|
+
/** A function call (`sin(x)`, `log(2, 8)`, `xor(a, b)`). */
|
|
47
|
+
export declare function call(name: any, args: any): {
|
|
48
|
+
type: string;
|
|
49
|
+
name: any;
|
|
50
|
+
args: any;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Free identifiers that always denote a constant rather than a variable.
|
|
54
|
+
* @type {Record<string, number>}
|
|
55
|
+
*/
|
|
56
|
+
export declare const CONSTANTS: Record<string, number>;
|
|
57
|
+
/** Is `name` a known constant identifier? */
|
|
58
|
+
export declare function isConstant(name: any): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Structural equality for ASTs (used by the round-trip fixed-point
|
|
61
|
+
* tests). NaN compares equal to NaN so `nan` literals round-trip.
|
|
62
|
+
* @param {any} a @param {any} b
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export declare function astEqual(a: any, b: any): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `compileExpr(ast, opts) → (scope) => number`. The second stage of
|
|
3
|
+
* the two-stage compiler: every dispatch decision — which operator
|
|
4
|
+
* closure, which function, whether an identifier is a constant — is made
|
|
5
|
+
* once, here, and baked into a nested closure. Evaluation then does no
|
|
6
|
+
* lookups on the hot path. No `eval`, no `new Function` (CSP-safe).
|
|
7
|
+
*
|
|
8
|
+
* `evaluate(source, scope?, opts?)` is the error-safe front door: it
|
|
9
|
+
* parses, compiles and runs, returning a tagged `{ ok, value } | { ok:
|
|
10
|
+
* false, error }` result so the app/render path never throws.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Compile an AST against an environment.
|
|
14
|
+
* @param {any} ast
|
|
15
|
+
* @param {{ env?: any }} [opts]
|
|
16
|
+
* @returns {(scope?: any) => number}
|
|
17
|
+
*/
|
|
18
|
+
export declare function compileExpr(ast: any, opts?: {
|
|
19
|
+
env?: any;
|
|
20
|
+
}): (scope?: any) => number;
|
|
21
|
+
export type EvalOk = {
|
|
22
|
+
ok: true;
|
|
23
|
+
value: number;
|
|
24
|
+
};
|
|
25
|
+
export type EvalErr = {
|
|
26
|
+
ok: false;
|
|
27
|
+
error: {
|
|
28
|
+
message: string;
|
|
29
|
+
line?: number;
|
|
30
|
+
column?: number;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} EvalOk
|
|
35
|
+
* @property {true} ok
|
|
36
|
+
* @property {number} value
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {object} EvalErr
|
|
40
|
+
* @property {false} ok
|
|
41
|
+
* @property {{ message: string, line?: number, column?: number }} error
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Parse + compile + evaluate a source expression, error-safe.
|
|
45
|
+
* @param {string} source
|
|
46
|
+
* @param {any} [scope]
|
|
47
|
+
* @param {{ env?: any }} [opts]
|
|
48
|
+
* @returns {EvalOk | EvalErr}
|
|
49
|
+
*/
|
|
50
|
+
export declare function evaluate(source: string, scope?: any, opts?: {
|
|
51
|
+
env?: any;
|
|
52
|
+
}): EvalOk | EvalErr;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The Calculator COMPONENT — part two of the package. Everything
|
|
3
|
+
* here is presentation + app glue; the engine knows none of it. `createCalcComponent(options)` returns the
|
|
4
|
+
* pieces the site (and a standalone `createApp`) compose into one
|
|
5
|
+
* `@jarenjs/app` document:
|
|
6
|
+
*
|
|
7
|
+
* - `initialState()` — the plain-JSON `$.calc` slice (immutable, COW).
|
|
8
|
+
* - `actions` — key/mode/plot/converter action query documents + the
|
|
9
|
+
* financial `@jarenjs/forms` write actions.
|
|
10
|
+
* - `mode` + `rules` — the JSLT `calculator` view (keypad, display, tape,
|
|
11
|
+
* mode menu, plot panel, financial form, converter).
|
|
12
|
+
* - `effects` / `subs` / `subEntry` — the live-rates effect + `when`-gated
|
|
13
|
+
* poll; the pure conversion stays in `@jarenjs/core/convert`.
|
|
14
|
+
* - `viewModel(state)` (a.k.a. `contributeCalcViewModel`) — the derivation
|
|
15
|
+
* boundary: display string, live result, four-base views, plot vnode,
|
|
16
|
+
* forms model, converter options — none of it stored in state.
|
|
17
|
+
* - `createApp()` — a standalone app document for reuse outside the site.
|
|
18
|
+
*
|
|
19
|
+
* The boundary is one-way: the component imports the engine, never the
|
|
20
|
+
* reverse.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Namespaced form action names so the financial panel never collides with
|
|
24
|
+
* another `@jarenjs/forms` instance on the same page (e.g. the website's
|
|
25
|
+
* schema playground, which also spreads `createFormActions`).
|
|
26
|
+
*/
|
|
27
|
+
export declare const FORM_ACTIONS: {
|
|
28
|
+
input: string;
|
|
29
|
+
check: string;
|
|
30
|
+
number: string;
|
|
31
|
+
add: string;
|
|
32
|
+
remove: string;
|
|
33
|
+
};
|
|
34
|
+
/** The financial form's view rules (mode 'calculator'), reusable by the site. */
|
|
35
|
+
export declare const calcFormViewRules: any[];
|
|
36
|
+
/**
|
|
37
|
+
* The initial `$.calc` state slice.
|
|
38
|
+
* @returns {any}
|
|
39
|
+
*/
|
|
40
|
+
export declare function calcInitialState(): any;
|
|
41
|
+
/**
|
|
42
|
+
* The action query documents. `dataPointer` for the financial form is
|
|
43
|
+
* `/calc/fin`.
|
|
44
|
+
* @returns {Record<string, any>}
|
|
45
|
+
*/
|
|
46
|
+
export declare const calcActions: {
|
|
47
|
+
'calc/key': {
|
|
48
|
+
patch: {
|
|
49
|
+
op: string;
|
|
50
|
+
path: string;
|
|
51
|
+
value: {
|
|
52
|
+
$concat: string[];
|
|
53
|
+
};
|
|
54
|
+
}[];
|
|
55
|
+
};
|
|
56
|
+
'calc/clear': {
|
|
57
|
+
patch: {
|
|
58
|
+
op: string;
|
|
59
|
+
path: string;
|
|
60
|
+
value: string;
|
|
61
|
+
}[];
|
|
62
|
+
};
|
|
63
|
+
'calc/set-entry': {
|
|
64
|
+
patch: {
|
|
65
|
+
op: string;
|
|
66
|
+
path: string;
|
|
67
|
+
value: string;
|
|
68
|
+
}[];
|
|
69
|
+
};
|
|
70
|
+
'calc/back': {
|
|
71
|
+
effects: {
|
|
72
|
+
run: string;
|
|
73
|
+
with: {
|
|
74
|
+
entry: string;
|
|
75
|
+
};
|
|
76
|
+
}[];
|
|
77
|
+
};
|
|
78
|
+
'calc/equals': {
|
|
79
|
+
effects: {
|
|
80
|
+
run: string;
|
|
81
|
+
with: {
|
|
82
|
+
entry: string;
|
|
83
|
+
mode: string;
|
|
84
|
+
angleMode: string;
|
|
85
|
+
wordBits: string;
|
|
86
|
+
signed: string;
|
|
87
|
+
ans: string;
|
|
88
|
+
memory: string;
|
|
89
|
+
};
|
|
90
|
+
}[];
|
|
91
|
+
};
|
|
92
|
+
'calc/commit': {
|
|
93
|
+
patch: ({
|
|
94
|
+
op: string;
|
|
95
|
+
path: string;
|
|
96
|
+
value: string;
|
|
97
|
+
} | {
|
|
98
|
+
op: string;
|
|
99
|
+
path: string;
|
|
100
|
+
value: {
|
|
101
|
+
expr: string;
|
|
102
|
+
result: string;
|
|
103
|
+
};
|
|
104
|
+
})[];
|
|
105
|
+
};
|
|
106
|
+
'calc/mode': {
|
|
107
|
+
patch: {
|
|
108
|
+
op: string;
|
|
109
|
+
path: string;
|
|
110
|
+
value: string;
|
|
111
|
+
}[];
|
|
112
|
+
};
|
|
113
|
+
'calc/angle': {
|
|
114
|
+
patch: {
|
|
115
|
+
op: string;
|
|
116
|
+
path: string;
|
|
117
|
+
value: string;
|
|
118
|
+
}[];
|
|
119
|
+
};
|
|
120
|
+
'calc/base': {
|
|
121
|
+
patch: {
|
|
122
|
+
op: string;
|
|
123
|
+
path: string;
|
|
124
|
+
value: string;
|
|
125
|
+
}[];
|
|
126
|
+
};
|
|
127
|
+
'calc/word': {
|
|
128
|
+
patch: {
|
|
129
|
+
op: string;
|
|
130
|
+
path: string;
|
|
131
|
+
value: {
|
|
132
|
+
$number: string;
|
|
133
|
+
};
|
|
134
|
+
}[];
|
|
135
|
+
};
|
|
136
|
+
'calc/sign': {
|
|
137
|
+
patch: {
|
|
138
|
+
op: string;
|
|
139
|
+
path: string;
|
|
140
|
+
value: {
|
|
141
|
+
$if: (string | boolean)[];
|
|
142
|
+
};
|
|
143
|
+
}[];
|
|
144
|
+
};
|
|
145
|
+
'calc/mem-add': {
|
|
146
|
+
patch: {
|
|
147
|
+
op: string;
|
|
148
|
+
path: string;
|
|
149
|
+
value: {
|
|
150
|
+
$add: string[];
|
|
151
|
+
};
|
|
152
|
+
}[];
|
|
153
|
+
};
|
|
154
|
+
'calc/mem-clear': {
|
|
155
|
+
patch: {
|
|
156
|
+
op: string;
|
|
157
|
+
path: string;
|
|
158
|
+
value: number;
|
|
159
|
+
}[];
|
|
160
|
+
};
|
|
161
|
+
'calc/plot-expr': {
|
|
162
|
+
patch: {
|
|
163
|
+
op: string;
|
|
164
|
+
path: string;
|
|
165
|
+
value: string;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
168
|
+
'calc/plot-kind': {
|
|
169
|
+
patch: {
|
|
170
|
+
op: string;
|
|
171
|
+
path: string;
|
|
172
|
+
value: string;
|
|
173
|
+
}[];
|
|
174
|
+
};
|
|
175
|
+
'calc/conv-dim': {
|
|
176
|
+
patch: {
|
|
177
|
+
op: string;
|
|
178
|
+
path: string;
|
|
179
|
+
value: string;
|
|
180
|
+
}[];
|
|
181
|
+
};
|
|
182
|
+
'calc/conv-from': {
|
|
183
|
+
patch: {
|
|
184
|
+
op: string;
|
|
185
|
+
path: string;
|
|
186
|
+
value: string;
|
|
187
|
+
}[];
|
|
188
|
+
};
|
|
189
|
+
'calc/conv-to': {
|
|
190
|
+
patch: {
|
|
191
|
+
op: string;
|
|
192
|
+
path: string;
|
|
193
|
+
value: string;
|
|
194
|
+
}[];
|
|
195
|
+
};
|
|
196
|
+
'calc/conv-value': {
|
|
197
|
+
patch: {
|
|
198
|
+
op: string;
|
|
199
|
+
path: string;
|
|
200
|
+
value: {
|
|
201
|
+
$number: string;
|
|
202
|
+
};
|
|
203
|
+
}[];
|
|
204
|
+
};
|
|
205
|
+
'calc/conv-swap': {
|
|
206
|
+
patch: {
|
|
207
|
+
op: string;
|
|
208
|
+
path: string;
|
|
209
|
+
value: string;
|
|
210
|
+
}[];
|
|
211
|
+
};
|
|
212
|
+
'calc/rates-refresh': {
|
|
213
|
+
effects: {
|
|
214
|
+
run: string;
|
|
215
|
+
with: {
|
|
216
|
+
force: boolean;
|
|
217
|
+
};
|
|
218
|
+
}[];
|
|
219
|
+
};
|
|
220
|
+
'calc/rates-ok': {
|
|
221
|
+
patch: {
|
|
222
|
+
op: string;
|
|
223
|
+
path: string;
|
|
224
|
+
value: string;
|
|
225
|
+
}[];
|
|
226
|
+
};
|
|
227
|
+
'calc/rates-err': {
|
|
228
|
+
patch: {
|
|
229
|
+
op: string;
|
|
230
|
+
path: string;
|
|
231
|
+
value: string;
|
|
232
|
+
}[];
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
/** All calculator view rules (the calculator UI + the financial form). */
|
|
236
|
+
export declare const calcViewRules: any[];
|
|
237
|
+
export { createRatesLayer, FALLBACK_RATES, CURRENCY_CODES } from './rates/index.js';
|
|
238
|
+
/** The `calc-edit` (backspace) and `calc-eval` (=) JS effect handlers. */
|
|
239
|
+
export declare const calcEditEffects: {
|
|
240
|
+
'calc-edit': (props: any, dispatch: any) => void;
|
|
241
|
+
'calc-eval': (props: any, dispatch: any) => void;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* The viewModel derivation for the calculator (`contributeCalcViewModel`).
|
|
245
|
+
* Pure: state in, UI document out, no dispatching.
|
|
246
|
+
* @param {any} state
|
|
247
|
+
* @param {{ theme?: any }} [options] plot theme (name, overrides, or
|
|
248
|
+
* `'host'` to follow the embedding host's tokens)
|
|
249
|
+
* @returns {any}
|
|
250
|
+
*/
|
|
251
|
+
export declare function contributeCalcViewModel(state: any, options?: {
|
|
252
|
+
theme?: any;
|
|
253
|
+
}): any;
|
|
254
|
+
/**
|
|
255
|
+
* Create the calculator component.
|
|
256
|
+
* @param {import('./rates/index.js').RatesLayerOptions} [options]
|
|
257
|
+
* @returns {any}
|
|
258
|
+
*/
|
|
259
|
+
export declare function createCalcComponent(options?: import('./rates/index.js').RatesLayerOptions): any;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The Binance rate adapter (alternative provider). Reads
|
|
3
|
+
* `/api/v3/ticker/price` (crypto USDT pairs), no key. Same one-way rule:
|
|
4
|
+
* fetch + normalize only; conversion is core's `convertCurrency`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a Binance ticker list into `{ base, rates, at }`. Only
|
|
8
|
+
* `*USDT` pairs are used; the base is USDT (≈ USD, also mapped to USD 1).
|
|
9
|
+
* @param {Array<{symbol:string, price:string|number}>} data @param {number} at
|
|
10
|
+
* @returns {{ base: string, rates: Record<string, number>, at: number }}
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeBinance(data: Array<{
|
|
13
|
+
symbol: string;
|
|
14
|
+
price: string | number;
|
|
15
|
+
}>, at?: number): {
|
|
16
|
+
base: string;
|
|
17
|
+
rates: Record<string, number>;
|
|
18
|
+
at: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Fetch and normalize Binance rates.
|
|
22
|
+
* @param {string[]} codes
|
|
23
|
+
* @param {{ fetch?: typeof globalThis.fetch, endpoint?: string, at?: number }} [opts]
|
|
24
|
+
* @returns {Promise<{ base: string, rates: Record<string, number>, at: number }>}
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchBinance(codes: string[], opts?: {
|
|
27
|
+
fetch?: typeof globalThis.fetch;
|
|
28
|
+
endpoint?: string;
|
|
29
|
+
at?: number;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
base: string;
|
|
32
|
+
rates: Record<string, number>;
|
|
33
|
+
at: number;
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The CoinGecko rate adapter. The **only**
|
|
3
|
+
* network code path for the default provider — it fetches and normalizes,
|
|
4
|
+
* nothing more. The pure conversion is `@jarenjs/core/convert`'s
|
|
5
|
+
* `convertCurrency`, never here. No API key; fiat *and* crypto in one
|
|
6
|
+
* call, everything priced through a USD base. `fetch` is injectable for
|
|
7
|
+
* tests; `endpoint` is overridable.
|
|
8
|
+
*/
|
|
9
|
+
/** Known CoinGecko ids ↔ ticker symbols. */
|
|
10
|
+
export declare const COINGECKO_IDS: {
|
|
11
|
+
bitcoin: string;
|
|
12
|
+
ethereum: string;
|
|
13
|
+
tether: string;
|
|
14
|
+
binancecoin: string;
|
|
15
|
+
solana: string;
|
|
16
|
+
cardano: string;
|
|
17
|
+
ripple: string;
|
|
18
|
+
dogecoin: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a `/simple/price` response into the common
|
|
22
|
+
* `{ base, rates, at }` shape. Crypto prices are USD directly; fiat rates
|
|
23
|
+
* are derived through the bitcoin pivot (rate[FIAT] = btc_usd / btc_fiat
|
|
24
|
+
* = value of one FIAT unit in USD).
|
|
25
|
+
* @param {any} data @param {string[]} codes @param {number} at
|
|
26
|
+
* @returns {{ base: string, rates: Record<string, number>, at: number }}
|
|
27
|
+
*/
|
|
28
|
+
export declare function normalizeCoinGecko(data: any, codes: string[], at?: number): {
|
|
29
|
+
base: string;
|
|
30
|
+
rates: Record<string, number>;
|
|
31
|
+
at: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Fetch and normalize rates for `codes`.
|
|
35
|
+
* @param {string[]} codes
|
|
36
|
+
* @param {{ fetch?: typeof globalThis.fetch, endpoint?: string, at?: number }} [opts]
|
|
37
|
+
* @returns {Promise<{ base: string, rates: Record<string, number>, at: number }>}
|
|
38
|
+
*/
|
|
39
|
+
export declare function fetchCoinGecko(codes: string[], opts?: {
|
|
40
|
+
fetch?: typeof globalThis.fetch;
|
|
41
|
+
endpoint?: string;
|
|
42
|
+
at?: number;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
base: string;
|
|
45
|
+
rates: Record<string, number>;
|
|
46
|
+
at: number;
|
|
47
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The rates layer — the component's impure
|
|
3
|
+
* half. It owns the `rates-fetch` effect and the `rates-poll`
|
|
4
|
+
* subscription, choosing a provider adapter, debouncing to a min refresh
|
|
5
|
+
* interval, and routing success/failure to app actions. It normalizes a
|
|
6
|
+
* rate table into `$.calc.rates`; the **conversion itself** is
|
|
7
|
+
* `@jarenjs/core/convert`'s pure `convertCurrency`, never here.
|
|
8
|
+
*
|
|
9
|
+
* Resilience is the point: a static `FALLBACK_RATES` table keeps the
|
|
10
|
+
* converter, SSR and offline tests working with no network; live rates
|
|
11
|
+
* layer on top only when a fetch succeeds; failures route to an error
|
|
12
|
+
* action.
|
|
13
|
+
*/
|
|
14
|
+
export { fetchCoinGecko, normalizeCoinGecko, COINGECKO_IDS } from './coingecko.js';
|
|
15
|
+
export { fetchBinance, normalizeBinance } from './binance.js';
|
|
16
|
+
/** The currency codes the converter offers (fiat + crypto). */
|
|
17
|
+
export declare const CURRENCY_CODES: string[];
|
|
18
|
+
/**
|
|
19
|
+
* The static last-resort table (value of one unit in USD). Deterministic,
|
|
20
|
+
* so tests / SSR / offline all render a real conversion with no network.
|
|
21
|
+
* @type {{ base: string, rates: Record<string, number>, at: number, stale: boolean, status: string }}
|
|
22
|
+
*/
|
|
23
|
+
export declare const FALLBACK_RATES: {
|
|
24
|
+
base: string;
|
|
25
|
+
rates: Record<string, number>;
|
|
26
|
+
at: number;
|
|
27
|
+
stale: boolean;
|
|
28
|
+
status: string;
|
|
29
|
+
};
|
|
30
|
+
export type RatesLayerOptions = {
|
|
31
|
+
provider?: 'coingecko' | 'binance' | ((codes: string[], o: any) => Promise<any>);
|
|
32
|
+
/**
|
|
33
|
+
* minimum interval between live fetches (debounce)
|
|
34
|
+
*/
|
|
35
|
+
refreshMs?: number;
|
|
36
|
+
fetch?: typeof globalThis.fetch;
|
|
37
|
+
endpoint?: string;
|
|
38
|
+
codes?: string[];
|
|
39
|
+
/**
|
|
40
|
+
* clock (injectable for tests)
|
|
41
|
+
*/
|
|
42
|
+
now?: () => number;
|
|
43
|
+
fallbackRates?: any;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} RatesLayerOptions
|
|
47
|
+
* @property {'coingecko'|'binance'|((codes:string[],o:any)=>Promise<any>)} [provider]
|
|
48
|
+
* @property {number} [refreshMs] minimum interval between live fetches (debounce)
|
|
49
|
+
* @property {typeof globalThis.fetch} [fetch]
|
|
50
|
+
* @property {string} [endpoint]
|
|
51
|
+
* @property {string[]} [codes]
|
|
52
|
+
* @property {() => number} [now] clock (injectable for tests)
|
|
53
|
+
* @property {any} [fallbackRates]
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Build the rates layer: the `rates-fetch` effect handler, the
|
|
57
|
+
* `rates-poll` subscription handler, the subscription entry and the
|
|
58
|
+
* fallback table.
|
|
59
|
+
* @param {RatesLayerOptions} [options]
|
|
60
|
+
*/
|
|
61
|
+
export declare function createRatesLayer(options?: RatesLayerOptions): {
|
|
62
|
+
effects: {
|
|
63
|
+
/** `{ run: 'rates-fetch', with?: { force?: boolean } }`. */
|
|
64
|
+
'rates-fetch': (props: any, dispatch: any) => void;
|
|
65
|
+
};
|
|
66
|
+
subs: {
|
|
67
|
+
/**
|
|
68
|
+
* `rates-poll`: kicks an immediate fetch and repeats every
|
|
69
|
+
* `refreshMs`; the cleanup stops the timer. The app only starts it
|
|
70
|
+
* while the `when` gate holds (converter + currency).
|
|
71
|
+
*/
|
|
72
|
+
'rates-poll': (props: any, dispatch: any) => () => void;
|
|
73
|
+
};
|
|
74
|
+
subEntry: {
|
|
75
|
+
run: string;
|
|
76
|
+
when: {
|
|
77
|
+
$and: {
|
|
78
|
+
$eq: string[];
|
|
79
|
+
}[];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
fallbackRates: any;
|
|
83
|
+
codes: string[];
|
|
84
|
+
};
|