@doswiftly/storefront-sdk 4.4.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/core/cart/types.d.ts +53 -20
  2. package/dist/core/cart/types.d.ts.map +1 -1
  3. package/dist/core/cart/types.js +3 -0
  4. package/dist/core/operations/cart.d.ts +15 -9
  5. package/dist/core/operations/cart.d.ts.map +1 -1
  6. package/dist/core/operations/cart.js +130 -58
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/package.json +9 -4
  10. package/src/__tests__/contract/storefront-api.contract.test.ts +0 -450
  11. package/src/__tests__/unit/auth-client.test.ts +0 -210
  12. package/src/__tests__/unit/bot-protection.test.ts +0 -461
  13. package/src/__tests__/unit/cart-client.test.ts +0 -233
  14. package/src/__tests__/unit/cart-store.test.ts +0 -349
  15. package/src/__tests__/unit/create-client.test.ts +0 -356
  16. package/src/__tests__/unit/helpers.test.ts +0 -377
  17. package/src/__tests__/unit/middleware.test.ts +0 -374
  18. package/src/__tests__/unit/test-helpers.ts +0 -103
  19. package/src/core/auth/auth-client.ts +0 -123
  20. package/src/core/auth/cookie-config.ts +0 -23
  21. package/src/core/auth/handlers.ts +0 -168
  22. package/src/core/auth/routes.ts +0 -26
  23. package/src/core/auth/token-client.ts +0 -51
  24. package/src/core/auth/types.ts +0 -54
  25. package/src/core/bot-protection/abstract-manager.ts +0 -185
  26. package/src/core/bot-protection/create-manager.ts +0 -37
  27. package/src/core/bot-protection/eucaptcha-manager.ts +0 -88
  28. package/src/core/bot-protection/fallback-manager.ts +0 -43
  29. package/src/core/bot-protection/turnstile-manager.ts +0 -92
  30. package/src/core/bot-protection/types/eucaptcha.d.ts +0 -28
  31. package/src/core/bot-protection/types/turnstile.d.ts +0 -33
  32. package/src/core/cache.ts +0 -102
  33. package/src/core/cart/cart-client.ts +0 -150
  34. package/src/core/cart/cookie-config.ts +0 -13
  35. package/src/core/cart/types.ts +0 -104
  36. package/src/core/client/compose.ts +0 -15
  37. package/src/core/client/create-client.ts +0 -129
  38. package/src/core/client/dedupe.ts +0 -19
  39. package/src/core/client/execute.ts +0 -70
  40. package/src/core/client/hash.ts +0 -21
  41. package/src/core/client/operation-name.ts +0 -12
  42. package/src/core/client/types.ts +0 -171
  43. package/src/core/currency/cookie-config.ts +0 -13
  44. package/src/core/errors.ts +0 -67
  45. package/src/core/format.ts +0 -254
  46. package/src/core/helpers/assert-no-user-errors.ts +0 -21
  47. package/src/core/helpers/normalize-connection.ts +0 -48
  48. package/src/core/helpers/sanitize-html.ts +0 -42
  49. package/src/core/image.ts +0 -22
  50. package/src/core/index.ts +0 -174
  51. package/src/core/language/cookie-config.ts +0 -13
  52. package/src/core/middleware/auth.ts +0 -27
  53. package/src/core/middleware/bot-protection.ts +0 -140
  54. package/src/core/middleware/currency.ts +0 -27
  55. package/src/core/middleware/errors.ts +0 -86
  56. package/src/core/middleware/language.ts +0 -30
  57. package/src/core/middleware/retry.ts +0 -75
  58. package/src/core/middleware/timeout.ts +0 -61
  59. package/src/core/operations/auth.ts +0 -123
  60. package/src/core/operations/cart.ts +0 -185
  61. package/src/index.ts +0 -25
  62. package/src/react/bot-protection/bot-protection-context.ts +0 -17
  63. package/src/react/bot-protection/bot-protection-widget.tsx +0 -46
  64. package/src/react/cookies.ts +0 -89
  65. package/src/react/helpers/create-store-context.ts +0 -56
  66. package/src/react/hooks/use-auth.ts +0 -218
  67. package/src/react/hooks/use-bot-protection.ts +0 -31
  68. package/src/react/hooks/use-cart-manager.ts +0 -236
  69. package/src/react/hooks/use-currency.ts +0 -23
  70. package/src/react/hooks/use-debounced-value.ts +0 -30
  71. package/src/react/hooks/use-hydrated.ts +0 -20
  72. package/src/react/hooks/use-storefront-client.ts +0 -12
  73. package/src/react/index.ts +0 -71
  74. package/src/react/providers/currency-provider.tsx +0 -30
  75. package/src/react/providers/language-provider.tsx +0 -34
  76. package/src/react/providers/storefront-client-provider.tsx +0 -107
  77. package/src/react/providers/storefront-provider.tsx +0 -99
  78. package/src/react/server/get-storefront-client.ts +0 -60
  79. package/src/react/server/index.ts +0 -1
  80. package/src/react/stores/auth.store.ts +0 -112
  81. package/src/react/stores/cart.context.ts +0 -10
  82. package/src/react/stores/cart.store.ts +0 -254
  83. package/src/react/stores/currency.store.ts +0 -93
  84. package/src/react/stores/index.ts +0 -17
  85. package/src/react/stores/language.store.ts +0 -90
  86. package/src/react/stores/store-context.tsx +0 -103
  87. package/src/react/types/shop-config.ts +0 -22
  88. package/tsconfig.json +0 -20
  89. package/vitest.config.ts +0 -14
