@01.software/sdk 0.30.1 → 0.31.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 (46) hide show
  1. package/README.md +74 -20
  2. package/dist/client.cjs +81 -16
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.cts +6 -6
  5. package/dist/client.d.ts +6 -6
  6. package/dist/client.js +81 -16
  7. package/dist/client.js.map +1 -1
  8. package/dist/{collection-client-B9d9kr1d.d.ts → collection-client-ByzY3hWK.d.ts} +3 -3
  9. package/dist/{collection-client-QPbwimkU.d.cts → collection-client-DFXXz0vk.d.cts} +3 -3
  10. package/dist/{const-VZuk2tWc.d.cts → const-AytzliEu.d.cts} +4 -4
  11. package/dist/{const-B75IFDRi.d.ts → const-BGCP-OJL.d.ts} +4 -4
  12. package/dist/{index-B2WbhEgT.d.cts → index-BGEhoDUs.d.cts} +1 -1
  13. package/dist/{index-B2WbhEgT.d.ts → index-BGEhoDUs.d.ts} +1 -1
  14. package/dist/index.cjs +156 -19
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +8 -8
  17. package/dist/index.d.ts +8 -8
  18. package/dist/index.js +156 -19
  19. package/dist/index.js.map +1 -1
  20. package/dist/{payload-types-DPjO_IbQ.d.cts → payload-types-Wa4-eC6x.d.cts} +790 -534
  21. package/dist/{payload-types-DPjO_IbQ.d.ts → payload-types-Wa4-eC6x.d.ts} +790 -534
  22. package/dist/query.cjs +63 -13
  23. package/dist/query.cjs.map +1 -1
  24. package/dist/query.d.cts +6 -6
  25. package/dist/query.d.ts +6 -6
  26. package/dist/query.js +63 -13
  27. package/dist/query.js.map +1 -1
  28. package/dist/realtime.d.cts +2 -2
  29. package/dist/realtime.d.ts +2 -2
  30. package/dist/server.cjs +142 -17
  31. package/dist/server.cjs.map +1 -1
  32. package/dist/server.d.cts +32 -7
  33. package/dist/server.d.ts +32 -7
  34. package/dist/server.js +142 -17
  35. package/dist/server.js.map +1 -1
  36. package/dist/{types-Dlb2mwpX.d.cts → types-BX2mqDf6.d.ts} +46 -6
  37. package/dist/{types-1fBLrYU7.d.ts → types-CVA10VC-.d.ts} +6 -2
  38. package/dist/{types-BwT0eeaz.d.cts → types-CmLG-7RL.d.cts} +6 -2
  39. package/dist/{types-DuSKPiY5.d.ts → types-DChFjQGz.d.cts} +46 -6
  40. package/dist/ui/form.d.cts +1 -1
  41. package/dist/ui/form.d.ts +1 -1
  42. package/dist/ui/video.d.cts +1 -1
  43. package/dist/ui/video.d.ts +1 -1
  44. package/dist/webhook.d.cts +3 -3
  45. package/dist/webhook.d.ts +3 -3
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,7 @@ export function App() {
42
42
  ```
43
43
 
44
44
  ```typescript
45
- // Main entry - browser client, query builder, hooks, utilities
45
+ // Main entry - browser client, query builder, commerce helpers, utilities
46
46
  import { createClient } from '@01.software/sdk'
47
47
 
48
48
  // Server-only entry - keep Secret Key code out of browser-facing imports
@@ -72,21 +72,21 @@ types lightweight. Server, React Query, and UI features live behind explicit
72
72
  sub-paths so consumers install feature peers only when they import the matching
73
73
  entry.
74
74
 
75
- | Import | Feature(s) | Install when used |
76
- | --- | --- | --- |
77
- | `@01.software/sdk` | browser-safe `createClient`, commerce helpers, collection helpers, types | none |
78
- | `@01.software/sdk/client` | browser-safe `createClient` entry | none |
79
- | `@01.software/sdk/server` | `createServerClient`, server-only collection and commerce APIs | none; keep `secretKey` code on the server |
80
- | `@01.software/sdk/query` | React Query hooks, cache helpers, `getQueryClient` | `@tanstack/react-query`, `react`, `react-dom` |
81
- | `@01.software/sdk/realtime` | `RealtimeConnection`, `useRealtimeQuery` | `@tanstack/react-query`, `react`, `react-dom` |
82
- | `@01.software/sdk/analytics/react` | `<Analytics />` | `react`, `react-dom` |
83
- | `@01.software/sdk/ui/rich-text` | `RichTextContent`, `StyledRichTextContent` | `react`, `react-dom`, `@payloadcms/richtext-lexical` |
84
- | `@01.software/sdk/ui/form` | `FormRenderer` | `react`, `react-dom` |
85
- | `@01.software/sdk/ui/code-block` | `CodeBlock`, `highlight` | `react`, `react-dom`, `shiki`, `hast-util-to-jsx-runtime` |
86
- | `@01.software/sdk/ui/canvas` | `CanvasRenderer`, `CanvasFrame`, `useCanvas`, `prefetchCanvas` | `react`, `react-dom`, `@tanstack/react-query`, `@xyflow/react`, `quickjs-emscripten`, `postcss`, `sucrase` |
87
- | `@01.software/sdk/ui/canvas/server` | canvas server helpers | none |
88
- | `@01.software/sdk/ui/video` | `VideoPlayer` | `react`, `react-dom`, `@mux/mux-player-react` |
89
- | `@01.software/sdk/ui/image` | `Image` | `react`, `react-dom` |
75
+ | Import | Feature(s) | Install when used |
76
+ | ----------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
77
+ | `@01.software/sdk` | browser-safe `createClient`, commerce helpers, collection helpers, types | none |
78
+ | `@01.software/sdk/client` | browser-safe `createClient` entry | none |
79
+ | `@01.software/sdk/server` | `createServerClient`, server-only collection, commerce, and preview APIs | none; keep `secretKey` code on the server |
80
+ | `@01.software/sdk/query` | React Query hooks, cache helpers, `getQueryClient` | `@tanstack/react-query`, `react`, `react-dom` |
81
+ | `@01.software/sdk/realtime` | `RealtimeConnection`, `useRealtimeQuery` | `@tanstack/react-query`, `react`, `react-dom` |
82
+ | `@01.software/sdk/analytics/react` | `<Analytics />` | `react`, `react-dom` |
83
+ | `@01.software/sdk/ui/rich-text` | `RichTextContent`, `StyledRichTextContent` | `react`, `react-dom`, `@payloadcms/richtext-lexical` |
84
+ | `@01.software/sdk/ui/form` | `FormRenderer` | `react`, `react-dom` |
85
+ | `@01.software/sdk/ui/code-block` | `CodeBlock`, `highlight` | `react`, `react-dom`, `shiki`, `hast-util-to-jsx-runtime` |
86
+ | `@01.software/sdk/ui/canvas` | `CanvasRenderer`, `CanvasFrame`, `useCanvas`, `prefetchCanvas` | `react`, `react-dom`, `@tanstack/react-query`, `@xyflow/react`, `quickjs-emscripten`, `postcss`, `sucrase` |
87
+ | `@01.software/sdk/ui/canvas/server` | canvas server helpers | none |
88
+ | `@01.software/sdk/ui/video` | `VideoPlayer` | `react`, `react-dom`, `@mux/mux-player-react` |
89
+ | `@01.software/sdk/ui/image` | `Image` | `react`, `react-dom` |
90
90
 
91
91
  If a feature is not listed here, it does not need a separate peer install.
92
92
  For the full component-to-peer mapping, see
@@ -157,6 +157,20 @@ await serverQuery.prefetchQuery({
157
157
  Always import `createServerClient` from `@01.software/sdk/server` so generated
158
158
  code and bundlers do not blur the Secret Key boundary.
159
159
 
160
+ Server-rendered preview routes can use `server.preview.detail()` with the
161
+ short-lived preview token issued by Console:
162
+
163
+ ```typescript
164
+ const preview = await server.preview.detail(
165
+ { collection: 'products', id: previewId },
166
+ { previewToken },
167
+ )
168
+ ```
169
+
170
+ For product pages, `server.commerce.product.previewDetail({ id }, {
171
+ previewToken })` returns the same shaped product detail as `detail()`, but allows
172
+ the saved draft/unpublished record addressed by the preview token.
173
+
160
174
  ## Getting product detail
161
175
 
162
176
  The recommended way to fetch a single product is the shaped helper:
@@ -204,6 +218,12 @@ const href = buildProductHref(product, {
204
218
  })
205
219
  ```
206
220
 
221
+ Selection media follows the resolved selection: a complete variant uses that
222
+ variant's media first; a partial option selection uses selected option-value
223
+ media first, then matching variant media, before falling back to listing or
224
+ product media. This keeps listing-card selection links and detail-page images
225
+ aligned without rebuilding media priority in storefront code.
226
+
207
227
  `availableValuesByOptionSlug` / `availableValuesByOptionId` include
208
228
  `availableStock`, `isUnlimited`, and `availableForSale` per value so option UIs
209
229
  can render stock state without recalculating from variants.
@@ -246,6 +266,14 @@ Use IDs from `detail.options[].id` and `detail.options[].values[].id` when
246
266
  building selection state. Slugs remain useful for display and old inbound URLs,
247
267
  but new outbound URLs should use the codec output.
248
268
 
269
+ For listing cards, pass the listing group returned by
270
+ `buildProductListingGroupsByOption()` or the listing-groups endpoint into
271
+ `buildProductHref(product, group, { detail })`. The detail object lets the SDK
272
+ emit canonical `variant=<variantId>` or `opt.<optionId>=<valueId>` params. When
273
+ full detail is not available on a product-list page, pass the group without
274
+ `detail`; `buildProductHref()` still emits the best available selection hint and
275
+ the detail page can resolve it through `resolveProductSelection()`.
276
+
249
277
  Do not use bare option query keys such as `?size=large`. The SDK rejects them
250
278
  as ambiguous because product pages commonly share URLs with unrelated search,
251
279
  filter, analytics, or framework parameters. Namespacing selection keys under
@@ -253,6 +281,32 @@ filter, analytics, or framework parameters. Namespacing selection keys under
253
281
  parameters while still allowing unrelated parameters such as `utm_campaign` to
254
282
  coexist without being interpreted as selection state.
255
283
 
284
+ ### Product listing card helper
285
+
286
+ `buildProductListingCard(item, options?)` turns a single
287
+ `commerce.product.listingGroups()` response item into a render-ready
288
+ `ProductListingCard`. The card carries product-level hero media
289
+ (`product.thumbnail` -> first `product.images` -> `null`), an aggregated
290
+ price range across all option-value groups, and a `swatches[]` array
291
+ derived from groups when there is more than one. Single-group products
292
+ emit `swatches: []`; storefronts that disagree can read `item.groups`
293
+ directly.
294
+
295
+ ```ts
296
+ import {
297
+ buildProductListingCard,
298
+ type ProductListingCard,
299
+ } from '@01.software/sdk'
300
+
301
+ const cards: ProductListingCard[] = response.docs.map((item) =>
302
+ buildProductListingCard(item, { basePath: '/shop' }),
303
+ )
304
+ ```
305
+
306
+ Each swatch carries a hint-only option-value href
307
+ (`?opt.<optionId>=<valueId>`); the detail page resolves it through
308
+ `resolveProductSelection(detail, { search })`.
309
+
256
310
  ## Advanced: direct Payload queries (escape hatch)
257
311
 
258
312
  Most consumers should use the helper APIs above (`commerce.product.detail`, etc.). The query builder below is the escape hatch for advanced cases the helpers do not cover: bulk operations, custom filter combinations, or fields the helper response does not expose.
@@ -283,7 +337,7 @@ await client.collections.from('products').find({
283
337
 
284
338
  ### `joins` — Payload join-field reverse-relations
285
339
 
286
- `joins` is the correct control for Payload `type: 'join'` virtual reverse-relation fields. In this platform's schema, `products.variants`, `products.options`, `products.collections`, `customers.orders`, `customers.addresses`, `posts.comments`, `article-authors.articles`, `orders.{items,transactions,fulfillments,returns}`, and similar reverse-relations are all join fields — you must use `joins` (not `depth`/`populate`) to control their pagination, sorting, filtering, and count.
340
+ `joins` is the correct control for Payload `type: 'join'` virtual reverse-relation fields. In this platform's public SDK schema, `products.variants`, `products.options`, `customers.orders`, `customers.addresses`, `posts.comments`, `article-authors.articles`, `orders.{items,transactions,fulfillments,returns}`, and similar reverse-relations are all join fields — you must use `joins` (not `depth`/`populate`) to control their pagination, sorting, filtering, and count. Internal backing joins such as product collection memberships are intentionally omitted from public SDK collection types.
287
341
 
288
342
  ```typescript
289
343
  // Canonical product detail query — variants/options are join fields on Products
@@ -843,9 +897,9 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 73)
843
897
  | Tenant | `tenants`, `tenant-metadata`, `tenant-logos` |
844
898
  | Products | `products`, `product-variants`, `product-options`, `product-option-values`, `product-categories`, `product-tags`, `product-collections`, `brands`, `brand-logos` |
845
899
  | Orders | `orders`, `order-items`, `returns`, `return-items`, `fulfillments`, `fulfillment-items`, `transactions` |
846
- | Customers | `customers`, `customer-profiles`, `customer-profile-lists`, `customer-addresses` |
900
+ | Customers | `customers`, `customer-profiles`, `customer-addresses` |
847
901
  | Carts | `carts`, `cart-items` |
848
- | Commerce | `discounts`, `shipping-policies` |
902
+ | Commerce | `discounts`, `shipping-policies`, `shipping-zones` |
849
903
  | Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
850
904
  | Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
851
905
  | Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
@@ -854,7 +908,7 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 73)
854
908
  | Live Streams | `live-streams` |
855
909
  | Media | `images` |
856
910
  | Forms | `forms`, `form-submissions` |
857
- | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories` |
911
+ | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`, `customer-profile-lists` |
858
912
  | Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
859
913
 
860
914
  Server-only collections: `customer-groups`, `reports`, and `community-bans`
package/dist/client.cjs CHANGED
@@ -72,8 +72,16 @@ var ValidationError = class extends SDKError {
72
72
  }
73
73
  };
