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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/dist/core/design-tokens/props.d.ts +52 -0
  3. package/dist/core/design-tokens/props.d.ts.map +1 -0
  4. package/dist/core/design-tokens/props.js +34 -0
  5. package/dist/core/exchange-rate/strategies.d.ts +52 -0
  6. package/dist/core/exchange-rate/strategies.d.ts.map +1 -0
  7. package/dist/core/exchange-rate/strategies.js +72 -0
  8. package/dist/core/index.d.ts +8 -3
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +8 -3
  11. package/dist/core/internal/message-resolver.d.ts +1 -1
  12. package/dist/core/internal/message-resolver.d.ts.map +1 -1
  13. package/dist/core/layout/types.d.ts +60 -0
  14. package/dist/core/layout/types.d.ts.map +1 -0
  15. package/dist/core/layout/types.js +4 -0
  16. package/dist/core/money/index.d.ts +1 -1
  17. package/dist/core/money/index.d.ts.map +1 -1
  18. package/dist/core/money/index.js +1 -1
  19. package/dist/core/money/money-primitive.d.ts +101 -0
  20. package/dist/core/money/money-primitive.d.ts.map +1 -0
  21. package/dist/core/money/money-primitive.js +161 -0
  22. package/dist/core/money/money.d.ts +74 -2
  23. package/dist/core/money/money.d.ts.map +1 -1
  24. package/dist/core/money/money.js +120 -2
  25. package/dist/core/shortcuts/types.d.ts +60 -0
  26. package/dist/core/shortcuts/types.d.ts.map +1 -0
  27. package/dist/core/shortcuts/types.js +4 -0
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +6 -4
  30. package/dist/state/api.svelte.js +2 -2
  31. package/dist/state/app.svelte.js +1 -1
  32. package/dist/state/auth/index.d.ts +2 -2
  33. package/dist/state/auth/index.js +1 -1
  34. package/dist/state/auth/session.svelte.d.ts +1 -1
  35. package/dist/state/auth/session.svelte.js +7 -5
  36. package/dist/state/auth/types.d.ts +1 -1
  37. package/dist/state/cart.svelte.d.ts +1 -1
  38. package/dist/state/cart.svelte.js +1 -1
  39. package/dist/state/commands.svelte.d.ts +7 -7
  40. package/dist/state/commands.svelte.js +1 -1
  41. package/dist/state/composables/useMoney.d.ts +19 -3
  42. package/dist/state/composables/useMoney.js +70 -6
  43. package/dist/state/composables/useMoneyFilter.d.ts +20 -0
  44. package/dist/state/composables/useMoneyFilter.js +81 -0
  45. package/dist/state/composables/usePersistence.d.ts +1 -1
  46. package/dist/state/composables/usePersistence.js +1 -1
  47. package/dist/state/composables/useRuneLab.d.ts +1 -1
  48. package/dist/state/composables/useRuneLab.js +2 -2
  49. package/dist/state/composables/useShortcuts.d.ts +33 -0
  50. package/dist/state/composables/useShortcuts.js +75 -0
  51. package/dist/state/context.d.ts +1 -0
  52. package/dist/state/context.js +1 -0
  53. package/dist/state/createConfigStore.svelte.d.ts +4 -31
  54. package/dist/state/createConfigStore.svelte.js +62 -51
  55. package/dist/state/currency.svelte.d.ts +13 -9
  56. package/dist/state/currency.svelte.js +26 -10
  57. package/dist/state/currency.test.d.ts +1 -0
  58. package/dist/state/currency.test.js +35 -0
  59. package/dist/state/exchange-rate.svelte.d.ts +43 -0
  60. package/dist/state/exchange-rate.svelte.js +145 -0
  61. package/dist/state/exchange-rate.test.d.ts +1 -0
  62. package/dist/state/exchange-rate.test.js +75 -0
  63. package/dist/state/index.d.ts +26 -19
  64. package/dist/state/index.js +25 -18
  65. package/dist/state/language.svelte.d.ts +3 -10
  66. package/dist/state/language.svelte.js +4 -5
  67. package/dist/state/layout.svelte.d.ts +1 -1
  68. package/dist/state/layout.svelte.js +4 -4
  69. package/dist/state/persistence/drivers.d.ts +1 -1
  70. package/dist/state/persistence/drivers.js +9 -7
  71. package/dist/state/persistence/drivers.test.d.ts +1 -0
  72. package/dist/state/persistence/drivers.test.js +79 -0
  73. package/dist/state/persistence/provider.d.ts +23 -0
  74. package/dist/state/persistence/provider.js +43 -0
  75. package/dist/state/persistence/provider.test.d.ts +1 -0
  76. package/dist/state/persistence/provider.test.js +51 -0
  77. package/dist/state/registry/index.d.ts +44 -0
  78. package/dist/state/registry/index.js +58 -0
  79. package/dist/state/registry/registry.test.d.ts +1 -0
  80. package/dist/state/registry/registry.test.js +112 -0
  81. package/dist/state/registry/types.d.ts +20 -0
  82. package/dist/state/registry/types.js +3 -0
  83. package/dist/state/shortcuts.svelte.js +4 -4
  84. package/dist/state/theme.svelte.d.ts +3 -10
  85. package/dist/state/theme.svelte.js +8 -8
  86. package/dist/state/toast-bridge.d.ts +1 -1
  87. package/dist/state/toast.svelte.js +1 -1
  88. package/dist/ui/components/ApiMonitor.svelte +2 -2
  89. package/dist/ui/components/Icon.svelte +1 -1
  90. package/dist/ui/components/RuneProvider.svelte +28 -8
  91. package/dist/ui/components/RuneProvider.svelte.d.ts +12 -5
  92. package/dist/ui/components/Toaster.svelte +1 -1
  93. package/dist/ui/components/money/MoneyDisplay.svelte +91 -18
  94. package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +15 -3
  95. package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -1
  96. package/dist/ui/components/money/MoneyDisplay.svelte.test.js +45 -2
  97. package/dist/ui/components/money/MoneyInput.svelte +123 -42
  98. package/dist/ui/components/money/MoneyInput.svelte.d.ts +14 -5
  99. package/dist/ui/features/command-palette/CommandPalette.svelte +3 -3
  100. package/dist/ui/features/config/APP_CONFIGURATIONS.d.ts +29 -0
  101. package/dist/ui/features/config/APP_CONFIGURATIONS.js +38 -0
  102. package/dist/ui/features/config/CurrencySelector.svelte +10 -36
  103. package/dist/ui/features/config/LanguageSelector.svelte +10 -33
  104. package/dist/ui/features/config/ResourceSelector.svelte +92 -0
  105. package/dist/ui/features/config/ResourceSelector.svelte.d.ts +25 -0
  106. package/dist/ui/features/config/ThemeSelector.svelte +11 -34
  107. package/dist/ui/features/shortcuts/ShortcutBinder.svelte +17 -0
  108. package/dist/ui/features/shortcuts/ShortcutBinder.svelte.d.ts +7 -0
  109. package/dist/ui/features/shortcuts/ShortcutPalette.svelte +3 -3
  110. package/dist/ui/index.d.ts +5 -1
  111. package/dist/ui/index.js +8 -3
  112. package/dist/ui/layout/ConnectedNavigationPanel.svelte +7 -8
  113. package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +1 -1
  114. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte +5 -3
  115. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +1 -1
  116. package/dist/ui/layout/NavigationPanel.svelte +1 -1
  117. package/dist/ui/layout/NavigationPanel.svelte.d.ts +1 -1
  118. package/dist/ui/layout/WorkspaceLayout.svelte +9 -1
  119. package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +7 -0
  120. package/dist/ui/layout/WorkspaceStrip.svelte +1 -1
  121. package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +1 -1
  122. package/dist/ui/layout/connection-factory.d.ts +50 -0
  123. package/dist/ui/layout/connection-factory.js +58 -0
  124. package/dist/ui/layout/index.d.ts +2 -2
  125. package/dist/ui/layout/index.js +1 -1
  126. package/dist/ui/paraglide/README.md +53 -0
  127. package/dist/ui/paraglide/runtime.d.ts +105 -124
  128. package/dist/ui/paraglide/runtime.js +162 -127
  129. package/dist/ui/paraglide/server.d.ts +6 -17
  130. package/dist/ui/paraglide/server.js +11 -20
  131. package/dist/ui/primitives/DatePicker.svelte +1 -1
  132. package/package.json +8 -8
  133. 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
