rune-lab 0.2.2 → 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.
- package/README.md +39 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/money/index.d.ts +2 -0
- package/dist/core/money/index.d.ts.map +1 -0
- package/dist/core/money/index.js +2 -0
- package/dist/core/money/money.d.ts +57 -0
- package/dist/core/money/money.d.ts.map +1 -0
- package/dist/core/money/money.js +132 -0
- package/dist/index.d.ts +0 -6
- package/dist/index.js +1 -7
- package/dist/state/app.svelte.d.ts +8 -1
- package/dist/state/app.svelte.js +10 -1
- package/dist/state/auth/index.d.ts +2 -0
- package/dist/state/auth/index.js +2 -0
- package/dist/state/auth/session.svelte.d.ts +40 -0
- package/dist/state/auth/session.svelte.js +57 -0
- package/dist/state/auth/types.d.ts +30 -0
- package/dist/state/auth/types.js +3 -0
- package/dist/state/cart.svelte.d.ts +64 -0
- package/dist/state/cart.svelte.js +122 -0
- package/dist/state/composables/useMoney.d.ts +15 -0
- package/dist/state/composables/useMoney.js +62 -0
- package/dist/state/composables/useRuneLab.d.ts +3 -1
- package/dist/state/composables/useRuneLab.js +11 -0
- package/dist/state/context.d.ts +2 -0
- package/dist/state/context.js +2 -0
- package/dist/state/createConfigStore.svelte.d.ts +8 -3
- package/dist/state/createConfigStore.svelte.js +10 -0
- package/dist/state/currency.svelte.d.ts +15 -6
- package/dist/state/currency.svelte.js +58 -7
- package/dist/state/index.d.ts +7 -4
- package/dist/state/index.js +6 -2
- package/dist/state/language.svelte.d.ts +1 -0
- package/dist/state/persistence/drivers.d.ts +3 -1
- package/dist/state/persistence/drivers.js +5 -1
- package/dist/state/theme.svelte.d.ts +9 -1
- package/dist/state/theme.svelte.js +34 -8
- package/dist/ui/components/Icon.svelte +37 -15
- package/dist/ui/components/Icon.svelte.d.ts +5 -0
- package/dist/ui/components/Kyntharil.svelte +48 -0
- package/dist/ui/components/Kyntharil.svelte.d.ts +6 -0
- package/dist/ui/components/RuneProvider.svelte +64 -5
- package/dist/ui/components/RuneProvider.svelte.d.ts +21 -1
- package/dist/ui/components/money/MoneyDisplay.svelte +69 -0
- package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +21 -0
- package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -0
- package/dist/ui/components/money/MoneyDisplay.svelte.test.js +74 -0
- package/dist/ui/components/money/MoneyInput.svelte +131 -0
- package/dist/ui/components/money/MoneyInput.svelte.d.ts +25 -0
- package/dist/ui/components/money/index.d.ts +2 -0
- package/dist/ui/components/money/index.js +3 -0
- package/dist/ui/components/user/UserAvatar.svelte +66 -0
- package/dist/ui/components/user/UserAvatar.svelte.d.ts +13 -0
- package/dist/ui/components/user/UserProfile.svelte +79 -0
- package/dist/ui/components/user/UserProfile.svelte.d.ts +17 -0
- package/dist/ui/components/user/index.d.ts +2 -0
- package/dist/ui/components/user/index.js +3 -0
- package/dist/ui/features/notifications/NotificationBell.svelte +89 -0
- package/dist/ui/features/notifications/NotificationBell.svelte.d.ts +11 -0
- package/dist/ui/features/notifications/index.d.ts +1 -0
- package/dist/ui/features/notifications/index.js +2 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +11 -0
- package/dist/ui/layout/WorkspaceLayout.svelte +26 -10
- package/dist/ui/paraglide/messages/_index.d.ts +4 -0
- package/dist/ui/paraglide/messages/_index.js +4 -0
- package/dist/ui/paraglide/messages/brl3.d.ts +17 -0
- package/dist/ui/paraglide/messages/brl3.js +85 -0
- package/dist/ui/paraglide/messages/cad3.d.ts +17 -0
- package/dist/ui/paraglide/messages/cad3.js +85 -0
- package/dist/ui/paraglide/messages/gbp3.d.ts +17 -0
- package/dist/ui/paraglide/messages/gbp3.js +85 -0
- package/dist/ui/paraglide/messages/inr3.d.ts +17 -0
- package/dist/ui/paraglide/messages/inr3.js +85 -0
- package/dist/ui/primitives/DatePicker.svelte +257 -0
- package/dist/ui/primitives/DatePicker.svelte.d.ts +17 -0
- package/dist/ui/primitives/index.d.ts +1 -0
- package/dist/ui/primitives/index.js +3 -0
- package/package.json +34 -22
- package/dist/state/config.d.ts +0 -4
- package/dist/state/config.js +0 -8
package/README.md
CHANGED
|
@@ -140,6 +140,45 @@ also scan the `rune-lab` dist output:
|
|
|
140
140
|
> component classes used by `rune-lab` will be included in your build and theme
|
|
141
141
|
> switching will work across library components and your own code alike.
|
|
142
142
|
|
|
143
|
+
## Money & Currency
|
|
144
|
+
|
|
145
|
+
Rune Lab provides a robust, Dinero.js-backed money layer that handles precision
|
|
146
|
+
arithmetic and locale-aware formatting.
|
|
147
|
+
|
|
148
|
+
### MoneyDisplay
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<script>
|
|
152
|
+
import { MoneyDisplay } from "rune-lab";
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<!-- Minor units (default): $150.00 -->
|
|
156
|
+
<MoneyDisplay amount={15000} currency="USD" />
|
|
157
|
+
|
|
158
|
+
<!-- Major units: $150.00 -->
|
|
159
|
+
<MoneyDisplay amount={150} unit="major" currency="USD" />
|
|
160
|
+
|
|
161
|
+
<!-- Compact notation: $1.2M -->
|
|
162
|
+
<MoneyDisplay amount={1200000} unit="major" compact />
|
|
163
|
+
|
|
164
|
+
<!-- Null handling with fallback: "—" -->
|
|
165
|
+
<MoneyDisplay amount={null} fallback="N/A" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### MoneyInput
|
|
169
|
+
|
|
170
|
+
A masked input that prevents floating-point precision errors by working
|
|
171
|
+
exclusively with integers.
|
|
172
|
+
|
|
173
|
+
```svelte
|
|
174
|
+
<script>
|
|
175
|
+
import { MoneyInput } from "rune-lab";
|
|
176
|
+
let price = $state(150.00);
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<MoneyInput bind:amount={price} unit="major" currency="USD" />
|
|
180
|
+
```
|
|
181
|
+
|
|
143
182
|
## Persistence Drivers
|
|
144
183
|
|
|
145
184
|
Rune Lab provides built-in drivers to remember user preferences (like theme,
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAE5C,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAE5C,cAAc,qBAAqB,CAAC;AAEpC,cAAc,eAAe,CAAC"}
|
package/dist/core/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sdk/core/src/money/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,YAAY,EACZ,WAAW,EACX,KAAK,WAAW,EAChB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Dinero, type DineroCurrency } from "dinero.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map of ISO 4217 currency codes to Dinero currency objects
|
|
4
|
+
*/
|
|
5
|
+
export declare const CURRENCY_MAP: Record<string, DineroCurrency<number>>;
|
|
6
|
+
/**
|
|
7
|
+
* Derived type of all built-in ISO 4217 currency codes.
|
|
8
|
+
* Enables IDE autocomplete while allowing any string for dynamic currencies.
|
|
9
|
+
*/
|
|
10
|
+
export type ISO4217Code = keyof typeof CURRENCY_MAP;
|
|
11
|
+
/**
|
|
12
|
+
* Register a new currency in CURRENCY_MAP atomically.
|
|
13
|
+
* Ensures the core registry and any store-level registries stay in sync.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerCurrency(code: string, currency: DineroCurrency<number>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes an unknown input into a valid integer for Dinero.js.
|
|
18
|
+
* Exported so consumers can apply the same defensive coercion.
|
|
19
|
+
*/
|
|
20
|
+
export declare function safeAmount(amount: unknown): number;
|
|
21
|
+
/**
|
|
22
|
+
* Converts a major-unit amount (e.g., pesos) to a minor-unit integer (e.g., centavos).
|
|
23
|
+
* Uses the currency's exponent from CURRENCY_MAP.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toMinorUnit(amount: unknown, currencyCode: ISO4217Code | string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Create a Dinero monetary value from an amount.
|
|
28
|
+
* @param amount - Amount (normalized internally via safeAmount)
|
|
29
|
+
* @param currencyCode - ISO 4217 currency code (e.g., "USD")
|
|
30
|
+
*/
|
|
31
|
+
export declare function createMoney(amount: unknown, currencyCode: ISO4217Code | string): Dinero<number>;
|
|
32
|
+
/**
|
|
33
|
+
* Format a Dinero value as a locale-aware string
|
|
34
|
+
* @param money - Dinero monetary value
|
|
35
|
+
* @param locale - BCP 47 locale string (e.g., "en-US", "es-MX")
|
|
36
|
+
* @param currencyCode - ISO 4217 code for Intl.NumberFormat (e.g., "USD")
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatMoney(money: Dinero<number>, locale?: string, currencyCode?: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Format a raw amount as a locale-aware currency string.
|
|
41
|
+
* Convenience wrapper around createMoney + formatMoney.
|
|
42
|
+
*/
|
|
43
|
+
export declare function formatAmount(amount: unknown, currencyCode: ISO4217Code | string, locale?: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Add two monetary values (must be same currency)
|
|
46
|
+
*/
|
|
47
|
+
export declare function addMoney(a: Dinero<number>, b: Dinero<number>): Dinero<number>;
|
|
48
|
+
/**
|
|
49
|
+
* Subtract two monetary values (must be same currency)
|
|
50
|
+
*/
|
|
51
|
+
export declare function subtractMoney(a: Dinero<number>, b: Dinero<number>): Dinero<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Multiply a monetary value by a factor
|
|
54
|
+
*/
|
|
55
|
+
export declare function multiplyMoney(money: Dinero<number>, factor: number): Dinero<number>;
|
|
56
|
+
export type { Dinero, DineroCurrency };
|
|
57
|
+
//# sourceMappingURL=money.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../../src/sdk/core/src/money/money.ts"],"names":[],"mappings":"AAIA,OAAO,EAML,KAAK,MAAM,EAEX,KAAK,cAAc,EAYpB,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAY/D,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,YAAY,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,GAC/B,IAAI,CAEN;AA2BD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,WAAW,GAAG,MAAM,GACjC,MAAM,CAUR;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,WAAW,GAAG,MAAM,GACjC,MAAM,CAAC,MAAM,CAAC,CAQhB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EACrB,MAAM,GAAE,MAAgB,EACxB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAeR;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,WAAW,GAAG,MAAM,EAClC,MAAM,GAAE,MAAgB,GACvB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EACjB,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAChB,MAAM,CAAC,MAAM,CAAC,CAEhB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EACjB,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAChB,MAAM,CAAC,MAAM,CAAC,CAEhB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EACrB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,CAAC,CAEhB;AAGD,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// sdk/core/src/money/money.ts
|
|
2
|
+
// Framework-agnostic money utilities using Dinero.js v2
|
|
3
|
+
// Consumers should never import Dinero directly — these helpers encapsulate it.
|
|
4
|
+
import { add as dineroAdd, AED, BRL, CAD, CNY, dinero, EUR, GBP, INR, JPY, KRW, multiply as dineroMultiply, MXN, subtract as dineroSubtract, toDecimal,
|
|
5
|
+
// ISO 4217 currency definitions
|
|
6
|
+
USD, } from "dinero.js";
|
|
7
|
+
/**
|
|
8
|
+
* Map of ISO 4217 currency codes to Dinero currency objects
|
|
9
|
+
*/
|
|
10
|
+
export const CURRENCY_MAP = {
|
|
11
|
+
USD,
|
|
12
|
+
EUR,
|
|
13
|
+
MXN,
|
|
14
|
+
JPY,
|
|
15
|
+
KRW,
|
|
16
|
+
CNY,
|
|
17
|
+
AED,
|
|
18
|
+
GBP,
|
|
19
|
+
CAD,
|
|
20
|
+
BRL,
|
|
21
|
+
INR,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Register a new currency in CURRENCY_MAP atomically.
|
|
25
|
+
* Ensures the core registry and any store-level registries stay in sync.
|
|
26
|
+
*/
|
|
27
|
+
export function registerCurrency(code, currency) {
|
|
28
|
+
CURRENCY_MAP[code] = currency;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Normalizes an unknown input into a valid finite number.
|
|
32
|
+
* Handles null, undefined, bigints, and SurrealDB Decimal objects via toString().
|
|
33
|
+
*/
|
|
34
|
+
function toNumber(amount) {
|
|
35
|
+
if (amount === null || amount === undefined)
|
|
36
|
+
return 0;
|
|
37
|
+
if (typeof amount === "number")
|
|
38
|
+
return Number.isFinite(amount) ? amount : 0;
|
|
39
|
+
if (typeof amount === "bigint")
|
|
40
|
+
return Number(amount);
|
|
41
|
+
// Handle SurrealDB Decimal (which has a toString method) or other objects
|
|
42
|
+
if (typeof amount === "object") {
|
|
43
|
+
try {
|
|
44
|
+
// SurrealDB Decimal.toString() returns a numeric string
|
|
45
|
+
const str = String(amount);
|
|
46
|
+
const parsed = parseFloat(str);
|
|
47
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const coerced = Number(amount);
|
|
54
|
+
return Number.isFinite(coerced) ? coerced : 0;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Normalizes an unknown input into a valid integer for Dinero.js.
|
|
58
|
+
* Exported so consumers can apply the same defensive coercion.
|
|
59
|
+
*/
|
|
60
|
+
export function safeAmount(amount) {
|
|
61
|
+
return Math.round(toNumber(amount));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Converts a major-unit amount (e.g., pesos) to a minor-unit integer (e.g., centavos).
|
|
65
|
+
* Uses the currency's exponent from CURRENCY_MAP.
|
|
66
|
+
*/
|
|
67
|
+
export function toMinorUnit(amount, currencyCode) {
|
|
68
|
+
const currency = CURRENCY_MAP[currencyCode];
|
|
69
|
+
if (!currency) {
|
|
70
|
+
throw new Error(`Unknown currency code: ${currencyCode}. Register it first via registerCurrency().`);
|
|
71
|
+
}
|
|
72
|
+
const base = Array.isArray(currency.base) ? currency.base[0] : currency.base;
|
|
73
|
+
const factor = Math.pow(Number(base), Number(currency.exponent));
|
|
74
|
+
return Math.round(toNumber(amount) * factor);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a Dinero monetary value from an amount.
|
|
78
|
+
* @param amount - Amount (normalized internally via safeAmount)
|
|
79
|
+
* @param currencyCode - ISO 4217 currency code (e.g., "USD")
|
|
80
|
+
*/
|
|
81
|
+
export function createMoney(amount, currencyCode) {
|
|
82
|
+
const currency = CURRENCY_MAP[currencyCode];
|
|
83
|
+
if (!currency) {
|
|
84
|
+
throw new Error(`Unknown currency code: ${currencyCode}. Register it first via registerCurrency().`);
|
|
85
|
+
}
|
|
86
|
+
return dinero({ amount: safeAmount(amount), currency });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Format a Dinero value as a locale-aware string
|
|
90
|
+
* @param money - Dinero monetary value
|
|
91
|
+
* @param locale - BCP 47 locale string (e.g., "en-US", "es-MX")
|
|
92
|
+
* @param currencyCode - ISO 4217 code for Intl.NumberFormat (e.g., "USD")
|
|
93
|
+
*/
|
|
94
|
+
export function formatMoney(money, locale = "en-US", currencyCode) {
|
|
95
|
+
return toDecimal(money, ({ value, currency }) => {
|
|
96
|
+
const code = currencyCode ??
|
|
97
|
+
Object.entries(CURRENCY_MAP).find(([, c]) => c.code === currency.code)?.[0] ??
|
|
98
|
+
currency.code;
|
|
99
|
+
return new Intl.NumberFormat(locale, {
|
|
100
|
+
style: "currency",
|
|
101
|
+
currency: code,
|
|
102
|
+
minimumFractionDigits: currency.exponent,
|
|
103
|
+
maximumFractionDigits: currency.exponent,
|
|
104
|
+
}).format(Number(value));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Format a raw amount as a locale-aware currency string.
|
|
109
|
+
* Convenience wrapper around createMoney + formatMoney.
|
|
110
|
+
*/
|
|
111
|
+
export function formatAmount(amount, currencyCode, locale = "en-US") {
|
|
112
|
+
const money = createMoney(amount, currencyCode);
|
|
113
|
+
return formatMoney(money, locale, currencyCode);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Add two monetary values (must be same currency)
|
|
117
|
+
*/
|
|
118
|
+
export function addMoney(a, b) {
|
|
119
|
+
return dineroAdd(a, b);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Subtract two monetary values (must be same currency)
|
|
123
|
+
*/
|
|
124
|
+
export function subtractMoney(a, b) {
|
|
125
|
+
return dineroSubtract(a, b);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Multiply a monetary value by a factor
|
|
129
|
+
*/
|
|
130
|
+
export function multiplyMoney(money, factor) {
|
|
131
|
+
return dineroMultiply(money, factor);
|
|
132
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,10 +2,4 @@ export * from "rune-lab/core";
|
|
|
2
2
|
export * from "rune-lab/state";
|
|
3
3
|
export * from "rune-lab/ui";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
cookieDriver,
|
|
7
|
-
localStorageDriver,
|
|
8
|
-
sessionStorageDriver,
|
|
9
|
-
} from "rune-lab/state";
|
|
10
|
-
|
|
11
|
-
export const version = () => "0.2.2";
|
|
5
|
+
export const version = () => "0.3.0";
|
|
@@ -25,9 +25,16 @@ export declare class AppStore {
|
|
|
25
25
|
homepage: string;
|
|
26
26
|
customIcons: Record<string, string>;
|
|
27
27
|
/**
|
|
28
|
-
* Initialize app store with metadata
|
|
28
|
+
* Initialize app store with metadata.
|
|
29
|
+
*
|
|
30
|
+
* @contract init() is idempotent. Call it once at app startup via RuneProvider.
|
|
31
|
+
* Subsequent calls are silently ignored to maintain stability across SSR/CSR cycles.
|
|
29
32
|
*/
|
|
30
33
|
init(data: Partial<AppData>): void;
|
|
34
|
+
/**
|
|
35
|
+
* @internal Test-only. Resets initialization guard.
|
|
36
|
+
*/
|
|
37
|
+
__reset(): void;
|
|
31
38
|
/**
|
|
32
39
|
* Get full app information object
|
|
33
40
|
*/
|
package/dist/state/app.svelte.js
CHANGED
|
@@ -18,7 +18,10 @@ export class AppStore {
|
|
|
18
18
|
customIcons = $state({});
|
|
19
19
|
#initialized = false;
|
|
20
20
|
/**
|
|
21
|
-
* Initialize app store with metadata
|
|
21
|
+
* Initialize app store with metadata.
|
|
22
|
+
*
|
|
23
|
+
* @contract init() is idempotent. Call it once at app startup via RuneProvider.
|
|
24
|
+
* Subsequent calls are silently ignored to maintain stability across SSR/CSR cycles.
|
|
22
25
|
*/
|
|
23
26
|
init(data) {
|
|
24
27
|
if (this.#initialized) {
|
|
@@ -43,6 +46,12 @@ export class AppStore {
|
|
|
43
46
|
this.homepage = data.homepage;
|
|
44
47
|
this.#initialized = true;
|
|
45
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* @internal Test-only. Resets initialization guard.
|
|
51
|
+
*/
|
|
52
|
+
__reset() {
|
|
53
|
+
this.#initialized = false;
|
|
54
|
+
}
|
|
46
55
|
/**
|
|
47
56
|
* Get full app information object
|
|
48
57
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Session, User } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* SessionStore — reactive authentication state.
|
|
4
|
+
*
|
|
5
|
+
* Once Better-Auth is fully integrated, this store will:
|
|
6
|
+
* - Initialize from the Better-Auth client SDK
|
|
7
|
+
* - Manage session lifecycle (login, logout, refresh)
|
|
8
|
+
* - Expose the current user + session tokens
|
|
9
|
+
* - Fire onSessionChange callbacks
|
|
10
|
+
*
|
|
11
|
+
* For now, it provides the interface contract that consuming apps
|
|
12
|
+
* and other rune-lab components (UserAvatar, UserProfile) can code against.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SessionStore {
|
|
15
|
+
/** Whether authentication has been checked (vs. loading) */
|
|
16
|
+
isReady: boolean;
|
|
17
|
+
/** Whether a user is currently authenticated */
|
|
18
|
+
isAuthenticated: boolean;
|
|
19
|
+
/** Current user (null when logged out) */
|
|
20
|
+
user: User | null;
|
|
21
|
+
/** Current session (null when logged out) */
|
|
22
|
+
session: Session | null;
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the session — placeholder for Better-Auth client setup
|
|
25
|
+
*/
|
|
26
|
+
init(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Log in — placeholder
|
|
29
|
+
*/
|
|
30
|
+
login(_credentials: {
|
|
31
|
+
email: string;
|
|
32
|
+
password: string;
|
|
33
|
+
}): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Log out — placeholder
|
|
36
|
+
*/
|
|
37
|
+
logout(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createSessionStore(): SessionStore;
|
|
40
|
+
export declare function getSessionStore(): SessionStore;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// sdk/state/src/auth/session.svelte.ts
|
|
2
|
+
// SessionStore — manages authentication state via Svelte 5 runes.
|
|
3
|
+
// This is a SKELETON — full Better-Auth wiring is deferred to a dedicated spec.
|
|
4
|
+
import { getContext } from "svelte";
|
|
5
|
+
import { RUNE_LAB_CONTEXT } from "../context";
|
|
6
|
+
/**
|
|
7
|
+
* SessionStore — reactive authentication state.
|
|
8
|
+
*
|
|
9
|
+
* Once Better-Auth is fully integrated, this store will:
|
|
10
|
+
* - Initialize from the Better-Auth client SDK
|
|
11
|
+
* - Manage session lifecycle (login, logout, refresh)
|
|
12
|
+
* - Expose the current user + session tokens
|
|
13
|
+
* - Fire onSessionChange callbacks
|
|
14
|
+
*
|
|
15
|
+
* For now, it provides the interface contract that consuming apps
|
|
16
|
+
* and other rune-lab components (UserAvatar, UserProfile) can code against.
|
|
17
|
+
*/
|
|
18
|
+
export class SessionStore {
|
|
19
|
+
/** Whether authentication has been checked (vs. loading) */
|
|
20
|
+
isReady = $state(false);
|
|
21
|
+
/** Whether a user is currently authenticated */
|
|
22
|
+
isAuthenticated = $state(false);
|
|
23
|
+
/** Current user (null when logged out) */
|
|
24
|
+
user = $state(null);
|
|
25
|
+
/** Current session (null when logged out) */
|
|
26
|
+
session = $state(null);
|
|
27
|
+
/**
|
|
28
|
+
* Initialize the session — placeholder for Better-Auth client setup
|
|
29
|
+
*/
|
|
30
|
+
async init() {
|
|
31
|
+
// TODO: Wire Better-Auth client.getSession() here
|
|
32
|
+
this.isReady = true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Log in — placeholder
|
|
36
|
+
*/
|
|
37
|
+
async login(_credentials) {
|
|
38
|
+
// TODO: Wire Better-Auth client.signIn.email() here
|
|
39
|
+
console.warn("SessionStore.login() is a stub — implement Better-Auth integration");
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Log out — placeholder
|
|
44
|
+
*/
|
|
45
|
+
async logout() {
|
|
46
|
+
// TODO: Wire Better-Auth client.signOut() here
|
|
47
|
+
this.user = null;
|
|
48
|
+
this.session = null;
|
|
49
|
+
this.isAuthenticated = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function createSessionStore() {
|
|
53
|
+
return new SessionStore();
|
|
54
|
+
}
|
|
55
|
+
export function getSessionStore() {
|
|
56
|
+
return getContext(RUNE_LAB_CONTEXT.session);
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal user representation for rune-lab's auth layer.
|
|
3
|
+
* Consuming apps can extend this with domain-specific fields.
|
|
4
|
+
*/
|
|
5
|
+
export interface User {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
email: string;
|
|
9
|
+
avatar_url?: string;
|
|
10
|
+
role?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Active session information
|
|
14
|
+
*/
|
|
15
|
+
export interface Session {
|
|
16
|
+
user: User;
|
|
17
|
+
accessToken: string;
|
|
18
|
+
expiresAt: Date;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Auth configuration passed to RuneProvider
|
|
22
|
+
*/
|
|
23
|
+
export interface AuthConfig {
|
|
24
|
+
/** Better-Auth client instance */
|
|
25
|
+
client?: any;
|
|
26
|
+
/** Callback when session changes (login, logout, expiry) */
|
|
27
|
+
onSessionChange?: (session: Session | null) => void;
|
|
28
|
+
/** Routes that require authentication */
|
|
29
|
+
protectedRoutes?: string[];
|
|
30
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PersistenceDriver } from "rune-lab/core";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for creating a CartStore
|
|
4
|
+
*/
|
|
5
|
+
export interface CartStoreConfig<T> {
|
|
6
|
+
/** Extract a unique identifier from an item */
|
|
7
|
+
idExtractor: (item: T) => string;
|
|
8
|
+
/** Extract the price (in minor units / cents) from an item */
|
|
9
|
+
priceExtractor: (item: T) => number;
|
|
10
|
+
/** Optional persistence driver for cart recovery */
|
|
11
|
+
driver?: PersistenceDriver;
|
|
12
|
+
/** Storage key for persistence */
|
|
13
|
+
storageKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CartEntry<T> {
|
|
16
|
+
item: T;
|
|
17
|
+
qty: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generic cart store factory.
|
|
21
|
+
* Works with any item type via extractors.
|
|
22
|
+
*
|
|
23
|
+
* **Note**: Consumers must call `setContext(RUNE_LAB_CONTEXT.cart, store)` manually
|
|
24
|
+
* since CartStore is app-specific (needs `idExtractor` and `priceExtractor`).
|
|
25
|
+
* RuneProvider can optionally wire it via `config.cart`.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* const cart = createCartStore<Property>({
|
|
29
|
+
* idExtractor: (p) => p.id,
|
|
30
|
+
* priceExtractor: (p) => p.price,
|
|
31
|
+
* });
|
|
32
|
+
* cart.add(someProperty);
|
|
33
|
+
* cart.totalPrice; // sum of all items * qty in minor units
|
|
34
|
+
*/
|
|
35
|
+
export declare function createCartStore<T>(config: CartStoreConfig<T>): {
|
|
36
|
+
/** All cart entries */
|
|
37
|
+
readonly items: CartEntry<T>[];
|
|
38
|
+
/** Total number of items (sum of quantities) */
|
|
39
|
+
readonly totalItems: number;
|
|
40
|
+
/** Total price in minor units */
|
|
41
|
+
readonly totalPrice: number;
|
|
42
|
+
/** Whether the cart is empty */
|
|
43
|
+
readonly isEmpty: boolean;
|
|
44
|
+
/** Add an item (or increment quantity if already present) */
|
|
45
|
+
add(item: T, qty?: number): void;
|
|
46
|
+
/** Remove an item entirely */
|
|
47
|
+
remove(id: string): void;
|
|
48
|
+
/** Update the quantity of an item (removes if qty ≤ 0) */
|
|
49
|
+
updateQty(id: string, qty: number): void;
|
|
50
|
+
/** Clear all items */
|
|
51
|
+
clear(): void;
|
|
52
|
+
/** Check if an item is in the cart */
|
|
53
|
+
has(id: string): boolean;
|
|
54
|
+
/** Get a specific entry by ID */
|
|
55
|
+
getEntry(id: string): CartEntry<T> | undefined;
|
|
56
|
+
};
|
|
57
|
+
/** Return type of createCartStore for context typing */
|
|
58
|
+
export type CartStore<T = unknown> = ReturnType<typeof createCartStore<T>>;
|
|
59
|
+
/**
|
|
60
|
+
* Get CartStore from Svelte context.
|
|
61
|
+
* Only works if the consuming app has called setContext(RUNE_LAB_CONTEXT.cart, store)
|
|
62
|
+
* or passed a cart config to RuneProvider.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getCartStore<T = unknown>(): CartStore<T>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// sdk/state/src/cart.svelte.ts
|
|
2
|
+
// Generic cart store factory — business-agnostic add/remove/total pattern.
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
import { RUNE_LAB_CONTEXT } from "./context";
|
|
5
|
+
import { DEV } from "esm-env";
|
|
6
|
+
/**
|
|
7
|
+
* Generic cart store factory.
|
|
8
|
+
* Works with any item type via extractors.
|
|
9
|
+
*
|
|
10
|
+
* **Note**: Consumers must call `setContext(RUNE_LAB_CONTEXT.cart, store)` manually
|
|
11
|
+
* since CartStore is app-specific (needs `idExtractor` and `priceExtractor`).
|
|
12
|
+
* RuneProvider can optionally wire it via `config.cart`.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const cart = createCartStore<Property>({
|
|
16
|
+
* idExtractor: (p) => p.id,
|
|
17
|
+
* priceExtractor: (p) => p.price,
|
|
18
|
+
* });
|
|
19
|
+
* cart.add(someProperty);
|
|
20
|
+
* cart.totalPrice; // sum of all items * qty in minor units
|
|
21
|
+
*/
|
|
22
|
+
export function createCartStore(config) {
|
|
23
|
+
const { idExtractor, priceExtractor, driver, storageKey = "cart", } = config;
|
|
24
|
+
// Internal entries store — using a plain array for correct serialization
|
|
25
|
+
let _entries = $state([]);
|
|
26
|
+
/** Lookup by ID */
|
|
27
|
+
function _findIndex(id) {
|
|
28
|
+
return _entries.findIndex((e) => idExtractor(e.item) === id);
|
|
29
|
+
}
|
|
30
|
+
/** @internal Persist current state (Map serialized as Array) */
|
|
31
|
+
function _persist() {
|
|
32
|
+
if (!driver)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
const data = _entries.map((entry) => ({
|
|
36
|
+
id: idExtractor(entry.item),
|
|
37
|
+
qty: entry.qty,
|
|
38
|
+
}));
|
|
39
|
+
driver.set(storageKey, JSON.stringify(data));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Persistence is best-effort
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
/** All cart entries */
|
|
47
|
+
get items() {
|
|
48
|
+
return _entries;
|
|
49
|
+
},
|
|
50
|
+
/** Total number of items (sum of quantities) */
|
|
51
|
+
get totalItems() {
|
|
52
|
+
let total = 0;
|
|
53
|
+
for (const entry of _entries)
|
|
54
|
+
total += entry.qty;
|
|
55
|
+
return total;
|
|
56
|
+
},
|
|
57
|
+
/** Total price in minor units */
|
|
58
|
+
get totalPrice() {
|
|
59
|
+
let total = 0;
|
|
60
|
+
for (const entry of _entries) {
|
|
61
|
+
total += priceExtractor(entry.item) * entry.qty;
|
|
62
|
+
}
|
|
63
|
+
return total;
|
|
64
|
+
},
|
|
65
|
+
/** Whether the cart is empty */
|
|
66
|
+
get isEmpty() {
|
|
67
|
+
return _entries.length === 0;
|
|
68
|
+
},
|
|
69
|
+
/** Add an item (or increment quantity if already present) */
|
|
70
|
+
add(item, qty = 1) {
|
|
71
|
+
const id = idExtractor(item);
|
|
72
|
+
const idx = _findIndex(id);
|
|
73
|
+
if (idx >= 0) {
|
|
74
|
+
_entries[idx] = { ..._entries[idx], qty: _entries[idx].qty + qty };
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
_entries = [..._entries, { item, qty }];
|
|
78
|
+
}
|
|
79
|
+
_persist();
|
|
80
|
+
if (DEV)
|
|
81
|
+
console.log(`🛒 Added ${id} (qty: ${qty})`);
|
|
82
|
+
},
|
|
83
|
+
/** Remove an item entirely */
|
|
84
|
+
remove(id) {
|
|
85
|
+
_entries = _entries.filter((e) => idExtractor(e.item) !== id);
|
|
86
|
+
_persist();
|
|
87
|
+
},
|
|
88
|
+
/** Update the quantity of an item (removes if qty ≤ 0) */
|
|
89
|
+
updateQty(id, qty) {
|
|
90
|
+
if (qty <= 0) {
|
|
91
|
+
this.remove(id);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const idx = _findIndex(id);
|
|
95
|
+
if (idx >= 0) {
|
|
96
|
+
_entries[idx] = { ..._entries[idx], qty };
|
|
97
|
+
_persist();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
/** Clear all items */
|
|
101
|
+
clear() {
|
|
102
|
+
_entries = [];
|
|
103
|
+
_persist();
|
|
104
|
+
},
|
|
105
|
+
/** Check if an item is in the cart */
|
|
106
|
+
has(id) {
|
|
107
|
+
return _findIndex(id) >= 0;
|
|
108
|
+
},
|
|
109
|
+
/** Get a specific entry by ID */
|
|
110
|
+
getEntry(id) {
|
|
111
|
+
return _entries.find((e) => idExtractor(e.item) === id);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get CartStore from Svelte context.
|
|
117
|
+
* Only works if the consuming app has called setContext(RUNE_LAB_CONTEXT.cart, store)
|
|
118
|
+
* or passed a cart config to RuneProvider.
|
|
119
|
+
*/
|
|
120
|
+
export function getCartStore() {
|
|
121
|
+
return getContext(RUNE_LAB_CONTEXT.cart);
|
|
122
|
+
}
|