@idlekit/money 0.0.0-stage → 0.3.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/dist/engine/breakEternity.d.ts +4 -0
  4. package/dist/engine/breakEternity.js +24 -0
  5. package/dist/engine/breakEternity.js.map +1 -0
  6. package/dist/engine/breakInfinity.d.ts +13 -0
  7. package/dist/engine/breakInfinity.js +104 -0
  8. package/dist/engine/breakInfinity.js.map +1 -0
  9. package/dist/engine/types.d.ts +22 -0
  10. package/dist/engine/types.js +2 -0
  11. package/dist/engine/types.js.map +1 -0
  12. package/dist/index.d.ts +12 -0
  13. package/dist/index.js +13 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/money/types.d.ts +12 -0
  16. package/dist/money/types.js +2 -0
  17. package/dist/money/types.js.map +1 -0
  18. package/dist/notation/formatMoney.d.ts +10 -0
  19. package/dist/notation/formatMoney.js +40 -0
  20. package/dist/notation/formatMoney.js.map +1 -0
  21. package/dist/notation/parseMoney.d.ts +9 -0
  22. package/dist/notation/parseMoney.js +52 -0
  23. package/dist/notation/parseMoney.js.map +1 -0
  24. package/dist/notation/suffixer.d.ts +9 -0
  25. package/dist/notation/suffixer.js +53 -0
  26. package/dist/notation/suffixer.js.map +1 -0
  27. package/dist/notation/visibilityTracker.d.ts +17 -0
  28. package/dist/notation/visibilityTracker.js +24 -0
  29. package/dist/notation/visibilityTracker.js.map +1 -0
  30. package/dist/policy/emitter.d.ts +7 -0
  31. package/dist/policy/emitter.js +17 -0
  32. package/dist/policy/emitter.js.map +1 -0
  33. package/dist/policy/facts.d.ts +6 -0
  34. package/dist/policy/facts.js +7 -0
  35. package/dist/policy/facts.js.map +1 -0
  36. package/dist/policy/strategies/accumulate.d.ts +13 -0
  37. package/dist/policy/strategies/accumulate.js +67 -0
  38. package/dist/policy/strategies/accumulate.js.map +1 -0
  39. package/dist/policy/strategies/drop.d.ts +13 -0
  40. package/dist/policy/strategies/drop.js +49 -0
  41. package/dist/policy/strategies/drop.js.map +1 -0
  42. package/dist/policy/strategies/shared.d.ts +3 -0
  43. package/dist/policy/strategies/shared.js +15 -0
  44. package/dist/policy/strategies/shared.js.map +1 -0
  45. package/dist/policy/tickMoney.d.ts +12 -0
  46. package/dist/policy/tickMoney.js +36 -0
  47. package/dist/policy/tickMoney.js.map +1 -0
  48. package/dist/policy/types.d.ts +60 -0
  49. package/dist/policy/types.js +2 -0
  50. package/dist/policy/types.js.map +1 -0
  51. package/dist/serde/moneyState.d.ts +20 -0
  52. package/dist/serde/moneyState.js +30 -0
  53. package/dist/serde/moneyState.js.map +1 -0
  54. package/package.json +56 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 imjlk
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 CHANGED
@@ -1,3 +1,62 @@
1
- # Temporary Holding Version
1
+ # @idlekit/money
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Money primitives for idle and incremental game economies.
4
+ Includes numeric engines, notation, money state transitions, serialization, and visibility tracking.
5
+ Official support in v1: Bun `>=1.4.2` only. Node.js and browser runtimes are not part of the v1 compatibility contract.
6
+
7
+ ```bash
8
+ bun add @idlekit/money
9
+ ```
10
+
11
+ Requires Bun `>=1.4.2`.
12
+
13
+ Use this package when you need:
14
+
15
+ - `Engine<N>` adapters
16
+ - `Money` / `MoneyState`
17
+ - `tickMoney` with `drop` / `accumulate`
18
+ - money formatting, parsing, and serde
19
+
20
+ ## Quick example
21
+
22
+ <!-- snippet: snippets/readme/money-quick-example.ts -->
23
+ ```ts
24
+ import {
25
+ createBreakInfinityEngine,
26
+ deserializeMoneyState,
27
+ formatMoney,
28
+ serializeMoneyState,
29
+ tickMoney,
30
+ } from "@idlekit/money";
31
+
32
+ const E = createBreakInfinityEngine();
33
+ const unit = { code: "COIN" as const };
34
+
35
+ const result = tickMoney({
36
+ E,
37
+ state: {
38
+ money: { unit, amount: E.from("1e6") },
39
+ bucket: E.zero(),
40
+ },
41
+ delta: { unit, amount: E.from("25") },
42
+ policy: { mode: "accumulate", maxLogGap: 6 },
43
+ });
44
+
45
+ const saved = serializeMoneyState(E, result.state, { engineName: "break_infinity.js" });
46
+ const restored = deserializeMoneyState(E, saved);
47
+
48
+ console.log(formatMoney(E, restored.money));
49
+ ```
50
+
51
+ ## Runtime
52
+
53
+ `@idlekit/money` is maintained as a Bun-first ESM package.
54
+
55
+ `Engine.comparisonIsExact` optionally declares that `cmp` has no comparison tolerance. Number and break-infinity adapters report `true` with epsilon 0 and `false` with their default epsilon. Custom adapters may declare `true` only for strict comparisons; omission means unknown to analyses that require an exact goal boundary.
56
+
57
+ ## Documentation
58
+
59
+ - Repository: [github.com/imjlk/idlekit](https://github.com/imjlk/idlekit)
60
+ - Product roadmap: [docs/roadmap.md](https://github.com/imjlk/idlekit/blob/main/docs/roadmap.md)
61
+ - Usage guide: [docs/money-library.md](https://github.com/imjlk/idlekit/blob/main/docs/money-library.md)
62
+ - Example: [examples/money-package](https://github.com/imjlk/idlekit/tree/main/examples/money-package)
@@ -0,0 +1,4 @@
1
+ import type { Engine } from "./types";
2
+ export declare const BREAK_ETERNITY_EXPERIMENTAL_MESSAGE = "breakEternityEngine is experimental and not implemented in idlekit v1. Use breakInfinityEngine or provide a custom Engine adapter.";
3
+ export declare function createBreakEternityEngine(): Engine<never>;
4
+ export declare const breakEternityEngine: Engine<never>;
@@ -0,0 +1,24 @@
1
+ export const BREAK_ETERNITY_EXPERIMENTAL_MESSAGE = "breakEternityEngine is experimental and not implemented in idlekit v1. Use breakInfinityEngine or provide a custom Engine adapter.";
2
+ function notImplemented() {
3
+ throw new Error(BREAK_ETERNITY_EXPERIMENTAL_MESSAGE);
4
+ }
5
+ export function createBreakEternityEngine() {
6
+ return {
7
+ zero: notImplemented,
8
+ from: notImplemented,
9
+ add: notImplemented,
10
+ sub: notImplemented,
11
+ mul: notImplemented,
12
+ div: notImplemented,
13
+ mulN: notImplemented,
14
+ divN: notImplemented,
15
+ cmp: notImplemented,
16
+ absLog10: notImplemented,
17
+ isFinite: notImplemented,
18
+ toString: notImplemented,
19
+ toNumber: notImplemented,
20
+ };
21
+ }
22
+ // Experimental placeholder kept only to preserve the public export.
23
+ export const breakEternityEngine = createBreakEternityEngine();
24
+ //# sourceMappingURL=breakEternity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakEternity.js","sourceRoot":"","sources":["../../src/engine/breakEternity.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,oIAAoI,CAAC;AAEvI,SAAS,cAAc;IACrB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,cAAc;QACpB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,cAAc;QACnB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,cAAc;QACpB,GAAG,EAAE,cAAc;QACnB,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE,cAAc;KACzB,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,yBAAyB,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Engine } from "./types";
2
+ import Decimal from "break_infinity.js";
3
+ import type { DecimalSource } from "break_infinity.js";
4
+ export type NumberEngineOptions = Readonly<{
5
+ epsilon?: number;
6
+ }>;
7
+ export type BreakInfinityEngineOptions = Readonly<{
8
+ epsilon?: DecimalSource;
9
+ }>;
10
+ export declare function createNumberEngine(opts?: NumberEngineOptions): Engine<number>;
11
+ export declare function createBreakInfinityEngine(opts?: BreakInfinityEngineOptions): Engine<Decimal>;
12
+ export declare const breakInfinityEngine: Engine<Decimal>;
13
+ export { Decimal };
@@ -0,0 +1,104 @@
1
+ import Decimal from "break_infinity.js";
2
+ export function createNumberEngine(opts) {
3
+ const epsilon = opts?.epsilon ?? 1e-12;
4
+ return {
5
+ comparisonIsExact: epsilon === 0,
6
+ zero: () => 0,
7
+ from(input) {
8
+ if (typeof input === "number")
9
+ return input;
10
+ if (typeof input === "string")
11
+ return Number(input);
12
+ return input;
13
+ },
14
+ add: (a, b) => a + b,
15
+ sub: (a, b) => a - b,
16
+ mul: (a, k) => a * k,
17
+ div: (a, k) => a / k,
18
+ mulN: (a, b) => a * b,
19
+ divN: (a, b) => a / b,
20
+ cmp(a, b) {
21
+ const d = a - b;
22
+ if (Math.abs(d) <= epsilon)
23
+ return 0;
24
+ return d < 0 ? -1 : 1;
25
+ },
26
+ absLog10(a) {
27
+ const n = Math.abs(a);
28
+ if (n === 0)
29
+ return -Infinity;
30
+ return Math.log10(n);
31
+ },
32
+ isFinite: (a) => Number.isFinite(a),
33
+ toString: (a) => String(a),
34
+ toNumber: (a) => a,
35
+ };
36
+ }
37
+ function toDecimal(input) {
38
+ return input instanceof Decimal ? input : new Decimal(input);
39
+ }
40
+ function decimalIsFinite(value) {
41
+ return Number.isFinite(value.mantissa) && Number.isFinite(value.exponent);
42
+ }
43
+ /** `String(1e21)` is `"1e+21"`. Settlement text needs one plain decimal exponent. */
44
+ function plainExponent(exponent) {
45
+ if (!Number.isFinite(exponent))
46
+ return String(exponent);
47
+ const sign = exponent < 0 ? "-" : "";
48
+ const magnitude = Math.abs(exponent);
49
+ if (magnitude < 1e21)
50
+ return `${sign}${String(magnitude)}`;
51
+ const text = magnitude.toExponential();
52
+ const match = /^(\d+)(?:\.(\d+))?e\+(\d+)$/.exec(text);
53
+ if (!match?.[1] || !match[3])
54
+ return `${sign}${text}`;
55
+ const fraction = match[2] ?? "";
56
+ const zeros = Number(match[3]) - fraction.length;
57
+ if (!Number.isInteger(zeros) || zeros < 0)
58
+ return `${sign}${text}`;
59
+ return `${sign}${match[1]}${fraction}${"0".repeat(zeros)}`;
60
+ }
61
+ export function createBreakInfinityEngine(opts) {
62
+ const epsilon = toDecimal(opts?.epsilon ?? "1e-12");
63
+ return {
64
+ comparisonIsExact: epsilon.eq(0),
65
+ zero: () => new Decimal(0),
66
+ from(input) {
67
+ return toDecimal(input);
68
+ },
69
+ add: (a, b) => a.add(b),
70
+ sub: (a, b) => a.sub(b),
71
+ mul: (a, k) => a.mul(k),
72
+ div: (a, k) => a.div(k),
73
+ mulN: (a, b) => a.mul(b),
74
+ divN: (a, b) => a.div(b),
75
+ cmp(a, b) {
76
+ const d = a.sub(b);
77
+ if (d.abs().lte(epsilon))
78
+ return 0;
79
+ return d.lt(0) ? -1 : 1;
80
+ },
81
+ absLog10(a) {
82
+ if (a.eq(0))
83
+ return -Infinity;
84
+ return a.absLog10();
85
+ },
86
+ isFinite: decimalIsFinite,
87
+ toString(value) {
88
+ const text = value.toString();
89
+ const underflow = (text === "0" || text === "-0") && value.mantissa !== 0;
90
+ if (!underflow && text !== "Infinity" && text !== "-Infinity")
91
+ return text;
92
+ if (!decimalIsFinite(value))
93
+ return text;
94
+ const sign = value.mantissa < 0 ? "-" : "";
95
+ const digits = String(Math.abs(value.mantissa));
96
+ return `${sign}${digits}e${plainExponent(value.exponent)}`;
97
+ },
98
+ toNumber: (a) => a.toNumber(),
99
+ };
100
+ }
101
+ // Canonical break_infinity.js adapter.
102
+ export const breakInfinityEngine = createBreakInfinityEngine();
103
+ export { Decimal };
104
+ //# sourceMappingURL=breakInfinity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakInfinity.js","sourceRoot":"","sources":["../../src/engine/breakInfinity.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,mBAAmB,CAAC;AAWxC,MAAM,UAAU,kBAAkB,CAAC,IAA0B;IAC3D,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,KAAK,CAAC;IAEvC,OAAO;QACL,iBAAiB,EAAE,OAAO,KAAK,CAAC;QAChC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACb,IAAI,CAAC,KAAK;YACR,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;YACpD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACpB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACpB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACpB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACpB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACrB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;QACrB,GAAG,CAAC,CAAC,EAAE,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,OAAO;gBAAE,OAAO,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;QACD,QAAQ,CAAC,CAAC;YACR,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,QAAQ,CAAC;YAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QACD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,KAA8B;IAC/C,OAAO,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC5E,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,SAAS,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IAC3D,MAAM,IAAI,GAAG,SAAS,CAAC,aAAa,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;IACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;IACnE,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,IAAiC;IACzE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC;IAEpD,OAAO;QACL,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAChC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK;YACR,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,GAAG,CAAC,CAAC,EAAE,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;QACD,QAAQ,CAAC,CAAC;YACR,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,QAAQ,CAAC;YAC9B,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtB,CAAC;QACD,QAAQ,EAAE,eAAe;QACzB,QAAQ,CAAC,KAAK;YACZ,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAC;YAC1E,IAAI,CAAC,SAAS,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,WAAW;gBAAE,OAAO,IAAI,CAAC;YAC3E,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACzC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChD,OAAO,GAAG,IAAI,GAAG,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,CAAC;QACD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;KAC9B,CAAC;AACJ,CAAC;AAED,uCAAuC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,yBAAyB,EAAE,CAAC;AAE/D,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ export interface Engine<N> {
2
+ zero(): N;
3
+ from(input: number | string | N): N;
4
+ add(a: N, b: N): N;
5
+ sub(a: N, b: N): N;
6
+ mul(a: N, k: number): N;
7
+ div(a: N, k: number): N;
8
+ mulN(a: N, b: N): N;
9
+ divN(a: N, b: N): N;
10
+ cmp(a: N, b: N): -1 | 0 | 1;
11
+ /** True only when cmp has no comparison tolerance; omission means unknown. */
12
+ readonly comparisonIsExact?: boolean;
13
+ /**
14
+ * Exact order for values whose `toString` is not a bare decimal.
15
+ * Settlement rejects the comparison when this hook is absent.
16
+ */
17
+ exactOrder?(a: N, b: N): -1 | 0 | 1;
18
+ absLog10(a: N): number;
19
+ isFinite(a: N): boolean;
20
+ toString(a: N): string;
21
+ toNumber(a: N): number;
22
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ export * from "./engine/types";
2
+ export * from "./engine/breakInfinity";
3
+ export * from "./engine/breakEternity";
4
+ export * from "./money/types";
5
+ export * from "./policy/types";
6
+ export * from "./policy/emitter";
7
+ export * from "./policy/tickMoney";
8
+ export * from "./notation/suffixer";
9
+ export * from "./notation/formatMoney";
10
+ export * from "./notation/parseMoney";
11
+ export * from "./notation/visibilityTracker";
12
+ export * from "./serde/moneyState";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export * from "./engine/types";
2
+ export * from "./engine/breakInfinity";
3
+ export * from "./engine/breakEternity";
4
+ export * from "./money/types";
5
+ export * from "./policy/types";
6
+ export * from "./policy/emitter";
7
+ export * from "./policy/tickMoney";
8
+ export * from "./notation/suffixer";
9
+ export * from "./notation/formatMoney";
10
+ export * from "./notation/parseMoney";
11
+ export * from "./notation/visibilityTracker";
12
+ export * from "./serde/moneyState";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,eAAe,CAAC;AAE9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAE7C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type Unit<U extends string> = Readonly<{
2
+ code: U;
3
+ symbol?: string;
4
+ }>;
5
+ export type Money<N, U extends string> = Readonly<{
6
+ unit: Unit<U>;
7
+ amount: N;
8
+ }>;
9
+ export type MoneyState<N, U extends string> = Readonly<{
10
+ money: Money<N, U>;
11
+ bucket: N;
12
+ }>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/money/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { Engine } from "../engine/types";
2
+ import type { Money } from "../money/types";
3
+ import { type Suffixer } from "./suffixer";
4
+ export type FormatMoneyOptions = Readonly<{
5
+ suffix?: Suffixer;
6
+ significantDigits?: number;
7
+ showUnit?: boolean;
8
+ trimTrailingZeros?: boolean;
9
+ }>;
10
+ export declare function formatMoney<N, U extends string>(E: Engine<N>, money: Money<N, U>, opts?: FormatMoneyOptions): string;
@@ -0,0 +1,40 @@
1
+ import { alphaInfiniteSuffix } from "./suffixer";
2
+ function suffixAt(suffix, index) {
3
+ if (index <= 0)
4
+ return "";
5
+ if (!suffix)
6
+ return alphaInfiniteSuffix(index, 2);
7
+ if (suffix.kind === "table")
8
+ return suffix.table[index] ?? "";
9
+ return alphaInfiniteSuffix(index, suffix.minLen ?? 2);
10
+ }
11
+ function trimZeros(n) {
12
+ if (!n.includes("."))
13
+ return n;
14
+ return n.replace(/\.0+$/, "").replace(/(\.\d*?[1-9])0+$/, "$1");
15
+ }
16
+ export function formatMoney(E, money, opts) {
17
+ const digits = opts?.significantDigits ?? 3;
18
+ const showUnit = opts?.showUnit ?? true;
19
+ const trim = opts?.trimTrailingZeros ?? true;
20
+ const amount = money.amount;
21
+ const absLog10 = E.absLog10(amount);
22
+ const sign = E.cmp(amount, E.zero()) < 0 ? -1 : 1;
23
+ if (!Number.isFinite(absLog10)) {
24
+ const raw = E.toString(amount);
25
+ return showUnit ? `${raw} ${money.unit.code}` : raw;
26
+ }
27
+ const group = Math.max(0, Math.floor(absLog10 / 3));
28
+ const scale = E.from(`1e${group * 3}`);
29
+ const mantissa = E.toNumber(E.divN(amount, scale));
30
+ let left = Number.isFinite(mantissa)
31
+ ? Math.abs(mantissa).toPrecision(digits)
32
+ : Math.abs(E.toNumber(amount)).toPrecision(digits);
33
+ if (trim)
34
+ left = trimZeros(left);
35
+ const suffix = suffixAt(opts?.suffix, group);
36
+ const signed = sign < 0 && !left.startsWith("-") ? `-${left}` : left;
37
+ const raw = `${signed}${suffix}`;
38
+ return showUnit ? `${raw} ${money.unit.code}` : raw;
39
+ }
40
+ //# sourceMappingURL=formatMoney.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatMoney.js","sourceRoot":"","sources":["../../src/notation/formatMoney.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Engine } from "../engine/types";
2
+ import type { Money, Unit } from "../money/types";
3
+ import { type Suffixer } from "./suffixer";
4
+ export type ParseMoneyOptions<U extends string> = Readonly<{
5
+ suffix?: Suffixer;
6
+ unit?: Unit<U>;
7
+ allowUnitInString?: boolean;
8
+ }>;
9
+ export declare function parseMoney<N, U extends string>(E: Engine<N>, input: string, opts: ParseMoneyOptions<U>): Money<N, U>;
@@ -0,0 +1,52 @@
1
+ import { alphaInfiniteIndex } from "./suffixer";
2
+ function parseSuffixIndex(token, suffix) {
3
+ if (!token)
4
+ return 0;
5
+ if (!suffix) {
6
+ return alphaInfiniteIndex(token, 2);
7
+ }
8
+ if (suffix.kind === "table") {
9
+ const idx = suffix.table.findIndex((x) => x.toLowerCase() === token.toLowerCase());
10
+ return idx >= 0 ? idx : -1;
11
+ }
12
+ return alphaInfiniteIndex(token, suffix.minLen ?? 2);
13
+ }
14
+ export function parseMoney(E, input, opts) {
15
+ const text = input.trim();
16
+ const parts = text.split(/\s+/).filter(Boolean);
17
+ if (parts.length === 0 || parts.length > 2) {
18
+ throw new Error(`Invalid money string: ${input}`);
19
+ }
20
+ const left = parts[0];
21
+ if (!left)
22
+ throw new Error(`Invalid money string: ${input}`);
23
+ const leftMatch = left.match(/^([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)([A-Za-z]+)?$/i);
24
+ if (!leftMatch)
25
+ throw new Error(`Invalid money string: ${input}`);
26
+ const numeric = leftMatch[1];
27
+ if (!numeric)
28
+ throw new Error(`Invalid numeric part: ${input}`);
29
+ const suffixToken = leftMatch[2];
30
+ const inlineUnit = parts.length === 2 ? parts[1] : undefined;
31
+ if (inlineUnit && !opts.allowUnitInString) {
32
+ throw new Error("Unit in string is not allowed. Set allowUnitInString=true.");
33
+ }
34
+ const suffixIndex = parseSuffixIndex(suffixToken, opts.suffix);
35
+ if (suffixToken && suffixIndex < 0) {
36
+ throw new Error(`Unknown suffix: ${suffixToken}`);
37
+ }
38
+ const exp3 = suffixIndex * 3;
39
+ const scaled = exp3 > 0 ? E.mulN(E.from(numeric), E.from(`1e${exp3}`)) : E.from(numeric);
40
+ const unit = opts.unit ?? (inlineUnit ? { code: inlineUnit } : undefined);
41
+ if (!unit) {
42
+ throw new Error("Unit is required. Provide opts.unit or unit in input string.");
43
+ }
44
+ if (inlineUnit && unit.code !== inlineUnit) {
45
+ throw new Error(`Unit mismatch: expected ${unit.code}, got ${inlineUnit}`);
46
+ }
47
+ return {
48
+ unit,
49
+ amount: scaled,
50
+ };
51
+ }
52
+ //# sourceMappingURL=parseMoney.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseMoney.js","sourceRoot":"","sources":["../../src/notation/parseMoney.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type Suffixer = {
2
+ kind: "alphaInfinite";
3
+ minLen?: number;
4
+ } | {
5
+ kind: "table";
6
+ table: string[];
7
+ };
8
+ export declare function alphaInfiniteSuffix(index: number, minLen?: number): string;
9
+ export declare function alphaInfiniteIndex(label: string, minLen?: number): number;
@@ -0,0 +1,53 @@
1
+ function assertValidMinLen(minLen) {
2
+ if (!Number.isInteger(minLen) || minLen < 1) {
3
+ throw new Error(`alphaInfinite minLen must be an integer >= 1 (received: ${minLen})`);
4
+ }
5
+ }
6
+ function pow26(exp) {
7
+ return 26 ** exp;
8
+ }
9
+ function countBeforeLength(length, minLen) {
10
+ let total = 0;
11
+ for (let len = minLen; len < length; len++)
12
+ total += pow26(len);
13
+ return total;
14
+ }
15
+ // 1 -> aa, 26 -> az, 27 -> ba, 676 -> zz, 677 -> aaa (minLen=2)
16
+ export function alphaInfiniteSuffix(index, minLen = 2) {
17
+ assertValidMinLen(minLen);
18
+ if (!Number.isFinite(index) || index <= 0)
19
+ return "";
20
+ let remaining = Math.floor(index);
21
+ let length = minLen;
22
+ while (true) {
23
+ const count = pow26(length);
24
+ if (remaining <= count)
25
+ break;
26
+ remaining -= count;
27
+ length += 1;
28
+ }
29
+ let offset = remaining - 1;
30
+ const chars = new Array(length);
31
+ for (let pos = length - 1; pos >= 0; pos--) {
32
+ const digit = offset % 26;
33
+ chars[pos] = String.fromCharCode(97 + digit);
34
+ offset = Math.floor(offset / 26);
35
+ }
36
+ return chars.join("");
37
+ }
38
+ export function alphaInfiniteIndex(label, minLen = 2) {
39
+ assertValidMinLen(minLen);
40
+ const s = label.trim().toLowerCase();
41
+ if (s.length < minLen)
42
+ return -1;
43
+ let offset = 0;
44
+ for (const ch of s) {
45
+ const code = ch.charCodeAt(0);
46
+ if (code < 97 || code > 122)
47
+ return -1;
48
+ offset = offset * 26 + (code - 97);
49
+ }
50
+ const prior = countBeforeLength(s.length, minLen);
51
+ return prior + offset + 1;
52
+ }
53
+ //# sourceMappingURL=suffixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suffixer.js","sourceRoot":"","sources":["../../src/notation/suffixer.ts"],"names":[],"mappings":"AAUA,SAAS,iBAAiB,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,2DAA2D,MAAM,GAAG,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,GAAW;IACxB,OAAO,EAAE,IAAI,GAAG,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAc,EAAE,MAAc;IACvD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,GAAG,GAAG,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE,GAAG,EAAE;QAAE,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IAChE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,MAAM,GAAG,CAAC;IAC3D,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAErD,IAAI,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,MAAM,GAAG,MAAM,CAAC;IACpB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,SAAS,IAAI,KAAK;YAAE,MAAM;QAC9B,SAAS,IAAI,KAAK,CAAC;QACnB,MAAM,IAAI,CAAC,CAAC;IACd,CAAC;IAED,IAAI,MAAM,GAAG,SAAS,GAAG,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAS,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,GAAG,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,GAAG,EAAE,CAAC;QAC1B,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC;QAC7C,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAM,GAAG,CAAC;IAC1D,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IAEjC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,GAAG;YAAE,OAAO,CAAC,CAAC,CAAC;QACvC,MAAM,GAAG,MAAM,GAAG,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,OAAO,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Engine } from "../engine/types";
2
+ import type { Money } from "../money/types";
3
+ import { type FormatMoneyOptions } from "./formatMoney";
4
+ export type VisibilityChange = Readonly<{
5
+ changed: boolean;
6
+ current: string;
7
+ previous?: string;
8
+ }>;
9
+ export declare class VisibilityTracker<N, U extends string> {
10
+ #private;
11
+ private readonly E;
12
+ private readonly opts?;
13
+ constructor(E: Engine<N>, opts?: FormatMoneyOptions | undefined);
14
+ observe(money: Money<N, U>): VisibilityChange;
15
+ current(): string | undefined;
16
+ reset(): void;
17
+ }
@@ -0,0 +1,24 @@
1
+ import { formatMoney } from "./formatMoney";
2
+ export class VisibilityTracker {
3
+ E;
4
+ opts;
5
+ #previous;
6
+ constructor(E, opts) {
7
+ this.E = E;
8
+ this.opts = opts;
9
+ }
10
+ observe(money) {
11
+ const current = formatMoney(this.E, money, this.opts);
12
+ const previous = this.#previous;
13
+ const changed = previous !== undefined && previous !== current;
14
+ this.#previous = current;
15
+ return { changed, current, previous };
16
+ }
17
+ current() {
18
+ return this.#previous;
19
+ }
20
+ reset() {
21
+ this.#previous = undefined;
22
+ }
23
+ }
24
+ //# sourceMappingURL=visibilityTracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visibilityTracker.js","sourceRoot":"","sources":["../../src/notation/visibilityTracker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAA2B,MAAM,eAAe,CAAC;AAQrE,MAAM,OAAO,iBAAiB;IAIT,CAAC;IACD,IAAI;IAJvB,SAAS,CAAU;IAEnB,YACmB,CAAY,EACZ,IAAyB;iBADzB,CAAC;oBACD,IAAI;IACpB,CAAC;IAEJ,OAAO,CAAC,KAAkB;QACxB,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,OAAO,CAAC;QAC/D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export type Emitter<E> = (events: readonly E[]) => void;
2
+ export declare function onType<E extends {
3
+ type: string;
4
+ }, T extends E["type"]>(type: T, handler: (e: Extract<E, {
5
+ type: T;
6
+ }>) => void): (events: readonly E[]) => void;
7
+ export declare function composeEmitters<E>(...emitters: Array<((events: readonly E[]) => void) | undefined>): (events: readonly E[]) => void;
@@ -0,0 +1,17 @@
1
+ export function onType(type, handler) {
2
+ return (events) => {
3
+ for (const e of events) {
4
+ if (e.type === type) {
5
+ handler(e);
6
+ }
7
+ }
8
+ };
9
+ }
10
+ export function composeEmitters(...emitters) {
11
+ const list = emitters.filter(Boolean);
12
+ return (events) => {
13
+ for (const em of list)
14
+ em(events);
15
+ };
16
+ }
17
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/policy/emitter.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,MAAM,CACpB,IAAO,EACP,OAA6C;IAE7C,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACpB,OAAO,CAAC,CAA4B,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAAG,QAA6D;IAEhE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAA0C,CAAC;IAC/E,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,KAAK,MAAM,EAAE,IAAI,IAAI;YAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { TickFactCounts } from "./types";
2
+ export declare const APPLIED_FACTS: TickFactCounts;
3
+ export declare const DROPPED_FACTS: TickFactCounts;
4
+ export declare const QUEUED_FACTS: TickFactCounts;
5
+ export declare const FLUSHED_FACTS: TickFactCounts;
6
+ export declare const BLOCKED_FACTS: TickFactCounts;
@@ -0,0 +1,7 @@
1
+ // Counts have no amounts or run identity and can be shared without per-tick allocation.
2
+ export const APPLIED_FACTS = Object.freeze({ applied: 1, dropped: 0, queued: 0, flushed: 0, blocked: 0 });
3
+ export const DROPPED_FACTS = Object.freeze({ applied: 0, dropped: 1, queued: 0, flushed: 0, blocked: 0 });
4
+ export const QUEUED_FACTS = Object.freeze({ applied: 0, dropped: 0, queued: 1, flushed: 0, blocked: 0 });
5
+ export const FLUSHED_FACTS = Object.freeze({ applied: 1, dropped: 0, queued: 0, flushed: 1, blocked: 0 });
6
+ export const BLOCKED_FACTS = Object.freeze({ applied: 0, dropped: 0, queued: 0, flushed: 0, blocked: 1 });
7
+ //# sourceMappingURL=facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facts.js","sourceRoot":"","sources":["../../src/policy/facts.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;AAC1H,MAAM,CAAC,MAAM,aAAa,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;AAC1H,MAAM,CAAC,MAAM,YAAY,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;AACzH,MAAM,CAAC,MAAM,aAAa,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;AAC1H,MAAM,CAAC,MAAM,aAAa,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Engine } from "../../engine/types";
2
+ import type { Money, MoneyState } from "../../money/types";
3
+ import type { Emitter } from "../emitter";
4
+ import type { MoneyEvent, TickPolicy, TickResult } from "../types";
5
+ export declare function applyAccumulatePolicy<N, U extends string>(args: {
6
+ E: Engine<N>;
7
+ state: MoneyState<N, U>;
8
+ delta: Money<N, any>;
9
+ policy: TickPolicy;
10
+ emit?: Emitter<MoneyEvent<N>>;
11
+ collectEvents: boolean;
12
+ collectFacts?: boolean;
13
+ }): TickResult<N, U>;
@@ -0,0 +1,67 @@
1
+ import { computeLogGap, isTooSmall } from "./shared";
2
+ import { APPLIED_FACTS, FLUSHED_FACTS, QUEUED_FACTS } from "../facts";
3
+ export function applyAccumulatePolicy(args) {
4
+ const { E, state, delta, policy, emit, collectEvents, collectFacts } = args;
5
+ const events = [];
6
+ const baseBefore = state.money.amount;
7
+ const bucketed = E.add(state.bucket, delta.amount);
8
+ const logGap = computeLogGap(E, baseBefore, bucketed);
9
+ if (isTooSmall(E, baseBefore, bucketed, policy.maxLogGap, logGap)) {
10
+ const nextState = {
11
+ money: state.money,
12
+ bucket: bucketed,
13
+ };
14
+ if (collectEvents) {
15
+ events.push({
16
+ type: "queued",
17
+ base: baseBefore,
18
+ delta: delta.amount,
19
+ bucketAfter: bucketed,
20
+ logGap,
21
+ reason: "tooSmall",
22
+ });
23
+ }
24
+ const result = collectFacts
25
+ ? { status: "ok", state: nextState, events, facts: QUEUED_FACTS }
26
+ : { status: "ok", state: nextState, events };
27
+ if (collectEvents && emit)
28
+ emit(events);
29
+ return result;
30
+ }
31
+ const baseAfter = E.add(baseBefore, bucketed);
32
+ const nextState = {
33
+ money: {
34
+ unit: state.money.unit,
35
+ amount: baseAfter,
36
+ },
37
+ bucket: E.zero(),
38
+ };
39
+ const flushed = (collectEvents || collectFacts) && E.cmp(state.bucket, E.zero()) !== 0;
40
+ if (collectEvents) {
41
+ if (flushed) {
42
+ events.push({
43
+ type: "flushed",
44
+ baseBefore,
45
+ baseAfter,
46
+ bucketFlushed: state.bucket,
47
+ reason: "becameSignificant",
48
+ });
49
+ }
50
+ events.push({
51
+ type: "applied",
52
+ baseBefore,
53
+ baseAfter,
54
+ delta: bucketed,
55
+ logGap: Number.isFinite(logGap) ? logGap : undefined,
56
+ });
57
+ }
58
+ const result = collectFacts
59
+ ? (flushed
60
+ ? { status: "ok", state: nextState, events, facts: FLUSHED_FACTS, appliedDelta: bucketed, flushedBucket: state.bucket }
61
+ : { status: "ok", state: nextState, events, facts: APPLIED_FACTS, appliedDelta: bucketed })
62
+ : { status: "ok", state: nextState, events };
63
+ if (collectEvents && emit)
64
+ emit(events);
65
+ return result;
66
+ }
67
+ //# sourceMappingURL=accumulate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accumulate.js","sourceRoot":"","sources":["../../../src/policy/strategies/accumulate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Engine } from "../../engine/types";
2
+ import type { Money, MoneyState } from "../../money/types";
3
+ import type { Emitter } from "../emitter";
4
+ import type { MoneyEvent, TickPolicy, TickResult } from "../types";
5
+ export declare function applyDropPolicy<N, U extends string>(args: {
6
+ E: Engine<N>;
7
+ state: MoneyState<N, U>;
8
+ delta: Money<N, any>;
9
+ policy: TickPolicy;
10
+ emit?: Emitter<MoneyEvent<N>>;
11
+ collectEvents: boolean;
12
+ collectFacts?: boolean;
13
+ }): TickResult<N, U>;
@@ -0,0 +1,49 @@
1
+ import { computeLogGap, isTooSmall } from "./shared";
2
+ import { APPLIED_FACTS, DROPPED_FACTS } from "../facts";
3
+ export function applyDropPolicy(args) {
4
+ const { E, state, delta, policy, emit, collectEvents, collectFacts } = args;
5
+ const events = [];
6
+ const baseBefore = state.money.amount;
7
+ const logGap = computeLogGap(E, baseBefore, delta.amount);
8
+ if (isTooSmall(E, baseBefore, delta.amount, policy.maxLogGap, logGap)) {
9
+ if (collectEvents) {
10
+ events.push({
11
+ type: "dropped",
12
+ base: baseBefore,
13
+ delta: delta.amount,
14
+ logGap,
15
+ reason: "tooSmall",
16
+ });
17
+ }
18
+ const result = collectFacts
19
+ ? { status: "ok", state, events, facts: DROPPED_FACTS }
20
+ : { status: "ok", state, events };
21
+ if (collectEvents && emit)
22
+ emit(events);
23
+ return result;
24
+ }
25
+ const baseAfter = E.add(baseBefore, delta.amount);
26
+ const nextState = {
27
+ money: {
28
+ unit: state.money.unit,
29
+ amount: baseAfter,
30
+ },
31
+ bucket: E.zero(),
32
+ };
33
+ if (collectEvents) {
34
+ events.push({
35
+ type: "applied",
36
+ baseBefore,
37
+ baseAfter,
38
+ delta: delta.amount,
39
+ logGap: Number.isFinite(logGap) ? logGap : undefined,
40
+ });
41
+ }
42
+ const result = collectFacts
43
+ ? { status: "ok", state: nextState, events, facts: APPLIED_FACTS, appliedDelta: delta.amount }
44
+ : { status: "ok", state: nextState, events };
45
+ if (collectEvents && emit)
46
+ emit(events);
47
+ return result;
48
+ }
49
+ //# sourceMappingURL=drop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drop.js","sourceRoot":"","sources":["../../../src/policy/strategies/drop.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,UAAU,eAAe,CAAsB,IAQpD;IACC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC5E,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IACtC,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE1D,IAAI,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;QACtE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,KAAK,CAAC,MAAM;gBACnB,MAAM;gBACN,MAAM,EAAE,UAAU;aACnB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAqB,YAAY;YAC3C,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE;YACvD,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACpC,IAAI,aAAa,IAAI,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAqB;QAClC,KAAK,EAAE;YACL,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI;YACtB,MAAM,EAAE,SAAS;SAClB;QACD,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE;KACjB,CAAC;IAEF,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,UAAU;YACV,SAAS;YACT,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;SACrD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAqB,YAAY;QAC3C,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE;QAC9F,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC/C,IAAI,aAAa,IAAI,IAAI;QAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "../../engine/types";
2
+ export declare function computeLogGap<N>(E: Engine<N>, base: N, delta: N): number;
3
+ export declare function isTooSmall<N>(E: Engine<N>, base: N, delta: N, maxLogGap?: number, knownGap?: number): boolean;
@@ -0,0 +1,15 @@
1
+ export function computeLogGap(E, base, delta) {
2
+ const zero = E.zero();
3
+ if (E.cmp(base, zero) <= 0)
4
+ return -Infinity;
5
+ if (E.cmp(delta, zero) === 0)
6
+ return Infinity;
7
+ return E.absLog10(base) - E.absLog10(delta);
8
+ }
9
+ export function isTooSmall(E, base, delta, maxLogGap, knownGap) {
10
+ if (maxLogGap === undefined)
11
+ return false;
12
+ const gap = knownGap ?? computeLogGap(E, base, delta);
13
+ return Number.isFinite(gap) && gap > maxLogGap;
14
+ }
15
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/policy/strategies/shared.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAI,CAAY,EAAE,IAAO,EAAE,KAAQ;IAC9D,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,QAAQ,CAAC;IAC7C,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC9C,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,UAAU,CAAI,CAAY,EAAE,IAAO,EAAE,KAAQ,EAAE,SAAkB,EAAE,QAAiB;IAClG,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,GAAG,GAAG,QAAQ,IAAI,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,SAAS,CAAC;AACjD,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Engine } from "../engine/types";
2
+ import type { Money, MoneyState } from "../money/types";
3
+ import type { CoreOptions, MoneyEvent, TickPolicy, TickResult } from "./types";
4
+ import type { Emitter } from "./emitter";
5
+ export declare function tickMoney<N, U extends string>(args: {
6
+ E: Engine<N>;
7
+ state: MoneyState<N, U>;
8
+ delta: Money<N, any>;
9
+ policy: TickPolicy;
10
+ emit?: Emitter<MoneyEvent<N>>;
11
+ options?: CoreOptions;
12
+ }): TickResult<N, U>;
@@ -0,0 +1,36 @@
1
+ import { applyAccumulatePolicy } from "./strategies/accumulate";
2
+ import { applyDropPolicy } from "./strategies/drop";
3
+ import { BLOCKED_FACTS } from "./facts";
4
+ export function tickMoney(args) {
5
+ const { E, state, delta, policy, emit } = args;
6
+ const collectEvents = args.options?.collectEvents ?? true;
7
+ const collectFacts = args.options?.collectFacts ?? false;
8
+ const events = [];
9
+ if (state.money.unit.code !== delta.unit.code) {
10
+ if (collectEvents) {
11
+ events.push({
12
+ type: "blocked",
13
+ reason: "unitMismatch",
14
+ baseUnit: state.money.unit.code,
15
+ deltaUnit: delta.unit.code,
16
+ });
17
+ }
18
+ const result = collectFacts ? {
19
+ status: "blocked",
20
+ state,
21
+ events,
22
+ facts: BLOCKED_FACTS,
23
+ } : {
24
+ status: "blocked",
25
+ state,
26
+ events,
27
+ };
28
+ if (collectEvents && emit)
29
+ emit(events);
30
+ return result;
31
+ }
32
+ return policy.mode === "drop"
33
+ ? applyDropPolicy({ E, state, delta, policy, emit, collectEvents, collectFacts })
34
+ : applyAccumulatePolicy({ E, state, delta, policy, emit, collectEvents, collectFacts });
35
+ }
36
+ //# sourceMappingURL=tickMoney.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tickMoney.js","sourceRoot":"","sources":["../../src/policy/tickMoney.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,UAAU,SAAS,CAAsB,IAO9C;IACC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;IAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,KAAK,CAAC;IACzD,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,cAAc;gBACtB,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI;gBAC/B,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI;aAC3B,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAqB,YAAY,CAAC,CAAC,CAAC;YAC9C,MAAM,EAAE,SAAS;YACjB,KAAK;YACL,MAAM;YACN,KAAK,EAAE,aAAa;SACrB,CAAC,CAAC,CAAC;YACF,MAAM,EAAE,SAAS;YACjB,KAAK;YACL,MAAM;SACP,CAAC;QAEF,IAAI,aAAa,IAAI,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM;QAC3B,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;QACjF,CAAC,CAAC,qBAAqB,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;AAC5F,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { MoneyState } from "../money/types";
2
+ export type TickPolicy = Readonly<{
3
+ mode: "drop" | "accumulate";
4
+ maxLogGap?: number;
5
+ }>;
6
+ export type CoreOptions = Readonly<{
7
+ collectEvents?: boolean;
8
+ /** Compact event counts and reward amounts, independent of event retention. */
9
+ collectFacts?: boolean;
10
+ }>;
11
+ export type MoneyEvent<N> = {
12
+ type: "blocked";
13
+ reason: "unitMismatch";
14
+ baseUnit: string;
15
+ deltaUnit: string;
16
+ } | {
17
+ type: "applied";
18
+ baseBefore: N;
19
+ baseAfter: N;
20
+ delta: N;
21
+ logGap?: number;
22
+ } | {
23
+ type: "dropped";
24
+ base: N;
25
+ delta: N;
26
+ logGap: number;
27
+ reason: "tooSmall";
28
+ } | {
29
+ type: "queued";
30
+ base: N;
31
+ delta: N;
32
+ bucketAfter: N;
33
+ logGap: number;
34
+ reason: "tooSmall";
35
+ } | {
36
+ type: "flushed";
37
+ baseBefore: N;
38
+ baseAfter: N;
39
+ bucketFlushed: N;
40
+ reason: "becameSignificant";
41
+ };
42
+ export type TickStatus = "ok" | "blocked";
43
+ export type TickFactCounts = Readonly<{
44
+ applied: number;
45
+ dropped: number;
46
+ queued: number;
47
+ flushed: number;
48
+ blocked: number;
49
+ }>;
50
+ export type TickResult<N, U extends string> = Readonly<{
51
+ status: TickStatus;
52
+ state: MoneyState<N, U>;
53
+ events: readonly MoneyEvent<N>[];
54
+ /** Present only when collectFacts is requested. Counts match the full event stream. */
55
+ facts?: TickFactCounts;
56
+ /** The applied event's delta, including queued money in accumulate mode. */
57
+ appliedDelta?: N;
58
+ /** The flushed event's prior bucket, when that event occurs. */
59
+ flushedBucket?: N;
60
+ }>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/policy/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { Engine } from "../engine/types";
2
+ import type { MoneyState, Unit } from "../money/types";
3
+ export type MoneyStateJSON = Readonly<{
4
+ v: 1;
5
+ unit: string;
6
+ amount: string;
7
+ bucket: string;
8
+ engine?: Readonly<{
9
+ name: string;
10
+ version?: string;
11
+ }>;
12
+ }>;
13
+ export declare function serializeMoneyState<N, U extends string>(E: Engine<N>, state: MoneyState<N, U>, meta?: {
14
+ engineName?: string;
15
+ engineVersion?: string;
16
+ }): MoneyStateJSON;
17
+ export declare function deserializeMoneyState<N, U extends string>(E: Engine<N>, json: MoneyStateJSON, opts?: {
18
+ unitFactory?: (code: string) => Unit<U>;
19
+ allowFutureVersions?: boolean;
20
+ }): MoneyState<N, U>;
@@ -0,0 +1,30 @@
1
+ export function serializeMoneyState(E, state, meta) {
2
+ return {
3
+ v: 1,
4
+ unit: state.money.unit.code,
5
+ amount: E.toString(state.money.amount),
6
+ bucket: E.toString(state.bucket),
7
+ engine: meta?.engineName
8
+ ? {
9
+ name: meta.engineName,
10
+ version: meta.engineVersion,
11
+ }
12
+ : undefined,
13
+ };
14
+ }
15
+ export function deserializeMoneyState(E, json, opts) {
16
+ if (json.v !== 1 && !opts?.allowFutureVersions) {
17
+ throw new Error(`Unsupported money state version: ${json.v}`);
18
+ }
19
+ const unit = opts?.unitFactory
20
+ ? opts.unitFactory(json.unit)
21
+ : { code: json.unit };
22
+ return {
23
+ money: {
24
+ unit,
25
+ amount: E.from(json.amount),
26
+ },
27
+ bucket: E.from(json.bucket),
28
+ };
29
+ }
30
+ //# sourceMappingURL=moneyState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moneyState.js","sourceRoot":"","sources":["../../src/serde/moneyState.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,mBAAmB,CACjC,CAAY,EACZ,KAAuB,EACvB,IAAsD;IAEtD,OAAO;QACL,CAAC,EAAE,CAAC;QACJ,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI;QAC3B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QACtC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAChC,MAAM,EAAE,IAAI,EAAE,UAAU;YACtB,CAAC,CAAC;gBACE,IAAI,EAAE,IAAI,CAAC,UAAU;gBACrB,OAAO,EAAE,IAAI,CAAC,aAAa;aAC5B;YACH,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,CAAY,EACZ,IAAoB,EACpB,IAGC;IAED,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,EAAE,WAAW;QAC5B,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7B,CAAC,CAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAS,EAAqB,CAAC;IAEjD,OAAO;QACL,KAAK,EAAE;YACL,IAAI;YACJ,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;SAC5B;QACD,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;KAC5B,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@idlekit/money",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Bun-first money primitives, notation, policies, and serialization for idle game economies.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "bun": "./dist/index.js",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/imjlk/idlekit.git",
27
+ "directory": "packages/money"
28
+ },
29
+ "homepage": "https://github.com/imjlk/idlekit/tree/main/packages/money#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/imjlk/idlekit/issues"
32
+ },
33
+ "keywords": [
34
+ "bun",
35
+ "idle-game",
36
+ "incremental-game",
37
+ "money",
38
+ "economy",
39
+ "currency",
40
+ "big-number"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "engines": {
46
+ "bun": ">=1.4.2"
47
+ },
48
+ "scripts": {
49
+ "build": "rm -rf dist && ttsc -p tsconfig.build.json",
50
+ "typecheck": "ttsc -p tsconfig.json --noEmit",
51
+ "test": "bun test src",
52
+ "prepack": "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/money",
53
+ "postpack": "cd ../.. && bun tools/package-manifest.ts restore packages/money"
54
+ },
55
+ "dependencies": {
56
+ "break_infinity.js": "^2.2.0"
57
+ }
58
+ }