@doswiftly/storefront-sdk 11.2.0 → 11.3.1
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/CHANGELOG.md +98 -0
- package/README.md +214 -2
- package/dist/core/auth/handlers.d.ts.map +1 -1
- package/dist/core/auth/handlers.js +29 -1
- package/dist/core/bot-protection/turnstile-manager.d.ts +0 -1
- package/dist/core/bot-protection/turnstile-manager.d.ts.map +1 -1
- package/dist/core/bot-protection/turnstile-manager.js +0 -1
- package/dist/react/components/AddToCartButton.d.ts +49 -0
- package/dist/react/components/AddToCartButton.d.ts.map +1 -0
- package/dist/react/components/AddToCartButton.js +47 -0
- package/dist/react/components/CartCount.d.ts +35 -0
- package/dist/react/components/CartCount.d.ts.map +1 -0
- package/dist/react/components/CartCount.js +23 -0
- package/dist/react/components/CartTotals.d.ts +54 -0
- package/dist/react/components/CartTotals.d.ts.map +1 -0
- package/dist/react/components/CartTotals.js +38 -0
- package/dist/react/components/Image.d.ts +42 -0
- package/dist/react/components/Image.d.ts.map +1 -0
- package/dist/react/components/Image.js +33 -0
- package/dist/react/components/Money.d.ts +32 -0
- package/dist/react/components/Money.d.ts.map +1 -0
- package/dist/react/components/Money.js +27 -0
- package/dist/react/components/PriceDisplay.d.ts +34 -0
- package/dist/react/components/PriceDisplay.d.ts.map +1 -0
- package/dist/react/components/PriceDisplay.js +21 -0
- package/dist/react/components/index.d.ts +15 -0
- package/dist/react/components/index.d.ts.map +1 -0
- package/dist/react/components/index.js +14 -0
- package/dist/react/hooks/use-auth.d.ts +19 -46
- package/dist/react/hooks/use-auth.d.ts.map +1 -1
- package/dist/react/hooks/use-auth.js +24 -141
- package/dist/react/hooks/use-cart-manager.d.ts +34 -0
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
- package/dist/react/hooks/use-cart-manager.js +22 -19
- package/dist/react/hooks/use-login.d.ts +40 -0
- package/dist/react/hooks/use-login.d.ts.map +1 -0
- package/dist/react/hooks/use-login.js +75 -0
- package/dist/react/hooks/use-logout.d.ts +40 -0
- package/dist/react/hooks/use-logout.d.ts.map +1 -0
- package/dist/react/hooks/use-logout.js +50 -0
- package/dist/react/hooks/use-refresh-token.d.ts +40 -0
- package/dist/react/hooks/use-refresh-token.d.ts.map +1 -0
- package/dist/react/hooks/use-refresh-token.js +66 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -0
- package/dist/react/server/get-storefront-client.d.ts +15 -5
- package/dist/react/server/get-storefront-client.d.ts.map +1 -1
- package/dist/react/stores/cart.store.d.ts.map +1 -1
- package/dist/react/stores/cart.store.js +0 -7
- package/dist/react/stores/store-context.d.ts.map +1 -1
- package/dist/react/stores/store-context.js +0 -2
- package/package.json +5 -1
- package/dist/__tests__/unit/test-helpers.d.ts +0 -46
- package/dist/__tests__/unit/test-helpers.d.ts.map +0 -1
- package/dist/__tests__/unit/test-helpers.js +0 -72
- package/dist/__tests__/unit/use-cart-manager.test.d.ts +0 -2
- package/dist/__tests__/unit/use-cart-manager.test.d.ts.map +0 -1
- package/dist/__tests__/unit/use-cart-manager.test.js +0 -400
|
@@ -42,8 +42,7 @@ import { createCartRecoveryRunner, recreateWithInput, } from '../../core/cart/ca
|
|
|
42
42
|
import { createBrowserCartCookieStore } from '../cookies';
|
|
43
43
|
export function useCartManager() {
|
|
44
44
|
const { cartClient } = useStorefrontClientContext();
|
|
45
|
-
const [
|
|
46
|
-
const [error, setError] = useState(null);
|
|
45
|
+
const [status, setStatus] = useState({ type: 'idle' });
|
|
47
46
|
// Cookie store is stateless — keep one instance per hook mount.
|
|
48
47
|
const cookieStore = useMemo(() => createBrowserCartCookieStore(), []);
|
|
49
48
|
// Recovery runner is bound to cartClient identity (changes only on provider
|
|
@@ -55,64 +54,67 @@ export function useCartManager() {
|
|
|
55
54
|
// `useEffect(() => onExpired(...), [onExpired])` so React re-subscribes when
|
|
56
55
|
// the provider (and thus the runner) remounts.
|
|
57
56
|
const onExpired = useCallback((listener) => runner.onExpired(listener), [runner]);
|
|
58
|
-
// Generic mutation wrapper —
|
|
59
|
-
// semantics to the runner.
|
|
60
|
-
const wrapMutation = useCallback(async (run, failureFallbackMessage) => {
|
|
61
|
-
|
|
62
|
-
setIsLoading(true);
|
|
57
|
+
// Generic mutation wrapper — drives the tagged-union status. Delegates
|
|
58
|
+
// recovery semantics to the runner (which fires onExpired separately).
|
|
59
|
+
const wrapMutation = useCallback(async (operation, run, failureFallbackMessage) => {
|
|
60
|
+
setStatus({ type: 'loading', operation });
|
|
63
61
|
try {
|
|
64
|
-
|
|
62
|
+
const result = await run();
|
|
63
|
+
setStatus({ type: 'success', operation });
|
|
64
|
+
return result;
|
|
65
65
|
}
|
|
66
66
|
catch (err) {
|
|
67
|
-
|
|
67
|
+
const error = err instanceof Error ? err : new Error(failureFallbackMessage);
|
|
68
|
+
setStatus({ type: 'error', operation, error });
|
|
68
69
|
throw err;
|
|
69
70
|
}
|
|
70
|
-
finally {
|
|
71
|
-
setIsLoading(false);
|
|
72
|
-
}
|
|
73
71
|
}, []);
|
|
74
72
|
// --- Read ---
|
|
75
73
|
const getCart = useCallback(() => runner.getCart(), [runner]);
|
|
76
74
|
const getCartId = useCallback(() => cookieStore.get(), [cookieStore]);
|
|
77
75
|
// --- Auto-replay mutations ---
|
|
78
|
-
const addItem = useCallback((lines) => wrapMutation(() => runner.execute({
|
|
76
|
+
const addItem = useCallback((lines) => wrapMutation('addItem', () => runner.execute({
|
|
79
77
|
name: 'addItem',
|
|
80
78
|
run: (cartId) => cartClient.addItems(cartId, lines),
|
|
81
79
|
recreateAndRun: recreateWithInput({ lines }),
|
|
82
80
|
}), 'Failed to add to cart'), [runner, cartClient, wrapMutation]);
|
|
83
|
-
const updateBuyerIdentity = useCallback((buyerIdentity) => wrapMutation(() => runner.execute({
|
|
81
|
+
const updateBuyerIdentity = useCallback((buyerIdentity) => wrapMutation('updateBuyerIdentity', () => runner.execute({
|
|
84
82
|
name: 'updateBuyerIdentity',
|
|
85
83
|
run: (cartId) => cartClient.updateBuyerIdentity(cartId, buyerIdentity),
|
|
86
84
|
recreateAndRun: recreateWithInput({ buyerIdentity }),
|
|
87
85
|
}), 'Failed to update buyer identity'), [runner, cartClient, wrapMutation]);
|
|
88
|
-
const setShippingAddress = useCallback((address) => wrapMutation(() => runner.execute({
|
|
86
|
+
const setShippingAddress = useCallback((address) => wrapMutation('setShippingAddress', () => runner.execute({
|
|
89
87
|
name: 'setShippingAddress',
|
|
90
88
|
run: (cartId) => cartClient.setShippingAddress({ cartId, address }),
|
|
91
89
|
recreateAndRun: recreateWithInput({ shippingAddress: address }),
|
|
92
90
|
}), 'Failed to set shipping address'), [runner, cartClient, wrapMutation]);
|
|
93
|
-
const updateDiscountCodes = useCallback((codes) => wrapMutation(() => runner.execute({
|
|
91
|
+
const updateDiscountCodes = useCallback((codes) => wrapMutation('updateDiscountCodes', () => runner.execute({
|
|
94
92
|
name: 'updateDiscountCodes',
|
|
95
93
|
run: (cartId) => cartClient.updateDiscountCodes(cartId, codes),
|
|
96
94
|
recreateAndRun: recreateWithInput({ discountCodes: codes }),
|
|
97
95
|
}), 'Failed to update discount codes'), [runner, cartClient, wrapMutation]);
|
|
98
|
-
const updateNote = useCallback((note) => wrapMutation(() => runner.execute({
|
|
96
|
+
const updateNote = useCallback((note) => wrapMutation('updateNote', () => runner.execute({
|
|
99
97
|
name: 'updateNote',
|
|
100
98
|
run: (cartId) => cartClient.updateNote(cartId, note),
|
|
101
99
|
recreateAndRun: recreateWithInput({ note }),
|
|
102
100
|
}), 'Failed to update note'), [runner, cartClient, wrapMutation]);
|
|
103
101
|
// --- Bail-on-stale mutations (no recreateAndRun — fires onExpired + throws) ---
|
|
104
|
-
const updateItem = useCallback((lines) => wrapMutation(() => runner.execute({
|
|
102
|
+
const updateItem = useCallback((lines) => wrapMutation('updateItem', () => runner.execute({
|
|
105
103
|
name: 'updateItem',
|
|
106
104
|
run: (cartId) => cartClient.updateItems(cartId, lines),
|
|
107
105
|
}), 'Failed to update cart'), [runner, cartClient, wrapMutation]);
|
|
108
|
-
const removeItem = useCallback((lineIds) => wrapMutation(() => runner.execute({
|
|
106
|
+
const removeItem = useCallback((lineIds) => wrapMutation('removeItem', () => runner.execute({
|
|
109
107
|
name: 'removeItem',
|
|
110
108
|
run: (cartId) => cartClient.removeItems(cartId, lineIds),
|
|
111
109
|
}), 'Failed to remove from cart'), [runner, cartClient, wrapMutation]);
|
|
112
110
|
// --- Lifecycle ---
|
|
113
111
|
const clearCart = useCallback(() => {
|
|
114
112
|
cookieStore.clear();
|
|
113
|
+
setStatus({ type: 'idle' });
|
|
115
114
|
}, [cookieStore]);
|
|
115
|
+
// Backward-compatible derived selectors over the tagged-union status.
|
|
116
|
+
const isLoading = status.type === 'loading';
|
|
117
|
+
const error = status.type === 'error' ? status.error.message : null;
|
|
116
118
|
return {
|
|
117
119
|
getCart,
|
|
118
120
|
getCartId,
|
|
@@ -125,6 +127,7 @@ export function useCartManager() {
|
|
|
125
127
|
removeItem,
|
|
126
128
|
clearCart,
|
|
127
129
|
onExpired,
|
|
130
|
+
status,
|
|
128
131
|
isLoading,
|
|
129
132
|
error,
|
|
130
133
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLogin — focused hook for the login flow.
|
|
3
|
+
*
|
|
4
|
+
* Composes the auth client + Zustand store + httpOnly cookie BFF callback.
|
|
5
|
+
* Returns `success`/`userErrors` rather than throwing on credential errors —
|
|
6
|
+
* standard form submission pattern.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { login, isLoggingIn, error } = useLogin({ onSetToken });
|
|
11
|
+
* const result = await login(email, password);
|
|
12
|
+
* if (!result.success) showErrors(result.userErrors);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface UseLoginOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Called after successful login with the new access token. Use this to set
|
|
18
|
+
* the httpOnly cookie via an API route handler (`createSetTokenHandler`).
|
|
19
|
+
*/
|
|
20
|
+
onSetToken?: (token: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface LoginResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
userErrors: Array<{
|
|
25
|
+
message: string;
|
|
26
|
+
field?: string[];
|
|
27
|
+
}>;
|
|
28
|
+
accessToken?: string;
|
|
29
|
+
expiresAt?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface UseLoginReturn {
|
|
32
|
+
/** Submit the login form. Resolves with `{success, userErrors}` rather than throwing on credential failures. */
|
|
33
|
+
login: (email: string, password: string) => Promise<LoginResult>;
|
|
34
|
+
/** `true` while a login request is in flight. */
|
|
35
|
+
isLoggingIn: boolean;
|
|
36
|
+
/** Last unexpected error message (network, server) — credential failures land in `userErrors` instead. */
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function useLogin(options?: UseLoginOptions): UseLoginReturn;
|
|
40
|
+
//# sourceMappingURL=use-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-login.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,gHAAgH;IAChH,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACjE,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,0GAA0G;IAC1G,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CA+DtE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLogin — focused hook for the login flow.
|
|
3
|
+
*
|
|
4
|
+
* Composes the auth client + Zustand store + httpOnly cookie BFF callback.
|
|
5
|
+
* Returns `success`/`userErrors` rather than throwing on credential errors —
|
|
6
|
+
* standard form submission pattern.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { login, isLoggingIn, error } = useLogin({ onSetToken });
|
|
11
|
+
* const result = await login(email, password);
|
|
12
|
+
* if (!result.success) showErrors(result.userErrors);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
'use client';
|
|
16
|
+
import { useCallback, useState } from 'react';
|
|
17
|
+
import { useStorefrontClientContext } from '../providers/storefront-client-provider';
|
|
18
|
+
import { useAuthStore } from '../stores/store-context';
|
|
19
|
+
import { StorefrontError } from '../../core/errors';
|
|
20
|
+
export function useLogin(options = {}) {
|
|
21
|
+
const { authClient } = useStorefrontClientContext();
|
|
22
|
+
const { setAuth } = useAuthStore();
|
|
23
|
+
const [isLoggingIn, setIsLoggingIn] = useState(false);
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
const login = useCallback(async (email, password) => {
|
|
26
|
+
setError(null);
|
|
27
|
+
setIsLoggingIn(true);
|
|
28
|
+
try {
|
|
29
|
+
const result = await authClient.login(email, password);
|
|
30
|
+
if (options.onSetToken) {
|
|
31
|
+
await options.onSetToken(result.accessToken);
|
|
32
|
+
}
|
|
33
|
+
// Fetch customer data and set store (best-effort — fallback to minimal data)
|
|
34
|
+
try {
|
|
35
|
+
const customer = await authClient.getCustomer();
|
|
36
|
+
if (customer) {
|
|
37
|
+
setAuth({
|
|
38
|
+
id: customer.id,
|
|
39
|
+
email: customer.email,
|
|
40
|
+
firstName: customer.firstName ?? undefined,
|
|
41
|
+
lastName: customer.lastName ?? undefined,
|
|
42
|
+
phone: customer.phone ?? undefined,
|
|
43
|
+
}, result.accessToken);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
setAuth({ id: '', email }, result.accessToken);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
setAuth({ id: '', email }, result.accessToken);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
userErrors: [],
|
|
55
|
+
accessToken: result.accessToken,
|
|
56
|
+
expiresAt: result.expiresAt,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (err instanceof StorefrontError && err.hasUserErrors) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
userErrors: err.userErrors.map((e) => ({ message: e.message, field: e.field })),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const message = err instanceof Error ? err.message : 'Login failed';
|
|
67
|
+
setError(message);
|
|
68
|
+
return { success: false, userErrors: [{ message }] };
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setIsLoggingIn(false);
|
|
72
|
+
}
|
|
73
|
+
}, [authClient, setAuth, options]);
|
|
74
|
+
return { login, isLoggingIn, error };
|
|
75
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLogout — focused hook for the logout flow.
|
|
3
|
+
*
|
|
4
|
+
* Calls the auth client logout (which clears the backend's httpOnly cookie),
|
|
5
|
+
* invokes the consumer's `onClearToken` callback (BFF), and resets the local
|
|
6
|
+
* auth store. Even when the backend request fails, the local state is
|
|
7
|
+
* cleared (defensive — better to log a user out than leave them in a half
|
|
8
|
+
* state).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { logout, isLoggingOut } = useLogout({ onClearToken });
|
|
13
|
+
* await logout();
|
|
14
|
+
* router.push('/');
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface UseLogoutOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Called after logout to clear the httpOnly cookie via an API route handler
|
|
20
|
+
* (`createClearTokenHandler`).
|
|
21
|
+
*/
|
|
22
|
+
onClearToken?: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface LogoutResult {
|
|
25
|
+
success: boolean;
|
|
26
|
+
userErrors: Array<{
|
|
27
|
+
message: string;
|
|
28
|
+
field?: string[];
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export interface UseLogoutReturn {
|
|
32
|
+
/** Run the logout flow. Resolves with `{success}` — local state is always cleared. */
|
|
33
|
+
logout: () => Promise<LogoutResult>;
|
|
34
|
+
/** `true` while the logout request is in flight. */
|
|
35
|
+
isLoggingOut: boolean;
|
|
36
|
+
/** Last error message (network, server) — local state is cleared regardless. */
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function useLogout(options?: UseLogoutOptions): UseLogoutReturn;
|
|
40
|
+
//# sourceMappingURL=use-logout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-logout.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-logout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,gFAAgF;IAChF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAgCzE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLogout — focused hook for the logout flow.
|
|
3
|
+
*
|
|
4
|
+
* Calls the auth client logout (which clears the backend's httpOnly cookie),
|
|
5
|
+
* invokes the consumer's `onClearToken` callback (BFF), and resets the local
|
|
6
|
+
* auth store. Even when the backend request fails, the local state is
|
|
7
|
+
* cleared (defensive — better to log a user out than leave them in a half
|
|
8
|
+
* state).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { logout, isLoggingOut } = useLogout({ onClearToken });
|
|
13
|
+
* await logout();
|
|
14
|
+
* router.push('/');
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
'use client';
|
|
18
|
+
import { useCallback, useState } from 'react';
|
|
19
|
+
import { useStorefrontClientContext } from '../providers/storefront-client-provider';
|
|
20
|
+
import { useAuthStore } from '../stores/store-context';
|
|
21
|
+
export function useLogout(options = {}) {
|
|
22
|
+
const { authClient } = useStorefrontClientContext();
|
|
23
|
+
const { clearAuth } = useAuthStore();
|
|
24
|
+
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
25
|
+
const [error, setError] = useState(null);
|
|
26
|
+
const logout = useCallback(async () => {
|
|
27
|
+
setError(null);
|
|
28
|
+
setIsLoggingOut(true);
|
|
29
|
+
try {
|
|
30
|
+
// Auth context resolved server-side from cookie/Bearer — no token arg needed
|
|
31
|
+
await authClient.logout();
|
|
32
|
+
if (options.onClearToken) {
|
|
33
|
+
await options.onClearToken();
|
|
34
|
+
}
|
|
35
|
+
clearAuth();
|
|
36
|
+
return { success: true, userErrors: [] };
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
// Even on error, clear local state — better than a stuck half-state
|
|
40
|
+
clearAuth();
|
|
41
|
+
const message = err instanceof Error ? err.message : 'Logout failed';
|
|
42
|
+
setError(message);
|
|
43
|
+
return { success: false, userErrors: [{ message }] };
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
setIsLoggingOut(false);
|
|
47
|
+
}
|
|
48
|
+
}, [authClient, clearAuth, options]);
|
|
49
|
+
return { logout, isLoggingOut, error };
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRefreshToken — focused hook for renewing the access token.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `AuthClient.refreshToken`. On success the new token is propagated to
|
|
5
|
+
* the consumer's BFF (`onSetToken`) and the store keeps the existing customer
|
|
6
|
+
* data with the new token attached.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Periodic refresh (e.g. every 30 min)
|
|
11
|
+
* const { refreshToken } = useRefreshToken({ onSetToken });
|
|
12
|
+
* useEffect(() => {
|
|
13
|
+
* const timer = setInterval(() => refreshToken(), 30 * 60 * 1000);
|
|
14
|
+
* return () => clearInterval(timer);
|
|
15
|
+
* }, [refreshToken]);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface UseRefreshTokenOptions {
|
|
19
|
+
/** Called after a successful refresh with the new access token (BFF cookie sync). */
|
|
20
|
+
onSetToken?: (token: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface TokenRefreshResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
userErrors: Array<{
|
|
25
|
+
message: string;
|
|
26
|
+
field?: string[];
|
|
27
|
+
}>;
|
|
28
|
+
accessToken?: string;
|
|
29
|
+
expiresAt?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface UseRefreshTokenReturn {
|
|
32
|
+
/** Renew the access token. Resolves with `{success}`. */
|
|
33
|
+
refreshToken: () => Promise<TokenRefreshResult>;
|
|
34
|
+
/** `true` while a refresh request is in flight. */
|
|
35
|
+
isRefreshingToken: boolean;
|
|
36
|
+
/** Last unexpected error message — credential / session expiry lands in `userErrors`. */
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function useRefreshToken(options?: UseRefreshTokenOptions): UseRefreshTokenReturn;
|
|
40
|
+
//# sourceMappingURL=use-refresh-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-refresh-token.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-refresh-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,MAAM,WAAW,sBAAsB;IACrC,qFAAqF;IACrF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,YAAY,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChD,mDAAmD;IACnD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,CA+C3F"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRefreshToken — focused hook for renewing the access token.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `AuthClient.refreshToken`. On success the new token is propagated to
|
|
5
|
+
* the consumer's BFF (`onSetToken`) and the store keeps the existing customer
|
|
6
|
+
* data with the new token attached.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Periodic refresh (e.g. every 30 min)
|
|
11
|
+
* const { refreshToken } = useRefreshToken({ onSetToken });
|
|
12
|
+
* useEffect(() => {
|
|
13
|
+
* const timer = setInterval(() => refreshToken(), 30 * 60 * 1000);
|
|
14
|
+
* return () => clearInterval(timer);
|
|
15
|
+
* }, [refreshToken]);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
'use client';
|
|
19
|
+
import { useCallback, useState } from 'react';
|
|
20
|
+
import { useStorefrontClientContext } from '../providers/storefront-client-provider';
|
|
21
|
+
import { useAuthStore, useAuthStoreApi } from '../stores/store-context';
|
|
22
|
+
import { StorefrontError } from '../../core/errors';
|
|
23
|
+
export function useRefreshToken(options = {}) {
|
|
24
|
+
const { authClient } = useStorefrontClientContext();
|
|
25
|
+
const { setAuth } = useAuthStore();
|
|
26
|
+
const authStore = useAuthStoreApi();
|
|
27
|
+
const [isRefreshingToken, setIsRefreshingToken] = useState(false);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
const refreshToken = useCallback(async () => {
|
|
30
|
+
setError(null);
|
|
31
|
+
setIsRefreshingToken(true);
|
|
32
|
+
try {
|
|
33
|
+
// Auth context resolved server-side from cookie/Bearer. If no active
|
|
34
|
+
// session, backend returns 401 and refreshToken throws.
|
|
35
|
+
const result = await authClient.refreshToken();
|
|
36
|
+
if (options.onSetToken) {
|
|
37
|
+
await options.onSetToken(result.accessToken);
|
|
38
|
+
}
|
|
39
|
+
const currentCustomer = authStore.getState().customer;
|
|
40
|
+
if (currentCustomer) {
|
|
41
|
+
setAuth(currentCustomer, result.accessToken);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
userErrors: [],
|
|
46
|
+
accessToken: result.accessToken,
|
|
47
|
+
expiresAt: result.expiresAt,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof StorefrontError && err.hasUserErrors) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
userErrors: err.userErrors.map((e) => ({ message: e.message, field: e.field })),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const message = err instanceof Error ? err.message : 'Token renewal failed';
|
|
58
|
+
setError(message);
|
|
59
|
+
return { success: false, userErrors: [{ message }] };
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
setIsRefreshingToken(false);
|
|
63
|
+
}
|
|
64
|
+
}, [authClient, setAuth, authStore, options]);
|
|
65
|
+
return { refreshToken, isRefreshingToken, error };
|
|
66
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -16,7 +16,10 @@ export { StorefrontClientProvider, type StorefrontClientProviderProps } from './
|
|
|
16
16
|
export { CurrencyProvider, type CurrencyProviderProps } from './providers/currency-provider';
|
|
17
17
|
export { LanguageProvider, type LanguageProviderProps } from './providers/language-provider';
|
|
18
18
|
export { useAuth, type UseAuthOptions, type LoginResult, type LogoutResult, type TokenRefreshResult } from './hooks/use-auth';
|
|
19
|
-
export {
|
|
19
|
+
export { useLogin, type UseLoginOptions, type UseLoginReturn } from './hooks/use-login';
|
|
20
|
+
export { useLogout, type UseLogoutOptions, type UseLogoutReturn } from './hooks/use-logout';
|
|
21
|
+
export { useRefreshToken, type UseRefreshTokenOptions, type UseRefreshTokenReturn } from './hooks/use-refresh-token';
|
|
22
|
+
export { useCartManager, type CartManagerOperation, type CartManagerStatus, type UseCartManagerResult } from './hooks/use-cart-manager';
|
|
20
23
|
export { useStorefrontClient } from './hooks/use-storefront-client';
|
|
21
24
|
export { useCurrency } from './hooks/use-currency';
|
|
22
25
|
export { useAuthStore, useAuthStoreApi, useAuthHydrated } from './stores/store-context';
|
|
@@ -36,4 +39,5 @@ export { createCartStore, selectCartId, selectIsCartOpen, selectCartIsLoading, }
|
|
|
36
39
|
export type { CartState, CartStoreConfig, CartActions, CartData, CartMutationAction, CartLineInput, CartLineUpdateInput, } from './stores/cart.store';
|
|
37
40
|
export { CartProvider, useCartStore, useCartStoreApi } from './stores/cart.context';
|
|
38
41
|
export { createStoreContext } from './helpers/create-store-context';
|
|
42
|
+
export { Money, type MoneyProps, Image, type ImageComponentProps, CartCount, type CartCountProps, AddToCartButton, type AddToCartButtonProps, PriceDisplay, type PriceDisplayProps, CartTotals, type CartTotalsProps, type CartTotalsLabels, } from './components';
|
|
39
43
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
package/dist/react/index.js
CHANGED
|
@@ -18,6 +18,9 @@ export { CurrencyProvider } from './providers/currency-provider';
|
|
|
18
18
|
export { LanguageProvider } from './providers/language-provider';
|
|
19
19
|
// Hooks
|
|
20
20
|
export { useAuth } from './hooks/use-auth';
|
|
21
|
+
export { useLogin } from './hooks/use-login';
|
|
22
|
+
export { useLogout } from './hooks/use-logout';
|
|
23
|
+
export { useRefreshToken } from './hooks/use-refresh-token';
|
|
21
24
|
export { useCartManager } from './hooks/use-cart-manager';
|
|
22
25
|
export { useStorefrontClient } from './hooks/use-storefront-client';
|
|
23
26
|
export { useCurrency } from './hooks/use-currency';
|
|
@@ -40,3 +43,5 @@ export { createCartStore, selectCartId, selectIsCartOpen, selectCartIsLoading, }
|
|
|
40
43
|
export { CartProvider, useCartStore, useCartStoreApi } from './stores/cart.context';
|
|
41
44
|
// Store context helper
|
|
42
45
|
export { createStoreContext } from './helpers/create-store-context';
|
|
46
|
+
// Pre-built components (headless — no styling, accessibility-aware)
|
|
47
|
+
export { Money, Image, CartCount, AddToCartButton, PriceDisplay, CartTotals, } from './components';
|
|
@@ -21,13 +21,23 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { StorefrontClient, StorefrontClientConfig, Middleware } from '../../core/client/types';
|
|
23
23
|
export interface ServerClientOptions extends Omit<StorefrontClientConfig, 'middleware'> {
|
|
24
|
-
/**
|
|
25
|
-
* Function that returns request-scoped headers (e.g. currency from cookie).
|
|
26
|
-
* Called once per client creation.
|
|
27
|
-
*/
|
|
28
|
-
getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
29
24
|
/**
|
|
30
25
|
* Additional middleware (prepended to default pipeline).
|
|
26
|
+
*
|
|
27
|
+
* Use this for request-scoped headers (e.g. currency from cookie, language
|
|
28
|
+
* from URL, custom tracking):
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* getStorefrontClient({
|
|
32
|
+
* apiUrl, shopSlug,
|
|
33
|
+
* middleware: [
|
|
34
|
+
* async (req, next) => {
|
|
35
|
+
* req.headers['X-Preferred-Currency'] = await readCookieFromHeaders();
|
|
36
|
+
* return next(req);
|
|
37
|
+
* },
|
|
38
|
+
* ],
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
31
41
|
*/
|
|
32
42
|
middleware?: Middleware[];
|
|
33
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC;IACrF
|
|
1
|
+
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC;IACrF;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CAYlF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/cart.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAIvC,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAChF,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAMtE,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACxD,iDAAiD;IACjD,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oDAAoD;IACpD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxE,4DAA4D;IAC5D,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,uDAAuD;IACvD,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtE;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACrE;AAMD,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEhG,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,wCAAwC;IACxC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzE,gCAAgC;IAChC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACvE;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC/C;AAMD,MAAM,WAAW,SAAS;IAExB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAGtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IAGvB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAMD,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,kBAAiB,CAAC;AAC/D,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,YAAiB,CAAC;AACnE,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,YAAoB,CAAC;AAMzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"cart.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/cart.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAIvC,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAChF,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAMtE,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACxD,iDAAiD;IACjD,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oDAAoD;IACpD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxE,4DAA4D;IAC5D,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,uDAAuD;IACvD,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtE;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACrE;AAMD,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEhG,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,wCAAwC;IACxC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzE,gCAAgC;IAChC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACvE;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC/C;AAMD,MAAM,WAAW,SAAS;IAExB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAGtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IAGvB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAMD,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,kBAAiB,CAAC;AAC/D,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,YAAiB,CAAC;AACnE,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,YAAoB,CAAC;AAMzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,yCAuNtD"}
|
|
@@ -62,13 +62,6 @@ export function createCartStore(config) {
|
|
|
62
62
|
let initPromise = null;
|
|
63
63
|
// Read initial cartId from cookie (client-side only, returns null on server)
|
|
64
64
|
const initialCartId = getCookie(CART_COOKIE_NAME);
|
|
65
|
-
// Clean up old localStorage entry from pre-cookie era (v1 persist)
|
|
66
|
-
if (typeof localStorage !== 'undefined') {
|
|
67
|
-
try {
|
|
68
|
-
localStorage.removeItem('cart-storage');
|
|
69
|
-
}
|
|
70
|
-
catch { /* ignore */ }
|
|
71
|
-
}
|
|
72
65
|
function emitExpired(event) {
|
|
73
66
|
if (!config.onExpired)
|
|
74
67
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-context.d.ts","sourceRoot":"","sources":["../../../src/react/stores/store-context.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMtD,eAAO,MAAM,gBAAgB,qDAAkD,CAAC;AAChF,eAAO,MAAM,oBAAoB,yDAAsD,CAAC;AACxF,eAAO,MAAM,oBAAoB,yDAAsD,CAAC;AAMxF,wBAAgB,YAAY,IAAI,SAAS,CAAC;AAC1C,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,CAAC;AAQlE,wBAAgB,eAAe,IAAI,QAAQ,CAAC,SAAS,CAAC,CAIrD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"store-context.d.ts","sourceRoot":"","sources":["../../../src/react/stores/store-context.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMtD,eAAO,MAAM,gBAAgB,qDAAkD,CAAC;AAChF,eAAO,MAAM,oBAAoB,yDAAsD,CAAC;AACxF,eAAO,MAAM,oBAAoB,yDAAsD,CAAC;AAMxF,wBAAgB,YAAY,IAAI,SAAS,CAAC;AAC1C,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,CAAC;AAQlE,wBAAgB,eAAe,IAAI,QAAQ,CAAC,SAAS,CAAC,CAIrD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAoBzC;AAMD,wBAAgB,gBAAgB,IAAI,aAAa,CAAC;AAClD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,CAAC,GAAG,CAAC,CAAC;AAQ1E,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,aAAa,CAAC,CAI7D;AAMD,wBAAgB,gBAAgB,IAAI,aAAa,CAAC;AAClD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,CAAC,GAAG,CAAC,CAAC;AAQ1E,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,aAAa,CAAC,CAI7D"}
|
|
@@ -39,9 +39,7 @@ export function useAuthHydrated() {
|
|
|
39
39
|
const [hydrated, setHydrated] = useState(false);
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
const persistApi = store.persist;
|
|
42
|
-
// Subscribe to future hydration completions
|
|
43
42
|
const unsubFinish = persistApi.onFinishHydration(() => setHydrated(true));
|
|
44
|
-
// Check if hydration already completed before effect ran
|
|
45
43
|
if (persistApi.hasHydrated())
|
|
46
44
|
setHydrated(true);
|
|
47
45
|
return unsubFinish;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.1",
|
|
4
4
|
"description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"files": [
|
|
7
8
|
"dist",
|
|
8
9
|
"README.md",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"@types/node": "^22.10.2",
|
|
45
46
|
"@types/react": "^18.3.0 || ^19.0.0",
|
|
46
47
|
"@types/react-dom": "^19.0.0",
|
|
48
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
47
49
|
"fast-check": "^3.23.2",
|
|
48
50
|
"jsdom": "^25.0.1",
|
|
49
51
|
"next": "^16.2.3",
|
|
@@ -74,6 +76,8 @@
|
|
|
74
76
|
"test:watch": "vitest",
|
|
75
77
|
"test:unit": "vitest run src/__tests__/unit/",
|
|
76
78
|
"test:contract": "vitest run src/__tests__/contract/",
|
|
79
|
+
"test:coverage": "vitest run --coverage",
|
|
80
|
+
"doctor": "node scripts/doctor.cjs",
|
|
77
81
|
"validate:cart": "node scripts/validate-cart-operations.cjs --strict"
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared test helpers — mock fetch factory, mock client builder.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Create a mock fetch that returns the specified GraphQL response.
|
|
6
|
-
*/
|
|
7
|
-
export declare function createMockFetch(responseData: unknown, options?: {
|
|
8
|
-
errors?: Array<{
|
|
9
|
-
message: string;
|
|
10
|
-
}>;
|
|
11
|
-
status?: number;
|
|
12
|
-
delay?: number;
|
|
13
|
-
}): typeof globalThis.fetch;
|
|
14
|
-
/**
|
|
15
|
-
* Create a mock fetch that tracks all calls and returns specified response.
|
|
16
|
-
*/
|
|
17
|
-
export declare function createSpyFetch(responseData: unknown, options?: {
|
|
18
|
-
errors?: Array<{
|
|
19
|
-
message: string;
|
|
20
|
-
}>;
|
|
21
|
-
status?: number;
|
|
22
|
-
}): {
|
|
23
|
-
fetch: typeof globalThis.fetch;
|
|
24
|
-
calls: {
|
|
25
|
-
url: string;
|
|
26
|
-
init: RequestInit;
|
|
27
|
-
}[];
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Create a mock fetch that fails with a network error.
|
|
31
|
-
*/
|
|
32
|
-
export declare function createNetworkErrorFetch(errorMessage?: string): typeof globalThis.fetch;
|
|
33
|
-
/**
|
|
34
|
-
* Create a mock fetch that returns different responses on subsequent calls.
|
|
35
|
-
*/
|
|
36
|
-
export declare function createSequenceFetch(responses: Array<{
|
|
37
|
-
data?: unknown;
|
|
38
|
-
errors?: Array<{
|
|
39
|
-
message: string;
|
|
40
|
-
}>;
|
|
41
|
-
status?: number;
|
|
42
|
-
}>): {
|
|
43
|
-
fetch: typeof globalThis.fetch;
|
|
44
|
-
callCount: () => number;
|
|
45
|
-
};
|
|
46
|
-
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/test-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,UAAU,CAAC,KAAK,CAqBzB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;WAqB0B,OAAO,UAAU,CAAC,KAAK;;aAnBxB,MAAM;cAAQ,WAAW;;EAoBpD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,SAAiB,GAAG,OAAO,UAAU,CAAC,KAAK,CAI9F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACzF;IAAE,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,MAAM,CAAA;CAAE,CAiB7D"}
|