@kasuvia/sdk 0.0.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.
Files changed (73) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +112 -0
  3. package/dist/index.d.mts +1891 -0
  4. package/dist/index.d.ts +1891 -0
  5. package/dist/index.js +3540 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +3314 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +59 -0
  10. package/src/client/authClient.ts +135 -0
  11. package/src/client/graphqlClient.ts +53 -0
  12. package/src/client/httpClient.ts +298 -0
  13. package/src/config/index.ts +102 -0
  14. package/src/graphql/collections.graphql.ts +56 -0
  15. package/src/graphql/index.ts +7 -0
  16. package/src/graphql/products.graphql.ts +710 -0
  17. package/src/graphql/wishlist.graphql.ts +33 -0
  18. package/src/hooks/index.ts +26 -0
  19. package/src/hooks/use-auth.ts +182 -0
  20. package/src/hooks/use-bespoke-availability.ts +20 -0
  21. package/src/hooks/use-cart.ts +207 -0
  22. package/src/hooks/use-cms.ts +165 -0
  23. package/src/hooks/use-collections.ts +40 -0
  24. package/src/hooks/use-consultation-stream.ts +141 -0
  25. package/src/hooks/use-consultations.ts +76 -0
  26. package/src/hooks/use-countries.ts +24 -0
  27. package/src/hooks/use-currencies.ts +27 -0
  28. package/src/hooks/use-google-translate.ts +164 -0
  29. package/src/hooks/use-is-mounted.ts +9 -0
  30. package/src/hooks/use-languages.ts +33 -0
  31. package/src/hooks/use-order-stream.ts +195 -0
  32. package/src/hooks/use-orders.ts +64 -0
  33. package/src/hooks/use-payment.ts +68 -0
  34. package/src/hooks/use-products.ts +113 -0
  35. package/src/hooks/use-service-booking.ts +118 -0
  36. package/src/hooks/use-states.ts +28 -0
  37. package/src/hooks/use-wishlist.ts +121 -0
  38. package/src/index.ts +65 -0
  39. package/src/services/app.service.ts +17 -0
  40. package/src/services/auth.service.ts +332 -0
  41. package/src/services/bespoke.service.ts +17 -0
  42. package/src/services/cart.service.ts +245 -0
  43. package/src/services/cms.service.ts +112 -0
  44. package/src/services/collection.service.ts +77 -0
  45. package/src/services/config.service.ts +42 -0
  46. package/src/services/consultation.service.ts +51 -0
  47. package/src/services/index.ts +21 -0
  48. package/src/services/logistics.service.ts +95 -0
  49. package/src/services/notifications.service.ts +50 -0
  50. package/src/services/order.service.ts +169 -0
  51. package/src/services/payment.service.ts +127 -0
  52. package/src/services/product.service.ts +678 -0
  53. package/src/services/service-booking.service.ts +86 -0
  54. package/src/services/wishlist.service.ts +84 -0
  55. package/src/types/auth/index.ts +126 -0
  56. package/src/types/bespoke/index.ts +39 -0
  57. package/src/types/business/index.ts +6 -0
  58. package/src/types/cms/index.ts +77 -0
  59. package/src/types/consultations/index.ts +79 -0
  60. package/src/types/index.ts +27 -0
  61. package/src/types/media/index.ts +41 -0
  62. package/src/types/payments/index.ts +120 -0
  63. package/src/types/services/index.ts +80 -0
  64. package/src/types/store/index.ts +282 -0
  65. package/src/types/store/shipping.ts +38 -0
  66. package/src/types/ui/index.ts +32 -0
  67. package/src/utils/cache.ts +33 -0
  68. package/src/utils/checkout.ts +39 -0
  69. package/src/utils/customer.ts +20 -0
  70. package/src/utils/error-handler.ts +14 -0
  71. package/src/utils/index.ts +10 -0
  72. package/src/utils/logger.ts +79 -0
  73. package/src/utils/sanitize-url.ts +55 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Auth Client - Token Store and Refresh Logic
