rune-lab 0.3.0 → 0.4.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 +1 -1
- package/dist/core/design-tokens/props.d.ts +52 -0
- package/dist/core/design-tokens/props.d.ts.map +1 -0
- package/dist/core/design-tokens/props.js +34 -0
- package/dist/core/exchange-rate/strategies.d.ts +52 -0
- package/dist/core/exchange-rate/strategies.d.ts.map +1 -0
- package/dist/core/exchange-rate/strategies.js +72 -0
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -3
- package/dist/core/internal/message-resolver.d.ts +1 -1
- package/dist/core/internal/message-resolver.d.ts.map +1 -1
- package/dist/core/layout/types.d.ts +60 -0
- package/dist/core/layout/types.d.ts.map +1 -0
- package/dist/core/layout/types.js +4 -0
- package/dist/core/money/index.d.ts +1 -1
- package/dist/core/money/index.d.ts.map +1 -1
- package/dist/core/money/index.js +1 -1
- package/dist/core/money/money-primitive.d.ts +101 -0
- package/dist/core/money/money-primitive.d.ts.map +1 -0
- package/dist/core/money/money-primitive.js +161 -0
- package/dist/core/money/money.d.ts +74 -2
- package/dist/core/money/money.d.ts.map +1 -1
- package/dist/core/money/money.js +120 -2
- package/dist/core/shortcuts/types.d.ts +60 -0
- package/dist/core/shortcuts/types.d.ts.map +1 -0
- package/dist/core/shortcuts/types.js +4 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +6 -4
- package/dist/state/api.svelte.js +2 -2
- package/dist/state/app.svelte.js +1 -1
- package/dist/state/auth/index.d.ts +2 -2
- package/dist/state/auth/index.js +1 -1
- package/dist/state/auth/session.svelte.d.ts +1 -1
- package/dist/state/auth/session.svelte.js +7 -5
- package/dist/state/auth/types.d.ts +1 -1
- package/dist/state/cart.svelte.d.ts +1 -1
- package/dist/state/cart.svelte.js +1 -1
- package/dist/state/commands.svelte.d.ts +7 -7
- package/dist/state/commands.svelte.js +1 -1
- package/dist/state/composables/useMoney.d.ts +19 -3
- package/dist/state/composables/useMoney.js +70 -6
- package/dist/state/composables/useMoneyFilter.d.ts +20 -0
- package/dist/state/composables/useMoneyFilter.js +81 -0
- package/dist/state/composables/usePersistence.d.ts +1 -1
- package/dist/state/composables/usePersistence.js +1 -1
- package/dist/state/composables/useRuneLab.d.ts +1 -1
- package/dist/state/composables/useRuneLab.js +2 -2
- package/dist/state/composables/useShortcuts.d.ts +33 -0
- package/dist/state/composables/useShortcuts.js +75 -0
- package/dist/state/context.d.ts +1 -0
- package/dist/state/context.js +1 -0
- package/dist/state/createConfigStore.svelte.d.ts +4 -31
- package/dist/state/createConfigStore.svelte.js +62 -51
- package/dist/state/currency.svelte.d.ts +13 -9
- package/dist/state/currency.svelte.js +26 -10
- package/dist/state/currency.test.d.ts +1 -0
- package/dist/state/currency.test.js +35 -0
- package/dist/state/exchange-rate.svelte.d.ts +43 -0
- package/dist/state/exchange-rate.svelte.js +145 -0
- package/dist/state/exchange-rate.test.d.ts +1 -0
- package/dist/state/exchange-rate.test.js +75 -0
- package/dist/state/index.d.ts +26 -19
- package/dist/state/index.js +25 -18
- package/dist/state/language.svelte.d.ts +3 -10
- package/dist/state/language.svelte.js +4 -5
- package/dist/state/layout.svelte.d.ts +1 -1
- package/dist/state/layout.svelte.js +4 -4
- package/dist/state/persistence/drivers.d.ts +1 -1
- package/dist/state/persistence/drivers.js +9 -7
- package/dist/state/persistence/drivers.test.d.ts +1 -0
- package/dist/state/persistence/drivers.test.js +79 -0
- package/dist/state/persistence/provider.d.ts +23 -0
- package/dist/state/persistence/provider.js +43 -0
- package/dist/state/persistence/provider.test.d.ts +1 -0
- package/dist/state/persistence/provider.test.js +51 -0
- package/dist/state/registry/index.d.ts +44 -0
- package/dist/state/registry/index.js +58 -0
- package/dist/state/registry/registry.test.d.ts +1 -0
- package/dist/state/registry/registry.test.js +112 -0
- package/dist/state/registry/types.d.ts +20 -0
- package/dist/state/registry/types.js +3 -0
- package/dist/state/shortcuts.svelte.js +4 -4
- package/dist/state/theme.svelte.d.ts +3 -10
- package/dist/state/theme.svelte.js +8 -8
- package/dist/state/toast-bridge.d.ts +1 -1
- package/dist/state/toast.svelte.js +1 -1
- package/dist/ui/components/ApiMonitor.svelte +2 -2
- package/dist/ui/components/Icon.svelte +1 -1
- package/dist/ui/components/RuneProvider.svelte +28 -8
- package/dist/ui/components/RuneProvider.svelte.d.ts +12 -5
- package/dist/ui/components/Toaster.svelte +1 -1
- package/dist/ui/components/money/MoneyDisplay.svelte +91 -18
- package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +15 -3
- package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -1
- package/dist/ui/components/money/MoneyDisplay.svelte.test.js +45 -2
- package/dist/ui/components/money/MoneyInput.svelte +123 -42
- package/dist/ui/components/money/MoneyInput.svelte.d.ts +14 -5
- package/dist/ui/features/command-palette/CommandPalette.svelte +3 -3
- package/dist/ui/features/config/APP_CONFIGURATIONS.d.ts +29 -0
- package/dist/ui/features/config/APP_CONFIGURATIONS.js +38 -0
- package/dist/ui/features/config/CurrencySelector.svelte +10 -36
- package/dist/ui/features/config/LanguageSelector.svelte +10 -33
- package/dist/ui/features/config/ResourceSelector.svelte +92 -0
- package/dist/ui/features/config/ResourceSelector.svelte.d.ts +25 -0
- package/dist/ui/features/config/ThemeSelector.svelte +11 -34
- package/dist/ui/features/shortcuts/ShortcutBinder.svelte +17 -0
- package/dist/ui/features/shortcuts/ShortcutBinder.svelte.d.ts +7 -0
- package/dist/ui/features/shortcuts/ShortcutPalette.svelte +3 -3
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.js +8 -3
- package/dist/ui/layout/ConnectedNavigationPanel.svelte +7 -8
- package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +1 -1
- package/dist/ui/layout/ConnectedWorkspaceStrip.svelte +5 -3
- package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +1 -1
- package/dist/ui/layout/NavigationPanel.svelte +1 -1
- package/dist/ui/layout/NavigationPanel.svelte.d.ts +1 -1
- package/dist/ui/layout/WorkspaceLayout.svelte +9 -1
- package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +7 -0
- package/dist/ui/layout/WorkspaceStrip.svelte +1 -1
- package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +1 -1
- package/dist/ui/layout/connection-factory.d.ts +50 -0
- package/dist/ui/layout/connection-factory.js +58 -0
- package/dist/ui/layout/index.d.ts +2 -2
- package/dist/ui/layout/index.js +1 -1
- package/dist/ui/paraglide/README.md +53 -0
- package/dist/ui/paraglide/runtime.d.ts +105 -124
- package/dist/ui/paraglide/runtime.js +162 -127
- package/dist/ui/paraglide/server.d.ts +6 -17
- package/dist/ui/paraglide/server.js +11 -20
- package/dist/ui/primitives/DatePicker.svelte +1 -1
- package/package.json +8 -8
- package/dist/state/daisyui.d.ts +0 -4
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// sdk/state/src/exchange-rate.svelte.ts
|
|
2
|
+
import { getContext } from "svelte";
|
|
3
|
+
import { convertAmount, convertMoney, createMoney, CURRENCY_MAP,
|
|
4
|
+
// Phase 2: ConversionStrategies from sdk/core
|
|
5
|
+
resolveRate, scaledRate, toMoneySnapshot, } from "@internal/core";
|
|
6
|
+
import { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
7
|
+
export class ExchangeRateStore {
|
|
8
|
+
#rates = $state({});
|
|
9
|
+
#baseCurrency = $state("USD");
|
|
10
|
+
/**
|
|
11
|
+
* The current exchange rates relative to the base currency.
|
|
12
|
+
*/
|
|
13
|
+
get rates() {
|
|
14
|
+
return this.#rates;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The base currency all rates are relative to.
|
|
18
|
+
*/
|
|
19
|
+
get baseCurrency() {
|
|
20
|
+
return this.#baseCurrency;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns true if exchange rates have been loaded.
|
|
24
|
+
*/
|
|
25
|
+
get hasRates() {
|
|
26
|
+
return Object.keys(this.#rates).length > 0;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set exchange rates from a human-readable float map.
|
|
30
|
+
* @param base - The base currency code (e.g., "USD")
|
|
31
|
+
* @param rawRates - A map of currency codes to float rates (e.g., { MXN: 17.23 })
|
|
32
|
+
*/
|
|
33
|
+
setRates(base, rawRates) {
|
|
34
|
+
const nextRates = {};
|
|
35
|
+
for (const [code, rate] of Object.entries(rawRates)) {
|
|
36
|
+
nextRates[code] = scaledRate(rate, 4);
|
|
37
|
+
}
|
|
38
|
+
this.#baseCurrency = base;
|
|
39
|
+
this.#rates = nextRates;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Directly set pre-scaled rates.
|
|
43
|
+
*/
|
|
44
|
+
setScaledRates(base, rates) {
|
|
45
|
+
this.#baseCurrency = base;
|
|
46
|
+
this.#rates = rates;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the exchange rate between two currencies.
|
|
50
|
+
* Returns a ScaledRate representing how many units of toCode are in 1 unit of fromCode.
|
|
51
|
+
*/
|
|
52
|
+
getRate(fromCode, toCode) {
|
|
53
|
+
if (fromCode === toCode)
|
|
54
|
+
return 1;
|
|
55
|
+
const fromCurrency = CURRENCY_MAP[fromCode];
|
|
56
|
+
const toCurrency = CURRENCY_MAP[toCode];
|
|
57
|
+
if (!fromCurrency || !toCurrency)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (!this.hasRates)
|
|
60
|
+
return undefined;
|
|
61
|
+
try {
|
|
62
|
+
// 1 unit of fromCode (major unit)
|
|
63
|
+
const oneUnit = Math.pow(Array.isArray(fromCurrency.base)
|
|
64
|
+
? fromCurrency.base[0]
|
|
65
|
+
: fromCurrency.base, fromCurrency.exponent);
|
|
66
|
+
const currentRates = this.#rates;
|
|
67
|
+
const currentBase = this.#baseCurrency;
|
|
68
|
+
const sourceMoney = createMoney(oneUnit, fromCode);
|
|
69
|
+
let targetMoney;
|
|
70
|
+
if (fromCode === currentBase) {
|
|
71
|
+
// Direct: base → target
|
|
72
|
+
targetMoney = convertMoney(sourceMoney, toCode, currentRates);
|
|
73
|
+
}
|
|
74
|
+
else if (toCode === currentBase) {
|
|
75
|
+
// Inverse: target → base
|
|
76
|
+
const rateToBase = currentRates[fromCode];
|
|
77
|
+
if (!rateToBase)
|
|
78
|
+
return undefined;
|
|
79
|
+
const r = resolveRate(rateToBase);
|
|
80
|
+
targetMoney = convertMoney(sourceMoney, toCode, {
|
|
81
|
+
[toCode]: scaledRate(1 / r, 6),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Triangular: source → base → target
|
|
86
|
+
const rateToBase = currentRates[fromCode];
|
|
87
|
+
const rateToTarget = currentRates[toCode];
|
|
88
|
+
if (!rateToBase || !rateToTarget)
|
|
89
|
+
return undefined;
|
|
90
|
+
const r1 = resolveRate(rateToBase);
|
|
91
|
+
const r2 = resolveRate(rateToTarget);
|
|
92
|
+
targetMoney = convertMoney(sourceMoney, toCode, {
|
|
93
|
+
[toCode]: scaledRate(r2 / r1, 6),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const snapshot = toMoneySnapshot(targetMoney);
|
|
97
|
+
return {
|
|
98
|
+
amount: snapshot.amount,
|
|
99
|
+
scale: snapshot.scale,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (_err) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Internal conversion logic that handles triangulation through base currency.
|
|
108
|
+
*/
|
|
109
|
+
convertAmount(amount, fromCode, toCode) {
|
|
110
|
+
if (fromCode === toCode)
|
|
111
|
+
return amount;
|
|
112
|
+
if (!this.hasRates)
|
|
113
|
+
return amount;
|
|
114
|
+
// Direct: Base → Target
|
|
115
|
+
if (fromCode === this.#baseCurrency) {
|
|
116
|
+
return convertAmount(amount, fromCode, toCode, this.#rates);
|
|
117
|
+
}
|
|
118
|
+
// Inverse: Target → Base
|
|
119
|
+
if (toCode === this.#baseCurrency) {
|
|
120
|
+
const rateToBase = this.#rates[fromCode];
|
|
121
|
+
if (!rateToBase)
|
|
122
|
+
return amount;
|
|
123
|
+
const r = resolveRate(rateToBase);
|
|
124
|
+
const inverseRate = scaledRate(1 / r, 6);
|
|
125
|
+
return convertAmount(amount, fromCode, toCode, {
|
|
126
|
+
[toCode]: inverseRate,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Triangulation: From → Base → To
|
|
130
|
+
const amountInBase = this.convertAmount(amount, fromCode, this.#baseCurrency);
|
|
131
|
+
return this.convertAmount(amountInBase, this.#baseCurrency, toCode);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Factory to create the ExchangeRateStore.
|
|
136
|
+
*/
|
|
137
|
+
export function createExchangeRateStore() {
|
|
138
|
+
return new ExchangeRateStore();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Consumer to retrieve the ExchangeRateStore from context.
|
|
142
|
+
*/
|
|
143
|
+
export function getExchangeRateStore() {
|
|
144
|
+
return getContext(RUNE_LAB_CONTEXT.exchangeRate);
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { ExchangeRateStore } from "./exchange-rate.svelte.ts";
|
|
3
|
+
import { createCurrencyStore } from "./currency.svelte.ts";
|
|
4
|
+
import { inMemoryDriver } from "./persistence/drivers.ts";
|
|
5
|
+
import { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
6
|
+
import { createLanguageStore } from "./language.svelte.ts";
|
|
7
|
+
// Mock useMoney since it uses getContext and other stores
|
|
8
|
+
vi.mock("./composables/useMoney.ts", () => ({
|
|
9
|
+
useMoney: () => ({
|
|
10
|
+
format: (amount, code, unit) => `${amount} ${code ?? 'USD'}`
|
|
11
|
+
})
|
|
12
|
+
}));
|
|
13
|
+
describe("ExchangeRateStore", () => {
|
|
14
|
+
it("should initialize with default base", () => {
|
|
15
|
+
const store = new ExchangeRateStore();
|
|
16
|
+
expect(store.baseCurrency).toBe("USD");
|
|
17
|
+
expect(store.hasRates).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it("should set and retrieve rates", () => {
|
|
20
|
+
const store = new ExchangeRateStore();
|
|
21
|
+
store.setRates("USD", { MXN: 17.23, EUR: 0.91 });
|
|
22
|
+
expect(store.hasRates).toBe(true);
|
|
23
|
+
expect(store.baseCurrency).toBe("USD");
|
|
24
|
+
const rateToMxn = store.getRate("USD", "MXN");
|
|
25
|
+
expect(rateToMxn).toEqual({ amount: 17230000, scale: 6 });
|
|
26
|
+
});
|
|
27
|
+
it("should handle inverse rates", () => {
|
|
28
|
+
const store = new ExchangeRateStore();
|
|
29
|
+
store.setRates("USD", { MXN: 20 });
|
|
30
|
+
const rateToBase = store.getRate("MXN", "USD");
|
|
31
|
+
expect(rateToBase).toEqual({ amount: 5000000, scale: 8 });
|
|
32
|
+
});
|
|
33
|
+
it("should handle triangulation (cross-rates)", () => {
|
|
34
|
+
const store = new ExchangeRateStore();
|
|
35
|
+
store.setRates("USD", { MXN: 20, EUR: 0.5 });
|
|
36
|
+
const rateMxnToEur = store.getRate("MXN", "EUR");
|
|
37
|
+
expect(rateMxnToEur).toEqual({ amount: 2500000, scale: 8 });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe("CurrencyStore Integration", () => {
|
|
41
|
+
it("should convert amount when ExchangeRateStore is wired", () => {
|
|
42
|
+
const exchangeRateStore = new ExchangeRateStore();
|
|
43
|
+
exchangeRateStore.setRates("USD", { MXN: 20 });
|
|
44
|
+
const currencyStore = createCurrencyStore({ exchangeRateStore, driver: inMemoryDriver });
|
|
45
|
+
expect(currencyStore.canConvert).toBe(true);
|
|
46
|
+
const converted = currencyStore.convertAmount(10000, "USD", "MXN");
|
|
47
|
+
expect(converted).toBe(200000);
|
|
48
|
+
});
|
|
49
|
+
it("should return original amount if no rates available", () => {
|
|
50
|
+
const exchangeRateStore = new ExchangeRateStore();
|
|
51
|
+
const currencyStore = createCurrencyStore({ exchangeRateStore, driver: inMemoryDriver });
|
|
52
|
+
expect(currencyStore.canConvert).toBe(false);
|
|
53
|
+
const converted = currencyStore.convertAmount(10000, "USD", "MXN");
|
|
54
|
+
expect(converted).toBe(10000);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("useMoneyFilter", () => {
|
|
58
|
+
// Simple test for logic, skipping full component mount for brevity
|
|
59
|
+
it("matches() should work with conversion", () => {
|
|
60
|
+
const exchangeRateStore = new ExchangeRateStore();
|
|
61
|
+
exchangeRateStore.setRates("USD", { MXN: 20 });
|
|
62
|
+
const currencyStore = createCurrencyStore({ exchangeRateStore, driver: inMemoryDriver });
|
|
63
|
+
// Manual mock of context for the composable
|
|
64
|
+
// In a real test we'd use render() from @testing-library/svelte
|
|
65
|
+
const mockContext = new Map();
|
|
66
|
+
mockContext.set(RUNE_LAB_CONTEXT.currency, currencyStore);
|
|
67
|
+
mockContext.set(RUNE_LAB_CONTEXT.exchangeRate, exchangeRateStore);
|
|
68
|
+
mockContext.set(RUNE_LAB_CONTEXT.language, createLanguageStore({ driver: inMemoryDriver }));
|
|
69
|
+
// We can't easily call useMoneyFilter outside component without more setup
|
|
70
|
+
// but we can verify the matches logic in CurrencyStore
|
|
71
|
+
const amountInMxn = 1000000; // 10,000 MXN
|
|
72
|
+
const amountInUsd = currencyStore.convertAmount(amountInMxn, "MXN", "USD");
|
|
73
|
+
expect(amountInUsd).toBe(50000); // 500 USD
|
|
74
|
+
});
|
|
75
|
+
});
|
package/dist/state/index.d.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
export { RUNE_LAB_CONTEXT } from "./context";
|
|
2
|
-
export { useRuneLab } from "./composables/useRuneLab";
|
|
3
|
-
export type { RuneLabContext } from "./composables/useRuneLab";
|
|
4
|
-
export { usePersistence } from "./composables/usePersistence";
|
|
5
|
-
export { useMoney } from "./composables/useMoney";
|
|
6
|
-
export {
|
|
7
|
-
export
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export { type
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
1
|
+
export { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
2
|
+
export { useRuneLab } from "./composables/useRuneLab.ts";
|
|
3
|
+
export type { RuneLabContext } from "./composables/useRuneLab.ts";
|
|
4
|
+
export { usePersistence } from "./composables/usePersistence.ts";
|
|
5
|
+
export { useMoney } from "./composables/useMoney.ts";
|
|
6
|
+
export { useMoneyFilter } from "./composables/useMoneyFilter.ts";
|
|
7
|
+
export type { MoneyFilterOptions } from "./composables/useMoneyFilter.ts";
|
|
8
|
+
export { useShortcuts } from "./composables/useShortcuts.ts";
|
|
9
|
+
export { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers.ts";
|
|
10
|
+
export { getDriverContext, resolveDriver, setDriverContext, } from "./persistence/provider.ts";
|
|
11
|
+
export { clearRegistry, getAllRegisteredStores, getRegisteredStore, registerStore, STORE_REGISTRY, unregisterStore, } from "./registry/index.ts";
|
|
12
|
+
export type { StoreFactory, StoreRegistryEntry } from "./registry/types.ts";
|
|
13
|
+
export { type AppData, AppStore, createAppStore, getAppStore, } from "./app.svelte.ts";
|
|
14
|
+
export { createLayoutStore, getLayoutStore, LayoutStore, type NavigationItem, type NavigationSection, type WorkspaceItem, } from "./layout.svelte.ts";
|
|
15
|
+
export { ApiStore, type ConnectionState, createApiStore, getApiStore, } from "./api.svelte.ts";
|
|
16
|
+
export { createLanguageStore, getLanguageStore, type Language, } from "./language.svelte.ts";
|
|
17
|
+
export { createCurrencyStore, type Currency, type CurrencyStore, type CurrencyStoreOptions, getCurrencyStore, } from "./currency.svelte.ts";
|
|
18
|
+
export { createExchangeRateStore, ExchangeRateStore, getExchangeRateStore, } from "./exchange-rate.svelte.ts";
|
|
19
|
+
export { createToastStore, getToastStore, ToastStore } from "./toast.svelte.ts";
|
|
20
|
+
export { type Command, CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte.ts";
|
|
21
|
+
export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, type ShortcutEntry, shortcutListener, type ShortcutMeta, ShortcutStore, } from "./shortcuts.svelte.ts";
|
|
22
|
+
export { createThemeStore, getThemeStore, type Theme, type ThemeStoreOptions, } from "./theme.svelte.ts";
|
|
23
|
+
export { type ConfigStore, type ConfigStoreOptions, createConfigStore, } from "./createConfigStore.svelte.ts";
|
|
24
|
+
export { createToastBridge, notify } from "./toast-bridge.ts";
|
|
25
|
+
export { type CartEntry, type CartStore, type CartStoreConfig, createCartStore, getCartStore, } from "./cart.svelte.ts";
|
|
26
|
+
export { type AuthConfig, createSessionStore, getSessionStore, type Session, SessionStore, type User, } from "./auth/index.ts";
|
package/dist/state/index.js
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
// sdk/state/src/index.ts
|
|
2
2
|
// Unified public barrel for @internal/state
|
|
3
3
|
// ── Context ───────────────────────────────────────────────────────────────────
|
|
4
|
-
export { RUNE_LAB_CONTEXT } from "./context";
|
|
4
|
+
export { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
5
5
|
// ── Composables ───────────────────────────────────────────────────────────────
|
|
6
|
-
export { useRuneLab } from "./composables/useRuneLab";
|
|
7
|
-
export { usePersistence } from "./composables/usePersistence";
|
|
8
|
-
export { useMoney } from "./composables/useMoney";
|
|
6
|
+
export { useRuneLab } from "./composables/useRuneLab.ts";
|
|
7
|
+
export { usePersistence } from "./composables/usePersistence.ts";
|
|
8
|
+
export { useMoney } from "./composables/useMoney.ts";
|
|
9
|
+
export { useMoneyFilter } from "./composables/useMoneyFilter.ts";
|
|
10
|
+
export { useShortcuts } from "./composables/useShortcuts.ts";
|
|
9
11
|
// ── Persistence Drivers ───────────────────────────────────────────────────────
|
|
10
|
-
export { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
12
|
+
export { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers.ts";
|
|
13
|
+
// ── Driver Provider (Phase 2) ─────────────────────────────────────────────────
|
|
14
|
+
export { getDriverContext, resolveDriver, setDriverContext, } from "./persistence/provider.ts";
|
|
15
|
+
// ── Store Registry (Phase 2) ──────────────────────────────────────────────────
|
|
16
|
+
export { clearRegistry, getAllRegisteredStores, getRegisteredStore, registerStore, STORE_REGISTRY, unregisterStore, } from "./registry/index.ts";
|
|
11
17
|
// Stores
|
|
12
|
-
export { AppStore, createAppStore, getAppStore, } from "./app.svelte";
|
|
13
|
-
export { createLayoutStore, getLayoutStore, LayoutStore, } from "./layout.svelte";
|
|
14
|
-
export { ApiStore, createApiStore, getApiStore, } from "./api.svelte";
|
|
15
|
-
export { createLanguageStore, getLanguageStore, } from "./language.svelte";
|
|
16
|
-
export { createCurrencyStore, getCurrencyStore, } from "./currency.svelte";
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
18
|
+
export { AppStore, createAppStore, getAppStore, } from "./app.svelte.ts";
|
|
19
|
+
export { createLayoutStore, getLayoutStore, LayoutStore, } from "./layout.svelte.ts";
|
|
20
|
+
export { ApiStore, createApiStore, getApiStore, } from "./api.svelte.ts";
|
|
21
|
+
export { createLanguageStore, getLanguageStore, } from "./language.svelte.ts";
|
|
22
|
+
export { createCurrencyStore, getCurrencyStore, } from "./currency.svelte.ts";
|
|
23
|
+
export { createExchangeRateStore, ExchangeRateStore, getExchangeRateStore, } from "./exchange-rate.svelte.ts";
|
|
24
|
+
export { createToastStore, getToastStore, ToastStore } from "./toast.svelte.ts";
|
|
25
|
+
export { CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte.ts";
|
|
26
|
+
export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, shortcutListener, ShortcutStore, } from "./shortcuts.svelte.ts";
|
|
27
|
+
export { createThemeStore, getThemeStore, } from "./theme.svelte.ts";
|
|
28
|
+
export { createConfigStore, } from "./createConfigStore.svelte.ts";
|
|
29
|
+
export { createToastBridge, notify } from "./toast-bridge.ts";
|
|
30
|
+
export { createCartStore, getCartStore, } from "./cart.svelte.ts";
|
|
24
31
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
25
|
-
export { createSessionStore, getSessionStore, SessionStore, } from "./auth/index";
|
|
32
|
+
export { createSessionStore, getSessionStore, SessionStore, } from "./auth/index.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ConfigStore } from "./createConfigStore.svelte";
|
|
1
|
+
import { type ConfigStore } from "./createConfigStore.svelte.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Language configuration
|
|
4
4
|
* Represents a supported language in the application
|
|
@@ -47,18 +47,11 @@ export declare const LANGUAGES: readonly [{
|
|
|
47
47
|
readonly code: "vi";
|
|
48
48
|
readonly flag: "🇻🇳";
|
|
49
49
|
}];
|
|
50
|
-
import type { PersistenceDriver } from "
|
|
50
|
+
import type { PersistenceDriver } from "@internal/core";
|
|
51
51
|
export interface LanguageStoreOptions {
|
|
52
52
|
driver?: PersistenceDriver | (() => PersistenceDriver | undefined);
|
|
53
53
|
onLocaleChange?: (code: string) => void;
|
|
54
54
|
locales?: readonly string[];
|
|
55
55
|
}
|
|
56
|
-
export declare function createLanguageStore(options?: LanguageStoreOptions):
|
|
57
|
-
current: string | undefined;
|
|
58
|
-
available: Language[];
|
|
59
|
-
set(id: string | undefined): void;
|
|
60
|
-
get(id: string | undefined): Language | undefined;
|
|
61
|
-
getProp<K extends keyof Language>(prop: K, id?: string | undefined): Language[K] | undefined;
|
|
62
|
-
addItems(newItems: Language[]): void;
|
|
63
|
-
};
|
|
56
|
+
export declare function createLanguageStore(options?: LanguageStoreOptions): ConfigStore<Language>;
|
|
64
57
|
export declare function getLanguageStore(): ConfigStore<Language>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// sdk/state/src/language.svelte.ts
|
|
2
|
-
import { createConfigStore, } from "./createConfigStore.svelte";
|
|
2
|
+
import { createConfigStore, } from "./createConfigStore.svelte.ts";
|
|
3
3
|
import { getContext } from "svelte";
|
|
4
|
-
import { RUNE_LAB_CONTEXT } from "./context";
|
|
4
|
+
import { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
5
5
|
export const LANGUAGES = [
|
|
6
6
|
// --- INDOEUROPEAS (Rama Romance / Latín) ---
|
|
7
7
|
{ code: "es", flag: "🇲🇽" },
|
|
@@ -22,10 +22,9 @@ export const LANGUAGES = [
|
|
|
22
22
|
{ code: "ko", flag: "🇰🇷" },
|
|
23
23
|
{ code: "vi", flag: "🇻🇳" },
|
|
24
24
|
];
|
|
25
|
+
import { resolveDriver } from "./persistence/provider.ts";
|
|
25
26
|
export function createLanguageStore(options) {
|
|
26
|
-
const driver =
|
|
27
|
-
? options.driver()
|
|
28
|
-
: options?.driver;
|
|
27
|
+
const driver = resolveDriver(options?.driver);
|
|
29
28
|
const items = options?.locales
|
|
30
29
|
? LANGUAGES.filter((l) => options.locales.includes(l.code))
|
|
31
30
|
: LANGUAGES;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/lib/state/layout.svelte.ts
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
-
import { RUNE_LAB_CONTEXT } from "./context";
|
|
3
|
+
import { RUNE_LAB_CONTEXT } from "./context.ts";
|
|
4
|
+
import { resolveDriver } from "./persistence/provider.ts";
|
|
4
5
|
export class LayoutStore {
|
|
5
6
|
workspaces = $state([]);
|
|
6
7
|
activeWorkspaceId = $state(null);
|
|
@@ -12,9 +13,8 @@ export class LayoutStore {
|
|
|
12
13
|
#initialized = false;
|
|
13
14
|
#driver;
|
|
14
15
|
constructor(driver) {
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
this.#driver = (typeof driver === "function" ? driver() : driver);
|
|
16
|
+
// Use resolveDriver but allow undefined result for SSR/no-driver scenarios
|
|
17
|
+
this.#driver = driver ? resolveDriver(driver) : undefined;
|
|
18
18
|
}
|
|
19
19
|
init(options) {
|
|
20
20
|
if (this.#initialized)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PersistenceDriver } from "
|
|
1
|
+
import type { PersistenceDriver } from "@internal/core";
|
|
2
2
|
export declare function createInMemoryDriver(): PersistenceDriver;
|
|
3
3
|
export declare const inMemoryDriver: PersistenceDriver;
|
|
4
4
|
export declare const localStorageDriver: PersistenceDriver;
|
|
@@ -4,7 +4,9 @@ export function createInMemoryDriver() {
|
|
|
4
4
|
return {
|
|
5
5
|
get: (key) => store.get(key) ?? null,
|
|
6
6
|
set: (key, value) => store.set(key, value),
|
|
7
|
-
remove: (key) =>
|
|
7
|
+
remove: (key) => {
|
|
8
|
+
store.delete(key);
|
|
9
|
+
},
|
|
8
10
|
};
|
|
9
11
|
}
|
|
10
12
|
export const inMemoryDriver = createInMemoryDriver();
|
|
@@ -12,34 +14,34 @@ export const localStorageDriver = {
|
|
|
12
14
|
get: (key) => {
|
|
13
15
|
if (!browser)
|
|
14
16
|
return null;
|
|
15
|
-
return
|
|
17
|
+
return globalThis.localStorage.getItem(key);
|
|
16
18
|
},
|
|
17
19
|
set: (key, value) => {
|
|
18
20
|
if (!browser)
|
|
19
21
|
return;
|
|
20
|
-
|
|
22
|
+
globalThis.localStorage.setItem(key, value);
|
|
21
23
|
},
|
|
22
24
|
remove: (key) => {
|
|
23
25
|
if (!browser)
|
|
24
26
|
return;
|
|
25
|
-
|
|
27
|
+
globalThis.localStorage.removeItem(key);
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
30
|
export const sessionStorageDriver = {
|
|
29
31
|
get: (key) => {
|
|
30
32
|
if (!browser)
|
|
31
33
|
return null;
|
|
32
|
-
return
|
|
34
|
+
return globalThis.sessionStorage.getItem(key);
|
|
33
35
|
},
|
|
34
36
|
set: (key, value) => {
|
|
35
37
|
if (!browser)
|
|
36
38
|
return;
|
|
37
|
-
|
|
39
|
+
globalThis.sessionStorage.setItem(key, value);
|
|
38
40
|
},
|
|
39
41
|
remove: (key) => {
|
|
40
42
|
if (!browser)
|
|
41
43
|
return;
|
|
42
|
-
|
|
44
|
+
globalThis.sessionStorage.removeItem(key);
|
|
43
45
|
},
|
|
44
46
|
};
|
|
45
47
|
export const createCookieDriver = (options = {}) => ({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createInMemoryDriver, inMemoryDriver } from "./drivers.ts";
|
|
3
|
+
describe("PersistenceDriver Contract Tests", () => {
|
|
4
|
+
describe("createInMemoryDriver (factory)", () => {
|
|
5
|
+
it("should return null for missing keys", () => {
|
|
6
|
+
const driver = createInMemoryDriver();
|
|
7
|
+
expect(driver.get("nonexistent")).toBeNull();
|
|
8
|
+
});
|
|
9
|
+
it("should set and get a value", () => {
|
|
10
|
+
const driver = createInMemoryDriver();
|
|
11
|
+
driver.set("key", "value");
|
|
12
|
+
expect(driver.get("key")).toBe("value");
|
|
13
|
+
});
|
|
14
|
+
it("should overwrite existing values", () => {
|
|
15
|
+
const driver = createInMemoryDriver();
|
|
16
|
+
driver.set("key", "v1");
|
|
17
|
+
driver.set("key", "v2");
|
|
18
|
+
expect(driver.get("key")).toBe("v2");
|
|
19
|
+
});
|
|
20
|
+
it("should remove a key", () => {
|
|
21
|
+
const driver = createInMemoryDriver();
|
|
22
|
+
driver.set("key", "value");
|
|
23
|
+
driver.remove("key");
|
|
24
|
+
expect(driver.get("key")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
it("should not throw when removing a nonexistent key", () => {
|
|
27
|
+
const driver = createInMemoryDriver();
|
|
28
|
+
expect(() => driver.remove("nonexistent")).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
it("should handle empty string values", () => {
|
|
31
|
+
const driver = createInMemoryDriver();
|
|
32
|
+
driver.set("key", "");
|
|
33
|
+
expect(driver.get("key")).toBe("");
|
|
34
|
+
});
|
|
35
|
+
it("should handle JSON string values", () => {
|
|
36
|
+
const driver = createInMemoryDriver();
|
|
37
|
+
const json = JSON.stringify({ theme: "dark", fontSize: 14 });
|
|
38
|
+
driver.set("settings", json);
|
|
39
|
+
expect(JSON.parse(driver.get("settings"))).toEqual({
|
|
40
|
+
theme: "dark",
|
|
41
|
+
fontSize: 14,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("Factory isolation", () => {
|
|
46
|
+
it("should create isolated instances (no cross-contamination)", () => {
|
|
47
|
+
const driverA = createInMemoryDriver();
|
|
48
|
+
const driverB = createInMemoryDriver();
|
|
49
|
+
driverA.set("shared-key", "fromA");
|
|
50
|
+
driverB.set("shared-key", "fromB");
|
|
51
|
+
expect(driverA.get("shared-key")).toBe("fromA");
|
|
52
|
+
expect(driverB.get("shared-key")).toBe("fromB");
|
|
53
|
+
});
|
|
54
|
+
it("remove on one instance does not affect another", () => {
|
|
55
|
+
const driverA = createInMemoryDriver();
|
|
56
|
+
const driverB = createInMemoryDriver();
|
|
57
|
+
driverA.set("key", "valueA");
|
|
58
|
+
driverB.set("key", "valueB");
|
|
59
|
+
driverA.remove("key");
|
|
60
|
+
expect(driverA.get("key")).toBeNull();
|
|
61
|
+
expect(driverB.get("key")).toBe("valueB");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("inMemoryDriver singleton", () => {
|
|
65
|
+
it("should implement the full PersistenceDriver interface", () => {
|
|
66
|
+
expect(typeof inMemoryDriver.get).toBe("function");
|
|
67
|
+
expect(typeof inMemoryDriver.set).toBe("function");
|
|
68
|
+
expect(typeof inMemoryDriver.remove).toBe("function");
|
|
69
|
+
});
|
|
70
|
+
it("should function as a working driver", () => {
|
|
71
|
+
// Use unique keys to avoid test interference
|
|
72
|
+
const key = `test-${Date.now()}`;
|
|
73
|
+
inMemoryDriver.set(key, "singleton-value");
|
|
74
|
+
expect(inMemoryDriver.get(key)).toBe("singleton-value");
|
|
75
|
+
inMemoryDriver.remove(key);
|
|
76
|
+
expect(inMemoryDriver.get(key)).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PersistenceDriver } from "@internal/core";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a driver input (function or instance) into a concrete PersistenceDriver.
|
|
4
|
+
* If the input is null/undefined, returns an inMemoryDriver as the Null Object.
|
|
5
|
+
*
|
|
6
|
+
* This is the single place where function-form drivers are unwrapped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveDriver(driver?: PersistenceDriver | (() => PersistenceDriver | undefined) | undefined): PersistenceDriver;
|
|
9
|
+
/**
|
|
10
|
+
* Sets the persistence driver context at the RuneProvider root.
|
|
11
|
+
* Should be called exactly once, before any store factories run.
|
|
12
|
+
*
|
|
13
|
+
* @param driver - A PersistenceDriver instance, a factory function, or undefined.
|
|
14
|
+
* Undefined falls back to inMemoryDriver.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setDriverContext(driver?: PersistenceDriver | (() => PersistenceDriver | undefined)): PersistenceDriver;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves the persistence driver from context.
|
|
19
|
+
* Always returns a concrete PersistenceDriver — never null or undefined.
|
|
20
|
+
*
|
|
21
|
+
* @throws Error if called outside a RuneProvider tree (no context set).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getDriverContext(): PersistenceDriver;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// sdk/state/src/persistence/provider.ts
|
|
2
|
+
// Context-based persistence driver injection.
|
|
3
|
+
// Resolves the driver once (handling function-vs-instance duality)
|
|
4
|
+
// and guarantees a concrete PersistenceDriver is always available.
|
|
5
|
+
import { getContext, setContext } from "svelte";
|
|
6
|
+
import { createInMemoryDriver } from "./drivers.ts";
|
|
7
|
+
const DRIVER_CONTEXT_KEY = Symbol("rl:driver-provider");
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a driver input (function or instance) into a concrete PersistenceDriver.
|
|
10
|
+
* If the input is null/undefined, returns an inMemoryDriver as the Null Object.
|
|
11
|
+
*
|
|
12
|
+
* This is the single place where function-form drivers are unwrapped.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDriver(driver) {
|
|
15
|
+
const resolved = typeof driver === "function" ? driver() : driver;
|
|
16
|
+
return resolved ?? createInMemoryDriver();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sets the persistence driver context at the RuneProvider root.
|
|
20
|
+
* Should be called exactly once, before any store factories run.
|
|
21
|
+
*
|
|
22
|
+
* @param driver - A PersistenceDriver instance, a factory function, or undefined.
|
|
23
|
+
* Undefined falls back to inMemoryDriver.
|
|
24
|
+
*/
|
|
25
|
+
export function setDriverContext(driver) {
|
|
26
|
+
const resolved = resolveDriver(driver);
|
|
27
|
+
setContext(DRIVER_CONTEXT_KEY, resolved);
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves the persistence driver from context.
|
|
32
|
+
* Always returns a concrete PersistenceDriver — never null or undefined.
|
|
33
|
+
*
|
|
34
|
+
* @throws Error if called outside a RuneProvider tree (no context set).
|
|
35
|
+
*/
|
|
36
|
+
export function getDriverContext() {
|
|
37
|
+
const driver = getContext(DRIVER_CONTEXT_KEY);
|
|
38
|
+
if (!driver) {
|
|
39
|
+
throw new Error("[DriverProvider] getDriverContext() called outside a <RuneProvider> tree. " +
|
|
40
|
+
"Wrap your application in <RuneProvider> to provide persistence.");
|
|
41
|
+
}
|
|
42
|
+
return driver;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|