@commercengine/storefront-sdk-nextjs 0.1.0-alpha.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 ADDED
@@ -0,0 +1,239 @@
1
+ # @commercengine/storefront-sdk-nextjs
2
+
3
+ **Zero-config Next.js wrapper** for the Commerce Engine Storefront SDK. Handles server/client complexity automatically with smart token management.
4
+
5
+ **✨ What makes this special:**
6
+ - **🔥 Zero token management** - Tokens created automatically on first API call
7
+ - **🧠 Smart environment detection** - One API works everywhere
8
+ - **🍪 Cookie-based storage** - Seamless server/client token sync
9
+ - **⚡ No setup required** - Just import and use
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @commercengine/storefront-sdk-nextjs
15
+ # or
16
+ pnpm add @commercengine/storefront-sdk-nextjs
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Initialize in your root layout
22
+
23
+ ```typescript
24
+ // app/layout.tsx
25
+ import { StorefrontSDKInitializer, Environment } from '@commercengine/storefront-sdk-nextjs';
26
+
27
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
28
+ return (
29
+ <html>
30
+ <body>
31
+ <StorefrontSDKInitializer
32
+ config={{
33
+ storeId: process.env.NEXT_PUBLIC_STORE_ID!,
34
+ environment: Environment.Production,
35
+ apiKey: process.env.NEXT_PUBLIC_API_KEY,
36
+ }}
37
+ />
38
+ {children}
39
+ </body>
40
+ </html>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### 2. Use anywhere - tokens created automatically!
46
+
47
+ ```typescript
48
+ // ✅ Client Component - Just works
49
+ 'use client';
50
+ import { getStorefrontSDK } from '@commercengine/storefront-sdk-nextjs';
51
+
52
+ export default function AddToCart({ productId }: { productId: string }) {
53
+ const handleAddToCart = async () => {
54
+ const sdk = getStorefrontSDK(); // No cookies needed on client
55
+
56
+ // First API call automatically creates anonymous tokens! 🎉
57
+ const { data } = await sdk.cart.addToCart({
58
+ product_id: productId,
59
+ quantity: 1,
60
+ });
61
+ };
62
+
63
+ return <button onClick={handleAddToCart}>Add to Cart</button>;
64
+ }
65
+ ```
66
+
67
+ ```typescript
68
+ // ✅ Server Action - Cookies required
69
+ 'use server';
70
+ import { cookies } from 'next/headers';
71
+ import { getStorefrontSDK } from '@commercengine/storefront-sdk-nextjs';
72
+
73
+ export async function addToCartAction(productId: string) {
74
+ const sdk = getStorefrontSDK(await cookies()); // Cookies needed on server
75
+
76
+ // Tokens automatically managed across server/client boundary! 🎉
77
+ const { data } = await sdk.cart.addToCart({
78
+ product_id: productId,
79
+ quantity: 1,
80
+ });
81
+
82
+ return { success: true };
83
+ }
84
+ ```
85
+
86
+ ```typescript
87
+ // ✅ Server Component - Cookies required
88
+ import { cookies } from 'next/headers';
89
+ import { getStorefrontSDK } from '@commercengine/storefront-sdk-nextjs';
90
+
91
+ export default async function ProductPage({ params }: { params: { id: string } }) {
92
+ const sdk = getStorefrontSDK(await cookies());
93
+
94
+ // Anonymous tokens work perfectly for public data! 🎉
95
+ const { data: product } = await sdk.catalog.getProduct({
96
+ product_id_or_slug: params.id
97
+ });
98
+
99
+ return <div>{product?.name}</div>;
100
+ }
101
+ ```
102
+
103
+
104
+ ## API Reference
105
+
106
+ ### `getStorefrontSDK()`
107
+
108
+ **Smart function** that works everywhere:
109
+
110
+ ```typescript
111
+ // ✅ Client-side (browser)
112
+ const sdk = getStorefrontSDK();
113
+
114
+ // ✅ Server-side (requires cookies for token persistence)
115
+ const sdk = getStorefrontSDK(await cookies());
116
+
117
+ // ❌ Server-side without cookies (helpful error message)
118
+ const sdk = getStorefrontSDK(); // Throws clear error with instructions
119
+ ```
120
+
121
+ ### `StorefrontSDKInitializer`
122
+
123
+ **Lightweight initializer** (recommended for most apps):
124
+
125
+ ```typescript
126
+ <StorefrontSDKInitializer
127
+ config={{
128
+ storeId: "your-store-id",
129
+ environment: Environment.Production, // or Environment.Staging
130
+ apiKey: "your-api-key",
131
+
132
+ // Optional: Advanced configuration
133
+ tokenStorageOptions: {
134
+ prefix: "ce_", // Cookie prefix (default)
135
+ maxAge: 30 * 24 * 60 * 60, // 30 days
136
+ secure: true, // Auto-detected from environment
137
+ sameSite: "Lax",
138
+ },
139
+ }}
140
+ />
141
+ ```
142
+
143
+ ## Automatic Token Management 🤖
144
+
145
+ The SDK now handles tokens **completely automatically**:
146
+
147
+ ### ✅ What Happens Automatically:
148
+
149
+ 1. **First API Call**: Creates anonymous tokens seamlessly
150
+ 2. **Token Expiry**: Refreshes tokens transparently
151
+ 3. **Token Corruption**: Cleans up orphaned tokens
152
+ 4. **User Login**: Upgrades from anonymous to authenticated tokens
153
+ 5. **User Logout**: Downgrades gracefully with continuity
154
+ 6. **Page Refresh**: Tokens persist via cookies across server/client
155
+
156
+ ### ✅ Zero Configuration Needed:
157
+
158
+ ```typescript
159
+ // This just works - no token setup required!
160
+ const sdk = getStorefrontSDK();
161
+ const { data } = await sdk.catalog.listProducts(); // ✨ Tokens created automatically
162
+ ```
163
+
164
+ ## Error Messages
165
+
166
+ ### Server Environment Detection
167
+ ```
168
+ 🚨 Server Environment Detected!
169
+
170
+ You're calling getStorefrontSDK() on the server without cookies.
171
+ Please pass the Next.js cookie store:
172
+
173
+ ✅ Correct usage:
174
+ import { cookies } from 'next/headers';
175
+
176
+ // Server Actions & Route Handlers
177
+ const sdk = getStorefrontSDK(await cookies());
178
+
179
+ // API Routes (Next.js 12)
180
+ const sdk = getStorefrontSDK(cookies());
181
+
182
+ ❌ Your current usage:
183
+ const sdk = getStorefrontSDK(); // Missing cookies!
184
+ ```
185
+
186
+ ## Migration from Base SDK
187
+
188
+ **Before (Base SDK):**
189
+ ```typescript
190
+ const sdk = new StorefrontSDK({ storeId: "...", tokenStorage: ... });
191
+ await sdk.auth.getAnonymousToken(); // Manual token creation
192
+ const { data } = await sdk.catalog.listProducts();
193
+ ```
194
+
195
+ **After (NextJS SDK):**
196
+ ```typescript
197
+ const sdk = getStorefrontSDK(); // Environment detected automatically
198
+ const { data } = await sdk.catalog.listProducts(); // Tokens created automatically!
199
+ ```
200
+
201
+ ## TypeScript Support
202
+
203
+ Full TypeScript support with all types from the base SDK:
204
+
205
+ ```typescript
206
+ import type {
207
+ StorefrontSDK,
208
+ NextJSSDKConfig,
209
+ Environment
210
+ } from '@commercengine/storefront-sdk-nextjs';
211
+ ```
212
+
213
+ ## Best Practices
214
+
215
+ 1. **✅ One-Line Setup**: `StorefrontSDKInitializer` in root layout is all you need
216
+ 2. **✅ Server Cookies**: Always pass `await cookies()` on server-side
217
+ 3. **✅ Error Handling**: Wrap API calls in try-catch
218
+ 4. **✅ Environment Variables**: Use Next.js env vars for configuration
219
+ 5. **⚡ Trust the Middleware**: No manual token management needed
220
+
221
+ ## Why This Wrapper Exists
222
+
223
+ **The base SDK is powerful but complex for Next.js:**
224
+ - Manual token storage setup
225
+ - Server/client environment handling
226
+ - Cookie configuration for SSR
227
+ - Token lifecycle management
228
+
229
+ **This wrapper makes it effortless:**
230
+ - Zero configuration token management
231
+ - Automatic environment detection
232
+ - Cookie-based persistence works out of the box
233
+ - One API that works everywhere
234
+
235
+ **Result: Focus on your app, not SDK complexity!** 🚀
236
+
237
+ ## License
238
+
239
+ All rights reserved - Commerce Engine
package/dist/index.cjs ADDED
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ClientTokenStorage: () => ClientTokenStorage,
34
+ Environment: () => import_storefront_sdk2.Environment,
35
+ ServerTokenStorage: () => ServerTokenStorage,
36
+ StorefrontSDKInitializer: () => StorefrontSDKInitializer,
37
+ getStorefrontSDK: () => getStorefrontSDK
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/sdk-manager.ts
42
+ var import_storefront_sdk = require("@commercengine/storefront-sdk");
43
+
44
+ // src/token-storage.ts
45
+ var ClientTokenStorage = class {
46
+ constructor(options = {}) {
47
+ const prefix = options.prefix || "ce_";
48
+ this.accessTokenKey = `${prefix}access_token`;
49
+ this.refreshTokenKey = `${prefix}refresh_token`;
50
+ this.options = {
51
+ maxAge: options.maxAge || 30 * 24 * 60 * 60,
52
+ // 30 days default
53
+ path: options.path || "/",
54
+ domain: options.domain,
55
+ secure: options.secure ?? (typeof window !== "undefined" && window.location?.protocol === "https:"),
56
+ sameSite: options.sameSite || "Lax"
57
+ };
58
+ }
59
+ async getAccessToken() {
60
+ return this.getCookie(this.accessTokenKey);
61
+ }
62
+ async setAccessToken(token) {
63
+ this.setCookie(this.accessTokenKey, token);
64
+ }
65
+ async getRefreshToken() {
66
+ return this.getCookie(this.refreshTokenKey);
67
+ }
68
+ async setRefreshToken(token) {
69
+ this.setCookie(this.refreshTokenKey, token);
70
+ }
71
+ async clearTokens() {
72
+ this.deleteCookie(this.accessTokenKey);
73
+ this.deleteCookie(this.refreshTokenKey);
74
+ }
75
+ getCookie(name) {
76
+ if (typeof document === "undefined") return null;
77
+ const value = `; ${document.cookie}`;
78
+ const parts = value.split(`; ${name}=`);
79
+ if (parts.length === 2) {
80
+ const cookieValue = parts.pop()?.split(";").shift();
81
+ return cookieValue ? decodeURIComponent(cookieValue) : null;
82
+ }
83
+ return null;
84
+ }
85
+ setCookie(name, value) {
86
+ if (typeof document === "undefined") return;
87
+ const encodedValue = encodeURIComponent(value);
88
+ let cookieString = `${name}=${encodedValue}`;
89
+ if (this.options.maxAge) {
90
+ cookieString += `; Max-Age=${this.options.maxAge}`;
91
+ }
92
+ if (this.options.path) {
93
+ cookieString += `; Path=${this.options.path}`;
94
+ }
95
+ if (this.options.domain) {
96
+ cookieString += `; Domain=${this.options.domain}`;
97
+ }
98
+ if (this.options.secure) {
99
+ cookieString += `; Secure`;
100
+ }
101
+ if (this.options.sameSite) {
102
+ cookieString += `; SameSite=${this.options.sameSite}`;
103
+ }
104
+ document.cookie = cookieString;
105
+ }
106
+ deleteCookie(name) {
107
+ if (typeof document === "undefined") return;
108
+ let cookieString = `${name}=; Max-Age=0`;
109
+ if (this.options.path) {
110
+ cookieString += `; Path=${this.options.path}`;
111
+ }
112
+ if (this.options.domain) {
113
+ cookieString += `; Domain=${this.options.domain}`;
114
+ }
115
+ document.cookie = cookieString;
116
+ }
117
+ };
118
+ var ServerTokenStorage = class {
119
+ constructor(cookieStore, options = {}) {
120
+ const prefix = options.prefix || "ce_";
121
+ this.accessTokenKey = `${prefix}access_token`;
122
+ this.refreshTokenKey = `${prefix}refresh_token`;
123
+ this.cookieStore = cookieStore;
124
+ this.options = {
125
+ maxAge: options.maxAge || 30 * 24 * 60 * 60,
126
+ // 30 days default
127
+ path: options.path || "/",
128
+ domain: options.domain,
129
+ secure: options.secure ?? process.env.NODE_ENV === "production",
130
+ sameSite: options.sameSite || "Lax"
131
+ };
132
+ }
133
+ async getAccessToken() {
134
+ try {
135
+ return this.cookieStore.get(this.accessTokenKey)?.value || null;
136
+ } catch (error) {
137
+ console.warn(`Could not get access token from server cookies:`, error);
138
+ return null;
139
+ }
140
+ }
141
+ async setAccessToken(token) {
142
+ try {
143
+ this.cookieStore.set(this.accessTokenKey, token, {
144
+ maxAge: this.options.maxAge,
145
+ path: this.options.path,
146
+ domain: this.options.domain,
147
+ secure: this.options.secure,
148
+ sameSite: this.options.sameSite?.toLowerCase(),
149
+ httpOnly: false
150
+ // Must be false for client-side access
151
+ });
152
+ } catch (error) {
153
+ console.warn(`Could not set access token on server:`, error);
154
+ }
155
+ }
156
+ async getRefreshToken() {
157
+ try {
158
+ return this.cookieStore.get(this.refreshTokenKey)?.value || null;
159
+ } catch (error) {
160
+ console.warn(`Could not get refresh token from server cookies:`, error);
161
+ return null;
162
+ }
163
+ }
164
+ async setRefreshToken(token) {
165
+ try {
166
+ this.cookieStore.set(this.refreshTokenKey, token, {
167
+ maxAge: this.options.maxAge,
168
+ path: this.options.path,
169
+ domain: this.options.domain,
170
+ secure: this.options.secure,
171
+ sameSite: this.options.sameSite?.toLowerCase(),
172
+ httpOnly: false
173
+ // Must be false for client-side access
174
+ });
175
+ } catch (error) {
176
+ console.warn(`Could not set refresh token on server:`, error);
177
+ }
178
+ }
179
+ async clearTokens() {
180
+ try {
181
+ this.cookieStore.delete(this.accessTokenKey);
182
+ this.cookieStore.delete(this.refreshTokenKey);
183
+ } catch (error) {
184
+ console.warn(`Could not clear tokens on server:`, error);
185
+ }
186
+ }
187
+ };
188
+
189
+ // src/sdk-manager.ts
190
+ var NextJSSDKManager = class _NextJSSDKManager {
191
+ constructor() {
192
+ this.clientSDK = null;
193
+ this.config = null;
194
+ }
195
+ static getInstance() {
196
+ if (!_NextJSSDKManager.instance) {
197
+ _NextJSSDKManager.instance = new _NextJSSDKManager();
198
+ }
199
+ return _NextJSSDKManager.instance;
200
+ }
201
+ /**
202
+ * Initialize the SDK with configuration (should be called once)
203
+ */
204
+ initialize(config) {
205
+ this.config = config;
206
+ if (typeof window !== "undefined" && !this.clientSDK) {
207
+ this.clientSDK = this.createClientSDK();
208
+ }
209
+ }
210
+ /**
211
+ * Get SDK instance for client-side usage
212
+ */
213
+ getClientSDK() {
214
+ if (!this.config) {
215
+ throw new Error("SDK not initialized. Call initialize() first.");
216
+ }
217
+ if (!this.clientSDK) {
218
+ this.clientSDK = this.createClientSDK();
219
+ }
220
+ return this.clientSDK;
221
+ }
222
+ /**
223
+ * Get SDK instance for server-side usage
224
+ */
225
+ getServerSDK(cookieStore) {
226
+ if (!this.config) {
227
+ throw new Error("SDK not initialized. Call initialize() first.");
228
+ }
229
+ return new import_storefront_sdk.StorefrontSDK({
230
+ ...this.config,
231
+ tokenStorage: new ServerTokenStorage(
232
+ cookieStore,
233
+ this.config.tokenStorageOptions
234
+ )
235
+ });
236
+ }
237
+ createClientSDK() {
238
+ if (!this.config) {
239
+ throw new Error("SDK not initialized. Call initialize() first.");
240
+ }
241
+ return new import_storefront_sdk.StorefrontSDK({
242
+ ...this.config,
243
+ tokenStorage: new ClientTokenStorage(this.config.tokenStorageOptions)
244
+ });
245
+ }
246
+ /**
247
+ * Check if SDK is initialized
248
+ */
249
+ isInitialized() {
250
+ return this.config !== null;
251
+ }
252
+ /**
253
+ * Reset the SDK (useful for testing)
254
+ */
255
+ reset() {
256
+ this.clientSDK = null;
257
+ this.config = null;
258
+ }
259
+ };
260
+ function getStorefrontSDK(cookieStore) {
261
+ const manager = NextJSSDKManager.getInstance();
262
+ if (typeof window !== "undefined") {
263
+ if (cookieStore) {
264
+ console.warn(
265
+ "Cookie store passed in client environment - this will be ignored"
266
+ );
267
+ }
268
+ return manager.getClientSDK();
269
+ }
270
+ if (!cookieStore) {
271
+ let autoDetectMessage = "";
272
+ try {
273
+ require.resolve("next/headers");
274
+ autoDetectMessage = `
275
+
276
+ \u{1F50D} Auto-detection attempted but failed. You may be in:
277
+ - Server Action (use: const sdk = getStorefrontSDK(await cookies()))
278
+ - API Route (use: const sdk = getStorefrontSDK(cookies()))
279
+ - Middleware (token access limited)
280
+ `;
281
+ } catch {
282
+ autoDetectMessage = `
283
+
284
+ \u{1F4A1} Make sure you have Next.js installed and are in a server context.
285
+ `;
286
+ }
287
+ throw new Error(
288
+ `
289
+ \u{1F6A8} Server Environment Detected!
290
+
291
+ You're calling getStorefrontSDK() on the server without cookies.
292
+ Please pass the Next.js cookie store:
293
+
294
+ \u2705 Correct usage:
295
+ import { cookies } from 'next/headers';
296
+
297
+ // Server Actions & Route Handlers
298
+ const sdk = getStorefrontSDK(await cookies());
299
+
300
+ // API Routes (Next.js 12)
301
+ const sdk = getStorefrontSDK(cookies());
302
+
303
+ \u274C Your current usage:
304
+ const sdk = getStorefrontSDK(); // Missing cookies!
305
+ ${autoDetectMessage}
306
+ This is required for server-side token access.
307
+ `.trim()
308
+ );
309
+ }
310
+ return manager.getServerSDK(cookieStore);
311
+ }
312
+ function initializeStorefrontSDK(config) {
313
+ const manager = NextJSSDKManager.getInstance();
314
+ manager.initialize(config);
315
+ }
316
+
317
+ // src/init-client.ts
318
+ var import_react = require("react");
319
+ function StorefrontSDKInitializer({
320
+ config
321
+ }) {
322
+ (0, import_react.useEffect)(() => {
323
+ initializeStorefrontSDK(config);
324
+ }, [config]);
325
+ return null;
326
+ }
327
+
328
+ // src/index.ts
329
+ var import_storefront_sdk2 = require("@commercengine/storefront-sdk");
330
+ // Annotate the CommonJS export names for ESM import in node:
331
+ 0 && (module.exports = {
332
+ ClientTokenStorage,
333
+ Environment,
334
+ ServerTokenStorage,
335
+ StorefrontSDKInitializer,
336
+ getStorefrontSDK
337
+ });
@@ -0,0 +1,116 @@
1
+ import { TokenStorage, StorefrontSDK, StorefrontSDKOptions } from '@commercengine/storefront-sdk';
2
+ export { Environment, StorefrontSDK, StorefrontSDKOptions } from '@commercengine/storefront-sdk';
3
+
4
+ /**
5
+ * Configuration options for NextJSTokenStorage
6
+ */
7
+ interface NextJSTokenStorageOptions {
8
+ /**
9
+ * Prefix for cookie names (default: "ce_")
10
+ */
11
+ prefix?: string;
12
+ /**
13
+ * Maximum age of cookies in seconds (default: 30 days)
14
+ */
15
+ maxAge?: number;
16
+ /**
17
+ * Cookie path (default: "/")
18
+ */
19
+ path?: string;
20
+ /**
21
+ * Cookie domain (default: current domain)
22
+ */
23
+ domain?: string;
24
+ /**
25
+ * Whether cookies should be secure (default: auto-detect based on environment)
26
+ */
27
+ secure?: boolean;
28
+ /**
29
+ * SameSite cookie attribute (default: "Lax")
30
+ */
31
+ sameSite?: "Strict" | "Lax" | "None";
32
+ }
33
+ /**
34
+ * Client-side token storage that uses document.cookie
35
+ */
36
+ declare class ClientTokenStorage implements TokenStorage {
37
+ private accessTokenKey;
38
+ private refreshTokenKey;
39
+ private options;
40
+ constructor(options?: NextJSTokenStorageOptions);
41
+ getAccessToken(): Promise<string | null>;
42
+ setAccessToken(token: string): Promise<void>;
43
+ getRefreshToken(): Promise<string | null>;
44
+ setRefreshToken(token: string): Promise<void>;
45
+ clearTokens(): Promise<void>;
46
+ private getCookie;
47
+ private setCookie;
48
+ private deleteCookie;
49
+ }
50
+ type NextCookieStore$1 = {
51
+ get: (name: string) => {
52
+ value: string;
53
+ } | undefined;
54
+ set: (name: string, value: string, options?: any) => void;
55
+ delete: (name: string) => void;
56
+ };
57
+ /**
58
+ * Server-side token storage that uses Next.js cookies API
59
+ */
60
+ declare class ServerTokenStorage implements TokenStorage {
61
+ private accessTokenKey;
62
+ private refreshTokenKey;
63
+ private options;
64
+ private cookieStore;
65
+ constructor(cookieStore: NextCookieStore$1, options?: NextJSTokenStorageOptions);
66
+ getAccessToken(): Promise<string | null>;
67
+ setAccessToken(token: string): Promise<void>;
68
+ getRefreshToken(): Promise<string | null>;
69
+ setRefreshToken(token: string): Promise<void>;
70
+ clearTokens(): Promise<void>;
71
+ }
72
+
73
+ /**
74
+ * Configuration for the NextJS SDK wrapper
75
+ */
76
+ interface NextJSSDKConfig extends Omit<StorefrontSDKOptions, "tokenStorage"> {
77
+ /**
78
+ * Token storage configuration options
79
+ */
80
+ tokenStorageOptions?: NextJSTokenStorageOptions;
81
+ }
82
+ type NextCookieStore = {
83
+ get: (name: string) => {
84
+ value: string;
85
+ } | undefined;
86
+ set: (name: string, value: string, options?: any) => void;
87
+ delete: (name: string) => void;
88
+ };
89
+ /**
90
+ * Smart SDK getter that automatically detects environment
91
+ *
92
+ * Usage:
93
+ * - Client-side: getStorefrontSDK()
94
+ * - Server-side: getStorefrontSDK(await cookies())
95
+ */
96
+ declare function getStorefrontSDK(): StorefrontSDK;
97
+ declare function getStorefrontSDK(cookieStore: NextCookieStore): StorefrontSDK;
98
+
99
+ interface StorefrontSDKInitializerProps {
100
+ config: NextJSSDKConfig;
101
+ }
102
+ /**
103
+ * Client-side initialization component
104
+ * Use this in your root layout to initialize the SDK once
105
+ *
106
+ * The core SDK middleware now automatically handles everything:
107
+ * - Creates anonymous tokens automatically on first API request (if no tokens exist)
108
+ * - Token state assessment and cleanup on first request per page load
109
+ * - Expired token refresh with automatic anonymous fallback
110
+ * - Graceful handling of partial token states
111
+ *
112
+ * No manual token creation needed - just make API calls and everything works!
113
+ */
114
+ declare function StorefrontSDKInitializer({ config, }: StorefrontSDKInitializerProps): null;
115
+
116
+ export { ClientTokenStorage, type NextJSSDKConfig, type NextJSTokenStorageOptions, ServerTokenStorage, StorefrontSDKInitializer, getStorefrontSDK };
@@ -0,0 +1,116 @@
1
+ import { TokenStorage, StorefrontSDK, StorefrontSDKOptions } from '@commercengine/storefront-sdk';
2
+ export { Environment, StorefrontSDK, StorefrontSDKOptions } from '@commercengine/storefront-sdk';
3
+
4
+ /**
5
+ * Configuration options for NextJSTokenStorage
6
+ */
7
+ interface NextJSTokenStorageOptions {
8
+ /**
9
+ * Prefix for cookie names (default: "ce_")
10
+ */
11
+ prefix?: string;
12
+ /**
13
+ * Maximum age of cookies in seconds (default: 30 days)
14
+ */
15
+ maxAge?: number;
16
+ /**
17
+ * Cookie path (default: "/")
18
+ */
19
+ path?: string;
20
+ /**
21
+ * Cookie domain (default: current domain)
22
+ */
23
+ domain?: string;
24
+ /**
25
+ * Whether cookies should be secure (default: auto-detect based on environment)
26
+ */
27
+ secure?: boolean;
28
+ /**
29
+ * SameSite cookie attribute (default: "Lax")
30
+ */
31
+ sameSite?: "Strict" | "Lax" | "None";
32
+ }
33
+ /**
34
+ * Client-side token storage that uses document.cookie
35
+ */
36
+ declare class ClientTokenStorage implements TokenStorage {
37
+ private accessTokenKey;
38
+ private refreshTokenKey;
39
+ private options;
40
+ constructor(options?: NextJSTokenStorageOptions);
41
+ getAccessToken(): Promise<string | null>;
42
+ setAccessToken(token: string): Promise<void>;
43
+ getRefreshToken(): Promise<string | null>;
44
+ setRefreshToken(token: string): Promise<void>;
45
+ clearTokens(): Promise<void>;
46
+ private getCookie;
47
+ private setCookie;
48
+ private deleteCookie;
49
+ }
50
+ type NextCookieStore$1 = {
51
+ get: (name: string) => {
52
+ value: string;
53
+ } | undefined;
54
+ set: (name: string, value: string, options?: any) => void;
55
+ delete: (name: string) => void;
56
+ };
57
+ /**
58
+ * Server-side token storage that uses Next.js cookies API
59
+ */
60
+ declare class ServerTokenStorage implements TokenStorage {
61
+ private accessTokenKey;
62
+ private refreshTokenKey;
63
+ private options;
64
+ private cookieStore;
65
+ constructor(cookieStore: NextCookieStore$1, options?: NextJSTokenStorageOptions);
66
+ getAccessToken(): Promise<string | null>;
67
+ setAccessToken(token: string): Promise<void>;
68
+ getRefreshToken(): Promise<string | null>;
69
+ setRefreshToken(token: string): Promise<void>;
70
+ clearTokens(): Promise<void>;
71
+ }
72
+
73
+ /**
74
+ * Configuration for the NextJS SDK wrapper
75
+ */
76
+ interface NextJSSDKConfig extends Omit<StorefrontSDKOptions, "tokenStorage"> {
77
+ /**
78
+ * Token storage configuration options
79
+ */
80
+ tokenStorageOptions?: NextJSTokenStorageOptions;
81
+ }
82
+ type NextCookieStore = {
83
+ get: (name: string) => {
84
+ value: string;
85
+ } | undefined;
86
+ set: (name: string, value: string, options?: any) => void;
87
+ delete: (name: string) => void;
88
+ };
89
+ /**
90
+ * Smart SDK getter that automatically detects environment
91
+ *
92
+ * Usage:
93
+ * - Client-side: getStorefrontSDK()
94
+ * - Server-side: getStorefrontSDK(await cookies())
95
+ */
96
+ declare function getStorefrontSDK(): StorefrontSDK;
97
+ declare function getStorefrontSDK(cookieStore: NextCookieStore): StorefrontSDK;
98
+
99
+ interface StorefrontSDKInitializerProps {
100
+ config: NextJSSDKConfig;
101
+ }
102
+ /**
103
+ * Client-side initialization component
104
+ * Use this in your root layout to initialize the SDK once
105
+ *
106
+ * The core SDK middleware now automatically handles everything:
107
+ * - Creates anonymous tokens automatically on first API request (if no tokens exist)
108
+ * - Token state assessment and cleanup on first request per page load
109
+ * - Expired token refresh with automatic anonymous fallback
110
+ * - Graceful handling of partial token states
111
+ *
112
+ * No manual token creation needed - just make API calls and everything works!
113
+ */
114
+ declare function StorefrontSDKInitializer({ config, }: StorefrontSDKInitializerProps): null;
115
+
116
+ export { ClientTokenStorage, type NextJSSDKConfig, type NextJSTokenStorageOptions, ServerTokenStorage, StorefrontSDKInitializer, getStorefrontSDK };
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/sdk-manager.ts
9
+ import {
10
+ StorefrontSDK
11
+ } from "@commercengine/storefront-sdk";
12
+
13
+ // src/token-storage.ts
14
+ var ClientTokenStorage = class {
15
+ constructor(options = {}) {
16
+ const prefix = options.prefix || "ce_";
17
+ this.accessTokenKey = `${prefix}access_token`;
18
+ this.refreshTokenKey = `${prefix}refresh_token`;
19
+ this.options = {
20
+ maxAge: options.maxAge || 30 * 24 * 60 * 60,
21
+ // 30 days default
22
+ path: options.path || "/",
23
+ domain: options.domain,
24
+ secure: options.secure ?? (typeof window !== "undefined" && window.location?.protocol === "https:"),
25
+ sameSite: options.sameSite || "Lax"
26
+ };
27
+ }
28
+ async getAccessToken() {
29
+ return this.getCookie(this.accessTokenKey);
30
+ }
31
+ async setAccessToken(token) {
32
+ this.setCookie(this.accessTokenKey, token);
33
+ }
34
+ async getRefreshToken() {
35
+ return this.getCookie(this.refreshTokenKey);
36
+ }
37
+ async setRefreshToken(token) {
38
+ this.setCookie(this.refreshTokenKey, token);
39
+ }
40
+ async clearTokens() {
41
+ this.deleteCookie(this.accessTokenKey);
42
+ this.deleteCookie(this.refreshTokenKey);
43
+ }
44
+ getCookie(name) {
45
+ if (typeof document === "undefined") return null;
46
+ const value = `; ${document.cookie}`;
47
+ const parts = value.split(`; ${name}=`);
48
+ if (parts.length === 2) {
49
+ const cookieValue = parts.pop()?.split(";").shift();
50
+ return cookieValue ? decodeURIComponent(cookieValue) : null;
51
+ }
52
+ return null;
53
+ }
54
+ setCookie(name, value) {
55
+ if (typeof document === "undefined") return;
56
+ const encodedValue = encodeURIComponent(value);
57
+ let cookieString = `${name}=${encodedValue}`;
58
+ if (this.options.maxAge) {
59
+ cookieString += `; Max-Age=${this.options.maxAge}`;
60
+ }
61
+ if (this.options.path) {
62
+ cookieString += `; Path=${this.options.path}`;
63
+ }
64
+ if (this.options.domain) {
65
+ cookieString += `; Domain=${this.options.domain}`;
66
+ }
67
+ if (this.options.secure) {
68
+ cookieString += `; Secure`;
69
+ }
70
+ if (this.options.sameSite) {
71
+ cookieString += `; SameSite=${this.options.sameSite}`;
72
+ }
73
+ document.cookie = cookieString;
74
+ }
75
+ deleteCookie(name) {
76
+ if (typeof document === "undefined") return;
77
+ let cookieString = `${name}=; Max-Age=0`;
78
+ if (this.options.path) {
79
+ cookieString += `; Path=${this.options.path}`;
80
+ }
81
+ if (this.options.domain) {
82
+ cookieString += `; Domain=${this.options.domain}`;
83
+ }
84
+ document.cookie = cookieString;
85
+ }
86
+ };
87
+ var ServerTokenStorage = class {
88
+ constructor(cookieStore, options = {}) {
89
+ const prefix = options.prefix || "ce_";
90
+ this.accessTokenKey = `${prefix}access_token`;
91
+ this.refreshTokenKey = `${prefix}refresh_token`;
92
+ this.cookieStore = cookieStore;
93
+ this.options = {
94
+ maxAge: options.maxAge || 30 * 24 * 60 * 60,
95
+ // 30 days default
96
+ path: options.path || "/",
97
+ domain: options.domain,
98
+ secure: options.secure ?? process.env.NODE_ENV === "production",
99
+ sameSite: options.sameSite || "Lax"
100
+ };
101
+ }
102
+ async getAccessToken() {
103
+ try {
104
+ return this.cookieStore.get(this.accessTokenKey)?.value || null;
105
+ } catch (error) {
106
+ console.warn(`Could not get access token from server cookies:`, error);
107
+ return null;
108
+ }
109
+ }
110
+ async setAccessToken(token) {
111
+ try {
112
+ this.cookieStore.set(this.accessTokenKey, token, {
113
+ maxAge: this.options.maxAge,
114
+ path: this.options.path,
115
+ domain: this.options.domain,
116
+ secure: this.options.secure,
117
+ sameSite: this.options.sameSite?.toLowerCase(),
118
+ httpOnly: false
119
+ // Must be false for client-side access
120
+ });
121
+ } catch (error) {
122
+ console.warn(`Could not set access token on server:`, error);
123
+ }
124
+ }
125
+ async getRefreshToken() {
126
+ try {
127
+ return this.cookieStore.get(this.refreshTokenKey)?.value || null;
128
+ } catch (error) {
129
+ console.warn(`Could not get refresh token from server cookies:`, error);
130
+ return null;
131
+ }
132
+ }
133
+ async setRefreshToken(token) {
134
+ try {
135
+ this.cookieStore.set(this.refreshTokenKey, token, {
136
+ maxAge: this.options.maxAge,
137
+ path: this.options.path,
138
+ domain: this.options.domain,
139
+ secure: this.options.secure,
140
+ sameSite: this.options.sameSite?.toLowerCase(),
141
+ httpOnly: false
142
+ // Must be false for client-side access
143
+ });
144
+ } catch (error) {
145
+ console.warn(`Could not set refresh token on server:`, error);
146
+ }
147
+ }
148
+ async clearTokens() {
149
+ try {
150
+ this.cookieStore.delete(this.accessTokenKey);
151
+ this.cookieStore.delete(this.refreshTokenKey);
152
+ } catch (error) {
153
+ console.warn(`Could not clear tokens on server:`, error);
154
+ }
155
+ }
156
+ };
157
+
158
+ // src/sdk-manager.ts
159
+ var NextJSSDKManager = class _NextJSSDKManager {
160
+ constructor() {
161
+ this.clientSDK = null;
162
+ this.config = null;
163
+ }
164
+ static getInstance() {
165
+ if (!_NextJSSDKManager.instance) {
166
+ _NextJSSDKManager.instance = new _NextJSSDKManager();
167
+ }
168
+ return _NextJSSDKManager.instance;
169
+ }
170
+ /**
171
+ * Initialize the SDK with configuration (should be called once)
172
+ */
173
+ initialize(config) {
174
+ this.config = config;
175
+ if (typeof window !== "undefined" && !this.clientSDK) {
176
+ this.clientSDK = this.createClientSDK();
177
+ }
178
+ }
179
+ /**
180
+ * Get SDK instance for client-side usage
181
+ */
182
+ getClientSDK() {
183
+ if (!this.config) {
184
+ throw new Error("SDK not initialized. Call initialize() first.");
185
+ }
186
+ if (!this.clientSDK) {
187
+ this.clientSDK = this.createClientSDK();
188
+ }
189
+ return this.clientSDK;
190
+ }
191
+ /**
192
+ * Get SDK instance for server-side usage
193
+ */
194
+ getServerSDK(cookieStore) {
195
+ if (!this.config) {
196
+ throw new Error("SDK not initialized. Call initialize() first.");
197
+ }
198
+ return new StorefrontSDK({
199
+ ...this.config,
200
+ tokenStorage: new ServerTokenStorage(
201
+ cookieStore,
202
+ this.config.tokenStorageOptions
203
+ )
204
+ });
205
+ }
206
+ createClientSDK() {
207
+ if (!this.config) {
208
+ throw new Error("SDK not initialized. Call initialize() first.");
209
+ }
210
+ return new StorefrontSDK({
211
+ ...this.config,
212
+ tokenStorage: new ClientTokenStorage(this.config.tokenStorageOptions)
213
+ });
214
+ }
215
+ /**
216
+ * Check if SDK is initialized
217
+ */
218
+ isInitialized() {
219
+ return this.config !== null;
220
+ }
221
+ /**
222
+ * Reset the SDK (useful for testing)
223
+ */
224
+ reset() {
225
+ this.clientSDK = null;
226
+ this.config = null;
227
+ }
228
+ };
229
+ function getStorefrontSDK(cookieStore) {
230
+ const manager = NextJSSDKManager.getInstance();
231
+ if (typeof window !== "undefined") {
232
+ if (cookieStore) {
233
+ console.warn(
234
+ "Cookie store passed in client environment - this will be ignored"
235
+ );
236
+ }
237
+ return manager.getClientSDK();
238
+ }
239
+ if (!cookieStore) {
240
+ let autoDetectMessage = "";
241
+ try {
242
+ __require.resolve("next/headers");
243
+ autoDetectMessage = `
244
+
245
+ \u{1F50D} Auto-detection attempted but failed. You may be in:
246
+ - Server Action (use: const sdk = getStorefrontSDK(await cookies()))
247
+ - API Route (use: const sdk = getStorefrontSDK(cookies()))
248
+ - Middleware (token access limited)
249
+ `;
250
+ } catch {
251
+ autoDetectMessage = `
252
+
253
+ \u{1F4A1} Make sure you have Next.js installed and are in a server context.
254
+ `;
255
+ }
256
+ throw new Error(
257
+ `
258
+ \u{1F6A8} Server Environment Detected!
259
+
260
+ You're calling getStorefrontSDK() on the server without cookies.
261
+ Please pass the Next.js cookie store:
262
+
263
+ \u2705 Correct usage:
264
+ import { cookies } from 'next/headers';
265
+
266
+ // Server Actions & Route Handlers
267
+ const sdk = getStorefrontSDK(await cookies());
268
+
269
+ // API Routes (Next.js 12)
270
+ const sdk = getStorefrontSDK(cookies());
271
+
272
+ \u274C Your current usage:
273
+ const sdk = getStorefrontSDK(); // Missing cookies!
274
+ ${autoDetectMessage}
275
+ This is required for server-side token access.
276
+ `.trim()
277
+ );
278
+ }
279
+ return manager.getServerSDK(cookieStore);
280
+ }
281
+ function initializeStorefrontSDK(config) {
282
+ const manager = NextJSSDKManager.getInstance();
283
+ manager.initialize(config);
284
+ }
285
+
286
+ // src/init-client.ts
287
+ import { useEffect } from "react";
288
+ function StorefrontSDKInitializer({
289
+ config
290
+ }) {
291
+ useEffect(() => {
292
+ initializeStorefrontSDK(config);
293
+ }, [config]);
294
+ return null;
295
+ }
296
+
297
+ // src/index.ts
298
+ import { Environment } from "@commercengine/storefront-sdk";
299
+ export {
300
+ ClientTokenStorage,
301
+ Environment,
302
+ ServerTokenStorage,
303
+ StorefrontSDKInitializer,
304
+ getStorefrontSDK
305
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@commercengine/storefront-sdk-nextjs",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Next.js wrapper for the Commerce Engine Storefront SDK",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "commerce engine",
21
+ "storefront",
22
+ "api",
23
+ "sdk",
24
+ "nextjs",
25
+ "react"
26
+ ],
27
+ "author": "Commerce Engine",
28
+ "license": "All rights reserved",
29
+ "peerDependencies": {
30
+ "next": ">=13.0.0",
31
+ "react": ">=18.0.0",
32
+ "react-dom": ">=18.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@commercengine/storefront-sdk": "0.8.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^24.3.0",
39
+ "@types/react": "^18.0.0",
40
+ "next": ">=13.0.0",
41
+ "react": ">=18.0.0",
42
+ "react-dom": ">=18.0.0",
43
+ "rimraf": "^6.0.1",
44
+ "tsup": "^8.5.0",
45
+ "typescript": "^5.9.2"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "clean": "rimraf dist"
53
+ }
54
+ }