@@ -1,377 +0,0 @@
1
- /**
2
- * Unit tests for helper utilities.
3
- *
4
- * Tests: assertNoUserErrors, hashQuery, getOperationName,
5
- * cache strategies, StorefrontError, TypedDocumentString.
6
- */
7
-
8
- import { describe, it, expect } from 'vitest';
9
- import { assertNoUserErrors } from '../../core/helpers/assert-no-user-errors';
10
- import { hashQuery } from '../../core/client/hash';
11
- import { getOperationName } from '../../core/client/operation-name';
12
- import { compose } from '../../core/client/compose';
13
- import { StorefrontError, ErrorCodes } from '../../core/errors';
14
- import { TypedDocumentString } from '../../core/client/types';
15
- import {
16
- cacheLong,
17
- cacheShort,
18
- cacheNone,
19
- cachePrivate,
20
- cacheCustom,
21
- generateCacheControlHeader,
22
- } from '../../core/cache';
23
- import type { ExecuteFn, GraphQLRequest, Middleware } from '../../core/client/types';
24
-
25
- // ---------------------------------------------------------------------------
26
- // assertNoUserErrors
27
- // ---------------------------------------------------------------------------
28
-
29
- describe('assertNoUserErrors', () => {
30
- it('should not throw when userErrors is empty', () => {
31
- expect(() => assertNoUserErrors({ userErrors: [] })).not.toThrow();
32
- });
33
-
34
- it('should not throw when userErrors is null', () => {
35
- expect(() => assertNoUserErrors({ userErrors: null })).not.toThrow();
36
- });
37
-
38
- it('should not throw when userErrors is undefined', () => {
39
- expect(() => assertNoUserErrors({})).not.toThrow();
40
- });
41
-
42
- it('should throw StorefrontError with USER_ERROR code', () => {
43
- try {
44
- assertNoUserErrors({
45
- userErrors: [{ message: 'Invalid email', field: ['email'] }],
46
- });
47
- expect.fail('Should have thrown');
48
- } catch (err) {
49
- expect(err).toBeInstanceOf(StorefrontError);
50
- const sfErr = err as StorefrontError;
51
- expect(sfErr.code).toBe(ErrorCodes.USER_ERROR);
52
- expect(sfErr.message).toBe('Invalid email');
53
- expect(sfErr.userErrors).toHaveLength(1);
54
- expect(sfErr.hasUserErrors).toBe(true);
55
- }
56
- });
57
-
58
- it('should use first error message', () => {
59
- try {
60
- assertNoUserErrors({
61
- userErrors: [
62
- { message: 'First error' },
63
- { message: 'Second error' },
64
- ],
65
- });
66
- } catch (err) {
67
- expect((err as StorefrontError).message).toBe('First error');
68
- expect((err as StorefrontError).userErrors).toHaveLength(2);
69
- }
70
- });
71
- });
72
-
73
- // ---------------------------------------------------------------------------
74
- // hashQuery
75
- // ---------------------------------------------------------------------------
76
-
77
- describe('hashQuery', () => {
78
- it('should produce consistent hash for same query', () => {
79
- const h1 = hashQuery('{ shop { id } }');
80
- const h2 = hashQuery('{ shop { id } }');
81
- expect(h1).toBe(h2);
82
- });
83
-
84
- it('should produce different hashes for different queries', () => {
85
- const h1 = hashQuery('{ shop { id } }');
86
- const h2 = hashQuery('{ product { id } }');
87
- expect(h1).not.toBe(h2);
88
- });
89
-
90
- it('should produce same hash regardless of variable key order', () => {
91
- const h1 = hashQuery('query Q', { a: 1, b: 2 });
92
- const h2 = hashQuery('query Q', { b: 2, a: 1 });
93
- expect(h1).toBe(h2);
94
- });
95
-
96
- it('should produce different hashes for different variables', () => {
97
- const h1 = hashQuery('query Q', { handle: 'a' });
98
- const h2 = hashQuery('query Q', { handle: 'b' });
99
- expect(h1).not.toBe(h2);
100
- });
101
-
102
- it('should handle nested objects in variables', () => {
103
- const h1 = hashQuery('query Q', { filter: { a: 1, b: 2 } });
104
- const h2 = hashQuery('query Q', { filter: { b: 2, a: 1 } });
105
- expect(h1).toBe(h2);
106
- });
107
-
108
- it('should handle undefined variables', () => {
109
- const h1 = hashQuery('{ shop { id } }');
110
- const h2 = hashQuery('{ shop { id } }', undefined);
111
- expect(h1).toBe(h2);
112
- });
113
-
114
- it('should trim whitespace in query', () => {
115
- const h1 = hashQuery(' { shop { id } } ');
116
- const h2 = hashQuery('{ shop { id } }');
117
- expect(h1).toBe(h2);
118
- });
119
- });
120
-
121
- // ---------------------------------------------------------------------------
122
- // getOperationName
123
- // ---------------------------------------------------------------------------
124
-
125
- describe('getOperationName', () => {
126
- it('should extract query name', () => {
127
- expect(getOperationName('query Product($id: ID!) { product(id: $id) { id } }')).toBe('Product');
128
- });
129
-
130
- it('should extract mutation name', () => {
131
- expect(getOperationName('mutation CartCreate($input: CartInput!) { cartCreate(input: $input) { cart { id } } }')).toBe('CartCreate');
132
- });
133
-
134
- it('should extract subscription name', () => {
135
- expect(getOperationName('subscription OrderUpdated { orderUpdated { id } }')).toBe('OrderUpdated');
136
- });
137
-
138
- it('should return "anonymous" for unnamed operations', () => {
139
- expect(getOperationName('{ shop { id } }')).toBe('anonymous');
140
- });
141
-
142
- it('should return "anonymous" for empty string', () => {
143
- expect(getOperationName('')).toBe('anonymous');
144
- });
145
- });
146
-
147
- // ---------------------------------------------------------------------------
148
- // compose
149
- // ---------------------------------------------------------------------------
150
-
151
- describe('compose', () => {
152
- it('should execute middleware in order', async () => {
153
- const order: number[] = [];
154
-
155
- const m1: Middleware = async (req, next) => { order.push(1); return next(req); };
156
- const m2: Middleware = async (req, next) => { order.push(2); return next(req); };
157
- const m3: Middleware = async (req, next) => { order.push(3); return next(req); };
158
-
159
- const execute: ExecuteFn = async () => ({
160
- data: {},
161
- status: 200,
162
- headers: new Headers(),
163
- });
164
-
165
- const pipeline = compose([m1, m2, m3], execute);
166
- await pipeline({ query: '', headers: {}, isMutation: false });
167
-
168
- expect(order).toEqual([1, 2, 3]);
169
- });
170
-
171
- it('should return execute directly when no middleware', async () => {
172
- const execute: ExecuteFn = async () => ({
173
- data: { test: true },
174
- status: 200,
175
- headers: new Headers(),
176
- });
177
-
178
- const pipeline = compose([], execute);
179
- const result = await pipeline({ query: '', headers: {}, isMutation: false });
180
-
181
- expect(result.data).toEqual({ test: true });
182
- });
183
-
184
- it('should allow middleware to modify response', async () => {
185
- const addField: Middleware = async (req, next) => {
186
- const response = await next(req);
187
- return { ...response, data: { ...response.data as object, added: true } };
188
- };
189
-
190
- const execute: ExecuteFn = async () => ({
191
- data: { original: true },
192
- status: 200,
193
- headers: new Headers(),
194
- });
195
-
196
- const pipeline = compose([addField], execute);
197
- const result = await pipeline({ query: '', headers: {}, isMutation: false });
198
-
199
- expect(result.data).toEqual({ original: true, added: true });
200
- });
201
- });
202
-
203
- // ---------------------------------------------------------------------------
204
- // StorefrontError
205
- // ---------------------------------------------------------------------------
206
-
207
- describe('StorefrontError', () => {
208
- it('should be instance of Error', () => {
209
- const err = new StorefrontError({ code: 'TEST', message: 'test' });
210
- expect(err).toBeInstanceOf(Error);
211
- expect(err).toBeInstanceOf(StorefrontError);
212
- });
213
-
214
- it('should have correct name', () => {
215
- const err = new StorefrontError({ code: 'TEST', message: 'test' });
216
- expect(err.name).toBe('StorefrontError');
217
- });
218
-
219
- it('should default status to 0', () => {
220
- const err = new StorefrontError({ code: 'TEST', message: 'test' });
221
- expect(err.status).toBe(0);
222
- });
223
-
224
- it('should default arrays to empty', () => {
225
- const err = new StorefrontError({ code: 'TEST', message: 'test' });
226
- expect(err.graphqlErrors).toEqual([]);
227
- expect(err.userErrors).toEqual([]);
228
- });
229
-
230
- it('should preserve all fields', () => {
231
- const err = new StorefrontError({
232
- code: ErrorCodes.GRAPHQL_ERROR,
233
- message: 'Field not found',
234
- status: 200,
235
- graphqlErrors: [{ message: 'Field not found', path: ['product', 'nonexistent'] }],
236
- userErrors: [{ message: 'Invalid', field: ['email'] }],
237
- });
238
-
239
- expect(err.code).toBe(ErrorCodes.GRAPHQL_ERROR);
240
- expect(err.message).toBe('Field not found');
241
- expect(err.status).toBe(200);
242
- expect(err.graphqlErrors).toHaveLength(1);
243
- expect(err.userErrors).toHaveLength(1);
244
- });
245
-
246
- it('hasUserErrors should be true when userErrors exist', () => {
247
- const err = new StorefrontError({
248
- code: ErrorCodes.USER_ERROR,
249
- message: 'test',
250
- userErrors: [{ message: 'Error' }],
251
- });
252
- expect(err.hasUserErrors).toBe(true);
253
- });
254
-
255
- it('hasUserErrors should be false when userErrors are empty', () => {
256
- const err = new StorefrontError({ code: 'TEST', message: 'test' });
257
- expect(err.hasUserErrors).toBe(false);
258
- });
259
-
260
- it('isNetworkError should be true for NETWORK_ERROR code', () => {
261
- const err = new StorefrontError({ code: ErrorCodes.NETWORK_ERROR, message: 'fetch failed' });
262
- expect(err.isNetworkError).toBe(true);
263
- expect(err.isTimeout).toBe(false);
264
- });
265
-
266
- it('isTimeout should be true for TIMEOUT code', () => {
267
- const err = new StorefrontError({ code: ErrorCodes.TIMEOUT, message: 'timeout' });
268
- expect(err.isTimeout).toBe(true);
269
- expect(err.isNetworkError).toBe(false);
270
- });
271
- });
272
-
273
- // ---------------------------------------------------------------------------
274
- // TypedDocumentString
275
- // ---------------------------------------------------------------------------
276
-
277
- describe('TypedDocumentString', () => {
278
- it('should extend String', () => {
279
- const doc = new TypedDocumentString('query { shop { id } }');
280
- expect(doc).toBeInstanceOf(String);
281
- });
282
-
283
- it('should return query string via toString()', () => {
284
- const query = 'query Product { product { id } }';
285
- const doc = new TypedDocumentString(query);
286
- expect(doc.toString()).toBe(query);
287
- });
288
-
289
- it('should return query string via valueOf()', () => {
290
- const query = 'query Product { product { id } }';
291
- const doc = new TypedDocumentString(query);
292
- expect(doc.valueOf()).toBe(query);
293
- });
294
- });
295
-
296
- // ---------------------------------------------------------------------------
297
- // Cache strategies
298
- // ---------------------------------------------------------------------------
299
-
300
- describe('cache strategies', () => {
301
- describe('cacheNone', () => {
302
- it('should return no-store mode', () => {
303
- const cache = cacheNone();
304
- expect(cache.mode).toBe('no-store');
305
- expect(cache.maxAge).toBe(0);
306
- });
307
-
308
- it('should accept tags override', () => {
309
- const cache = cacheNone({ tags: ['cart'] });
310
- expect(cache.tags).toEqual(['cart']);
311
- });
312
- });
313
-
314
- describe('cacheShort', () => {
315
- it('should default to 1s max-age, 9s swr', () => {
316
- const cache = cacheShort();
317
- expect(cache.maxAge).toBe(1);
318
- expect(cache.staleWhileRevalidate).toBe(9);
319
- expect(cache.mode).toBe('public');
320
- });
321
-
322
- it('should accept overrides', () => {
323
- const cache = cacheShort({ maxAge: 5, staleWhileRevalidate: 30, tags: ['products'] });
324
- expect(cache.maxAge).toBe(5);
325
- expect(cache.staleWhileRevalidate).toBe(30);
326
- expect(cache.tags).toEqual(['products']);
327
- });
328
- });
329
-
330
- describe('cacheLong', () => {
331
- it('should default to 1h max-age, 23h swr', () => {
332
- const cache = cacheLong();
333
- expect(cache.maxAge).toBe(3600);
334
- expect(cache.staleWhileRevalidate).toBe(82800);
335
- expect(cache.mode).toBe('public');
336
- });
337
-
338
- it('should accept tags', () => {
339
- const cache = cacheLong({ tags: ['product', 'cool-shirt'] });
340
- expect(cache.tags).toEqual(['product', 'cool-shirt']);
341
- });
342
- });
343
-
344
- describe('cachePrivate', () => {
345
- it('should return private mode', () => {
346
- const cache = cachePrivate();
347
- expect(cache.mode).toBe('private');
348
- expect(cache.maxAge).toBe(1);
349
- });
350
- });
351
-
352
- describe('cacheCustom', () => {
353
- it('should pass through all options', () => {
354
- const cache = cacheCustom({ maxAge: 300, staleWhileRevalidate: 600, mode: 'public', tags: ['custom'] });
355
- expect(cache.maxAge).toBe(300);
356
- expect(cache.staleWhileRevalidate).toBe(600);
357
- expect(cache.mode).toBe('public');
358
- expect(cache.tags).toEqual(['custom']);
359
- });
360
- });
361
-
362
- describe('generateCacheControlHeader', () => {
363
- it('should generate no-store for cacheNone', () => {
364
- expect(generateCacheControlHeader(cacheNone())).toBe('no-store, no-cache, must-revalidate');
365
- });
366
-
367
- it('should generate public max-age for cacheLong', () => {
368
- const header = generateCacheControlHeader(cacheLong());
369
- expect(header).toBe('public, max-age=3600, stale-while-revalidate=82800');
370
- });
371
-
372
- it('should generate private max-age for cachePrivate', () => {
373
- const header = generateCacheControlHeader(cachePrivate());
374
- expect(header).toBe('private, max-age=1, stale-while-revalidate=9');
375
- });
376
- });
377
- });