+ });
@@ -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 { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
7
- export { type AppData, AppStore, createAppStore, getAppStore, } from "./app.svelte";
8
- export { createLayoutStore, getLayoutStore, LayoutStore, type NavigationItem, type NavigationSection, type WorkspaceItem, } from "./layout.svelte";
9
- export { ApiStore, type ConnectionState, createApiStore, getApiStore, } from "./api.svelte";
10
- export { createLanguageStore, getLanguageStore, type Language, } from "./language.svelte";
11
- export { createCurrencyStore, type Currency, type CurrencyStore, type CurrencyStoreOptions, getCurrencyStore, } from "./currency.svelte";
12
- export { createToastStore, getToastStore, ToastStore } from "./toast.svelte";
13
- export { type Command, CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte";
14
- export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, type ShortcutEntry, shortcutListener, type ShortcutMeta, ShortcutStore, } from "./shortcuts.svelte";
15
- export { createThemeStore, getThemeStore, type Theme, type ThemeStoreOptions, } from "./theme.svelte";
16
- export { type ConfigItem, type ConfigStore, type ConfigStoreOptions, createConfigStore, } from "./createConfigStore.svelte";
17
- export { createToastBridge, notify } from "./toast-bridge";
18
- export { type CartEntry, type CartStore, type CartStoreConfig, createCartStore, getCartStore, } from "./cart.svelte";
19
- export { type AuthConfig, createSessionStore, getSessionStore, type Session, SessionStore, type User, } from "./auth/index";
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";
@@ -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 { createToastStore, getToastStore, ToastStore } from "./toast.svelte";
18
- export { CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte";
19
- export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, shortcutListener, ShortcutStore, } from "./shortcuts.svelte";
20
- export { createThemeStore, getThemeStore, } from "./theme.svelte";
21
- export { createConfigStore, } from "./createConfigStore.svelte";
22
- export { createToastBridge, notify } from "./toast-bridge";
23
- export { createCartStore, getCartStore, } from "./cart.svelte";
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 "rune-lab/core";
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 = typeof options?.driver === "function"
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,4 +1,4 @@
1
- import type { PersistenceDriver } from "rune-lab/core";
1
+ import type { PersistenceDriver } from "@internal/core";
2
2
  export interface WorkspaceItem {
3
3
  id: string;
4
4
  icon: string;
@@ -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
- // We import locally here or at top-level. We can just fallback to window if undefined,
16
- // but better to expect it via inject. The app supplies it in createLayoutStore.
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 "rune-lab/core";
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) => store.delete(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 window.localStorage.getItem(key);
17
+ return globalThis.localStorage.getItem(key);
16
18
  },
17
19
  set: (key, value) => {
18
20
  if (!browser)
19
21
  return;
20
- window.localStorage.setItem(key, value);
22
+ globalThis.localStorage.setItem(key, value);
21
23
  },
22
24
  remove: (key) => {
23
25
  if (!browser)
24
26
  return;
25
- window.localStorage.removeItem(key);
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 window.sessionStorage.getItem(key);
34
+ return globalThis.sessionStorage.getItem(key);
33
35
  },
34
36
  set: (key, value) => {
35
37
  if (!browser)
36
38
  return;
37
- window.sessionStorage.setItem(key, value);
39
+ globalThis.sessionStorage.setItem(key, value);
38
40
  },
39
41
  remove: (key) => {
40
42
  if (!browser)
41
43
  return;
42
- window.sessionStorage.removeItem(key);
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 {};