74
74
  var ApiError = class extends SDKError {
75
- constructor(message, status, details, userMessage, suggestion) {
76
- super("API_ERROR", message, status, details, userMessage, suggestion);
75
+ constructor(message, status, details, userMessage, suggestion, requestId) {
76
+ super(
77
+ "API_ERROR",
78
+ message,
79
+ status,
80
+ details,
81
+ userMessage,
82
+ suggestion,
83
+ requestId
84
+ );
77
85
  this.name = "ApiError";
78
86
  }
79
87
  };
@@ -104,19 +112,43 @@ var UsageLimitError = class extends SDKError {
104
112
  };
105
113
  var AuthError = class extends SDKError {
106
114
  constructor(message, details, userMessage, suggestion, requestId) {
107
- super("auth_error", message, 401, details, userMessage, suggestion, requestId);
115
+ super(
116
+ "auth_error",
117
+ message,
118
+ 401,
119
+ details,
120
+ userMessage,
121
+ suggestion,
122
+ requestId
123
+ );
108
124
  this.name = "AuthError";
109
125
  }
110
126
  };
111
127
  var PermissionError = class extends SDKError {
112
128
  constructor(message, details, userMessage, suggestion, requestId) {
113
- super("permission_error", message, 403, details, userMessage, suggestion, requestId);
129
+ super(
130
+ "permission_error",
131
+ message,
132
+ 403,
133
+ details,
134
+ userMessage,
135
+ suggestion,
136
+ requestId
137
+ );
114
138
  this.name = "PermissionError";
115
139
  }
116
140
  };
117
141
  var NotFoundError = class extends SDKError {
118
142
  constructor(message, details, userMessage, suggestion, requestId) {
119
- super("not_found", message, 404, details, userMessage, suggestion, requestId);
143
+ super(
144
+ "not_found",
145
+ message,
146
+ 404,
147
+ details,
148
+ userMessage,
149
+ suggestion,
150
+ requestId
151
+ );
120
152
  this.name = "NotFoundError";
121
153
  }
122
154
  };
@@ -128,14 +160,22 @@ var ConflictError = class extends SDKError {
128
160
  };
129
161
  var RateLimitError = class extends SDKError {
130
162
  constructor(message, retryAfter, details, userMessage, suggestion, requestId) {
131
- super("rate_limit_exceeded", message, 429, details, userMessage, suggestion, requestId);
163
+ super(
164
+ "rate_limit_exceeded",
165
+ message,
166
+ 429,
167
+ details,
168
+ userMessage,
169
+ suggestion,
170
+ requestId
171
+ );
132
172
  this.name = "RateLimitError";
133
173
  this.retryAfter = retryAfter;
134
174
  }
135
175
  };
136
176
  var createNetworkError = (message, status, details, userMessage, suggestion) => new NetworkError(message, status, details, userMessage, suggestion);
137
177
  var createValidationError = (message, details, userMessage, suggestion, status) => new ValidationError(message, details, userMessage, suggestion, status);
138
- var createApiError = (message, status, details, userMessage, suggestion) => new ApiError(message, status, details, userMessage, suggestion);
178
+ var createApiError = (message, status, details, userMessage, suggestion, requestId) => new ApiError(message, status, details, userMessage, suggestion, requestId);
139
179
  var createConfigError = (message, details, userMessage, suggestion) => new ConfigError(message, details, userMessage, suggestion);
140
180
  var createTimeoutError = (message, details, userMessage, suggestion) => new TimeoutError(message, details, userMessage, suggestion);
141
181
  var createUsageLimitError = (message, usage, details, userMessage, suggestion) => new UsageLimitError(message, usage, details, userMessage, suggestion);
@@ -143,7 +183,14 @@ var createAuthError = (message, details, userMessage, suggestion, requestId) =>
143
183
  var createPermissionError = (message, details, userMessage, suggestion, requestId) => new PermissionError(message, details, userMessage, suggestion, requestId);
144
184
  var createNotFoundError = (message, details, userMessage, suggestion, requestId) => new NotFoundError(message, details, userMessage, suggestion, requestId);
145
185
  var createConflictError = (message, details, userMessage, suggestion, requestId) => new ConflictError(message, details, userMessage, suggestion, requestId);
146
- var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(message, retryAfter, details, userMessage, suggestion, requestId);
186
+ var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(
187
+ message,
188
+ retryAfter,
189
+ details,
190
+ userMessage,
191
+ suggestion,
192
+ requestId
193
+ );
147
194
 
148
195
  // src/core/internal/utils/credentials.ts
149
196
  function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
@@ -183,6 +230,22 @@ function debugLog(debug, type, message, data) {
183
230
  console.groupEnd();
184
231
  }
185
232
  }
