@doswiftly/storefront-sdk 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/core/image.d.ts +64 -0
- package/dist/core/image.d.ts.map +1 -0
- package/dist/core/image.js +68 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/package.json +1 -1
- package/src/core/image.ts +103 -0
- package/src/core/index.ts +9 -0
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ Layered runtime SDK for DoSwiftly Commerce storefronts. Hydrogen-aligned archite
|
|
|
7
7
|
```
|
|
8
8
|
@doswiftly/storefront-sdk
|
|
9
9
|
├── core (.) — Framework-agnostic: transport, middleware, CartClient, AuthClient,
|
|
10
|
-
│ cache, format utilities,
|
|
11
|
-
│ auth cookie config/handlers/token client, route matching
|
|
10
|
+
│ cache, format utilities, image loader, sanitizeHtml,
|
|
11
|
+
│ normalizeConnection, auth cookie config/handlers/token client, route matching
|
|
12
12
|
├── react (./react) — React adapter: providers, Zustand stores (Context-based), hooks,
|
|
13
13
|
│ useHydrated, useDebouncedValue, createStoreContext
|
|
14
14
|
├── react/server — Server-side client factory
|
|
@@ -93,7 +93,7 @@ const { currency, setCurrency } = useCurrencyStore();
|
|
|
93
93
|
|
|
94
94
|
| Path | Description | Dependencies |
|
|
95
95
|
|------|-------------|-------------|
|
|
96
|
-
| `@doswiftly/storefront-sdk` | Core: transport, middleware, clients, errors, format, sanitize, auth handlers, route matching | **0** |
|
|
96
|
+
| `@doswiftly/storefront-sdk` | Core: transport, middleware, clients, errors, format, image loader, sanitize, auth handlers, route matching | **0** |
|
|
97
97
|
| `@doswiftly/storefront-sdk/react` | Providers, hooks, store hooks, useHydrated, useDebouncedValue, createStoreContext | react, zustand |
|
|
98
98
|
| `@doswiftly/storefront-sdk/react/server` | Server-side client factory | react |
|
|
99
99
|
| `@doswiftly/storefront-sdk/cache` | Cache strategy functions | **0** |
|
|
@@ -213,6 +213,20 @@ formatDate(new Date()); // "Dec 9, 2025"
|
|
|
213
213
|
formatPercentage(0.15); // "15%"
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
### Image Loader (CDN)
|
|
217
|
+
|
|
218
|
+
Template ships with global `loaderFile` in `next.config.ts` — ALL `<Image>` components are optimized automatically. No per-component configuration needed.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// For custom frameworks (Svelte, Astro, etc.) — import the loader function directly:
|
|
222
|
+
import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
|
|
223
|
+
|
|
224
|
+
const url = storefrontImageLoader({ src: image.url, width: 800, quality: 85 });
|
|
225
|
+
// → image.url + "?width=800&quality=85&format=webp"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Width quantized to Next.js-aligned breakpoints for CDN cache efficiency. `ImageData` type matches GraphQL `Image` fragment: `{ url, altText?, width?, height?, id? }`.
|
|
229
|
+
|
|
216
230
|
### HTML Sanitizer
|
|
217
231
|
|
|
218
232
|
```typescript
|
|
@@ -442,6 +456,7 @@ SDK provides: Template owns:
|
|
|
442
456
|
├── Providers + Zustand stores ├── lib/graphql/fragments/
|
|
443
457
|
├── Format utilities ├── hooks/use-cart-di.ts (CartActions DI impl)
|
|
444
458
|
├── sanitizeHtml ├── hooks/use-cart-actions.ts (UX wrapper)
|
|
459
|
+
├── createImageLoader (CDN) ├── components/product/product-image.tsx
|
|
445
460
|
├── normalizeConnection ├── stores/ (checkout, wishlist via createStoreContext)
|
|
446
461
|
├── Auth handlers + token client ├── lib/auth/routes.ts (route config)
|
|
447
462
|
├── useHydrated + useDebouncedValue
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image utilities for DoSwiftly storefronts.
|
|
3
|
+
*
|
|
4
|
+
* Zero configuration — GraphQL API returns ready-to-use CDN URLs.
|
|
5
|
+
* The loader just appends resize query params. No keys, no env vars.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { storefrontImageLoader, type ImageData } from '@doswiftly/storefront-sdk';
|
|
10
|
+
*
|
|
11
|
+
* <Image loader={storefrontImageLoader} src={product.featuredImage.url} width={800} alt="..." />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Image data from GraphQL API (matches Image type in storefront schema).
|
|
16
|
+
*/
|
|
17
|
+
export interface ImageData {
|
|
18
|
+
/** Image URL from GraphQL (ready-to-use CDN URL) */
|
|
19
|
+
url: string;
|
|
20
|
+
/** Alt text for accessibility + SEO */
|
|
21
|
+
altText?: string | null;
|
|
22
|
+
/** Original image width in pixels */
|
|
23
|
+
width?: number | null;
|
|
24
|
+
/** Original image height in pixels */
|
|
25
|
+
height?: number | null;
|
|
26
|
+
/** Image ID */
|
|
27
|
+
id?: string | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Next.js Image loader function signature.
|
|
31
|
+
*/
|
|
32
|
+
export interface ImageLoaderParams {
|
|
33
|
+
src: string;
|
|
34
|
+
width: number;
|
|
35
|
+
quality?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Image format for imgproxy output.
|
|
39
|
+
*/
|
|
40
|
+
export type ImageFormat = 'webp' | 'avif' | 'jpeg';
|
|
41
|
+
/**
|
|
42
|
+
* Default image loader — WebP format. Used as global loaderFile in next.config.ts.
|
|
43
|
+
* Zero configuration: works out-of-the-box with GraphQL image URLs.
|
|
44
|
+
* Width quantized to preset breakpoints for optimal CDN cache.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
|
|
49
|
+
* <Image loader={storefrontImageLoader} src={image.url} width={800} alt="..." />
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function storefrontImageLoader({ src, width, quality }: ImageLoaderParams): string;
|
|
53
|
+
/**
|
|
54
|
+
* Create a custom loader with specific format (avif, jpeg).
|
|
55
|
+
* Use as per-component override when you need a different format than default WebP.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* const avifLoader = createImageLoader('avif');
|
|
60
|
+
* <Image loader={avifLoader} src={image.url} width={800} alt="..." />
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createImageLoader(format: ImageFormat): (params: ImageLoaderParams) => string;
|
|
64
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/core/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAenD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,MAAM,CAExF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,MAAM,CAE5F"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image utilities for DoSwiftly storefronts.
|
|
3
|
+
*
|
|
4
|
+
* Zero configuration — GraphQL API returns ready-to-use CDN URLs.
|
|
5
|
+
* The loader just appends resize query params. No keys, no env vars.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { storefrontImageLoader, type ImageData } from '@doswiftly/storefront-sdk';
|
|
10
|
+
*
|
|
11
|
+
* <Image loader={storefrontImageLoader} src={product.featuredImage.url} width={800} alt="..." />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Preset widths for CDN cache optimization.
|
|
16
|
+
* Loader quantizes requested width to the nearest preset — limits cache variants.
|
|
17
|
+
*/
|
|
18
|
+
const PRESET_WIDTHS = [150, 320, 640, 750, 828, 1080, 1200, 1600, 1920, 2048];
|
|
19
|
+
function nearestPresetWidth(requested) {
|
|
20
|
+
for (const w of PRESET_WIDTHS) {
|
|
21
|
+
if (w >= requested)
|
|
22
|
+
return w;
|
|
23
|
+
}
|
|
24
|
+
return PRESET_WIDTHS[PRESET_WIDTHS.length - 1];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default image loader — WebP format. Used as global loaderFile in next.config.ts.
|
|
28
|
+
* Zero configuration: works out-of-the-box with GraphQL image URLs.
|
|
29
|
+
* Width quantized to preset breakpoints for optimal CDN cache.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
|
|
34
|
+
* <Image loader={storefrontImageLoader} src={image.url} width={800} alt="..." />
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function storefrontImageLoader({ src, width, quality }) {
|
|
38
|
+
return buildImgproxyUrl(src, width, quality);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a custom loader with specific format (avif, jpeg).
|
|
42
|
+
* Use as per-component override when you need a different format than default WebP.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* const avifLoader = createImageLoader('avif');
|
|
47
|
+
* <Image loader={avifLoader} src={image.url} width={800} alt="..." />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function createImageLoader(format) {
|
|
51
|
+
return ({ src, width, quality }) => buildImgproxyUrl(src, width, quality, format);
|
|
52
|
+
}
|
|
53
|
+
function buildImgproxyUrl(src, width, quality, format = 'webp') {
|
|
54
|
+
const w = nearestPresetWidth(width);
|
|
55
|
+
const q = quality ?? 85;
|
|
56
|
+
if (!src.startsWith('http')) {
|
|
57
|
+
return src;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(src);
|
|
61
|
+
const path = url.pathname.slice(1);
|
|
62
|
+
return `${url.origin}/rs:fit:${w}:0:0/q:${q}/f:${format}/plain/${path}`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Malformed URL — return as-is, let browser handle it
|
|
66
|
+
return src;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from './cart/cookie-config';
|
|
|
61
61
|
export { matchesRoute, type RouteProtectionConfig } from './auth/routes';
|
|
62
62
|
export { createSetTokenHandler, createClearTokenHandler } from './auth/handlers';
|
|
63
63
|
export { createAuthTokenClient, type AuthTokenClient } from './auth/token-client';
|
|
64
|
+
export { storefrontImageLoader, createImageLoader, type ImageData, type ImageLoaderParams, type ImageFormat, } from './image';
|
|
64
65
|
export { getOperationName } from './client/operation-name';
|
|
65
66
|
export { hashQuery } from './client/hash';
|
|
66
67
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
|
package/dist/core/index.js
CHANGED
|
@@ -74,6 +74,8 @@ export { matchesRoute } from './auth/routes';
|
|
|
74
74
|
export { createSetTokenHandler, createClearTokenHandler } from './auth/handlers';
|
|
75
75
|
// Auth token client (client-side fetch helpers)
|
|
76
76
|
export { createAuthTokenClient } from './auth/token-client';
|
|
77
|
+
// Image utilities
|
|
78
|
+
export { storefrontImageLoader, createImageLoader, } from './image';
|
|
77
79
|
// Utilities
|
|
78
80
|
export { getOperationName } from './client/operation-name';
|
|
79
81
|
export { hashQuery } from './client/hash';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
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
6
|
"types": "dist/core/index.d.ts",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image utilities for DoSwiftly storefronts.
|
|
3
|
+
*
|
|
4
|
+
* Zero configuration — GraphQL API returns ready-to-use CDN URLs.
|
|
5
|
+
* The loader just appends resize query params. No keys, no env vars.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { storefrontImageLoader, type ImageData } from '@doswiftly/storefront-sdk';
|
|
10
|
+
*
|
|
11
|
+
* <Image loader={storefrontImageLoader} src={product.featuredImage.url} width={800} alt="..." />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Image data from GraphQL API (matches Image type in storefront schema).
|
|
17
|
+
*/
|
|
18
|
+
export interface ImageData {
|
|
19
|
+
/** Image URL from GraphQL (ready-to-use CDN URL) */
|
|
20
|
+
url: string;
|
|
21
|
+
/** Alt text for accessibility + SEO */
|
|
22
|
+
altText?: string | null;
|
|
23
|
+
/** Original image width in pixels */
|
|
24
|
+
width?: number | null;
|
|
25
|
+
/** Original image height in pixels */
|
|
26
|
+
height?: number | null;
|
|
27
|
+
/** Image ID */
|
|
28
|
+
id?: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Next.js Image loader function signature.
|
|
33
|
+
*/
|
|
34
|
+
export interface ImageLoaderParams {
|
|
35
|
+
src: string;
|
|
36
|
+
width: number;
|
|
37
|
+
quality?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Image format for imgproxy output.
|
|
42
|
+
*/
|
|
43
|
+
export type ImageFormat = 'webp' | 'avif' | 'jpeg';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Preset widths for CDN cache optimization.
|
|
47
|
+
* Loader quantizes requested width to the nearest preset — limits cache variants.
|
|
48
|
+
*/
|
|
49
|
+
const PRESET_WIDTHS = [150, 320, 640, 750, 828, 1080, 1200, 1600, 1920, 2048] as const;
|
|
50
|
+
|
|
51
|
+
function nearestPresetWidth(requested: number): number {
|
|
52
|
+
for (const w of PRESET_WIDTHS) {
|
|
53
|
+
if (w >= requested) return w;
|
|
54
|
+
}
|
|
55
|
+
return PRESET_WIDTHS[PRESET_WIDTHS.length - 1];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Default image loader — WebP format. Used as global loaderFile in next.config.ts.
|
|
60
|
+
* Zero configuration: works out-of-the-box with GraphQL image URLs.
|
|
61
|
+
* Width quantized to preset breakpoints for optimal CDN cache.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
|
|
66
|
+
* <Image loader={storefrontImageLoader} src={image.url} width={800} alt="..." />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function storefrontImageLoader({ src, width, quality }: ImageLoaderParams): string {
|
|
70
|
+
return buildImgproxyUrl(src, width, quality);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a custom loader with specific format (avif, jpeg).
|
|
75
|
+
* Use as per-component override when you need a different format than default WebP.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const avifLoader = createImageLoader('avif');
|
|
80
|
+
* <Image loader={avifLoader} src={image.url} width={800} alt="..." />
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function createImageLoader(format: ImageFormat): (params: ImageLoaderParams) => string {
|
|
84
|
+
return ({ src, width, quality }) => buildImgproxyUrl(src, width, quality, format);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildImgproxyUrl(src: string, width: number, quality?: number, format: ImageFormat = 'webp'): string {
|
|
88
|
+
const w = nearestPresetWidth(width);
|
|
89
|
+
const q = quality ?? 85;
|
|
90
|
+
|
|
91
|
+
if (!src.startsWith('http')) {
|
|
92
|
+
return src;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const url = new URL(src);
|
|
97
|
+
const path = url.pathname.slice(1);
|
|
98
|
+
return `${url.origin}/rs:fit:${w}:0:0/q:${q}/f:${format}/plain/${path}`;
|
|
99
|
+
} catch {
|
|
100
|
+
// Malformed URL — return as-is, let browser handle it
|
|
101
|
+
return src;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -166,6 +166,15 @@ export { createSetTokenHandler, createClearTokenHandler } from './auth/handlers'
|
|
|
166
166
|
// Auth token client (client-side fetch helpers)
|
|
167
167
|
export { createAuthTokenClient, type AuthTokenClient } from './auth/token-client';
|
|
168
168
|
|
|
169
|
+
// Image utilities
|
|
170
|
+
export {
|
|
171
|
+
storefrontImageLoader,
|
|
172
|
+
createImageLoader,
|
|
173
|
+
type ImageData,
|
|
174
|
+
type ImageLoaderParams,
|
|
175
|
+
type ImageFormat,
|
|
176
|
+
} from './image';
|
|
177
|
+
|
|
169
178
|
// Utilities
|
|
170
179
|
export { getOperationName } from './client/operation-name';
|
|
171
180
|
export { hashQuery } from './client/hash';
|