@commerce-blocks/sdk 2.0.0-alpha.1 → 2.0.0-alpha.2
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 +185 -93
- package/dist/index.d.ts +135 -148
- package/dist/index.js +1218 -1164
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,29 +138,38 @@ await collection.execute({ filters: { color: 'Red' } }) // filterMap applied
|
|
|
138
138
|
|
|
139
139
|
### `sdk.collection()` - Browse Collections
|
|
140
140
|
|
|
141
|
-
Creates a collection controller for browsing products in a collection. Controllers expose
|
|
141
|
+
Creates a collection controller for browsing products in a collection. Controllers expose three ways to consume results:
|
|
142
142
|
|
|
143
|
-
- **Reactive**:
|
|
143
|
+
- **Reactive (signals)**: Access `controller.state` (a `ReadonlySignal<QueryState<T>>`) directly with `effect()` from `@preact/signals-core`
|
|
144
|
+
- **Reactive (callback)**: Use `controller.subscribe(callback)` — receives state updates without needing signals
|
|
144
145
|
- **Imperative**: `await controller.execute()` returns `Result<T, SdkError>` directly
|
|
145
146
|
|
|
146
|
-
This pattern applies to all controllers (`collection`, `search`, `blocks`).
|
|
147
|
+
This pattern applies to all controllers (`collection`, `search`, `blocks`, `autocomplete`).
|
|
147
148
|
|
|
148
149
|
```typescript
|
|
149
|
-
import { effect } from '@preact/signals-core'
|
|
150
|
-
|
|
151
150
|
const collection = sdk.collection({
|
|
152
151
|
handle: 'shirts',
|
|
153
152
|
defaultSort: 'featured', // optional, uses first sort if omitted
|
|
154
153
|
})
|
|
155
154
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
const { data, error, isFetching } = collection.state.value
|
|
155
|
+
// Option 1: Controller subscribe() — no signal import needed
|
|
156
|
+
const unsubscribe = collection.subscribe(({ data, error, isFetching }) => {
|
|
159
157
|
if (isFetching) console.log('Loading...')
|
|
160
158
|
if (error) console.error('Error:', error.message)
|
|
161
159
|
if (data) console.log('Products:', data.products)
|
|
162
160
|
})
|
|
163
161
|
|
|
162
|
+
// Option 2: Standalone subscribe() — works with any signal
|
|
163
|
+
const unsubscribe = subscribe(collection.state, ({ data, error, isFetching }) => {
|
|
164
|
+
// same as above
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Option 3: Direct signal access (for custom reactivity)
|
|
168
|
+
effect(() => {
|
|
169
|
+
const { data, error, isFetching } = collection.state.value
|
|
170
|
+
// same as above
|
|
171
|
+
})
|
|
172
|
+
|
|
164
173
|
// Execute queries — returns Result<CollectionResult, SdkError>
|
|
165
174
|
const result = await collection.execute() // initial load
|
|
166
175
|
if (result.error) console.error(result.error.message)
|
|
@@ -170,16 +179,20 @@ await collection.execute({ page: 2 }) // pagination
|
|
|
170
179
|
await collection.execute({ sortOrderCode: 'price_asc' }) // change sort
|
|
171
180
|
await collection.execute({ filters: { color: 'Red' } }) // with filters
|
|
172
181
|
|
|
173
|
-
//
|
|
174
|
-
|
|
182
|
+
// Unsubscribe
|
|
183
|
+
unsubscribe() // remove single subscription
|
|
184
|
+
collection.dispose() // cleanup all subscriptions + abort pending requests
|
|
175
185
|
```
|
|
176
186
|
|
|
187
|
+
The `subscribe()` method is an effect abstraction — it wraps `@preact/signals-core`'s `effect()` so you can react to state changes without importing signals directly. The callback receives the full `QueryState<T>` object (not a signal), making it easy to use in any framework.
|
|
188
|
+
|
|
177
189
|
**Options:**
|
|
178
190
|
|
|
179
|
-
| Parameter | Type
|
|
180
|
-
| ------------- |
|
|
181
|
-
| `handle` | `string`
|
|
182
|
-
| `defaultSort` | `string`
|
|
191
|
+
| Parameter | Type | Required | Description |
|
|
192
|
+
| ------------- | ------------- | -------- | ---------------------------------------------- |
|
|
193
|
+
| `handle` | `string` | Yes | Collection URL handle |
|
|
194
|
+
| `defaultSort` | `string` | No | Default sort code (uses first configured sort) |
|
|
195
|
+
| `signal` | `AbortSignal` | No | Shared abort signal |
|
|
183
196
|
|
|
184
197
|
**Execute parameters:**
|
|
185
198
|
|
|
@@ -189,7 +202,7 @@ collection.dispose()
|
|
|
189
202
|
| `limit` | `number` | Products per page (default: 24) |
|
|
190
203
|
| `sortOrderCode` | `string` | Sort option code |
|
|
191
204
|
| `filters` | `unknown` | Filter criteria |
|
|
192
|
-
| `signal` | `AbortSignal` |
|
|
205
|
+
| `signal` | `AbortSignal` | Per-call abort signal |
|
|
193
206
|
| `includeMeta` | `boolean` | Fetch collection metadata |
|
|
194
207
|
| `includeFilters` | `boolean` | Include filter counts in response |
|
|
195
208
|
| `dynamicLinking` | `Record<string, unknown>` | Custom dynamic linking parameters |
|
|
@@ -203,18 +216,28 @@ Creates a blocks controller for product recommendations powered by Layers blocks
|
|
|
203
216
|
```typescript
|
|
204
217
|
const blocks = sdk.blocks({
|
|
205
218
|
blockId: 'block-abc123',
|
|
206
|
-
|
|
219
|
+
anchorId: 'gold-necklace', // anchor by product ID or handle
|
|
207
220
|
})
|
|
208
221
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
const { data, error, isFetching } = blocks.state.value
|
|
222
|
+
// Option 1: Controller subscribe() — no signal import needed
|
|
223
|
+
const unsubscribe = blocks.subscribe(({ data, error, isFetching }) => {
|
|
212
224
|
if (data) {
|
|
213
225
|
console.log('Recommendations:', data.products)
|
|
214
226
|
console.log('Block info:', data.block) // { title, anchor_type, strategy_type, ... }
|
|
215
227
|
}
|
|
216
228
|
})
|
|
217
229
|
|
|
230
|
+
// Option 2: Standalone subscribe() — works with any signal
|
|
231
|
+
const unsubscribe = subscribe(blocks.state, ({ data, error, isFetching }) => {
|
|
232
|
+
// same as above
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Option 3: Direct signal access (for custom reactivity)
|
|
236
|
+
effect(() => {
|
|
237
|
+
const { data, error, isFetching } = blocks.state.value
|
|
238
|
+
// same as above
|
|
239
|
+
})
|
|
240
|
+
|
|
218
241
|
// Execute queries
|
|
219
242
|
await blocks.execute()
|
|
220
243
|
await blocks.execute({ page: 2, limit: 12 })
|
|
@@ -232,17 +255,18 @@ await blocks.execute({
|
|
|
232
255
|
discountEntitlements: [{ id: 'discount-123' }],
|
|
233
256
|
})
|
|
234
257
|
|
|
235
|
-
//
|
|
236
|
-
|
|
258
|
+
// Unsubscribe
|
|
259
|
+
unsubscribe() // remove single subscription
|
|
260
|
+
blocks.dispose() // cleanup all subscriptions + abort pending requests
|
|
237
261
|
```
|
|
238
262
|
|
|
239
263
|
**Options:**
|
|
240
264
|
|
|
241
|
-
| Parameter
|
|
242
|
-
|
|
|
243
|
-
| `blockId`
|
|
244
|
-
| `anchorId`
|
|
245
|
-
| `
|
|
265
|
+
| Parameter | Type | Required | Description |
|
|
266
|
+
| ---------- | ------------- | -------- | -------------------------------------- |
|
|
267
|
+
| `blockId` | `string` | Yes | Layers block ID |
|
|
268
|
+
| `anchorId` | `string` | No | Anchor product/collection ID or handle |
|
|
269
|
+
| `signal` | `AbortSignal` | No | Shared abort signal |
|
|
246
270
|
|
|
247
271
|
**Execute parameters:**
|
|
248
272
|
|
|
@@ -290,89 +314,116 @@ Creates a standalone autocomplete controller with debounced search and local cac
|
|
|
290
314
|
```typescript
|
|
291
315
|
const autocomplete = sdk.autocomplete({ debounceMs: 300 })
|
|
292
316
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
const { data, isFetching, error } = autocomplete.state.value
|
|
317
|
+
// Option 1: Controller subscribe() — no signal import needed
|
|
318
|
+
const unsubscribe = autocomplete.subscribe(({ data, isFetching, error }) => {
|
|
296
319
|
if (isFetching) console.log('Loading suggestions...')
|
|
297
320
|
if (data) renderSuggestions(data.matchedQueries)
|
|
298
321
|
})
|
|
299
322
|
|
|
323
|
+
// Option 2: Standalone subscribe() — works with any signal
|
|
324
|
+
const unsubscribe = subscribe(autocomplete.state, ({ data, isFetching, error }) => {
|
|
325
|
+
// same as above
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// Option 3: Direct signal access (for custom reactivity)
|
|
329
|
+
effect(() => {
|
|
330
|
+
const { data, isFetching, error } = autocomplete.state.value
|
|
331
|
+
// same as above
|
|
332
|
+
})
|
|
333
|
+
|
|
300
334
|
// Wire to input (debounced automatically)
|
|
301
335
|
input.addEventListener('input', (e) => {
|
|
302
336
|
autocomplete.execute(e.target.value)
|
|
303
337
|
})
|
|
304
338
|
|
|
305
|
-
//
|
|
306
|
-
|
|
339
|
+
// Unsubscribe
|
|
340
|
+
unsubscribe() // remove single subscription
|
|
341
|
+
autocomplete.dispose() // cleanup all subscriptions, abort controller, and timers
|
|
307
342
|
```
|
|
308
343
|
|
|
309
344
|
**Options:**
|
|
310
345
|
|
|
311
|
-
| Parameter | Type | Description
|
|
312
|
-
| ------------ | ------------- |
|
|
313
|
-
| `debounceMs` | `number` | Debounce delay (default: 300)
|
|
314
|
-
| `signal` | `AbortSignal` |
|
|
346
|
+
| Parameter | Type | Description |
|
|
347
|
+
| ------------ | ------------- | -------------------------------------------------------- |
|
|
348
|
+
| `debounceMs` | `number` | Debounce delay (default: 300) |
|
|
349
|
+
| `signal` | `AbortSignal` | Shared abort signal (acts like `dispose()` when aborted) |
|
|
315
350
|
|
|
316
351
|
**Controller methods:**
|
|
317
352
|
|
|
318
|
-
| Method | Description
|
|
319
|
-
| ---------------- |
|
|
320
|
-
| `execute(query)` | Debounced predictive search for autocomplete
|
|
321
|
-
| `
|
|
353
|
+
| Method | Description |
|
|
354
|
+
| ---------------- | -------------------------------------------------------- |
|
|
355
|
+
| `execute(query)` | Debounced predictive search for autocomplete |
|
|
356
|
+
| `subscribe(cb)` | Subscribe to state changes, returns unsubscribe function |
|
|
357
|
+
| `dispose()` | Cleanup all subscriptions, abort controller and timers |
|
|
322
358
|
|
|
323
359
|
### `sdk.search()` - Search Products
|
|
324
360
|
|
|
325
|
-
Creates a search controller for full text search
|
|
361
|
+
Creates a search controller for full text search. Options persist across calls — subsequent `execute()` and `prepare()` calls merge with these defaults.
|
|
326
362
|
|
|
327
363
|
```typescript
|
|
328
|
-
|
|
364
|
+
// Initialize with base options
|
|
365
|
+
const search = sdk.search({ query: 'ring', limit: 20 })
|
|
329
366
|
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
const { data, isFetching, error } = search.state.value
|
|
367
|
+
// Option 1: Controller subscribe() — no signal import needed
|
|
368
|
+
const unsubscribe = search.subscribe(({ data, isFetching, error }) => {
|
|
333
369
|
if (isFetching) console.log('Searching...')
|
|
334
370
|
if (data) renderResults(data.products)
|
|
335
371
|
})
|
|
336
372
|
|
|
337
|
-
//
|
|
338
|
-
|
|
373
|
+
// Option 2: Standalone subscribe() — works with any signal
|
|
374
|
+
const unsubscribe = subscribe(search.state, ({ data, isFetching, error }) => {
|
|
375
|
+
// same as above
|
|
376
|
+
})
|
|
339
377
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
query: inputValue,
|
|
345
|
-
limit: 20,
|
|
346
|
-
filters: { color: 'Blue' },
|
|
347
|
-
})
|
|
348
|
-
if (result.data) console.log('Results:', result.data.products)
|
|
378
|
+
// Option 3: Direct signal access (for custom reactivity)
|
|
379
|
+
effect(() => {
|
|
380
|
+
const { data, isFetching, error } = search.state.value
|
|
381
|
+
// same as above
|
|
349
382
|
})
|
|
350
383
|
|
|
351
|
-
//
|
|
384
|
+
// Execute with initial options
|
|
385
|
+
await search.execute()
|
|
386
|
+
|
|
387
|
+
// Override and persist new options
|
|
388
|
+
await search.execute({ page: 2 }) // page=2 persists
|
|
389
|
+
await search.execute({ filters: { vendor: 'Adidas' } }) // filters updated
|
|
390
|
+
|
|
391
|
+
// Temporary override without persisting
|
|
392
|
+
await search.execute({ query: 'shoes', transient: true }) // query stays 'ring' for next call
|
|
393
|
+
|
|
394
|
+
// Prepare search (optional, caches searchId for faster execute)
|
|
395
|
+
await search.prepare()
|
|
396
|
+
await search.execute() // uses cached searchId
|
|
397
|
+
|
|
398
|
+
// Unsubscribe
|
|
399
|
+
unsubscribe()
|
|
352
400
|
search.dispose()
|
|
353
401
|
```
|
|
354
402
|
|
|
355
|
-
**
|
|
356
|
-
|
|
357
|
-
|
|
|
358
|
-
| ---------------- |
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
403
|
+
**SearchQuery parameters** (used for init, execute, and prepare):
|
|
404
|
+
|
|
405
|
+
| Parameter | Type | Description |
|
|
406
|
+
| ---------------- | ------------------------- | --------------------------------------------- |
|
|
407
|
+
| `query` | `string` | Search query (required for first execute) |
|
|
408
|
+
| `searchId` | `string` | Use specific searchId (skips prepare) |
|
|
409
|
+
| `page` | `number` | Page number (default: 1) |
|
|
410
|
+
| `limit` | `number` | Products per page (default: 24) |
|
|
411
|
+
| `filters` | `unknown` | Filter criteria |
|
|
412
|
+
| `tuning` | `LayersTuning` | Search tuning parameters |
|
|
413
|
+
| `signal` | `AbortSignal` | Abort signal (shared at init, per-call later) |
|
|
414
|
+
| `dynamicLinking` | `Record<string, unknown>` | Custom dynamic linking parameters |
|
|
415
|
+
| `params` | `Record<string, unknown>` | Additional request parameters |
|
|
416
|
+
| `transformBody` | `(body) => body` | Custom request body mutation function |
|
|
417
|
+
| `transient` | `boolean` | If true, overrides don't persist |
|
|
362
418
|
|
|
363
|
-
**
|
|
419
|
+
**Controller methods:**
|
|
364
420
|
|
|
365
|
-
|
|
|
366
|
-
|
|
|
367
|
-
| `query`
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `tuning` | `LayersTuning` | Search tuning parameters |
|
|
372
|
-
| `signal` | `AbortSignal` | External abort signal |
|
|
373
|
-
| `dynamicLinking` | `Record<string, unknown>` | Custom dynamic linking parameters |
|
|
374
|
-
| `params` | `Record<string, unknown>` | Additional request parameters |
|
|
375
|
-
| `transformBody` | `(body) => body` | Custom request body mutation function |
|
|
421
|
+
| Method | Description |
|
|
422
|
+
| ----------------- | -------------------------------------------------------- |
|
|
423
|
+
| `prepare(query?)` | Prepare search and cache searchId for reuse |
|
|
424
|
+
| `execute(query?)` | Execute search (uses cached searchId if available) |
|
|
425
|
+
| `subscribe(cb)` | Subscribe to state changes, returns unsubscribe function |
|
|
426
|
+
| `dispose()` | Cleanup all subscriptions and abort pending requests |
|
|
376
427
|
|
|
377
428
|
**`LayersTuning`:**
|
|
378
429
|
|
|
@@ -393,12 +444,18 @@ const file = fileInput.files[0]
|
|
|
393
444
|
|
|
394
445
|
const state = sdk.uploadImage({ image: file, signal })
|
|
395
446
|
|
|
396
|
-
|
|
397
|
-
|
|
447
|
+
// Option 1: Standalone subscribe() — works with any signal
|
|
448
|
+
const unsubscribe = subscribe(state, ({ data, error, isFetching }) => {
|
|
398
449
|
if (isFetching) console.log('Uploading...')
|
|
399
450
|
if (error) console.error('Upload failed:', error.message)
|
|
400
451
|
if (data) console.log('Image ID:', data.imageId)
|
|
401
452
|
})
|
|
453
|
+
|
|
454
|
+
// Option 2: Direct signal access (for custom reactivity)
|
|
455
|
+
effect(() => {
|
|
456
|
+
const { data, error, isFetching } = state.value
|
|
457
|
+
// same as above
|
|
458
|
+
})
|
|
402
459
|
```
|
|
403
460
|
|
|
404
461
|
### `sdk.imageSearch()` - Search by Image
|
|
@@ -412,9 +469,15 @@ const state = sdk.imageSearch({
|
|
|
412
469
|
filters: { vendor: 'Nike' },
|
|
413
470
|
})
|
|
414
471
|
|
|
472
|
+
// Option 1: Standalone subscribe() — works with any signal
|
|
473
|
+
const unsubscribe = subscribe(state, ({ data, error, isFetching }) => {
|
|
474
|
+
if (data) console.log('Similar products:', data.products)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// Option 2: Direct signal access (for custom reactivity)
|
|
415
478
|
effect(() => {
|
|
416
479
|
const { data, error, isFetching } = state.value
|
|
417
|
-
|
|
480
|
+
// same as above
|
|
418
481
|
})
|
|
419
482
|
```
|
|
420
483
|
|
|
@@ -443,14 +506,20 @@ const state = sdk.storefront({
|
|
|
443
506
|
},
|
|
444
507
|
})
|
|
445
508
|
|
|
446
|
-
|
|
447
|
-
|
|
509
|
+
// Option 1: Standalone subscribe() — works with any signal
|
|
510
|
+
const unsubscribe = subscribe(state, ({ data, error, isFetching }) => {
|
|
448
511
|
if (data) {
|
|
449
512
|
console.log('Products:', data.products)
|
|
450
513
|
console.log('Collection:', data.collection)
|
|
451
514
|
console.log('Page:', data.page)
|
|
452
515
|
}
|
|
453
516
|
})
|
|
517
|
+
|
|
518
|
+
// Option 2: Direct signal access (for custom reactivity)
|
|
519
|
+
effect(() => {
|
|
520
|
+
const { data, error, isFetching } = state.value
|
|
521
|
+
// same as above
|
|
522
|
+
})
|
|
454
523
|
```
|
|
455
524
|
|
|
456
525
|
**Parameters:**
|
|
@@ -465,24 +534,47 @@ effect(() => {
|
|
|
465
534
|
|
|
466
535
|
## Abort Signals
|
|
467
536
|
|
|
468
|
-
|
|
537
|
+
Controllers support two levels of abort signals:
|
|
538
|
+
|
|
539
|
+
1. **Shared signal** — passed at initialization, affects all operations
|
|
540
|
+
2. **Per-call signal** — passed to `execute()` or `prepare()`, affects only that call
|
|
541
|
+
|
|
542
|
+
Both signals are linked internally using `linkedAbort()` — when either signal aborts, the request cancels. This allows component-level cancellation (shared) alongside request-level cancellation (per-call).
|
|
469
543
|
|
|
470
544
|
```typescript
|
|
471
|
-
|
|
545
|
+
// Shared signal at init — cancels everything when component unmounts
|
|
546
|
+
const componentController = new AbortController()
|
|
547
|
+
|
|
548
|
+
const search = sdk.search({
|
|
549
|
+
query: 'ring',
|
|
550
|
+
signal: componentController.signal, // shared: affects all operations
|
|
551
|
+
})
|
|
472
552
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
553
|
+
const autocomplete = sdk.autocomplete({
|
|
554
|
+
signal: componentController.signal, // shared: acts like dispose() when aborted
|
|
555
|
+
})
|
|
476
556
|
|
|
477
|
-
//
|
|
478
|
-
const
|
|
479
|
-
|
|
557
|
+
// Per-call signal — cancels only this specific request
|
|
558
|
+
const requestController = new AbortController()
|
|
559
|
+
await search.execute({ page: 2, signal: requestController.signal })
|
|
560
|
+
requestController.abort() // cancels only this request
|
|
480
561
|
|
|
481
|
-
//
|
|
482
|
-
|
|
562
|
+
// Shared signal abort cancels everything
|
|
563
|
+
componentController.abort() // cancels all pending requests + acts like dispose()
|
|
483
564
|
```
|
|
484
565
|
|
|
485
|
-
|
|
566
|
+
**How signals compose:**
|
|
567
|
+
|
|
568
|
+
| Scenario | Behavior |
|
|
569
|
+
| -------------------- | ----------------------------------------- |
|
|
570
|
+
| Only shared signal | All requests use shared signal |
|
|
571
|
+
| Only per-call signal | That request uses per-call signal |
|
|
572
|
+
| Both signals | Request cancels if either aborts |
|
|
573
|
+
| Neither signal | Controller manages its own internal abort |
|
|
574
|
+
|
|
575
|
+
**Auto-cancellation:**
|
|
576
|
+
|
|
577
|
+
Controllers that don't support concurrent requests (collection, blocks) automatically abort the previous request when a new one starts. The abort signals add external cancellation paths on top of this built-in behavior.
|
|
486
578
|
|
|
487
579
|
## Response Types
|
|
488
580
|
|
|
@@ -730,7 +822,7 @@ card.getSwatches('Color') // Swatch definitions
|
|
|
730
822
|
card.isOptionAvailable('Size', 'L') // Check if selecting 'L' results in available variant
|
|
731
823
|
card.getVariantByOptions([{ name: 'Size', value: 'L' }])
|
|
732
824
|
|
|
733
|
-
//
|
|
825
|
+
// Unsubscribe
|
|
734
826
|
card.dispose()
|
|
735
827
|
```
|
|
736
828
|
|
|
@@ -894,7 +986,7 @@ import { browseKey, searchKey, similarKey, blocksKey, productsKey } from '@comme
|
|
|
894
986
|
browseKey('shirts', { page: 1, limit: 24 }) // '/browse/shirts?limit=24&page=1'
|
|
895
987
|
searchKey('red dress', { page: 2 }) // '/search/red%20dress?page=2'
|
|
896
988
|
similarKey(123, { limit: 10 }) // '/similar/123?limit=10'
|
|
897
|
-
blocksKey('block-abc', {
|
|
989
|
+
blocksKey('block-abc', { anchorId: 'gold-necklace', page: 1 })
|
|
898
990
|
productsKey(['gid://shopify/Product/1', 'gid://shopify/Product/2'])
|
|
899
991
|
```
|
|
900
992
|
|