233
+ function redactSensitiveHeader(value) {
234
+ const prefix = value.toLowerCase().startsWith("bearer ") ? "Bearer " : "";
235
+ return value.length > 20 ? `${prefix}...****${value.slice(-8)}` : "****";
236
+ }
237
+ function redactSensitiveHeaders(headers) {
238
+ const redacted = Object.fromEntries(headers.entries());
239
+ if (redacted.authorization) {
240
+ redacted.authorization = redactSensitiveHeader(redacted.authorization);
241
+ }
242
+ if (redacted["x-preview-token"]) {
243
+ redacted["x-preview-token"] = redactSensitiveHeader(
244
+ redacted["x-preview-token"]
245
+ );
246
+ }
247
+ return redacted;
248
+ }
186
249
  function getErrorSuggestion(status) {
187
250
  if (status === 400)
188
251
  return "The request data failed validation. Check field values and types.";
@@ -257,6 +320,12 @@ async function parseErrorBody(response) {
257
320
  return fallback;
258
321
  }
259
322
  }
323
+ function getParsedErrorSuggestion(status, parsed) {
324
+ if (status === 403 && parsed.reason === "origin_not_allowed") {
325
+ return "Add the request origin to the tenant Browser API origins, then retry the browser request.";
326
+ }
327
+ return getErrorSuggestion(status);
328
+ }
260
329
  async function delay(ms) {
261
330
  return new Promise((resolve) => setTimeout(resolve, ms));
262
331
  }