3
+ *
4
+ * - Access token: in-memory only (XSS protection)
5
+ * - Refresh token: localStorage (persists across page loads), keyed per tenant
6
+ */
7
+ import axios from 'axios'
8
+ import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
9
+
10
+ let _accessToken: string | null = null
11
+ let _isRefreshing = false
12
+ let _pendingQueue: Array<{
13
+ resolve: (newAccessToken: string) => void
14
+ reject: (error: unknown) => void
15
+ }> = []
16
+
17
+ /**
18
+ * Returns a BID-prefixed key for storage isolation.
19
+ * Prevents data leakage when multiple storefronts run on the same domain.
20
+ */
21
+ function getStorageKey(): string {
22
+ if (!isInitialized()) return 'kasuvia-sf-anon-refresh'
23
+ const config = getRuntimeConfig()
24
+ return `kasuvia-sf-${config.businessId}-refresh`
25
+ }
26
+
27
+ function drainQueue(error: unknown, newToken: string | null = null): void {
28
+ _pendingQueue.forEach(({ resolve, reject }) => {
29
+ if (newToken) resolve(newToken)
30
+ else reject(error)
31
+ })
32
+ _pendingQueue = []
33
+ }
34
+
35
+ export const tokenStore = {
36
+ getAccessToken: (): string | null => _accessToken,
37
+
38
+ setAccessToken: (token: string | null): void => {
39
+ _accessToken = token
40
+ },
41
+
42
+ getRefreshToken: (): string | null => {
43
+ if (typeof window === 'undefined') return null
44
+ const key = getStorageKey()
45
+ return localStorage.getItem(key) || sessionStorage.getItem(key)
46
+ },
47
+
48
+ setRefreshToken: (token: string | null, remember = true): void => {
49
+ if (typeof window === 'undefined') return
50
+ const key = getStorageKey()
51
+ localStorage.removeItem(key)
52
+ sessionStorage.removeItem(key)
53
+ if (token) {
54
+ const store = remember ? localStorage : sessionStorage
55
+ store.setItem(key, token)
56
+ }
57
+ },
58
+
59
+ clearAll: (): void => {
60
+ _accessToken = null
61
+ if (typeof window !== 'undefined') {
62
+ const key = getStorageKey()
63
+ localStorage.removeItem(key)
64
+ sessionStorage.removeItem(key)
65
+ }
66
+ },
67
+
68
+ hasSession: (): boolean => {
69
+ if (typeof window === 'undefined') return false
70
+ const key = getStorageKey()
71
+ return !!(localStorage.getItem(key) || sessionStorage.getItem(key))
72
+ },
73
+
74
+ isPersistent: (): boolean => {
75
+ if (typeof window === 'undefined') return true
76
+ const key = getStorageKey()
77
+ return !!localStorage.getItem(key)
78
+ },
79
+ } as const
80
+
81
+ /**
82
+ * Refresh-only Axios instance.
83
+ * CRITICAL: NO auth interceptors — avoids infinite recursion when the 401
84
+ * interceptor on authAxios calls refreshToken() and itself receives a 401.
85
+ * baseURL is set eagerly from the static BACKEND_URLS constant.
86
+ */
87
+ const refreshAxios = axios.create({
88
+ baseURL: getBackendUrl('auth'),
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ Accept: 'application/json',
92
+ },
93
+ withCredentials: true,
94
+ timeout: 30_000,
95
+ })
96
+
97
+ /**
98
+ * Perform token refresh with queue-coalescing to avoid parallel refresh races.
99
+ */
100
+ export async function refreshToken(): Promise<string> {
101
+ if (_isRefreshing) {
102
+ // Queue subsequent callers so they all get the new token from a single refresh
103
+ return new Promise((resolve, reject) => {
104
+ _pendingQueue.push({ resolve, reject })
105
+ })
106
+ }
107
+
108
+ _isRefreshing = true
109
+
110
+ const refresh = tokenStore.getRefreshToken()
111
+ if (!refresh) {
112
+ _isRefreshing = false
113
+ tokenStore.clearAll()
114
+ throw new Error('[kasuvia SDK] No refresh token available')
115
+ }
116
+
117
+ try {
118
+ const { data } = await refreshAxios.post('/auth/token/refresh/', { refresh })
119
+ const newAccess: string = data.access
120
+ tokenStore.setAccessToken(newAccess)
121
+
122
+ if (data.refresh) {
123
+ tokenStore.setRefreshToken(data.refresh, tokenStore.isPersistent())
124
+ }
125
+
126
+ drainQueue(null, newAccess)
127
+ return newAccess
128
+ } catch (err) {
129
+ drainQueue(err)
130
+ tokenStore.clearAll()
131
+ throw err
132
+ } finally {
133
+ _isRefreshing = false
134
+ }
135
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * GraphQL Client
3
+ *
4
+ * Provides a GraphQL client with tenant and auth headers for product queries.
5
+ * The endpoint URL is derived from the static BACKEND_URLS constant (eager,
6
+ * module-load safe). Auth headers are injected fresh on each request via a
7
+ * factory so the token is always current.
8
+ */
9
+ import { GraphQLClient } from 'graphql-request'
10
+ import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
11
+ import { tokenStore } from './authClient'
12
+
13
+ const GRAPHQL_URL = `${getBackendUrl('store')}/graphql`
14
+
15
+ /**
16
+ * Build fresh request headers.
17
+ * Called on every getGraphQLClient() so token changes are always reflected.
18
+ */
19
+ function buildHeaders(): Record<string, string> {
20
+ const headers: Record<string, string> = {
21
+ 'Content-Type': 'application/json',
22
+ Accept: 'application/json',
23
+ }
24
+
25
+ if (isInitialized()) {
26
+ const config = getRuntimeConfig()
27
+ headers['X-Storefront-Key'] = config.storefrontApiKey
28
+ headers['X-Business-ID'] = config.businessId
29
+ }
30
+
31
+ const token = tokenStore.getAccessToken()
32
+ if (token) headers['Authorization'] = `Bearer ${token}`
33
+
34
+ return headers
35
+ }
36
+
37
+ /**
38
+ * Returns a GraphQL client instance with up-to-date auth headers.
39
+ * A new client is created on each call so token/config changes take effect
40
+ * immediately without requiring an explicit reset.
41
+ */
42
+ export function getGraphQLClient(): GraphQLClient {
43
+ return new GraphQLClient(GRAPHQL_URL, { headers: buildHeaders() })
44
+ }
45
+
46
+ /**
47
+ * No-op — kept for backward compatibility.
48
+ * Clients are now stateless (created fresh on each call), so there is no
49
+ * cached instance to reset.
50
+ */
51
+ export function resetGraphQLClient(): void {
52
+ // No-op: clients are built fresh on every call.
53
+ }
@@ -0,0 +1,298 @@
1
+ /**
2
+ * HTTP Client - Pre-configured Axios instances
3
+ *
4
+ * Each Axios instance is created with a default baseURL based on the static
5
+ * service URL. The baseURLs are complete URLs including the API version,
6
+ * selected based on the configured environment (development/production).
7
+ */
8
+ import axios, { type AxiosInstance } from 'axios'
9
+ import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
10
+ import { tokenStore, refreshToken } from './authClient'
11
+
12
+ // ── Guest session management ─────────────────────────────────────────────────
13
+
14
+ let _guestSessionId: string | null = null
15
+
16
+ export function getOrCreateGuestSessionId(): string {
17
+ if (_guestSessionId) return _guestSessionId
18
+
19
+ const storageKey = isInitialized()
20
+ ? `kasuvia-guest-${getRuntimeConfig().businessId}`
21
+ : 'kasuvia-guest-anon'
22
+
23
+ if (typeof window !== 'undefined') {
24
+ _guestSessionId = sessionStorage.getItem(storageKey) || crypto.randomUUID()
25
+ sessionStorage.setItem(storageKey, _guestSessionId)
26
+ } else {
27
+ _guestSessionId = crypto.randomUUID()
28
+ }
29
+
30
+ return _guestSessionId
31
+ }
32
+
33
+ export function getGuestSessionId(): string | null {
34
+ if (!_guestSessionId && typeof window !== 'undefined') {
35
+ const storageKey = isInitialized()
36
+ ? `kasuvia-guest-${getRuntimeConfig().businessId}`
37
+ : 'kasuvia-guest-anon'
38
+ _guestSessionId = sessionStorage.getItem(storageKey)
39
+ }
40
+ return _guestSessionId
41
+ }
42
+
43
+ export function clearGuestSessionId(): void {
44
+ _guestSessionId = null
45
+ if (typeof window !== 'undefined') {
46
+ const storageKey = isInitialized()
47
+ ? `kasuvia-guest-${getRuntimeConfig().businessId}`
48
+ : 'kasuvia-guest-anon'
49
+ sessionStorage.removeItem(storageKey)
50
+ }
51
+ }
52
+
53
+ // ── Store Management Axios ───────────────────────────────────────────────────
54
+
55
+ export const storeManagementAxios = axios.create({
56
+ baseURL: getBackendUrl('store'),
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ Accept: 'application/json',
60
+ },
61
+ timeout: 30_000,
62
+ }) as AxiosInstance
63
+
64
+ storeManagementAxios.interceptors.request.use((cfg) => {
65
+ const config = getRuntimeConfig()
66
+
67
+ const token = tokenStore.getAccessToken()
68
+ if (token) {
69
+ cfg.headers = cfg.headers ?? {}
70
+ cfg.headers['Authorization'] = `Bearer ${token}`
71
+ }
72
+
73
+ cfg.headers = cfg.headers ?? {}
74
+ cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
75
+ cfg.headers['X-Business-ID'] = config.businessId
76
+
77
+ if (!token) {
78
+ const sid = getOrCreateGuestSessionId()
79
+ if (sid) cfg.headers['X-Guest-Session'] = sid
80
+ }
81
+
82
+ return cfg
83
+ })
84
+
85
+ storeManagementAxios.interceptors.response.use(
86
+ (res) => res,
87
+ async (err) => {
88
+ const originalRequest = err.config
89
+ if (!originalRequest) return Promise.reject(err)
90
+
91
+ const status = err.response?.status
92
+ const isUnauthorized = status === 401
93
+ const isRefreshEndpoint = originalRequest.url?.includes('/auth/token/refresh/')
94
+ const alreadyRetried = originalRequest._retry === true
95
+
96
+ if (isUnauthorized && isRefreshEndpoint) {
97
+ tokenStore.clearAll()
98
+ return Promise.reject(err)
99
+ }
100
+
101
+ if (!isUnauthorized || alreadyRetried) return Promise.reject(err)
102
+
103
+ try {
104
+ const newToken = await refreshToken()
105
+ originalRequest._retry = true
106
+ if (originalRequest.headers) {
107
+ originalRequest.headers['Authorization'] = `Bearer ${newToken}`
108
+ }
109
+ return storeManagementAxios(originalRequest)
110
+ } catch (refreshErr) {
111
+ tokenStore.clearAll()
112
+ return Promise.reject(refreshErr)
113
+ }
114
+ }
115
+ )
116
+
117
+ // ── Public Store Axios ───────────────────────────────────────────────────────
118
+
119
+ export const publicStoreAxios = axios.create({
120
+ baseURL: getBackendUrl('store'),
121
+ headers: {
122
+ 'Content-Type': 'application/json',
123
+ Accept: 'application/json',
124
+ },
125
+ timeout: 30_000,
126
+ })
127
+
128
+ publicStoreAxios.interceptors.request.use((cfg) => {
129
+ const config = getRuntimeConfig()
130
+ cfg.headers = cfg.headers ?? {}
131
+ cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
132
+ cfg.headers['X-Business-ID'] = config.businessId
133
+ return cfg
134
+ })
135
+
136
+ publicStoreAxios.interceptors.response.use(
137
+ (res) => res,
138
+ (err) => Promise.reject(err)
139
+ )
140
+
141
+ // ── Auth Axios ───────────────────────────────────────────────────────────────
142
+
143
+ export const authAxios = axios.create({
144
+ baseURL: getBackendUrl('auth'),
145
+ headers: {
146
+ 'Content-Type': 'application/json',
147
+ Accept: 'application/json',
148
+ },
149
+ withCredentials: true,
150
+ timeout: 30_000,
151
+ }) as AxiosInstance
152
+
153
+ authAxios.interceptors.request.use((cfg) => {
154
+ const config = getRuntimeConfig()
155
+
156
+ const token = tokenStore.getAccessToken()
157
+ if (token) {
158
+ cfg.headers = cfg.headers ?? {}
159
+ cfg.headers['Authorization'] = `Bearer ${token}`
160
+ }
161
+
162
+ cfg.headers = cfg.headers ?? {}
163
+ cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
164
+ cfg.headers['X-Business-ID'] = config.businessId
165
+
166
+ return cfg
167
+ })
168
+
169
+ authAxios.interceptors.response.use(
170
+ (res) => res,
171
+ async (err) => {
172
+ const originalRequest = err.config
173
+ if (!originalRequest) return Promise.reject(err)
174
+
175
+ const status = err.response?.status
176
+ const isUnauthorized = status === 401
177
+ const isRefreshEndpoint = originalRequest.url?.includes('/auth/token/refresh/')
178
+ const alreadyRetried = originalRequest._retry === true
179
+
180
+ if (isUnauthorized && isRefreshEndpoint) {
181
+ tokenStore.clearAll()
182
+ return Promise.reject(err)
183
+ }
184
+
185
+ if (!isUnauthorized || alreadyRetried) return Promise.reject(err)
186
+
187
+ try {
188
+ const newToken = await refreshToken()
189
+ originalRequest._retry = true
190
+ if (originalRequest.headers) {
191
+ originalRequest.headers['Authorization'] = `Bearer ${newToken}`
192
+ }
193
+ return authAxios(originalRequest)
194
+ } catch (refreshErr) {
195
+ tokenStore.clearAll()
196
+ return Promise.reject(refreshErr)
197
+ }
198
+ }
199
+ )
200
+
201
+ // ── Payment Axios ────────────────────────────────────────────────────────────
202
+
203
+ export const paymentAxios = axios.create({
204
+ baseURL: getBackendUrl('payment'),
205
+ headers: {
206
+ 'Content-Type': 'application/json',
207
+ Accept: 'application/json',
208
+ },
209
+ timeout: 30_000,
210
+ })
211
+
212
+ paymentAxios.interceptors.request.use((cfg) => {
213
+ const config = getRuntimeConfig()
214
+ cfg.headers = cfg.headers ?? {}
215
+ cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
216
+ cfg.headers['X-Business-ID'] = config.businessId
217
+ return cfg
218
+ })
219
+
220
+ paymentAxios.interceptors.response.use(
221
+ (res) => res,
222
+ (err) => Promise.reject(err)
223
+ )
224
+
225
+ // ── Notifications Axios ──────────────────────────────────────────────────────
226
+
227
+ export const notificationsAxios = axios.create({
228
+ baseURL: getBackendUrl('notifications'),
229
+ headers: {
230
+ 'Content-Type': 'application/json',
231
+ Accept: 'application/json',
232
+ },
233
+ timeout: 30_000,
234
+ })
235
+
236
+ notificationsAxios.interceptors.response.use(
237
+ (res) => res,
238
+ (err) => Promise.reject(err)
239
+ )
240
+
241
+ // ── CMS Axios ───────────────────────────────────────────────────────────────
242
+
243
+ export const cmsAxios = axios.create({
244
+ baseURL: getBackendUrl('cms'),
245
+ headers: {
246
+ 'Content-Type': 'application/json',
247
+ Accept: 'application/json',
248
+ },
249
+ timeout: 30_000,
250
+ })
251
+
252
+ cmsAxios.interceptors.request.use((cfg) => {
253
+ const config = getRuntimeConfig()
254
+ cfg.headers = cfg.headers ?? {}
255
+ cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
256
+ cfg.headers['X-Business-ID'] = config.businessId
257
+ return cfg
258
+ })
259
+
260
+ cmsAxios.interceptors.response.use(
261
+ (res) => res,
262
+ (err) => Promise.reject(err)
263
+ )
264
+
265
+ // ── Refresh Axios ────────────────────────────────────────────────────────────
266
+
267
+ export const refreshAxios = axios.create({
268
+ baseURL: getBackendUrl('auth'),
269
+ headers: {
270
+ 'Content-Type': 'application/json',
271
+ Accept: 'application/json',
272
+ },
273
+ withCredentials: true,
274
+ timeout: 30_000,
275
+ })
276
+
277
+ /**
278
+ * configureHttpClients()
279
+ * Updates the baseURL of all Axios instances to match the configured environment.
280
+ * This must be called after initSdk() to ensure the correct environment URLs are used.
281
+ */
282
+ export function configureHttpClients(): void {
283
+ if (!isInitialized()) return
284
+
285
+ const storeUrl = getBackendUrl('store')
286
+ const authUrl = getBackendUrl('auth')
287
+ const paymentUrl = getBackendUrl('payment')
288
+ const notificationsUrl = getBackendUrl('notifications')
289
+ const cmsUrl = getBackendUrl('cms')
290
+
291
+ storeManagementAxios.defaults.baseURL = storeUrl
292
+ publicStoreAxios.defaults.baseURL = storeUrl
293
+ authAxios.defaults.baseURL = authUrl
294
+ paymentAxios.defaults.baseURL = paymentUrl
295
+ notificationsAxios.defaults.baseURL = notificationsUrl
296
+ cmsAxios.defaults.baseURL = cmsUrl
297
+ refreshAxios.defaults.baseURL = authUrl
298
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Kasuvia SDK Configuration
3
+ *
4
+ * This module manages SDK initialization and runtime configuration.
5
+ * The SDK is client-based and runs in the browser.
6
+ * All backend URLs are bundled with the SDK.
7
+ *
8
+ * SECURITY NOTE: The storefront API key has limited permissions - it only provides
9
+ * customer-based resources from a business owner to customers, not full platform access.
10
+ * This key is safe to expose to the browser as a NEXT_PUBLIC_ environment variable.
11
+ */
12
+
13
+ import { logger, setEnvironmentMode, setLoggerEnvironmentMode } from "../utils"
14
+
15
+ export interface SdkConfig {
16
+ businessId: string
17
+ storefrontApiKey: string
18
+ }
19
+
20
+ interface SdkRuntimeConfig {
21
+ businessId: string
22
+ storefrontApiKey: string
23
+ }
24
+
25
+ let runtimeConfig: SdkRuntimeConfig | null = null
26
+
27
+ /**
28
+ * Backend URLs - bundled with the SDK
29
+ * These are the complete URLs (including API version) that the SDK uses internally.
30
+ * The storefront never sees or configures these URLs.
31
+ *
32
+ * Currently set to localhost development URLs.
33
+ * Update to production URLs when ready for deployment.
34
+ */
35
+ const BACKEND_URLS = {
36
+ auth: 'https://127.0.0.1:8000/api/v1',
37
+ store: 'https://127.0.0.1:8001/api/v1',
38
+ payment: 'https://127.0.0.1:8002/api/v1',
39
+ notifications: 'https://127.0.0.1:8007/api/v1',
40
+ cms: 'https://127.0.0.1:8003/api/v1',
41
+ } as const
42
+
43
+ /**
44
+ * Initialize the SDK with runtime credentials
45
+ * This must be called before any SDK operations
46
+ *
47
+ * IMPORTANT: In Next.js, this should be called in layout.tsx (server component)
48
+ * using private environment variables (kasuvia_BUSINESS_ID, kasuvia_STOREFRONT_API_KEY).
49
+ * The config is stored in module scope and will be available to client components
50
+ * through the SDK's bundled code.
51
+ *
52
+ * @param config - SDK configuration including credentials
53
+ */
54
+ export function initSdk(config: SdkConfig): void {
55
+ runtimeConfig = {
56
+ businessId: config.businessId,
57
+ storefrontApiKey: config.storefrontApiKey,
58
+ }
59
+
60
+ // Determine environment from NODE_ENV for logging/cache behavior
61
+ // In browser (window exists), default to production behavior
62
+ // In Node.js (window undefined), check NODE_ENV
63
+ const isDevelopment = typeof window === 'undefined' &&
64
+ (globalThis as any).process?.env?.NODE_ENV === 'development'
65
+ setEnvironmentMode(isDevelopment)
66
+ setLoggerEnvironmentMode(isDevelopment)
67
+
68
+ logger.log('SDK initialized with businessId:', config.businessId)
69
+ }
70
+
71
+ /**
72
+ * Get the current runtime configuration
73
+ * Throws if SDK is not initialized
74
+ */
75
+ export function getRuntimeConfig(): SdkRuntimeConfig {
76
+ if (!runtimeConfig) {
77
+ throw new Error('SDK not initialized. Call initSdk() before using SDK functions.')
78
+ }
79
+ return runtimeConfig
80
+ }
81
+
82
+ /**
83
+ * Get backend URL for a specific service
84
+ * Internal use only - never exposed to storefront
85
+ */
86
+ export function getBackendUrl(service: 'auth' | 'store' | 'payment' | 'notifications' | 'cms'): string {
87
+ return BACKEND_URLS[service]
88
+ }
89
+
90
+ /**
91
+ * Check if SDK is initialized
92
+ */
93
+ export function isInitialized(): boolean {
94
+ return runtimeConfig !== null
95
+ }
96
+
97
+ /**
98
+ * Reset SDK configuration (mainly for testing)
99
+ */
100
+ export function resetConfig(): void {
101
+ runtimeConfig = null
102
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GraphQL queries for collections
3
+ */
4
+ import { gql } from 'graphql-request'
5
+
6
+ export const GET_STOREFRONT_COLLECTIONS = gql`
7
+ query GetStorefrontCollections($page: Int = 1, $pageSize: Int = 20) {
8
+ storefront_collections(page: $page, pageSize: $pageSize) {
9
+ items {
10
+ id
11
+ business_id
12
+ name
13
+ slug
14
+ description
15
+ collection_type
16
+ rules
17
+ image {
18
+ id
19
+ url
20
+ alt
21
+ }
22
+ status
23
+ sort_order
24
+ created_at
25
+ updated_at
26
+ }
27
+ total
28
+ page
29
+ page_size
30
+ total_pages
31
+ }
32
+ }
33
+ `
34
+
35
+ export const GET_STOREFRONT_COLLECTION_BY_SLUG = gql`
36
+ query GetStorefrontCollectionBySlug($slug: String!) {
37
+ storefront_collection_by_slug(slug: $slug) {
38
+ id
39
+ business_id
40
+ name
41
+ slug
42
+ description
43
+ collection_type
44
+ rules
45
+ image {
46
+ id
47
+ url
48
+ alt
49
+ }
50
+ status
51
+ sort_order
52
+ created_at
53
+ updated_at
54
+ }
55
+ }
56
+ `
@@ -0,0 +1,7 @@
1
+ /**
2
+ * GraphQL Queries Barrel Export
3
+ */
4
+
5
+ export * from './products.graphql'
6
+ export * from './collections.graphql'
7
+ export * from './wishlist.graphql'