@doswiftly/storefront-sdk 22.8.0 → 22.9.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.
@@ -50,6 +50,15 @@ export interface ImageLoaderConfig {
50
50
  * framework already fingerprints). Defaults to {@link FRAMEWORK_BUILD_PREFIXES}.
51
51
  */
52
52
  buildAssetPrefixes?: readonly string[];
53
+ /**
54
+ * Build clean asset URLs without the `/s/{shopId}` storage namespace. Set for a storefront served
55
+ * from a custom domain (`{cdnBase}/_next/static/media/…`), where the platform restores the namespace
56
+ * toward storage at the edge so the stored object key is unchanged. Defaults to `false` — the
57
+ * platform CDN keeps the namespace in the URL (`{cdnBase}/s/{shopId}/…`). The deploy pipeline sets
58
+ * this from the same provisioning signal that picks the asset host, so the URL form and the storage
59
+ * key can never disagree.
60
+ */
61
+ cleanUrl?: boolean;
53
62
  }
54
63
  /**
55
64
  * Framework build-output path prefixes — hashed/immutable assets the framework already
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/core/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,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;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,uDAAwD,CAAC;AA2C9F,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC,MAAM,CAwDR;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiD5F"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/core/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,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;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,uDAAwD,CAAC;AAyF9F,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC,MAAM,CAqER;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiD5F"}
@@ -69,10 +69,58 @@ const NEXT_STATIC_MEDIA_PREFIX = '/_next/static/media/';
69
69
  function nextStaticMediaSuffix(pathname) {
70
70
  return pathname.startsWith(NEXT_STATIC_MEDIA_PREFIX) ? pathname.slice(NEXT_STATIC_MEDIA_PREFIX.length) : null;
71
71
  }
72
+ /** Scheme + authority of an absolute http(s) URL — everything up to the first path `/`. */
73
+ const ABSOLUTE_URL_AUTHORITY = /^https?:\/\/[^/]+/i;
74
+ /**
75
+ * Suffix after the media segment when `src` is an ABSOLUTE URL — a code-imported image whose `src`
76
+ * carries the storefront's Next.js `assetPrefix` (which Next prepends before the loader runs). The
77
+ * anchor is the storage namespace `ns` (`/s/{shopId}` by default, empty when `cleanUrl`):
78
+ * - default: `{assetPrefixBase}/s/{shopId}/_next/static/media/{suffix}` (`ns` = `/s/{shopId}`);
79
+ * - clean: `{assetPrefixBase}/_next/static/media/{suffix}` (`ns` = ``).
80
+ * Anchored on the EXACT `${ns}/_next/static/media/`, so a foreign host that merely embeds the marker
81
+ * mid-path is rejected. `null` for relative srcs, other namespaces, and foreign hosts. Matched on
82
+ * path structure, not host — the asset-prefix host is not part of the loader config, only the resize
83
+ * base is.
84
+ *
85
+ * The path is taken from the RAW `src` (strip scheme/authority + query/hash), NOT via `URL.pathname`:
86
+ * that percent-encodes the path, which would double-encode in {@link buildNextMediaCdnUrl}. The
87
+ * root-relative counterpart {@link nextStaticMediaSuffix} likewise feeds a raw path, so both `src`
88
+ * forms of one import — relative (no `assetPrefix`) and absolute (with it) — encode identically.
89
+ */
90
+ function absoluteNextMediaSuffix(src, ns) {
91
+ // Fast reject before any allocation: must be an absolute http(s) URL that mentions the media segment.
92
+ if (!ABSOLUTE_URL_AUTHORITY.test(src) || !src.includes(NEXT_STATIC_MEDIA_PREFIX))
93
+ return null;
94
+ const path = src.replace(ABSOLUTE_URL_AUTHORITY, '').replace(/[?#].*$/, '');
95
+ const prefix = `${ns}${NEXT_STATIC_MEDIA_PREFIX}`;
96
+ return path.startsWith(prefix) ? path.slice(prefix.length) : null;
97
+ }
98
+ /**
99
+ * Image-CDN URL for a Next build-output media image (content-hashed → the hash IS the version, so the
100
+ * key is immutable: only the per-srcset `width`, no `?v=`). The same key shape the deploy mirror
101
+ * writes, so the resize CDN finds the file. Shared by both `src` forms of one import: root-relative
102
+ * ({@link nextStaticMediaSuffix}) and absolute `assetPrefix` ({@link absoluteNextMediaSuffix}).
103
+ *
104
+ * `encodeURIComponent` keeps `..` (unreserved) — harmless while the image CDN is unsigned + public;
105
+ * if HMAC signing is ever added, strip `..` segments here AND in the public/ branch.
106
+ *
107
+ * `ns` is the storage namespace (empty for a custom domain, `/s/{shopId}` for the platform CDN); the
108
+ * platform restores it toward storage for a custom domain, so the stored object key is the same.
109
+ *
110
+ * @sync-with backend deploy mirror (`_next/static/media/` mirrored 1:1 into the image-CDN key)
111
+ */
112
+ function buildNextMediaCdnUrl(base, ns, suffix, width) {
113
+ const encoded = suffix.split('/').map(encodeURIComponent).join('/');
114
+ return `${base}${ns}/_next/static/media/${encoded}?width=${width}`;
115
+ }
72
116
  export function buildImageLoaderUrl(config, args) {
73
117
  // `||` (not `??`): an empty injected base must still fall back to the platform default.
74
118
  const base = (config.cdnBase || IMAGE_CDN_BASE_URL).replace(/\/+$/, '');
75
119
  const { src, width } = args;
120
+ // Storage namespace prepended to local-asset URLs: empty when clean (a custom domain restores it
121
+ // toward storage), `/s/{shopId}` otherwise. Computed once, threaded through both URL builders and
122
+ // the absolute-media anchor so the form is decided in one place.
123
+ const ns = (config.cleanUrl ?? false) ? '' : `/s/${config.shopId}`;
76
124
  // (1) Product image — already an absolute CDN URL. Set the per-srcset width.
77
125
  if (src.startsWith(base)) {
78
126
  try {
@@ -84,6 +132,18 @@ export function buildImageLoaderUrl(config, args) {
84
132
  return src;
85
133
  }
86
134
  }
135
+ // (1b) Absolute build-output media URL — a code-imported image whose `src` carries the storefront's
136
+ // next.config `assetPrefix` (`{base}/s/{shopId}/_next/static/media/...`, prepended by Next
137
+ // before this loader). Routed to the SAME image-CDN key as the root-relative case (2a) — the two
138
+ // are just the assetPrefix / no-assetPrefix forms of one import. Fonts (`.woff2`) share
139
+ // `_next/static/media/` but are not images, so the extension gate leaves them to load raw from
140
+ // the asset CDN (they are not mirrored to the resize CDN).
141
+ if (config.shopId) {
142
+ const mediaSuffix = absoluteNextMediaSuffix(src, ns);
143
+ if (mediaSuffix !== null && LOADER_IMAGE_EXTENSIONS.test(mediaSuffix)) {
144
+ return buildNextMediaCdnUrl(base, ns, mediaSuffix, width);
145
+ }
146
+ }
87
147
  // (2) Local root-relative image — NOT protocol-relative (`//host`). Two sub-cases:
88
148
  // (2a) a Next build-output media image (`/_next/static/media/*`), and (2b) a `public/` image.
89
149
  if (src.startsWith('/') && !src.startsWith('//')) {
@@ -96,14 +156,10 @@ export function buildImageLoaderUrl(config, args) {
96
156
  // extension gate is the safety that keeps framework build chunks out of the resize CDN.
97
157
  if (!LOADER_IMAGE_EXTENSIONS.test(pathname))
98
158
  return src;
99
- // (2a) Next build-output media image. The content hash in the filename IS the version, so
100
- // the CDN key is immutable → no `?v=`. The deploy mirrors these to `s/{shopId}/_next/static/media/`.
159
+ // (2a) Next build-output media image same image-CDN key as the absolute (assetPrefix) form (1b).
101
160
  const mediaSuffix = nextStaticMediaSuffix(pathname);
102
161
  if (mediaSuffix !== null) {
103
- // `encodeURIComponent` keeps `..` (unreserved) — harmless while the image CDN is unsigned +
104
- // public; if HMAC signing is ever added, strip `..` segments here AND in the public/ branch.
105
- const encoded = mediaSuffix.split('/').map(encodeURIComponent).join('/');
106
- return `${base}/s/${config.shopId}/_next/static/media/${encoded}?width=${width}`;
162
+ return buildNextMediaCdnUrl(base, ns, mediaSuffix, width);
107
163
  }
108
164
  // Any other framework build output (hashed/immutable, NOT mirrored) → untouched.
109
165
  const buildPrefixes = config.buildAssetPrefixes ?? FRAMEWORK_BUILD_PREFIXES;
@@ -120,7 +176,7 @@ export function buildImageLoaderUrl(config, args) {
120
176
  .split('/')
121
177
  .map(encodeURIComponent)
122
178
  .join('/');
123
- return `${base}/s/${config.shopId}/public/${path}?width=${width}&v=${config.version}`;
179
+ return `${base}${ns}/public/${path}?width=${width}&v=${config.version}`;
124
180
  }
125
181
  // (3) Build assets, protocol-relative/external URLs, data URIs — untouched.
126
182
  return src;
@@ -1 +1 @@
1
- {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkcH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qCAAqC,QAehD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,QAO1C,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAO/B,CAAC;AAMH,eAAO,MAAM,WAAW,QAYtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAWjC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAWrB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAY/B,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,QAWvC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
1
+ {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmcH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qCAAqC,QAehD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,QAO1C,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAO/B,CAAC;AAMH,eAAO,MAAM,WAAW,QAYtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAWjC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAWrB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAY/B,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,QAWvC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
@@ -277,6 +277,7 @@ const ORDER_FRAGMENT = `
277
277
  expiredAt
278
278
  shippingAddress { ...MailingAddress }
279
279
  itemCount
280
+ customerNote
280
281
  discountAllocations {
281
282
  discountCode
282
283
  amount { ...Money }
@@ -12,10 +12,10 @@
12
12
  * export default createImageLoader();
13
13
  * ```
14
14
  *
15
- * Zero configuration: the shop identifier, deployment version, and image-CDN base URL are
16
- * read from the public environment variables the DoSwiftly deploy pipeline injects
17
- * (`NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`, `NEXT_PUBLIC_IMGPROXY_BASE`).
18
- * Pass overrides only for tests or non-standard hosting.
15
+ * Zero configuration: the shop identifier, deployment version, image-CDN base URL, and clean-URL
16
+ * flag are read from the public environment variables the DoSwiftly deploy pipeline injects
17
+ * (`NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`, `NEXT_PUBLIC_IMGPROXY_BASE`,
18
+ * `NEXT_PUBLIC_ASSET_CLEAN_URL`). Pass overrides only for tests or non-standard hosting.
19
19
  *
20
20
  * This entry is Next-specific (it reads `NEXT_PUBLIC_*`); the framework-agnostic core (`.`)
21
21
  * stays free of it.
@@ -1 +1 @@
1
- {"version":3,"file":"image-loader.d.ts","sourceRoot":"","sources":["../../src/next/image-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,CAAC,IAAI,EAAE,mBAAmB,KAAK,MAAM,CAevC"}
1
+ {"version":3,"file":"image-loader.d.ts","sourceRoot":"","sources":["../../src/next/image-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,CAAC,IAAI,EAAE,mBAAmB,KAAK,MAAM,CAgBvC"}
@@ -12,10 +12,10 @@
12
12
  * export default createImageLoader();
13
13
  * ```
14
14
  *
15
- * Zero configuration: the shop identifier, deployment version, and image-CDN base URL are
16
- * read from the public environment variables the DoSwiftly deploy pipeline injects
17
- * (`NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`, `NEXT_PUBLIC_IMGPROXY_BASE`).
18
- * Pass overrides only for tests or non-standard hosting.
15
+ * Zero configuration: the shop identifier, deployment version, image-CDN base URL, and clean-URL
16
+ * flag are read from the public environment variables the DoSwiftly deploy pipeline injects
17
+ * (`NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`, `NEXT_PUBLIC_IMGPROXY_BASE`,
18
+ * `NEXT_PUBLIC_ASSET_CLEAN_URL`). Pass overrides only for tests or non-standard hosting.
19
19
  *
20
20
  * This entry is Next-specific (it reads `NEXT_PUBLIC_*`); the framework-agnostic core (`.`)
21
21
  * stays free of it.
@@ -39,6 +39,7 @@ export function createImageLoader(config) {
39
39
  shopId: config?.shopId ?? process.env.NEXT_PUBLIC_SHOP_ID ?? '',
40
40
  version: config?.version ?? process.env.NEXT_PUBLIC_DEPLOYMENT_COMMIT ?? '',
41
41
  cdnBase: config?.cdnBase ?? process.env.NEXT_PUBLIC_IMGPROXY_BASE,
42
+ cleanUrl: config?.cleanUrl ?? (process.env.NEXT_PUBLIC_ASSET_CLEAN_URL === 'true'),
42
43
  };
43
44
  // Dev-only hint: surface raster images that the loader can't optimize (an external host, or
44
45
  // a build asset outside `/_next/static/media/`). Only when a shopId is resolved — without one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-sdk",
3
- "version": "22.8.0",
3
+ "version": "22.9.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
  "sideEffects": false,