@@ -270,7 +339,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
270
339
  ...parsed.errors && { errors: parsed.errors },
271
340
  ...parsed.body && { body: parsed.body }
272
341
  };
273
- const suggestion = getErrorSuggestion(status);
342
+ const suggestion = getParsedErrorSuggestion(status, parsed);
274
343
  if (status === 400 || status === 422) {
275
344
  return attachRequestId(
276
345
  createValidationError(
@@ -376,11 +445,7 @@ async function httpFetch(url, options) {
376
445
  if (!headers.has("Content-Type") && requestInit.body && !(requestInit.body instanceof FormData)) {
377
446
  headers.set("Content-Type", "application/json");
378
447
  }
379
- const redactedHeaders = Object.fromEntries(headers.entries());
380
- if (redactedHeaders["authorization"]) {
381
- const token = redactedHeaders["authorization"];
382
- redactedHeaders["authorization"] = token.length > 20 ? `Bearer ...****${token.slice(-8)}` : "****";
383
- }
448
+ const redactedHeaders = redactSensitiveHeaders(headers);
384
449
  debugLog(debug, "request", url, {
385
450
  method: requestInit.method || "GET",
386
451
  headers: redactedHeaders,
@@ -398,7 +463,7 @@ async function httpFetch(url, options) {
398
463
  debugLog(debug, "response", url, {
399
464
  status: response.status,
400
465
  statusText: response.statusText,
401
- headers: Object.fromEntries(response.headers.entries())
466
+ headers: redactSensitiveHeaders(response.headers)
402
467
  });
403
468
  if (!response.ok) {
404
469
  if (isUsageLimitExceededResponse(response)) {
@@ -831,7 +896,7 @@ async function parseApiResponse(response, endpoint) {
831
896
  if (reason === "validation_failed") {
832
897
  throw attachRequestId(createValidationError(errorMessage, data, errorMessage), requestId);
833
898
  }
834
- if (reason === "token_expired" || reason === "token_invalid" || reason === "key_invalid" || reason === "key_revoked") {
899
+ if (reason === "token_expired" || reason === "token_invalid" || reason === "preview_token_invalid" || reason === "preview_token_required" || reason === "key_invalid" || reason === "key_revoked") {
835
900
  throw attachRequestId(createAuthError(errorMessage, data, errorMessage), requestId);
836
901
  }
837
902
  if (reason === "forbidden") {