@swell/apps-sdk 1.0.142 → 1.0.143

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2025-present Swell Commerce Corp.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to use,
6
+ copy, modify, merge, publish, distribute, sublicense, sell and/or create
7
+ derivative works of the Software or any part thereof, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The rights granted above may only be exercised to develop storefront apps that integrate
11
+ or interoperate with Swell software or services, and, if applicable, to
12
+ distribute, offer for sale or otherwise make available any such storefront apps via the Swell App Store. All other uses of the Software are strictly prohibited.
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
19
+ A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # Swell Apps SDK
2
+
3
+ The Swell Apps SDK is a TypeScript-based library designed to simplify the development of isomorphic Swell apps by providing streamlined API access, theme rendering capabilities, and comprehensive caching solutions.
4
+
5
+ ## Features
6
+
7
+ ### Core functionality
8
+ - **Unified API access** - Seamless integration with both Swell Backend API and Storefront API
9
+ - **Authentication handling** - Automatic scoped access token management based on app permissions
10
+ - **Theme rendering** - Complete Shopify-compatible theme system with Liquid templating
11
+ - **Resource management** - Deferred loading of storefront resources (products, categories, etc.)
12
+ - **Caching system** - Multi-tier caching with Cloudflare KV integration
13
+ - **Shopify compatibility** - Full compatibility layer for migrating Shopify themes and apps
14
+
15
+ ### Theme capabilities
16
+ - **Liquid templating** - Enhanced Liquid engine with Swell-specific objects and filters
17
+ - **Section rendering** - Dynamic section management with schema support
18
+ - **Settings resolution** - Automatic theme and section settings processing
19
+ - **Layout system** - Flexible layout rendering with section groups
20
+ - **Asset management** - Optimized asset loading and URL generation
21
+ - **Localization** - Multi-language support with translation rendering
22
+
23
+ ### Developer experience
24
+ - **TypeScript support** - Full type safety with comprehensive type definitions
25
+ - **Isomorphic design** - Works seamlessly in both browser and server environments
26
+ - **Error handling** - Robust error management with detailed debugging information
27
+ - **Performance optimized** - Built-in caching and resource optimization
28
+ - **Extensible architecture** - Plugin system for custom resource types
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @swell/apps-sdk
34
+ ```
35
+
36
+ ## Getting started
37
+
38
+ ### Basic setup
39
+
40
+ ```typescript
41
+ import { Swell } from '@swell/apps-sdk';
42
+
43
+ // Initialize Swell instance in your app frontend
44
+ const swell = new Swell({
45
+ serverHeaders: context.request.headers, // Headers from worker environment
46
+ });
47
+
48
+ // Make backend API calls
49
+ const products = await swell.backend.get('/products');
50
+
51
+ // Make storefront API calls
52
+ const cart = await swell.storefront.get('/cart');
53
+ ```
54
+
55
+ ### Headers and app proxying
56
+
57
+ When your Swell app is deployed, it runs behind Swell's proxy infrastructure. The proxy automatically injects essential headers that contain authentication tokens, store configuration, and storefront context. These headers are critical for the SDK to function properly:
58
+
59
+ ```typescript
60
+ // Headers passed from Swell's proxy contain:
61
+ // - swell-store-id: The store identifier
62
+ // - swell-public-key: Frontend API access key
63
+ // - swell-access-token: Backend API access token (scoped to app permissions)
64
+ // - swell-storefront-id: Current storefront instance
65
+ // - swell-environment-id: Environment (development, staging, production)
66
+ // - swell-theme-id: Active theme identifier
67
+ // - swell-storefront-context: Preloaded cart/account data
68
+
69
+ const swell = new Swell({
70
+ serverHeaders: context.request.headers, // Contains all proxy-injected headers
71
+ getCookie: (name) => getCookieValue(name),
72
+ setCookie: (name, value, options) => setCookieValue(name, value, options),
73
+ });
74
+ ```
75
+
76
+ Without these headers, the SDK cannot:
77
+ - Authenticate with Swell APIs
78
+ - Determine which store and storefront to operate on
79
+ - Access cached resources or maintain session state
80
+ - Render themes with proper configuration
81
+
82
+ The `serverHeaders` parameter should always be passed the complete headers object from your app's request context to ensure full functionality.
83
+
84
+ ### Theme rendering
85
+
86
+ ```typescript
87
+ import { Swell, SwellTheme, SwellProduct } from '@swell/apps-sdk';
88
+
89
+ const swell = new Swell({
90
+ serverHeaders: context.request.headers,
91
+ ...options,
92
+ });
93
+
94
+ // Initialize theme with optional configuration
95
+ const theme = new SwellTheme(swell, {
96
+ forms: formConfigs,
97
+ resources: customResources,
98
+ globals: additionalGlobals,
99
+ });
100
+
101
+ // Fetch settings and set global context
102
+ await theme.initGlobals('product'); // page ID
103
+
104
+ // Create page data with deferred resource loading
105
+ const data = {
106
+ product: new SwellProduct(swell, context.params.id),
107
+ };
108
+
109
+ // Render theme page
110
+ const renderedPage = await theme.renderPage(data);
111
+ ```
112
+
113
+ ## API reference
114
+
115
+ ### Swell class
116
+
117
+ The main entry point for SDK functionality:
118
+
119
+ ```typescript
120
+ class Swell {
121
+ // API access
122
+ backend: SwellBackendAPI;
123
+ storefront: typeof SwellJS;
124
+
125
+ // Configuration
126
+ config: SwellAppConfig;
127
+ url: URL;
128
+ headers: Record<string, string>;
129
+ queryParams: ParsedQs;
130
+
131
+ // State
132
+ isEditor: boolean;
133
+ isPreview: boolean;
134
+ storefrontContext: SwellData;
135
+
136
+ // Methods
137
+ get<T>(url: string, query?: SwellData): Promise<T>;
138
+ post<T>(url: string, data: SwellData): Promise<T>;
139
+ put<T>(url: string, data: SwellData): Promise<T>;
140
+ delete<T>(url: string, data?: SwellData): Promise<T>;
141
+ getCachedResource<T>(key: string, args: unknown[], handler: () => T): Promise<T>;
142
+ }
143
+ ```
144
+
145
+ ### SwellTheme class
146
+
147
+ Handles theme rendering and management:
148
+
149
+ ```typescript
150
+ class SwellTheme {
151
+ // Core properties
152
+ swell: Swell;
153
+ globals: ThemeGlobals;
154
+ liquidSwell: LiquidSwell;
155
+
156
+ // Methods
157
+ initGlobals(pageId: string, altTemplate?: string): Promise<void>;
158
+ renderPage(pageData?: SwellData, altTemplate?: string): Promise<string>;
159
+ renderSection(sectionId: string, pageData?: SwellData): Promise<string>;
160
+ renderLayout(layoutName?: string, data?: SwellData): Promise<string>;
161
+ getSectionSchema(sectionName: string): Promise<ThemeSectionSchema>;
162
+ setGlobals(globals: Partial<ThemeGlobals>): void;
163
+ }
164
+ ```
165
+
166
+ ### Resource classes
167
+
168
+ Built-in storefront resource classes for deferred loading:
169
+
170
+ #### Standard resources
171
+ - `SwellAccount` - Customer account management
172
+ - `SwellBlog` - Blog post content
173
+ - `SwellBlogCategory` - Blog categorization
174
+ - `SwellCart` - Shopping cart state
175
+ - `SwellCategory` - Product categories
176
+ - `SwellOrder` - Order information
177
+ - `SwellPage` - Static pages
178
+ - `SwellProduct` - Product details
179
+ - `SwellVariant` - Product variants
180
+
181
+ #### Primitive resources
182
+ - `SwellStorefrontCollection` - Collection results with pagination
183
+ - `SwellStorefrontRecord` - Individual records
184
+ - `SwellStorefrontSingleton` - Unique resources (cart, account)
185
+
186
+ ```typescript
187
+ // Create custom resource class
188
+ class MyAppCollection extends SwellStorefrontCollection {
189
+ constructor(swell: Swell, query: SwellData = {}) {
190
+ super(swell, 'my-app-collection', query);
191
+ return this._getProxy();
192
+ }
193
+ }
194
+
195
+ // Usage in theme data
196
+ const data = {
197
+ myCollection: new MyAppCollection(swell, { limit: 20 }),
198
+ };
199
+ ```
200
+
201
+ ## Caching
202
+
203
+ ### Memory caching
204
+ Resources are automatically cached in memory per worker instance:
205
+
206
+ ```typescript
207
+ // Cached resource with custom handler
208
+ const cachedData = await swell.getCachedResource(
209
+ 'expensive-operation',
210
+ [param1, param2],
211
+ async () => {
212
+ return await performExpensiveOperation(param1, param2);
213
+ }
214
+ );
215
+ ```
216
+
217
+ ### Cloudflare KV caching
218
+ For production scalability, enable KV caching:
219
+
220
+ ```typescript
221
+ const swell = new Swell({
222
+ serverHeaders: context.request.headers,
223
+ workerEnv: context.locals.runtime.env, // Contains THEME KV binding
224
+ workerCtx: context.locals.runtime.ctx, // Worker context
225
+ });
226
+ ```
227
+
228
+ ### Cache invalidation
229
+ Caches are automatically invalidated based on:
230
+ - Session cookies (for cart/account data)
231
+ - Theme configuration versions
232
+ - Storefront environment changes
233
+
234
+ ## Shopify compatibility
235
+
236
+ The SDK includes comprehensive Shopify compatibility for theme migration.
237
+
238
+ ### Supported Shopify features
239
+ - **Template mapping** - Direct file path compatibility
240
+ - **Liquid objects** - Full object structure compatibility
241
+ - **Form handling** - Compatible form endpoints and validation
242
+ - **Section schemas** - Shopify section configuration format
243
+ - **Settings data** - `settings_data.json` and `settings_schema.json`
244
+
245
+ ## Liquid templating
246
+
247
+ Enhanced Liquid templating with Swell-specific features. See [Swell Liquid documentation](https://developers.swell.is/storefronts/swell-liquid-reference) for details.
248
+
249
+ ## Performance optimization
250
+
251
+ ### Lazy loading
252
+ Resources are loaded only when accessed in templates:
253
+
254
+ ```liquid
255
+ <!-- Product data is fetched only when this line executes -->
256
+ {{ product.name }}
257
+
258
+ <!-- Collection is fetched only when iteration begins -->
259
+ {% for item in collection.products %}
260
+ {{ item.name }}
261
+ {% endfor %}
262
+ ```
263
+
264
+ ## Development
265
+
266
+ ### Building the SDK
267
+ ```bash
268
+ # Install dependencies
269
+ npm install
270
+
271
+ # Build for production
272
+ npm run build
273
+
274
+ # Watch for changes
275
+ npm run watch
276
+
277
+ # Run tests
278
+ npm test
279
+ ```
280
+
281
+ ### Project structure
282
+ ```
283
+ src/
284
+ ├── api.ts # Core Swell class and API handling
285
+ ├── theme.ts # SwellTheme class and rendering
286
+ ├── resources.ts # Storefront resource classes
287
+ ├── liquid/ # Liquid templating engine
288
+ ├── compatibility/ # Shopify compatibility layer
289
+ ├── cache/ # Caching implementations
290
+ ├── utils/ # Utility functions
291
+ └── index.ts # Main exports
292
+ ```
293
+
294
+ ## Resources
295
+
296
+ - [Swell Documentation](https://developers.swell.is/)
297
+ - [Apps Development Guide](https://developers.swell.is/apps/overview)
298
+ - [Proxima Example App](https://developers.swell.is/storefronts/proxima)
299
+ - [Swell Liquid Reference](https://developers.swell.is/storefronts/swell-liquid-reference)
300
+ - [GitHub Repository](https://github.com/swellstores/swell-apps-sdk)
301
+
302
+ ## 📄 License
303
+
304
+ See the [LICENSE](LICENSE) file for details.
package/dist/index.cjs CHANGED
@@ -7536,6 +7536,16 @@ function transformSwellVariant(params, product, variant) {
7536
7536
  }
7537
7537
 
7538
7538
  // src/resources/product.ts
7539
+ var SORT_OPTIONS = [
7540
+ { value: "", name: "Featured" },
7541
+ { value: "popularity", name: "Popularity", query: "popularity desc" },
7542
+ { value: "price_asc", name: "Price, low to high", query: "price asc" },
7543
+ { value: "price_desc", name: "Price, high to low", query: "price desc" },
7544
+ { value: "date_asc", name: "Date, old to new", query: "date asc" },
7545
+ { value: "date_desc", name: "Date, new to old", query: "date desc" },
7546
+ { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
7547
+ { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
7548
+ ];
7539
7549
  function transformSwellProduct(params, product) {
7540
7550
  if (!product) {
7541
7551
  return product;
@@ -7572,6 +7582,28 @@ var SwellProduct = class extends SwellStorefrontRecord {
7572
7582
  return res;
7573
7583
  }
7574
7584
  };
7585
+ function productQueryWithFilters(swell, query = {}) {
7586
+ const sortBy = swell.queryParams.sort || "";
7587
+ const filters2 = Object.entries(swell.queryParams).reduce(
7588
+ (acc, [key, value]) => {
7589
+ if (key.startsWith("filter_")) {
7590
+ const qkey = key.replace("filter_", "");
7591
+ if (value?.gte !== void 0 || value?.lte !== void 0) {
7592
+ acc[qkey] = [value.gte || 0, value.lte || void 0];
7593
+ } else {
7594
+ acc[qkey] = value;
7595
+ }
7596
+ }
7597
+ return acc;
7598
+ },
7599
+ {}
7600
+ );
7601
+ return {
7602
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
7603
+ $filters: filters2,
7604
+ ...query
7605
+ };
7606
+ }
7575
7607
 
7576
7608
  // src/api.ts
7577
7609
  var DEFAULT_API_HOST = "https://api.schema.io";
@@ -15012,9 +15044,11 @@ function ShopifyCollection(instance, category) {
15012
15044
  if (category instanceof StorefrontResource) {
15013
15045
  category = cloneStorefrontResource(category);
15014
15046
  }
15047
+ const productMapper = (product) => ShopifyProduct(instance, product);
15015
15048
  const resolveProducts = makeProductsCollectionResolve(
15049
+ instance,
15016
15050
  category,
15017
- (product) => ShopifyProduct(instance, product)
15051
+ productMapper
15018
15052
  );
15019
15053
  return new ShopifyResource({
15020
15054
  all_products_count: defer(
@@ -15066,8 +15100,9 @@ function ShopifyCollection(instance, category) {
15066
15100
  category,
15067
15101
  (category2) => getFirstImage(instance, category2)
15068
15102
  ),
15069
- filters: defer(
15070
- async () => ((await resolveProducts())?.filter_options ?? []).map(
15103
+ filters: deferWith(
15104
+ category,
15105
+ (category2) => (category2?.filter_options ?? []).map(
15071
15106
  (filter) => ShopifyFilter(instance, filter)
15072
15107
  )
15073
15108
  ),
@@ -15077,9 +15112,7 @@ function ShopifyCollection(instance, category) {
15077
15112
  metafields: {},
15078
15113
  next_product: void 0,
15079
15114
  previous_product: void 0,
15080
- products: defer(async () => {
15081
- return (await resolveProducts())?.results ?? [];
15082
- }),
15115
+ products: getProducts(instance, category, productMapper),
15083
15116
  products_count: defer(
15084
15117
  async () => (await resolveProducts())?.results?.length || 0
15085
15118
  ),
@@ -15118,28 +15151,35 @@ function convertToShopifySorting(value) {
15118
15151
  return "manual";
15119
15152
  }
15120
15153
  }
15121
- function makeProductsCollectionResolve(object, mapper) {
15122
- const productResults = deferWith(object, (object2) => {
15123
- if (object2.products) {
15124
- if (object2.products instanceof SwellStorefrontCollection) {
15125
- return object2.products._cloneWithCompatibilityResult((products) => {
15126
- return {
15127
- ...products,
15128
- results: products.results.map(mapper)
15129
- };
15130
- });
15154
+ function getProducts(instance, object, mapper) {
15155
+ return deferWith(object, (object2) => {
15156
+ const { page, limit: limit2 } = instance.swell.queryParams;
15157
+ const categoryFilter = object2.id && object2.id !== "all" ? object2.id : void 0;
15158
+ const productQuery = categoryFilter ? { category: categoryFilter, $variants: true } : { $variants: true };
15159
+ const filterQuery = productQueryWithFilters(instance.swell, productQuery);
15160
+ const products = new SwellStorefrontCollection(
15161
+ instance.swell,
15162
+ "products",
15163
+ {
15164
+ page,
15165
+ limit: limit2,
15166
+ ...filterQuery
15167
+ },
15168
+ async function() {
15169
+ return this._defaultGetter().call(this);
15131
15170
  }
15132
- if (Array.isArray(object2.products?.results)) {
15133
- return {
15134
- ...object2.products,
15135
- results: object2.products.results.map(mapper)
15136
- };
15171
+ );
15172
+ return products._cloneWithCompatibilityResult(
15173
+ (products2) => {
15174
+ return { ...products2, results: products2.results.map(mapper) };
15137
15175
  }
15138
- }
15139
- return null;
15176
+ );
15140
15177
  });
15178
+ }
15179
+ function makeProductsCollectionResolve(instance, object, mapper) {
15180
+ const products = getProducts(instance, object, mapper);
15141
15181
  async function resolveProducts() {
15142
- const resolved = await productResults.resolve();
15182
+ const resolved = await products.resolve();
15143
15183
  if (resolved && "_resolve" in resolved) {
15144
15184
  return resolved._resolve();
15145
15185
  }
@@ -15903,11 +15943,15 @@ function ShopifySearch(instance, search) {
15903
15943
  if (search instanceof ShopifyResource) {
15904
15944
  return search.clone();
15905
15945
  }
15906
- const resolveProducts = makeProductsCollectionResolve(search, (product) => {
15907
- const shopifyProduct = ShopifyProduct(instance, product);
15908
- shopifyProduct.object_type = "product";
15909
- return shopifyProduct;
15910
- });
15946
+ const resolveProducts = makeProductsCollectionResolve(
15947
+ instance,
15948
+ search,
15949
+ (product) => {
15950
+ const shopifyProduct = ShopifyProduct(instance, product);
15951
+ shopifyProduct.object_type = "product";
15952
+ return shopifyProduct;
15953
+ }
15954
+ );
15911
15955
  return new ShopifyResource({
15912
15956
  default_sort_by: deferWith(
15913
15957
  search,